summaryrefslogtreecommitdiff
path: root/src/checkout.c
diff options
context:
space:
mode:
authorcashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
committercashmere <cashmere@autistici.org>2026-09-04 09:05:24 +0200
commit4f49b16079b27d7ce633619e4cddfdf2bffbc952 (patch)
treec296c1e7f3c3e2f6859c6d3a1b1b208779b89679 /src/checkout.c
downloadrelic-4f49b16079b27d7ce633619e4cddfdf2bffbc952.tar.gz
initial commitHEADmaster
Diffstat (limited to 'src/checkout.c')
-rw-r--r--src/checkout.c655
1 files changed, 655 insertions, 0 deletions
diff --git a/src/checkout.c b/src/checkout.c
new file mode 100644
index 0000000..35f8f29
--- /dev/null
+++ b/src/checkout.c
@@ -0,0 +1,655 @@
+/*
+** Copyright (c) 2007 D. Richard Hipp
+**
+** This program is free software; you can redistribute it and/or
+** modify it under the terms of the Simplified BSD License (also
+** known as the "2-Clause License" or "FreeBSD License".)
+
+** This program is distributed in the hope that it will be useful,
+** but without any warranty; without even the implied warranty of
+** merchantability or fitness for a particular purpose.
+**
+** Author contact information:
+** drh@hwaci.com
+** http://www.hwaci.com/drh/
+**
+*******************************************************************************
+**
+** This file contains code used to check-out versions of the project
+** from the local repository.
+*/
+#include "config.h"
+#include "checkout.h"
+#include <assert.h>
+#include <zlib.h>
+
+/*
+** Check to see if there is an existing check-out that has been
+** modified. Return values:
+**
+** 0: There is an existing check-out but it is unmodified
+** 1: There is a modified check-out - there are unsaved changes
+*/
+int unsaved_changes(unsigned int cksigFlags){
+ int vid;
+ db_must_be_within_tree();
+ vid = db_lget_int("checkout",0);
+ vfile_check_signature(vid, cksigFlags|CKSIG_ENOTFILE);
+ return db_exists("SELECT 1 FROM vfile WHERE chnged"
+ " OR coalesce(origname!=pathname,0)");
+}
+
+/*
+** Undo the current check-out. Unlink all files from the disk.
+** Clear the VFILE table.
+**
+** Also delete any directory that becomes empty as a result of deleting
+** files due to this operation, as long as that directory is not the
+** current working directory and is not on the empty-dirs list.
+*/
+void uncheckout(int vid){
+ char *zPwd;
+ if( vid<=0 ) return;
+ sqlite3_create_function(g.db, "dirname",1,SQLITE_UTF8,0,
+ file_dirname_sql_function, 0, 0);
+ sqlite3_create_function(g.db, "unlink",1,SQLITE_UTF8|SQLITE_DIRECTONLY,0,
+ file_delete_sql_function, 0, 0);
+ sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
+ file_rmdir_sql_function, 0, 0);
+ db_multi_exec(
+ "CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID",
+ filename_collation()
+ );
+ db_multi_exec(
+ "INSERT OR IGNORE INTO dir_to_delete(name)"
+ " SELECT dirname(pathname) FROM vfile"
+ " WHERE vid=%d AND mrid>0",
+ vid
+ );
+ do{
+ db_multi_exec(
+ "INSERT OR IGNORE INTO dir_to_delete(name)"
+ " SELECT dirname(name) FROM dir_to_delete;"
+ );
+ }while( db_changes() );
+ db_multi_exec(
+ "SELECT unlink(%Q||pathname) FROM vfile"
+ " WHERE vid=%d AND mrid>0;",
+ g.zLocalRoot, vid
+ );
+ ensure_empty_dirs_created(1);
+ zPwd = file_getcwd(0,0);
+ db_multi_exec(
+ "SELECT rmdir(%Q||name) FROM dir_to_delete"
+ " WHERE (%Q||name)<>%Q ORDER BY name DESC",
+ g.zLocalRoot, g.zLocalRoot, zPwd
+ );
+ fossil_free(zPwd);
+ db_multi_exec("DELETE FROM vfile WHERE vid=%d", vid);
+}
+
+
+/*
+** Given the abbreviated hash of a version, load the content of that
+** version in the VFILE table. Return the VID for the version.
+**
+** If anything goes wrong, panic.
+*/
+int load_vfile(const char *zName, int forceMissingFlag){
+ Blob uuid;
+ int vid;
+
+ blob_init(&uuid, zName, -1);
+ if( name_to_uuid(&uuid, 1, "ci") ){
+ fossil_fatal("%s", g.zErrMsg);
+ }
+ vid = db_int(0, "SELECT rid FROM blob WHERE uuid=%B", &uuid);
+ if( vid==0 ){
+ fossil_fatal("no such check-in: %s", g.argv[2]);
+ }
+ if( !is_a_version(vid) ){
+ fossil_fatal("object [%S] is not a check-in", blob_str(&uuid));
+ }
+ if( load_vfile_from_rid(vid) && !forceMissingFlag ){
+ fossil_fatal("missing content, unable to check out");
+ };
+ return vid;
+}
+
+/*
+** Set or clear the vfile.isexe flag for a file.
+*/
+static void set_or_clear_isexe(const char *zFilename, int vid, int onoff){
+ static Stmt s;
+ db_static_prepare(&s,
+ "UPDATE vfile SET isexe=:isexe"
+ " WHERE vid=:vid AND pathname=:path AND isexe!=:isexe"
+ );
+ db_bind_int(&s, ":isexe", onoff);
+ db_bind_int(&s, ":vid", vid);
+ db_bind_text(&s, ":path", zFilename);
+ db_step(&s);
+ db_reset(&s);
+}
+
+/*
+** Set or clear the execute permission bit (as appropriate) for all
+** files in the current check-out, and replace files that have
+** symlink bit with actual symlinks.
+*/
+void checkout_set_all_exe(int vid){
+ Blob filename;
+ int baseLen;
+ Manifest *pManifest;
+ ManifestFile *pFile;
+
+ /* Check the EXE permission status of all files
+ */
+ pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0);
+ if( pManifest==0 ) return;
+ blob_zero(&filename);
+ blob_appendf(&filename, "%s", g.zLocalRoot);
+ baseLen = blob_size(&filename);
+ manifest_file_rewind(pManifest);
+ while( (pFile = manifest_file_next(pManifest, 0))!=0 ){
+ int isExe;
+ blob_append(&filename, pFile->zName, -1);
+ isExe = pFile->zPerm && strstr(pFile->zPerm, "x");
+ file_setexe(blob_str(&filename), isExe);
+ set_or_clear_isexe(pFile->zName, vid, isExe);
+ blob_resize(&filename, baseLen);
+ }
+ blob_reset(&filename);
+ manifest_destroy(pManifest);
+}
+
+
+/*
+** If the "manifest" setting is true, then automatically generate
+** files named "manifest" and "manifest.uuid" containing, respectively,
+** the text of the manifest and the artifact ID of the manifest.
+** If the manifest setting is set, but is not a boolean value, then treat
+** each character as a flag to enable writing "manifest", "manifest.uuid" or
+** "manifest.tags".
+*/
+void manifest_to_disk(int vid){
+ char *zManFile;
+ int flg;
+
+ flg = db_get_manifest_setting(0);
+
+ if( flg & MFESTFLG_RAW ){
+ Blob manifest = BLOB_INITIALIZER;
+ content_get(vid, &manifest);
+ sterilize_manifest(&manifest, CFTYPE_MANIFEST);
+ zManFile = mprintf("%smanifest", g.zLocalRoot);
+ blob_write_to_file(&manifest, zManFile);
+ free(zManFile);
+ blob_reset(&manifest);
+ }else{
+ if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest'") ){
+ zManFile = mprintf("%smanifest", g.zLocalRoot);
+ file_delete(zManFile);
+ free(zManFile);
+ }
+ }
+ if( flg & MFESTFLG_UUID ){
+ Blob hash;
+ zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
+ blob_set_dynamic(&hash, rid_to_uuid(vid));
+ blob_append(&hash, "\n", 1);
+ blob_write_to_file(&hash, zManFile);
+ free(zManFile);
+ blob_reset(&hash);
+ }else{
+ if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.uuid'") ){
+ zManFile = mprintf("%smanifest.uuid", g.zLocalRoot);
+ file_delete(zManFile);
+ free(zManFile);
+ }
+ }
+ if( flg & MFESTFLG_TAGS ){
+ Blob taglist = BLOB_INITIALIZER;
+ zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
+ get_checkin_taglist(vid, &taglist);
+ blob_write_to_file(&taglist, zManFile);
+ free(zManFile);
+ blob_reset(&taglist);
+ }else{
+ if( !db_exists("SELECT 1 FROM vfile WHERE pathname='manifest.tags'") ){
+ zManFile = mprintf("%smanifest.tags", g.zLocalRoot);
+ file_delete(zManFile);
+ free(zManFile);
+ }
+ }
+}
+
+/*
+** Find the branch name and all symbolic tags for a particular check-in
+** identified by "rid".
+**
+** The branch name is actually only extracted if this procedure is run
+** from within a local check-out. And the branch name is not the branch
+** name for "rid" but rather the branch name for the current check-out.
+** It is unclear if the rid parameter is always the same as the current
+** check-out.
+*/
+void get_checkin_taglist(int rid, Blob *pOut){
+ Stmt stmt;
+ char *zCurrent;
+ blob_reset(pOut);
+ zCurrent = db_text(0, "SELECT value FROM tagxref"
+ " WHERE rid=%d AND tagid=%d", rid, TAG_BRANCH);
+ blob_appendf(pOut, "branch %s\n", zCurrent);
+ db_prepare(&stmt, "SELECT substr(tagname, 5)"
+ " FROM tagxref, tag"
+ " WHERE tagxref.rid=%d"
+ " AND tagxref.tagtype>0"
+ " AND tag.tagid=tagxref.tagid"
+ " AND tag.tagname GLOB 'sym-*'", rid);
+ while( db_step(&stmt)==SQLITE_ROW ){
+ const char *zName;
+ zName = db_column_text(&stmt, 0);
+ blob_appendf(pOut, "tag %s\n", zName);
+ }
+ db_reset(&stmt);
+ db_finalize(&stmt);
+}
+
+
+/*
+** COMMAND: checkout*
+** COMMAND: co#
+**
+** Usage: %fossil checkout ?VERSION | --latest? ?OPTIONS?
+** or: %fossil co ?VERSION | --latest? ?OPTIONS?
+**
+** NOTE: Most people use "fossil update" instead of "fossil checkout" for
+** day-to-day operations. If you are new to Fossil and trying to learn your
+** way around, it is recommended that you become familiar with the
+** "fossil update" command first.
+**
+** This command changes the current check-out to the version specified
+** as an argument. The command aborts if there are edited files in the
+** current check-out unless the --force option is used. The --keep option
+** leaves files on disk unchanged, except the manifest and manifest.uuid
+** files.
+**
+** The --latest flag can be used in place of VERSION to check-out the
+** latest version in the repository.
+**
+** Options:
+** -f|--force Ignore edited files in the current check-out
+** -k|--keep Only update the manifest file(s)
+** --force-missing Force check-out even if content is missing
+** --prompt Prompt before overwriting when --force is used
+** --setmtime Set timestamps of all files to match their SCM-side
+** times (the timestamp of the last check-in which modified
+** them)
+**
+** See also: [[update]]
+*/
+void checkout_cmd(void){
+ int forceFlag; /* Force check-out even if edits exist */
+ int forceMissingFlag; /* Force check-out even if missing content */
+ int keepFlag; /* Do not change any files on disk */
+ int latestFlag; /* Check out the latest version */
+ char *zVers; /* Version to check out */
+ int promptFlag; /* True to prompt before overwriting */
+ int vid, prior;
+ int setmtimeFlag; /* --setmtime. Set mtimes on files */
+ Blob cksum1, cksum1b, cksum2;
+
+ db_must_be_within_tree();
+ db_begin_transaction();
+ forceMissingFlag = find_option("force-missing",0,0)!=0;
+ keepFlag = find_option("keep","k",0)!=0;
+ forceFlag = find_option("force","f",0)!=0;
+ latestFlag = find_option("latest",0,0)!=0;
+ promptFlag = find_option("prompt",0,0)!=0 || forceFlag==0;
+ setmtimeFlag = find_option("setmtime",0,0)!=0;
+
+ if( keepFlag != 0 ){
+ /* After flag collection, in order not to affect promptFlag */
+ forceFlag=1;
+ }
+
+ /* We should be done with options.. */
+ verify_all_options();
+
+ if( (latestFlag!=0 && g.argc!=2) || (latestFlag==0 && g.argc!=3) ){
+ usage("VERSION|--latest ?--force? ?--keep?");
+ }
+ if( !forceFlag && unsaved_changes(0) ){
+ fossil_fatal("there are unsaved changes in the current check-out");
+ }
+ if( forceFlag ){
+ db_multi_exec("DELETE FROM vfile");
+ prior = 0;
+ }else{
+ prior = db_lget_int("checkout",0);
+ }
+ if( latestFlag ){
+ compute_leaves(db_lget_int("checkout",0), 1);
+ zVers = db_text(0, "SELECT uuid FROM leaves, event, blob"
+ " WHERE event.objid=leaves.rid AND blob.rid=leaves.rid"
+ " ORDER BY event.mtime DESC");
+ if( zVers==0 ){
+ zVers = db_text(0, "SELECT uuid FROM event, blob"
+ " WHERE event.objid=blob.rid AND event.type='ci'"
+ " ORDER BY event.mtime DESC");
+ }
+ if( zVers==0 ){
+ db_end_transaction(0);
+ return;
+ }
+ }else{
+ zVers = g.argv[2];
+ }
+ vid = load_vfile(zVers, forceMissingFlag);
+ if( prior==vid ){
+ if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME);
+ db_end_transaction(0);
+ return;
+ }
+ if( !keepFlag ){
+ uncheckout(prior);
+ }
+ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
+ if( !keepFlag ){
+ vfile_to_disk(vid, 0, !g.fQuiet, promptFlag);
+ }
+ checkout_set_all_exe(vid);
+ ensure_empty_dirs_created(0);
+ db_set_checkout(vid, 1);
+ undo_reset();
+ db_multi_exec("DELETE FROM vmerge");
+ if( !keepFlag && db_get_boolean("repo-cksum",1) ){
+ vfile_aggregate_checksum_manifest(vid, &cksum1, &cksum1b);
+ vfile_aggregate_checksum_disk(vid, &cksum2);
+ if( blob_compare(&cksum1, &cksum2) ){
+ fossil_print("WARNING: manifest checksum does not agree with disk\n");
+ }
+ if( blob_size(&cksum1b) && blob_compare(&cksum1, &cksum1b) ){
+ fossil_print("WARNING: manifest checksum does not agree with manifest\n");
+ }
+ }
+ if( setmtimeFlag ) vfile_check_signature(vid, CKSIG_SETMTIME);
+ db_end_transaction(0);
+}
+
+/*
+** Unlink the local database file
+*/
+static void unlink_local_database(int manifestOnly){
+ const char *zReserved;
+ int i;
+ for(i=0; (zReserved = fossil_reserved_name(i, 1))!=0; i++){
+ if( manifestOnly==0 || zReserved[0]=='m' ){
+ char *z;
+ z = mprintf("%s%s", g.zLocalRoot, zReserved);
+ file_delete(z);
+ free(z);
+ }
+ }
+}
+
+/*
+** COMMAND: close*
+**
+** Usage: %fossil close ?OPTIONS?
+**
+** The opposite of "[[open]]". Close the current database connection.
+** Require a -f or --force flag if there are unsaved changes in the
+** current check-out or if there is non-empty stash.
+**
+** Options:
+** -f|--force Necessary to close a check-out with uncommitted changes
+**
+** See also: [[open]]
+*/
+void close_cmd(void){
+ int forceFlag = find_option("force","f",0)!=0;
+ db_must_be_within_tree();
+
+ /* We should be done with options.. */
+ verify_all_options();
+
+ if( !forceFlag && unsaved_changes(0) ){
+ fossil_fatal("there are unsaved changes in the current check-out");
+ }
+ if( !forceFlag
+ && db_table_exists("localdb","stash")
+ && db_exists("SELECT 1 FROM localdb.stash")
+ ){
+ fossil_fatal("closing the check-out will delete your stash");
+ }
+ if( db_is_writeable("repository") ){
+ db_unset_mprintf(1, "ckout:%q", g.zLocalRoot);
+ }
+ unlink_local_database(1);
+ db_close(1);
+ unlink_local_database(0);
+}
+
+
+/*
+** COMMAND: get
+**
+** Usage: %fossil get URL ?VERSION? ?OPTIONS?
+**
+** Download a single check-in from a remote repository named URL and
+** unpack all of the files into a subdirectory. The specific check-in
+** to download is identified by VERSION. If VERSION is omitted, the
+** latest trunk check-in is used. The URL can be a traditional "https:",
+** "ssh:", or "file:" URL similar to the examples shown below, or it can
+** be the name of a local repository/
+**
+** * https://domain.com/project
+** * ssh://my-server/project.fossil
+** * file:/home/user/Fossils/project.fossil
+**
+** This command works by downloading an SQL archive of the requested
+** check-in and then extracting all the files from the archive.
+**
+** Options:
+** --dest DIRECTORY Extract files into DIRECTORY. Use "--dest ." to
+** extract into the local directory. If this option is
+** omitted, Fossil invents a subdirectory name derived
+** from base filename in the URL and from the VERSION.
+** -f|--force Overwrite existing files
+** --list List all the files that would have been checked
+** out but do not actually write anything to the
+** filesystem.
+** --sqlar ARCHIVE Leave the check-out in an SQL-archive named ARCHIVE
+** rather than unpacking into separate files.
+** -v|--verbose Show all files as they are extracted
+*/
+void get_cmd(void){
+ int forceFlag = find_option("force","f",0)!=0;
+ int bVerbose = find_option("verbose","v",0)!=0;
+ int bQuiet = g.fQuiet;
+ int bDebug = find_option("debug",0,0)!=0;
+ int bList = find_option("list",0,0)!=0;
+ const char *zSqlArchive = find_option("sqlar",0,1);
+ const char *z;
+ char *zDest = 0; /* Where to store results */
+ char *zSql; /* SQL used to query the results */
+ const char *zUrl; /* Url to get */
+ const char *zVers; /* Version name to get */
+ unsigned int mHttpFlags = HTTP_GENERIC|HTTP_NOCOMPRESS;
+ Blob in, out; /* I/O for the HTTP request */
+ Blob file; /* A file to extract */
+ sqlite3 *db; /* Database containing downloaded sqlar */
+ sqlite3_stmt *pStmt; /* Statement for querying the database */
+ int rc; /* Result of subroutine calls */
+ int nFile = 0; /* Number of files written */
+ int nDir = 0; /* Number of directories written */
+ i64 nByte = 0; /* Number of bytes written */
+
+ z = find_option("dest",0,1);
+ if( z ) zDest = fossil_strdup(z);
+ verify_all_options();
+ if( g.argc<3 || g.argc>4 ){
+ usage("URL ?VERSION? ?OPTIONS?");
+ }
+ zUrl = g.argv[2];
+ zVers = g.argc==4 ? g.argv[3] : db_main_branch();
+
+ /* Parse the URL of the repository */
+ url_parse(zUrl, 0);
+
+ /* Construct an appropriate name for the destination directory */
+ if( zDest==0 ){
+ int i;
+ const char *zTail;
+ const char *zDot;
+ int n;
+ if( g.url.isFile ){
+ zTail = file_tail(g.url.name);
+ }else{
+ zTail = file_tail(g.url.path);
+ }
+ zDot = strchr(zTail,'.');
+ if( zDot==0 ) zDot = zTail+strlen(zTail);
+ n = (int)(zDot - zTail);
+ zDest = mprintf("%.*s-%s", n, zTail, zVers);
+ for(i=0; zDest[i]; i++){
+ char c = zDest[i];
+ if( !fossil_isalnum(c) && c!='-' && c!='^' && c!='~' && c!='_' ){
+ zDest[i] = '-';
+ }
+ }
+ }
+ if( bDebug ){
+ fossil_print("dest = %s\n", zDest);
+ }
+
+ /* Error checking */
+ if( zDest!=file_tail(zDest) ){
+ fossil_fatal("--dest must be a simple directory name, not a path");
+ }
+ if( zVers!=file_tail(zVers) ){
+ fossil_fatal("The \"fossil get\" command does not currently work with"
+ " version names that contain \"/\". This will be fixed in"
+ " a future release.");
+ }
+ /* To relax the restrictions above, change the subpath URL formula below
+ ** to use query parameters. Ex: /sqlar?r=%t&name=%t */
+
+ if( !forceFlag ){
+ if( zSqlArchive ){
+ if( file_isdir(zSqlArchive, ExtFILE)>0 ){
+ fossil_fatal("file already exists: \"%s\"", zSqlArchive);
+ }
+ }else if( file_isdir(zDest, ExtFILE)>0 ){
+ if( fossil_strcmp(zDest,".")==0 ){
+ if( file_directory_list(zDest,0,1,1,0) ){
+ fossil_fatal("current directory is not empty");
+ }
+ }else{
+ fossil_fatal("\"%s\" already exists", zDest);
+ }
+ }
+ }
+
+ /* Construct a subpath on the URL if necessary */
+ if( g.url.isFile ){
+ g.url.subpath = mprintf("/sqlar/%t/%t.sqlar", zVers, zDest);
+ }else{
+ g.url.subpath = mprintf("%s/sqlar/%t/%t.sqlar", g.url.path, zVers, zDest);
+ }
+
+ if( bDebug ){
+ urlparse_print(0);
+ }
+
+ /* Fetch the ZIP archive for the requested check-in */
+ blob_init(&in, 0, 0);
+ blob_init(&out, 0, 0);
+ if( bDebug ) mHttpFlags |= HTTP_VERBOSE;
+ if( bQuiet ) mHttpFlags |= HTTP_QUIET;
+ rc = http_exchange(&in, &out, mHttpFlags, 4, 0);
+ if( rc
+ || out.nUsed<512
+ || (out.nUsed%512)!=0
+ || memcmp(out.aData,"SQLite format 3",16)!=0
+ ){
+ fossil_fatal("Server did not return the requested check-in.");
+ }
+
+ if( zSqlArchive ){
+ blob_write_to_file(&out, zSqlArchive);
+ if( bVerbose ) fossil_print("%s\n", zSqlArchive);
+ return;
+ }
+
+ rc = sqlite3_open(":memory:", &db);
+ if( rc==SQLITE_OK ){
+ int sz = blob_size(&out);
+ rc = sqlite3_deserialize(db, 0, (unsigned char*)blob_buffer(&out), sz, sz,
+ SQLITE_DESERIALIZE_READONLY);
+ }
+ if( rc!=SQLITE_OK ){
+ fossil_fatal("Cannot create an in-memory database: %s",
+ sqlite3_errmsg(db));
+ }
+ zSql = mprintf("SELECT name, mode, sz, data, mtime FROM sqlar"
+ " WHERE name GLOB '%q*'", zDest);
+ rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0);
+ fossil_free(zSql);
+ if( rc!=0 ){
+ fossil_fatal("SQL error: %s\n", sqlite3_errmsg(db));
+ }
+ blob_init(&file, 0, 0);
+ while( sqlite3_step(pStmt)==SQLITE_ROW ){
+ const char *zFilename = (const char*)sqlite3_column_text(pStmt, 0);
+ int mode = sqlite3_column_int(pStmt, 1);
+ int sz = sqlite3_column_int(pStmt, 2);
+ if( bList ){
+ fossil_print("%s\n", zFilename);
+ }else if( mode & 0x4000 ){
+ /* A directory name */
+ nDir++;
+ file_mkdir(zFilename, ExtFILE, 1);
+ }else{
+ /* A file */
+ unsigned char *inBuf = (unsigned char*)sqlite3_column_blob(pStmt,3);
+ unsigned int nIn = (unsigned int)sqlite3_column_bytes(pStmt,3);
+ unsigned long int nOut2 = (unsigned long int)sz;
+ nFile++;
+ nByte += sz;
+ blob_resize(&file, sz);
+ if( nIn<sz ){
+ rc = uncompress((unsigned char*)blob_buffer(&file), &nOut2,
+ inBuf, nIn);
+ if( rc!=Z_OK ){
+ fossil_fatal("Failed to uncompress file %s", zFilename);
+ }
+ }else{
+ memcpy(blob_buffer(&file), inBuf, sz);
+ }
+ blob_write_to_file(&file, zFilename);
+ if( mode & 0x40 ){
+ file_setexe(zFilename, 1);
+ }
+ blob_zero(&file);
+ file_set_mtime(zFilename, sqlite3_column_int64(pStmt,4));
+ if( bVerbose ){
+ fossil_print("%s\n", zFilename);
+ }
+ }
+ }
+ sqlite3_finalize(pStmt);
+ sqlite3_close(db);
+ blob_zero(&out);
+ if( !bVerbose && !bQuiet && nFile>0 && zDest ){
+ fossil_print("%d files (%,lld bytes) written into %s",
+ nFile, nByte, zDest);
+ if( nDir>1 ){
+ fossil_print(" and %d subdirectories\n", nDir-1);
+ }else{
+ fossil_print("\n");
+ }
+ }
+}