/* ** COMMAND: test-uncompress */ void uncompress_cmd(void){ Blob f; if( g.argc!=4 ) usage("INPUTFILE OUTPUTFILE"); blob_read_from_file(&f, g.argv[2]); blob_uncompress(&f, &f); blob_write_to_file(&f, g.argv[3]); }
/* ** Clearsign the given blob. Put the signed version in ** pOut. */ int clearsign(Blob *pIn, Blob *pOut){ char *zRand; char *zIn; char *zOut; char *zBase = db_get("pgp-command", "gpg --clearsign -o "); char *zCmd; int rc; if( is_false(zBase) ){ return 0; } zRand = db_text(0, "SELECT hex(randomblob(10))"); zOut = mprintf("out-%s", zRand); zIn = mprintf("in-%z", zRand); blob_write_to_file(pIn, zOut); zCmd = mprintf("%s %s %s", zBase, zIn, zOut); rc = fossil_system(zCmd); free(zCmd); if( rc==0 ){ if( pOut==pIn ){ blob_reset(pIn); } blob_zero(pOut); blob_read_from_file(pOut, zIn); }else{ if( pOut!=pIn ){ blob_copy(pOut, pIn); } } file_delete(zOut); file_delete(zIn); free(zOut); free(zIn); return rc; }
/* ** COMMAND: tarball* ** ** Usage: %fossil tarball VERSION OUTPUTFILE [--name DIRECTORYNAME] [-R|--repository REPO] ** ** Generate a compressed tarball for a specified version. If the --name ** option is used, its argument becomes the name of the top-level directory ** in the resulting tarball. If --name is omitted, the top-level directory ** named is derived from the project name, the check-in date and time, and ** the artifact ID of the check-in. */ void tarball_cmd(void){ int rid; Blob tarball; const char *zName; zName = find_option("name", 0, 1); db_find_and_open_repository(0, 0); if( g.argc!=4 ){ usage("VERSION OUTPUTFILE"); } rid = name_to_typed_rid(g.argv[2], "ci"); if( rid==0 ){ fossil_fatal("Checkin not found: %s", g.argv[2]); return; } if( zName==0 ){ zName = db_text("default-name", "SELECT replace(%Q,' ','_') " " || strftime('_%%Y-%%m-%%d_%%H%%M%%S_', event.mtime) " " || substr(blob.uuid, 1, 10)" " FROM event, blob" " WHERE event.objid=%d" " AND blob.rid=%d", db_get("project-name", "unnamed"), rid, rid ); } tarball_of_checkin(rid, &tarball, zName); blob_write_to_file(&tarball, g.argv[3]); blob_reset(&tarball); }
/* ** COMMAND: test-compress-2 */ void compress2_cmd(void){ Blob f1, f2; if( g.argc!=5 ) usage("INPUTFILE1 INPUTFILE2 OUTPUTFILE"); blob_read_from_file(&f1, g.argv[2]); blob_read_from_file(&f2, g.argv[3]); blob_compress2(&f1, &f2, &f1); blob_write_to_file(&f1, g.argv[4]); }
/* ** Show the difference between two files, both in memory. ** ** The difference is the set of edits needed to transform pFile1 into ** pFile2. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ static void diff_file_mem( Blob *pFile1, /* In memory content to compare from */ Blob *pFile2, /* In memory content to compare to */ const char *zName, /* Display name of the file */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ blob_zero(&out); text_diff(pFile1, pFile2, &out, 5, ignoreEolWs); printf("--- %s\n+++ %s\n", zName, zName); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&out); }else{ Blob cmd; char zTemp1[300]; char zTemp2[300]; /* Construct a temporary file names */ file_tempname(sizeof(zTemp1), zTemp1); file_tempname(sizeof(zTemp2), zTemp2); blob_write_to_file(pFile1, zTemp1); blob_write_to_file(pFile2, zTemp2); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, zTemp1); blob_append(&cmd, " ", 1); shell_escape(&cmd, zTemp2); /* Run the external diff command */ portable_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(zTemp1); unlink(zTemp2); blob_reset(&cmd); } }
/* ** Show the difference between two files, one in memory and one on disk. ** ** The difference is the set of edits needed to transform pFile1 into ** zFile2. The content of pFile1 is in memory. zFile2 exists on disk. ** ** Use the internal diff logic if zDiffCmd is NULL. Otherwise call the ** command zDiffCmd to do the diffing. */ static void diff_file( Blob *pFile1, /* In memory content to compare from */ const char *zFile2, /* On disk content to compare to */ const char *zName, /* Display name of the file */ const char *zDiffCmd, /* Command for comparison */ int ignoreEolWs /* Ignore whitespace at end of lines */ ){ if( zDiffCmd==0 ){ Blob out; /* Diff output text */ Blob file2; /* Content of zFile2 */ /* Read content of zFile2 into memory */ blob_zero(&file2); blob_read_from_file(&file2, zFile2); /* Compute and output the differences */ blob_zero(&out); //text_diff(pFile1, &file2, &out, 5, ignoreEolWs); csvs_diff(pFile1, &file2, &out); printf("--- %s\n+++ %s\n", zName, zName); printf("%s\n", blob_str(&out)); /* Release memory resources */ blob_reset(&file2); blob_reset(&out); }else{ int cnt = 0; Blob nameFile1; /* Name of temporary file to old pFile1 content */ Blob cmd; /* Text of command to run */ /* Construct a temporary file to hold pFile1 based on the name of ** zFile2 */ blob_zero(&nameFile1); do{ blob_reset(&nameFile1); blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++); }while( access(blob_str(&nameFile1),0)==0 ); blob_write_to_file(pFile1, blob_str(&nameFile1)); /* Construct the external diff command */ blob_zero(&cmd); blob_appendf(&cmd, "%s ", zDiffCmd); shell_escape(&cmd, blob_str(&nameFile1)); blob_append(&cmd, " ", 1); shell_escape(&cmd, zFile2); /* Run the external diff command */ portable_system(blob_str(&cmd)); /* Delete the temporary file and clean up memory used */ unlink(blob_str(&nameFile1)); blob_reset(&nameFile1); blob_reset(&cmd); } }
/* ** Write a BLOB into a random filename. Return the name of the file. */ char *write_blob_to_temp_file(Blob *pBlob){ sqlite3_uint64 r; char *zOut = 0; do{ sqlite3_free(zOut); sqlite3_randomness(8, &r); zOut = sqlite3_mprintf("file-%08llx", r); }while( file_size(zOut)>=0 ); blob_write_to_file(pBlob, zOut); return zOut; }
/* ** COMMAND: test-content-rawget ** ** Extract a blob from the database and write it into a file. This ** version does not expand the delta. */ void test_content_rawget_cmd(void){ int rid; Blob content; const char *zFile; if( g.argc!=4 && g.argc!=3 ) usage("RECORDID ?FILENAME?"); zFile = g.argc==4 ? g.argv[3] : "-"; db_must_be_within_tree(); rid = name_to_rid(g.argv[2]); blob_zero(&content); db_blob(&content, "SELECT content FROM blob WHERE rid=%d", rid); blob_uncompress(&content, &content); blob_write_to_file(&content, zFile); }
/* ** COMMAND: artifact* ** ** Usage: %fossil artifact ARTIFACT-ID ?OUTPUT-FILENAME? ?OPTIONS? ** ** Extract an artifact by its SHA1 hash and write the results on ** standard output, or if the optional 4th argument is given, in ** the named output file. ** ** Options: ** -R|--repository FILE Extract artifacts from repository FILE ** ** See also: finfo */ void artifact_cmd(void){ int rid; Blob content; const char *zFile; db_find_and_open_repository(OPEN_ANY_SCHEMA, 0); if( g.argc!=4 && g.argc!=3 ) usage("ARTIFACT-ID ?FILENAME? ?OPTIONS?"); zFile = g.argc==4 ? g.argv[3] : "-"; rid = name_to_rid(g.argv[2]); if( rid==0 ){ fossil_fatal("%s",g.zErrMsg); } content_get(rid, &content); blob_write_to_file(&content, zFile); }
/* fossil bundle cat BUNDLE UUID... ** ** Write elements of a bundle on standard output */ static void bundle_cat_cmd(void){ int i; Blob x; verify_all_options(); if( g.argc<5 ) usage("cat BUNDLE UUID..."); bundle_attach_file(g.argv[3], "b1", 1); blob_zero(&x); for(i=4; i<g.argc; i++){ int blobid = db_int(0,"SELECT blobid FROM bblob WHERE uuid LIKE '%q%%'", g.argv[i]); if( blobid==0 ){ fossil_fatal("no such artifact in bundle: %s", g.argv[i]); } bundle_extract_item(blobid, &x); blob_write_to_file(&x, "-"); blob_reset(&x); } }
/* ** Write SQL text into file zFilename that will restore the configuration ** area identified by mask to its current state from any other state. */ static void export_config( int groupMask, /* Mask indicating which configuration to export */ const char *zMask, /* Name of the configuration */ sqlite3_int64 iStart, /* Start date */ const char *zFilename /* Write into this file */ ) { Blob out; blob_zero(&out); blob_appendf(&out, "# The \"%s\" configuration exported from\n" "# repository \"%s\"\n" "# on %s\n", zMask, g.zRepositoryName, db_text(0, "SELECT datetime('now')") ); configure_send_group(&out, groupMask, iStart); blob_write_to_file(&out, zFilename); blob_reset(&out); }
/* ** COMMAND: test-tarball ** ** Generate a GZIP-compressed tarball in the file given by the first argument ** that contains files given in the second and subsequent arguments. */ void test_tarball_cmd(void){ int i; Blob zip; Blob file; if( g.argc<3 ){ usage("ARCHIVE FILE...."); } sqlite3_open(":memory:", &g.db); tar_begin(0); for(i=3; i<g.argc; i++){ blob_zero(&file); blob_read_from_file(&file, g.argv[i]); tar_add_file(g.argv[i], &file, file_wd_perm(g.argv[i]), file_wd_mtime(g.argv[i])); blob_reset(&file); } tar_finish(&zip); blob_write_to_file(&zip, g.argv[2]); }
/* ** Create symlink to file on Unix, or plain-text file with ** symlink target if "allow-symlinks" is off or we're on Windows. ** ** Arguments: target file (symlink will point to it), link file **/ void symlink_create(const char *zTargetFile, const char *zLinkFile) { #if !defined(_WIN32) if( g.allowSymlinks ) { int i, nName; char *zName, zBuf[1000]; nName = strlen(zLinkFile); if( nName>=sizeof(zBuf) ) { zName = mprintf("%s", zLinkFile); } else { zName = zBuf; memcpy(zName, zLinkFile, nName+1); } nName = file_simplify_name(zName, nName); for(i=1; i<nName; i++) { if( zName[i]=='/' ) { zName[i] = 0; if( file_mkdir(zName, 1) ) { fossil_fatal_recursive("unable to create directory %s", zName); return; } zName[i] = '/'; } } if( zName!=zBuf ) free(zName); if( symlink(zTargetFile, zName)!=0 ) { fossil_fatal_recursive("unable to create symlink \"%s\"", zName); } } else #endif { Blob content; blob_set(&content, zTargetFile); blob_write_to_file(&content, zLinkFile); blob_reset(&content); } }
/* ** COMMAND: cat ** ** Usage: %fossil cat FILENAME ... ?OPTIONS? ** ** Print on standard output the content of one or more files as they exist ** in the repository. The version currently checked out is shown by default. ** Other versions may be specified using the -r option. ** ** Options: ** -R|--repository FILE Extract artifacts from repository FILE ** -r VERSION The specific check-in containing the file ** ** See also: finfo */ void cat_cmd(void){ int i; int rc; Blob content, fname; const char *zRev; db_find_and_open_repository(0, 0); zRev = find_option("r","r",1); /* We should be done with options.. */ verify_all_options(); for(i=2; i<g.argc; i++){ file_tree_name(g.argv[i], &fname, 0, 1); blob_zero(&content); rc = historical_version_of_file(zRev, blob_str(&fname), &content, 0,0,0,2); if( rc==2 ){ fossil_fatal("no such file: %s", g.argv[i]); } blob_write_to_file(&content, "-"); blob_reset(&fname); blob_reset(&content); } }
/* ** COMMAND: finfo ** ** Usage: %fossil finfo ?OPTIONS? FILENAME ** ** Print the complete change history for a single file going backwards ** in time. The default is -l. ** ** For the -l|--log option: If "-b|--brief" is specified one line per revision ** is printed, otherwise the full comment is printed. The "--limit N" ** and "--offset P" options limits the output to the first N changes ** after skipping P changes. ** ** In the -s form prints the status as <status> <revision>. This is ** a quick status and does not check for up-to-date-ness of the file. ** ** In the -p form, there's an optional flag "-r|--revision REVISION". ** The specified version (or the latest checked out version) is printed ** to stdout. ** ** Options: ** --brief|-b display a brief (one line / revision) summary ** --limit N display the first N changes ** --log|-l select log mode (the default) ** --offset P skip P changes ** -p select print mode ** --revision|-r R print the given revision (or ckout, if none is given) ** to stdout (only in print mode) ** -s select status mode (print a status indicator for FILE) ** --case-sensitive B Enable or disable case-sensitive filenames. B is a ** boolean: "yes", "no", "true", "false", etc. ** ** See also: artifact, descendants, info, leaves */ void finfo_cmd(void) { capture_case_sensitive_option(); db_must_be_within_tree(); if (find_option("status","s",0)) { Stmt q; Blob line; Blob fname; int vid; if( g.argc!=3 ) usage("-s|--status FILENAME"); vid = db_lget_int("checkout", 0); if( vid==0 ) { fossil_panic("no checkout to finfo files in"); } vfile_check_signature(vid, 1, 0); file_tree_name(g.argv[2], &fname, 1); db_prepare(&q, "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)" " FROM vfile WHERE vfile.pathname=%B %s", &fname, filename_collation()); blob_zero(&line); if ( db_step(&q)==SQLITE_ROW ) { Blob uuid; int isDeleted = db_column_int(&q, 1); int isNew = db_column_int(&q,2) == 0; int chnged = db_column_int(&q,3); int renamed = db_column_int(&q,4); blob_zero(&uuid); db_blob(&uuid, "SELECT uuid FROM blob, mlink, vfile WHERE " "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND " "vfile.pathname=%B %s", &fname, filename_collation() ); if( isNew ) { blob_appendf(&line, "new"); } else if( isDeleted ) { blob_appendf(&line, "deleted"); } else if( renamed ) { blob_appendf(&line, "renamed"); } else if( chnged ) { blob_appendf(&line, "edited"); } else { blob_appendf(&line, "unchanged"); } blob_appendf(&line, " "); blob_appendf(&line, " %10.10s", blob_str(&uuid)); blob_reset(&uuid); } else { blob_appendf(&line, "unknown 0000000000"); } db_finalize(&q); fossil_print("%s\n", blob_str(&line)); blob_reset(&fname); blob_reset(&line); } else if( find_option("print","p",0) ) { Blob record; Blob fname; const char *zRevision = find_option("revision", "r", 1); file_tree_name(g.argv[2], &fname, 1); if( zRevision ) { historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0); } else { int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", &fname, filename_collation()); if( rid==0 ) { fossil_fatal("no history for file: %b", &fname); } content_get(rid, &record); } blob_write_to_file(&record, "-"); blob_reset(&record); blob_reset(&fname); } else { Blob line; Stmt q; Blob fname; int rid; const char *zFilename; const char *zLimit; const char *zOffset; int iLimit, iOffset, iBrief; if( find_option("log","l",0) ) { /* this is the default, no-op */ } zLimit = find_option("limit",0,1); iLimit = zLimit ? atoi(zLimit) : -1; zOffset = find_option("offset",0,1); iOffset = zOffset ? atoi(zOffset) : 0; iBrief = (find_option("brief","b",0) == 0); if( g.argc!=3 ) { usage("?-l|--log? ?-b|--brief? FILENAME"); } file_tree_name(g.argv[2], &fname, 1); rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", &fname, filename_collation()); if( rid==0 ) { fossil_fatal("no history for file: %b", &fname); } zFilename = blob_str(&fname); db_prepare(&q, "SELECT b.uuid, ci.uuid, date(event.mtime,'localtime')," " coalesce(event.ecomment, event.comment)," " coalesce(event.euser, event.user)" " FROM mlink, blob b, event, blob ci, filename" " WHERE filename.name=%Q %s" " AND mlink.fnid=filename.fnid" " AND b.rid=mlink.fid" " AND event.objid=mlink.mid" " AND event.objid=ci.rid" " ORDER BY event.mtime DESC LIMIT %d OFFSET %d", zFilename, filename_collation(), iLimit, iOffset ); blob_zero(&line); if( iBrief ) { fossil_print("History of %s\n", blob_str(&fname)); } while( db_step(&q)==SQLITE_ROW ) { const char *zFileUuid = db_column_text(&q, 0); const char *zCiUuid = db_column_text(&q,1); const char *zDate = db_column_text(&q, 2); const char *zCom = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 4); char *zOut; if( iBrief ) { fossil_print("%s ", zDate); zOut = sqlite3_mprintf("[%.10s] %s (user: %s, artifact: [%.10s])", zCiUuid, zCom, zUser, zFileUuid); comment_print(zOut, 11, 79); sqlite3_free(zOut); } else { blob_reset(&line); blob_appendf(&line, "%.10s ", zCiUuid); blob_appendf(&line, "%.10s ", zDate); blob_appendf(&line, "%8.8s ", zUser); blob_appendf(&line,"%-40.40s\n", zCom ); comment_print(blob_str(&line), 0, 79); } } db_finalize(&q); blob_reset(&fname); } }
/* ** Rebuild cross-referencing information for the artifact ** rid with content pBase and all of its descendants. This ** routine clears the content buffer before returning. ** ** If the zFNameFormat variable is set, then this routine is ** called to run "fossil deconstruct" instead of the usual ** "fossil rebuild". In that case, instead of rebuilding the ** cross-referencing information, write the file content out ** to the appropriate directory. ** ** In both cases, this routine automatically recurses to process ** other artifacts that are deltas off of the current artifact. ** This is the most efficient way to extract all of the original ** artifact content from the Fossil repository. */ static void rebuild_step(int rid, int size, Blob *pBase){ static Stmt q1; Bag children; Blob copy; Blob *pUse; int nChild, i, cid; while( rid>0 ){ /* Fix up the "blob.size" field if needed. */ if( size!=blob_size(pBase) ){ db_multi_exec( "UPDATE blob SET size=%d WHERE rid=%d", blob_size(pBase), rid ); } /* Find all children of artifact rid */ db_static_prepare(&q1, "SELECT rid FROM delta WHERE srcid=:rid"); db_bind_int(&q1, ":rid", rid); bag_init(&children); while( db_step(&q1)==SQLITE_ROW ){ int cid = db_column_int(&q1, 0); if( !bag_find(&bagDone, cid) ){ bag_insert(&children, cid); } } nChild = bag_count(&children); db_reset(&q1); /* Crosslink the artifact */ if( nChild==0 ){ pUse = pBase; }else{ blob_copy(©, pBase); pUse = © } if( zFNameFormat==0 ){ /* We are doing "fossil rebuild" */ manifest_crosslink(rid, pUse, MC_NONE); }else{ /* We are doing "fossil deconstruct" */ char *zUuid = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", rid); char *zFile = mprintf(zFNameFormat, zUuid, zUuid+prefixLength); blob_write_to_file(pUse,zFile); free(zFile); free(zUuid); blob_reset(pUse); } assert( blob_is_reset(pUse) ); rebuild_step_done(rid); /* Call all children recursively */ rid = 0; for(cid=bag_first(&children), i=1; cid; cid=bag_next(&children, cid), i++){ static Stmt q2; int sz; db_static_prepare(&q2, "SELECT content, size FROM blob WHERE rid=:rid"); db_bind_int(&q2, ":rid", cid); if( db_step(&q2)==SQLITE_ROW && (sz = db_column_int(&q2,1))>=0 ){ Blob delta, next; db_ephemeral_blob(&q2, 0, &delta); blob_uncompress(&delta, &delta); blob_delta_apply(pBase, &delta, &next); blob_reset(&delta); db_reset(&q2); if( i<nChild ){ rebuild_step(cid, sz, &next); }else{ /* Tail recursion */ rid = cid; size = sz; blob_reset(pBase); *pBase = next; } }else{ db_reset(&q2); blob_reset(pBase); } } bag_clear(&children); } }
/* ** Write all files from vid to the disk. Or if vid==0 and id!=0 ** write just the specific file where VFILE.ID=id. */ void vfile_to_disk( int vid, /* vid to write to disk */ int id, /* Write this one file, if not zero */ int verbose, /* Output progress information */ int promptFlag /* Prompt user to confirm overwrites */ ){ Stmt q; Blob content; int nRepos = strlen(g.zLocalRoot); if( vid>0 && id==0 ){ db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " FROM vfile" " WHERE vid=%d AND mrid>0", g.zLocalRoot, vid); }else{ assert( vid==0 && id>0 ); db_prepare(&q, "SELECT id, %Q || pathname, mrid, isexe, islink" " FROM vfile" " WHERE id=%d AND mrid>0", g.zLocalRoot, id); } while( db_step(&q)==SQLITE_ROW ){ int id, rid, isExe, isLink; const char *zName; id = db_column_int(&q, 0); zName = db_column_text(&q, 1); rid = db_column_int(&q, 2); isExe = db_column_int(&q, 3); isLink = db_column_int(&q, 4); content_get(rid, &content); if( file_is_the_same(&content, zName) ){ blob_reset(&content); if( file_wd_setexe(zName, isExe) ){ db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } continue; } if( promptFlag && file_wd_size(zName)>=0 ){ Blob ans; char *zMsg; char cReply; zMsg = mprintf("overwrite %s (a=always/y/N)? ", zName); prompt_user(zMsg, &ans); free(zMsg); cReply = blob_str(&ans)[0]; blob_reset(&ans); if( cReply=='a' || cReply=='A' ){ promptFlag = 0; cReply = 'y'; } if( cReply=='n' || cReply=='N' ){ blob_reset(&content); continue; } } if( verbose ) vcs_print("%s\n", &zName[nRepos]); if( file_wd_isdir(zName) == 1 ){ /*TODO(dchest): remove directories? */ vcs_fatal("%s is directory, cannot overwrite\n", zName); } if( file_wd_size(zName)>=0 && (isLink || file_wd_islink(zName)) ){ file_delete(zName); } if( isLink ){ symlink_create(blob_str(&content), zName); }else{ blob_write_to_file(&content, zName); } file_wd_setexe(zName, isExe); blob_reset(&content); db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d", file_wd_mtime(zName), id); } db_finalize(&q); }
/* ** COMMAND: merge ** ** Usage: %fossil merge ?OPTIONS? ?VERSION? ** ** The argument VERSION is a version that should be merged into the ** current checkout. All changes from VERSION back to the nearest ** common ancestor are merged. Except, if either of the --cherrypick or ** --backout options are used only the changes associated with the ** single check-in VERSION are merged. The --backout option causes ** the changes associated with VERSION to be removed from the current ** checkout rather than added. ** ** If the VERSION argument is omitted, then Fossil attempts to find ** a recent fork on the current branch to merge. ** ** Only file content is merged. The result continues to use the ** file and directory names from the current checkout even if those ** names might have been changed in the branch being merged in. ** ** Other options: ** ** --baseline BASELINE Use BASELINE as the "pivot" of the merge instead ** of the nearest common ancestor. This allows ** a sequence of changes in a branch to be merged ** without having to merge the entire branch. ** ** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary ** and do not try to merge parallel changes. This ** option overrides the "binary-glob" setting. ** ** --case-sensitive BOOL Override the case-sensitive setting. If false, ** files whose names differ only in case are taken ** to be the same file. ** ** -f|--force Force the merge even if it would be a no-op. ** ** --force-missing Force the merge even if there is missing content. ** ** --integrate Merged branch will be closed when committing. ** ** -n|--dry-run If given, display instead of run actions ** ** -v|--verbose Show additional details of the merge */ void merge_cmd(void){ int vid; /* Current version "V" */ int mid; /* Version we are merging from "M" */ int pid; /* The pivot version - most recent common ancestor P */ int verboseFlag; /* True if the -v|--verbose option is present */ int integrateFlag; /* True if the --integrate option is present */ int pickFlag; /* True if the --cherrypick option is present */ int backoutFlag; /* True if the --backout option is present */ int dryRunFlag; /* True if the --dry-run or -n option is present */ int forceFlag; /* True if the --force or -f option is present */ int forceMissingFlag; /* True if the --force-missing option is present */ const char *zBinGlob; /* The value of --binary */ const char *zPivot; /* The value of --baseline */ int debugFlag; /* True if --debug is present */ int nChng; /* Number of file name changes */ int *aChng; /* An array of file name changes */ int i; /* Loop counter */ int nConflict = 0; /* Number of conflicts seen */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ Stmt q; /* Notation: ** ** V The current checkout ** M The version being merged in ** P The "pivot" - the most recent common ancestor of V and M. */ undo_capture_command_line(); verboseFlag = find_option("verbose","v",0)!=0; forceMissingFlag = find_option("force-missing",0,0)!=0; if( !verboseFlag ){ verboseFlag = find_option("detail",0,0)!=0; /* deprecated */ } pickFlag = find_option("cherrypick",0,0)!=0; integrateFlag = find_option("integrate",0,0)!=0; backoutFlag = find_option("backout",0,0)!=0; debugFlag = find_option("debug",0,0)!=0; zBinGlob = find_option("binary",0,1); dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ){ dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } forceFlag = find_option("force","f",0)!=0; zPivot = find_option("baseline",0,1); verify_all_options(); db_must_be_within_tree(); if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_fatal("nothing is checked out"); } /* Find mid, the artifactID of the version to be merged into the current ** check-out */ if( g.argc==3 ){ /* Mid is specified as an argument on the command-line */ mid = name_to_typed_rid(g.argv[2], "ci"); if( mid==0 || !is_a_version(mid) ){ fossil_fatal("not a version: %s", g.argv[2]); } }else if( g.argc==2 ){ /* No version specified on the command-line so pick the most recent ** leaf that is (1) not the version currently checked out and (2) ** has not already been merged into the current checkout and (3) ** the leaf is not closed and (4) the leaf is in the same branch ** as the current checkout. */ Stmt q; if( pickFlag || backoutFlag || integrateFlag){ fossil_fatal("cannot use --backout, --cherrypick or --integrate with a fork merge"); } mid = db_int(0, "SELECT leaf.rid" " FROM leaf, event" " WHERE leaf.rid=event.objid" " AND leaf.rid!=%d" /* Constraint (1) */ " AND leaf.rid NOT IN (SELECT merge FROM vmerge)" /* Constraint (2) */ " AND NOT EXISTS(SELECT 1 FROM tagxref" /* Constraint (3) */ " WHERE rid=leaf.rid" " AND tagid=%d" " AND tagtype>0)" " AND (SELECT value FROM tagxref" /* Constraint (4) */ " WHERE tagid=%d AND rid=%d AND tagtype>0) =" " (SELECT value FROM tagxref" " WHERE tagid=%d AND rid=leaf.rid AND tagtype>0)" " ORDER BY event.mtime DESC LIMIT 1", vid, TAG_CLOSED, TAG_BRANCH, vid, TAG_BRANCH ); if( mid==0 ){ fossil_fatal("no unmerged forks of branch \"%s\"", db_text(0, "SELECT value FROM tagxref" " WHERE tagid=%d AND rid=%d AND tagtype>0", TAG_BRANCH, vid) ); } db_prepare(&q, "SELECT blob.uuid," " datetime(event.mtime%s)," " coalesce(ecomment, comment)," " coalesce(euser, user)" " FROM event, blob" " WHERE event.objid=%d AND blob.rid=%d", timeline_utc(), mid, mid ); if( db_step(&q)==SQLITE_ROW ){ char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"", db_column_text(&q, 0), db_column_text(&q, 1), db_column_text(&q, 3), db_column_text(&q, 2)); comment_print(zCom, db_column_text(&q,2), 0, -1, g.comFmtFlags); fossil_free(zCom); } db_finalize(&q); }else{ usage("?OPTIONS? ?VERSION?"); return; } if( zPivot ){ pid = name_to_typed_rid(zPivot, "ci"); if( pid==0 || !is_a_version(pid) ){ fossil_fatal("not a version: %s", zPivot); } if( pickFlag ){ fossil_fatal("incompatible options: --cherrypick & --baseline"); } }else if( pickFlag || backoutFlag ){ if( integrateFlag ){ fossil_fatal("incompatible options: --integrate & --cherrypick or --backout"); } pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); if( pid<=0 ){ fossil_fatal("cannot find an ancestor for %s", g.argv[2]); } }else{ pivot_set_primary(mid); pivot_set_secondary(vid); db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); while( db_step(&q)==SQLITE_ROW ){ pivot_set_secondary(db_column_int(&q,0)); } db_finalize(&q); pid = pivot_find(); if( pid<=0 ){ fossil_fatal("cannot find a common ancestor between the current " "checkout and %s", g.argv[2]); } } if( backoutFlag ){ int t = pid; pid = mid; mid = t; } if( !is_a_version(pid) ){ fossil_fatal("not a version: record #%d", pid); } if( !forceFlag && mid==pid ){ fossil_print("Merge skipped because it is a no-op. " " Use --force to override.\n"); return; } if( integrateFlag && !is_a_leaf(mid)){ fossil_warning("ignoring --integrate: %s is not a leaf", g.argv[2]); integrateFlag = 0; } if( verboseFlag ){ print_checkin_description(mid, 12, integrateFlag?"integrate:":"merge-from:"); print_checkin_description(pid, 12, "baseline:"); } vfile_check_signature(vid, CKSIG_ENOTFILE); db_begin_transaction(); if( !dryRunFlag ) undo_begin(); if( load_vfile_from_rid(mid) && !forceMissingFlag ){ fossil_fatal("missing content, unable to merge"); } if( load_vfile_from_rid(pid) && !forceMissingFlag ){ fossil_fatal("missing content, unable to merge"); } if( debugFlag ){ char *z; z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid); fossil_print("P=%d %z\n", pid, z); z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); fossil_print("M=%d %z\n", mid, z); z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); fossil_print("V=%d %z\n", vid, z); } /* ** The vfile.pathname field is used to match files against each other. The ** FV table contains one row for each each unique filename in ** in the current checkout, the pivot, and the version being merged. */ db_multi_exec( "DROP TABLE IF EXISTS fv;" "CREATE TEMP TABLE fv(" " fn TEXT PRIMARY KEY %s," /* The filename */ " idv INTEGER," /* VFILE entry for current version */ " idp INTEGER," /* VFILE entry for the pivot */ " idm INTEGER," /* VFILE entry for version merging in */ " chnged BOOLEAN," /* True if current version has been edited */ " ridv INTEGER," /* Record ID for current version */ " ridp INTEGER," /* Record ID for pivot */ " ridm INTEGER," /* Record ID for merge */ " isexe BOOLEAN," /* Execute permission enabled */ " fnp TEXT %s," /* The filename in the pivot */ " fnm TEXT %s," /* the filename in the merged version */ " islinkv BOOLEAN," /* True if current version is a symlink */ " islinkm BOOLEAN" /* True if merged version in is a symlink */ ");", filename_collation(), filename_collation(), filename_collation() ); /* Add files found in V */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, id, 0, 0, rid, 0, 0, isexe, chnged " " FROM vfile WHERE vid=%d", vid ); /* ** Compute name changes from P->V */ find_filename_changes(pid, vid, 0, &nChng, &aChng, debugFlag ? "P->V" : 0); if( nChng ){ for(i=0; i<nChng; i++){ char *z; z = db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i*2]); db_multi_exec( "UPDATE fv SET fnp=%Q, fnm=%Q" " WHERE fn=(SELECT name FROM filename WHERE fnid=%d)", z, z, aChng[i*2+1] ); free(z); } fossil_free(aChng); db_multi_exec("UPDATE fv SET fnm=fnp WHERE fnp!=fn"); } /* Add files found in P but not in V */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d AND pathname %s NOT IN (SELECT fnp FROM fv)", pid, filename_collation() ); /* ** Compute name changes from P->M */ find_filename_changes(pid, mid, 0, &nChng, &aChng, debugFlag ? "P->M" : 0); if( nChng ){ if( nChng>4 ) db_multi_exec("CREATE INDEX fv_fnp ON fv(fnp)"); for(i=0; i<nChng; i++){ db_multi_exec( "UPDATE fv SET fnm=(SELECT name FROM filename WHERE fnid=%d)" " WHERE fnp=(SELECT name FROM filename WHERE fnid=%d)", aChng[i*2+1], aChng[i*2] ); } fossil_free(aChng); } /* Add files found in M but not in P or V. */ db_multi_exec( "INSERT OR IGNORE" " INTO fv(fn,fnp,fnm,idv,idp,idm,ridv,ridp,ridm,isexe,chnged)" " SELECT pathname, pathname, pathname, 0, 0, 0, 0, 0, 0, isexe, 0 " " FROM vfile" " WHERE vid=%d" " AND pathname %s NOT IN (SELECT fnp FROM fv UNION SELECT fnm FROM fv)", mid, filename_collation() ); /* ** Compute the file version ids for P and M. */ db_multi_exec( "UPDATE fv SET" " idp=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnp=pathname),0)," " ridp=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnp=pathname),0)," " idm=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnm=pathname),0)," " ridm=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnm=pathname),0)," " islinkv=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnm=pathname),0)," " islinkm=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnm=pathname),0)", pid, pid, mid, mid, vid, mid ); if( debugFlag ){ db_prepare(&q, "SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, " " isexe, islinkv, islinkm FROM fv" ); while( db_step(&q)==SQLITE_ROW ){ fossil_print("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d " " islinkv=%d islinkm=%d\n", db_column_int(&q, 0), db_column_int(&q, 5), db_column_int(&q, 6), db_column_int(&q, 7), db_column_int(&q, 4), db_column_int(&q, 8), db_column_int(&q, 9), db_column_int(&q, 10)); fossil_print(" fn = [%s]\n", db_column_text(&q, 1)); fossil_print(" fnp = [%s]\n", db_column_text(&q, 2)); fossil_print(" fnm = [%s]\n", db_column_text(&q, 3)); } db_finalize(&q); } /* ** Find files in M and V but not in P and report conflicts. ** The file in M will be ignored. It will be treated as if it ** does not exist. */ db_prepare(&q, "SELECT idm FROM fv WHERE idp=0 AND idv>0 AND idm>0" ); while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); char *zName = db_text(0, "SELECT pathname FROM vfile WHERE id=%d", idm); fossil_warning("WARNING - no common ancestor: %s", zName); free(zName); db_multi_exec("UPDATE fv SET idm=0 WHERE idm=%d", idm); } db_finalize(&q); /* ** Add to V files that are not in V or P but are in M */ db_prepare(&q, "SELECT idm, rowid, fnm FROM fv AS x" " WHERE idp=0 AND idv=0 AND idm>0" ); while( db_step(&q)==SQLITE_ROW ){ int idm = db_column_int(&q, 0); int rowid = db_column_int(&q, 1); int idv; const char *zName; char *zFullName; db_multi_exec( "INSERT INTO vfile(vid,chnged,deleted,rid,mrid,isexe,islink,pathname)" " SELECT %d,%d,0,rid,mrid,isexe,islink,pathname FROM vfile WHERE id=%d", vid, integrateFlag?5:3, idm ); idv = db_last_insert_rowid(); db_multi_exec("UPDATE fv SET idv=%d WHERE rowid=%d", idv, rowid); zName = db_column_text(&q, 2); zFullName = mprintf("%s%s", g.zLocalRoot, zName); if( file_wd_isfile_or_link(zFullName) ){ fossil_print("ADDED %s (overwrites an unmanaged file)\n", zName); nOverwrite++; }else{ fossil_print("ADDED %s\n", zName); } fossil_free(zFullName); if( !dryRunFlag ){ undo_save(zName); vfile_to_disk(0, idm, 0, 0); } } db_finalize(&q); /* ** Find files that have changed from P->M but not P->V. ** Copy the M content over into V. */ db_prepare(&q, "SELECT idv, ridm, fn, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND ridv=ridp AND NOT chnged" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); int ridm = db_column_int(&q, 1); const char *zName = db_column_text(&q, 2); int islinkm = db_column_int(&q, 3); /* Copy content from idm over into idv. Overwrite idv. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag ){ undo_save(zName); db_multi_exec( "UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d " " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, idv ); vfile_to_disk(0, idv, 0, 0); } } db_finalize(&q); /* ** Do a three-way merge on files that have changes on both P->M and P->V. */ db_prepare(&q, "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv" " WHERE idp>0 AND idv>0 AND idm>0" " AND ridm!=ridp AND (ridv!=ridp OR chnged)", glob_expr("fv.fn", zBinGlob) ); while( db_step(&q)==SQLITE_ROW ){ int ridm = db_column_int(&q, 0); int idv = db_column_int(&q, 1); int ridp = db_column_int(&q, 2); int ridv = db_column_int(&q, 3); int isBinary = db_column_int(&q, 4); const char *zName = db_column_text(&q, 5); int isExe = db_column_int(&q, 6); int islinkv = db_column_int(&q, 7); int islinkm = db_column_int(&q, 8); int rc; char *zFullPath; Blob m, p, r; /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ if( verboseFlag ){ fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", zName, ridp, ridm, ridv); }else{ fossil_print("MERGE %s\n", zName); } if( islinkv || islinkm /* || file_wd_islink(zFullPath) */ ){ fossil_print("***** Cannot merge symlink %s\n", zName); nConflict++; }else{ undo_save(zName); zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); content_get(ridp, &p); content_get(ridm, &m); if( isBinary ){ rc = -1; blob_zero(&r); }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags); } if( rc>=0 ){ if( !dryRunFlag ){ blob_write_to_file(&r, zFullPath); file_wd_setexe(zFullPath, isExe); } db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); if( rc>0 ){ fossil_print("***** %d merge conflicts in %s\n", rc, zName); nConflict++; } }else{ fossil_print("***** Cannot merge binary file %s\n", zName); nConflict++; } blob_reset(&p); blob_reset(&m); blob_reset(&r); } db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(%d,%d)", idv,ridm); } db_finalize(&q); /* ** Drop files that are in P and V but not in M */ db_prepare(&q, "SELECT idv, fn, chnged FROM fv" " WHERE idp>0 AND idv>0 AND idm=0" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); const char *zName = db_column_text(&q, 1); int chnged = db_column_int(&q, 2); /* Delete the file idv */ fossil_print("DELETE %s\n", zName); if( chnged ){ fossil_warning("WARNING: local edits lost for %s\n", zName); nConflict++; } undo_save(zName); db_multi_exec( "UPDATE vfile SET deleted=1 WHERE id=%d", idv ); if( !dryRunFlag ){ char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); file_delete(zFullPath); free(zFullPath); } } db_finalize(&q); /* ** Rename files that have taken a rename on P->M but which keep the same ** name o P->V. If a file is renamed on P->V only or on both P->V and ** P->M then we retain the V name of the file. */ db_prepare(&q, "SELECT idv, fnp, fnm FROM fv" " WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp" ); while( db_step(&q)==SQLITE_ROW ){ int idv = db_column_int(&q, 0); const char *zOldName = db_column_text(&q, 1); const char *zNewName = db_column_text(&q, 2); fossil_print("RENAME %s -> %s\n", zOldName, zNewName); undo_save(zOldName); undo_save(zNewName); db_multi_exec( "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" " WHERE id=%d AND vid=%d", zNewName, idv, vid ); if( !dryRunFlag ){ char *zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName); char *zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); if( file_wd_islink(zFullOldPath) ){ symlink_copy(zFullOldPath, zFullNewPath); }else{ file_copy(zFullOldPath, zFullNewPath); } file_delete(zFullOldPath); free(zFullNewPath); free(zFullOldPath); } } db_finalize(&q); /* Report on conflicts */ if( nConflict ){ fossil_warning("WARNING: %d merge conflicts", nConflict); } if( nOverwrite ){ fossil_warning("WARNING: %d unmanaged files were overwritten", nOverwrite); } if( dryRunFlag ){ fossil_warning("REMINDER: this was a dry run -" " no files were actually changed."); } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); if( pickFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-1,%d)",mid); /* For a cherry-pick merge, make the default check-in comment the same ** as the check-in comment on the check-in that is being merged in. */ db_multi_exec( "REPLACE INTO vvar(name,value)" " SELECT 'ci-comment', coalesce(ecomment,comment) FROM event" " WHERE type='ci' AND objid=%d", mid ); }else if( backoutFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-2,%d)",pid); }else if( integrateFlag ){ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(-4,%d)",mid); }else{ db_multi_exec("INSERT OR IGNORE INTO vmerge(id,merge) VALUES(0,%d)", mid); } undo_finish(); db_end_transaction(dryRunFlag); }
/* ** COMMAND: finfo ** ** Usage: %fossil finfo ?OPTIONS? FILENAME ** ** Print the complete change history for a single file going backwards ** in time. The default mode is -l. ** ** For the -l|--log mode: If "-b|--brief" is specified one line per revision ** is printed, otherwise the full comment is printed. The "-n|--limit N" ** and "--offset P" options limits the output to the first N changes ** after skipping P changes. ** ** In the -s mode prints the status as <status> <revision>. This is ** a quick status and does not check for up-to-date-ness of the file. ** ** In the -p mode, there's an optional flag "-r|--revision REVISION". ** The specified version (or the latest checked out version) is printed ** to stdout. The -p mode is another form of the "cat" command. ** ** Options: ** -b|--brief display a brief (one line / revision) summary ** --case-sensitive B Enable or disable case-sensitive filenames. B is a ** boolean: "yes", "no", "true", "false", etc. ** -l|--log select log mode (the default) ** -n|--limit N Display the first N changes (default unlimited). ** N<=0 means no limit. ** --offset P skip P changes ** -p|--print select print mode ** -r|--revision R print the given revision (or ckout, if none is given) ** to stdout (only in print mode) ** -s|--status select status mode (print a status indicator for FILE) ** -W|--width <num> Width of lines (default is to auto-detect). Must be ** >22 or 0 (= no limit, resulting in a single line per ** entry). ** ** See also: artifact, cat, descendants, info, leaves */ void finfo_cmd(void){ db_must_be_within_tree(); if( find_option("status","s",0) ){ Stmt q; Blob line; Blob fname; int vid; /* We should be done with options.. */ verify_all_options(); if( g.argc!=3 ) usage("-s|--status FILENAME"); vid = db_lget_int("checkout", 0); if( vid==0 ){ fossil_fatal("no checkout to finfo files in"); } vfile_check_signature(vid, CKSIG_ENOTFILE); file_tree_name(g.argv[2], &fname, 0, 1); db_prepare(&q, "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)" " FROM vfile WHERE vfile.pathname=%B %s", &fname, filename_collation()); blob_zero(&line); if( db_step(&q)==SQLITE_ROW ) { Blob uuid; int isDeleted = db_column_int(&q, 1); int isNew = db_column_int(&q,2) == 0; int chnged = db_column_int(&q,3); int renamed = db_column_int(&q,4); blob_zero(&uuid); db_blob(&uuid, "SELECT uuid FROM blob, mlink, vfile WHERE " "blob.rid = mlink.mid AND mlink.fid = vfile.rid AND " "vfile.pathname=%B %s", &fname, filename_collation() ); if( isNew ){ blob_appendf(&line, "new"); }else if( isDeleted ){ blob_appendf(&line, "deleted"); }else if( renamed ){ blob_appendf(&line, "renamed"); }else if( chnged ){ blob_appendf(&line, "edited"); }else{ blob_appendf(&line, "unchanged"); } blob_appendf(&line, " "); blob_appendf(&line, " %10.10s", blob_str(&uuid)); blob_reset(&uuid); }else{ blob_appendf(&line, "unknown 0000000000"); } db_finalize(&q); fossil_print("%s\n", blob_str(&line)); blob_reset(&fname); blob_reset(&line); }else if( find_option("print","p",0) ){ Blob record; Blob fname; const char *zRevision = find_option("revision", "r", 1); /* We should be done with options.. */ verify_all_options(); file_tree_name(g.argv[2], &fname, 0, 1); if( zRevision ){ historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0); }else{ int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", &fname, filename_collation()); if( rid==0 ){ fossil_fatal("no history for file: %b", &fname); } content_get(rid, &record); } blob_write_to_file(&record, "-"); blob_reset(&record); blob_reset(&fname); }else{ Blob line; Stmt q; Blob fname; int rid; const char *zFilename; const char *zLimit; const char *zWidth; const char *zOffset; int iLimit, iOffset, iBrief, iWidth; if( find_option("log","l",0) ){ /* this is the default, no-op */ } zLimit = find_option("limit","n",1); zWidth = find_option("width","W",1); iLimit = zLimit ? atoi(zLimit) : -1; zOffset = find_option("offset",0,1); iOffset = zOffset ? atoi(zOffset) : 0; iBrief = (find_option("brief","b",0) == 0); if( iLimit==0 ){ iLimit = -1; } if( zWidth ){ iWidth = atoi(zWidth); if( (iWidth!=0) && (iWidth<=22) ){ fossil_fatal("-W|--width value must be >22 or 0"); } }else{ iWidth = -1; } /* We should be done with options.. */ verify_all_options(); if( g.argc!=3 ){ usage("?-l|--log? ?-b|--brief? FILENAME"); } file_tree_name(g.argv[2], &fname, 0, 1); rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s", &fname, filename_collation()); if( rid==0 ){ fossil_fatal("no history for file: %b", &fname); } zFilename = blob_str(&fname); db_prepare(&q, "SELECT DISTINCT b.uuid, ci.uuid, date(event.mtime%s)," " coalesce(event.ecomment, event.comment)," " coalesce(event.euser, event.user)," " (SELECT value FROM tagxref WHERE tagid=%d AND tagtype>0" " AND tagxref.rid=mlink.mid)" /* Tags */ " FROM mlink, blob b, event, blob ci, filename" " WHERE filename.name=%Q %s" " AND mlink.fnid=filename.fnid" " AND b.rid=mlink.fid" " AND event.objid=mlink.mid" " AND event.objid=ci.rid" " ORDER BY event.mtime DESC LIMIT %d OFFSET %d", timeline_utc(), TAG_BRANCH, zFilename, filename_collation(), iLimit, iOffset ); blob_zero(&line); if( iBrief ){ fossil_print("History of %s\n", blob_str(&fname)); } while( db_step(&q)==SQLITE_ROW ){ const char *zFileUuid = db_column_text(&q, 0); const char *zCiUuid = db_column_text(&q,1); const char *zDate = db_column_text(&q, 2); const char *zCom = db_column_text(&q, 3); const char *zUser = db_column_text(&q, 4); const char *zBr = db_column_text(&q, 5); char *zOut; if( zBr==0 ) zBr = "trunk"; if( iBrief ){ fossil_print("%s ", zDate); zOut = mprintf( "[%S] %s (user: %s, artifact: [%S], branch: %s)", zCiUuid, zCom, zUser, zFileUuid, zBr); comment_print(zOut, zCom, 11, iWidth, g.comFmtFlags); fossil_free(zOut); }else{ blob_reset(&line); blob_appendf(&line, "%S ", zCiUuid); blob_appendf(&line, "%.10s ", zDate); blob_appendf(&line, "%8.8s ", zUser); blob_appendf(&line, "%8.8s ", zBr); blob_appendf(&line,"%-39.39s", zCom ); comment_print(blob_str(&line), zCom, 0, iWidth, g.comFmtFlags); } } db_finalize(&q); blob_reset(&fname); } }
/* ** COMMAND: update ** ** Usage: %fossil update ?OPTIONS? ?VERSION? ?FILES...? ** ** Change the version of the current checkout to VERSION. Any ** uncommitted changes are retained and applied to the new checkout. ** ** The VERSION argument can be a specific version or tag or branch ** name. If the VERSION argument is omitted, then the leaf of the ** subtree that begins at the current version is used, if there is ** only a single leaf. VERSION can also be "current" to select the ** leaf of the current version or "latest" to select the most recent ** check-in. ** ** If one or more FILES are listed after the VERSION then only the ** named files are candidates to be updated, and any updates to them ** will be treated as edits to the current version. Using a directory ** name for one of the FILES arguments is the same as using every ** subdirectory and file beneath that directory. ** ** If FILES is omitted, all files in the current checkout are subject ** to being updated and the version of the current checkout is changed ** to VERSION. Any uncommitted changes are retained and applied to the ** new checkout. ** ** The -n or --dry-run option causes this command to do a "dry run". ** It prints out what would have happened but does not actually make ** any changes to the current checkout or the repository. ** ** The -v or --verbose option prints status information about ** unchanged files in addition to those file that actually do change. ** ** Options: ** --case-sensitive <BOOL> override case-sensitive setting ** --debug print debug information on stdout ** --latest acceptable in place of VERSION, update to latest version ** --force-missing force update if missing content after sync ** -n|--dry-run If given, display instead of run actions ** -v|--verbose print status information about all files ** -W|--width <num> Width of lines (default is to auto-detect). Must be >20 ** or 0 (= no limit, resulting in a single line per entry). ** ** See also: revert */ void update_cmd(void) { int vid; /* Current version */ int tid=0; /* Target version - version we are changing to */ Stmt q; int latestFlag; /* --latest. Pick the latest version if true */ int dryRunFlag; /* -n or --dry-run. Do a dry run */ int verboseFlag; /* -v or --verbose. Output extra information */ int forceMissingFlag; /* --force-missing. Continue if missing content */ int debugFlag; /* --debug option */ int setmtimeFlag; /* --setmtime. Set mtimes on files */ int nChng; /* Number of file renames */ int *aChng; /* Array of file renames */ int i; /* Loop counter */ int nConflict = 0; /* Number of merge conflicts */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ int nUpdate = 0; /* Number of changes of any kind */ int width; /* Width of printed comment lines */ Stmt mtimeXfer; /* Statement to transfer mtimes */ const char *zWidth; /* Width option string value */ if( !internalUpdate ) { undo_capture_command_line(); url_proxy_options(); } zWidth = find_option("width","W",1); if( zWidth ) { width = atoi(zWidth); if( (width!=0) && (width<=20) ) { fossil_fatal("-W|--width value must be >20 or 0"); } } else { width = -1; } latestFlag = find_option("latest",0, 0)!=0; dryRunFlag = find_option("dry-run","n",0)!=0; if( !dryRunFlag ) { dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } verboseFlag = find_option("verbose","v",0)!=0; forceMissingFlag = find_option("force-missing",0,0)!=0; debugFlag = find_option("debug",0,0)!=0; setmtimeFlag = find_option("setmtime",0,0)!=0; /* We should be done with options.. */ verify_all_options(); db_must_be_within_tree(); vid = db_lget_int("checkout", 0); user_select(); if( !dryRunFlag && !internalUpdate ) { if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, db_get_int("autosync-tries", 1)) ) { fossil_fatal("Cannot proceed with update"); } } /* Create any empty directories now, as well as after the update, ** so changes in settings are reflected now */ if( !dryRunFlag ) ensure_empty_dirs_created(); if( internalUpdate ) { tid = internalUpdate; } else if( g.argc>=3 ) { if( fossil_strcmp(g.argv[2], "current")==0 ) { /* If VERSION is "current", then use the same algorithm to find the ** target as if VERSION were omitted. */ } else if( fossil_strcmp(g.argv[2], "latest")==0 ) { /* If VERSION is "latest", then use the same algorithm to find the ** target as if VERSION were omitted and the --latest flag is present. */ latestFlag = 1; } else { tid = name_to_typed_rid(g.argv[2],"ci"); if( tid==0 || !is_a_version(tid) ) { fossil_fatal("no such check-in: %s", g.argv[2]); } } } /* If no VERSION is specified on the command-line, then look for a ** descendent of the current version. If there are multiple descendants, ** look for one from the same branch as the current version. If there ** are still multiple descendants, show them all and refuse to update ** until the user selects one. */ if( tid==0 ) { int closeCode = 1; compute_leaves(vid, closeCode); if( !db_exists("SELECT 1 FROM leaves") ) { closeCode = 0; compute_leaves(vid, closeCode); } if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ) { db_multi_exec( "DELETE FROM leaves WHERE rid NOT IN" " (SELECT leaves.rid FROM leaves, tagxref" " WHERE leaves.rid=tagxref.rid AND tagxref.tagid=%d" " AND tagxref.value==(SELECT value FROM tagxref" " WHERE tagid=%d AND rid=%d))", TAG_BRANCH, TAG_BRANCH, vid ); if( db_int(0, "SELECT count(*) FROM leaves")>1 ) { compute_leaves(vid, closeCode); db_prepare(&q, "%s " " AND event.objid IN leaves" " ORDER BY event.mtime DESC", timeline_query_for_tty() ); print_timeline(&q, -100, width, 0); db_finalize(&q); fossil_fatal("Multiple descendants"); } } tid = db_int(0, "SELECT rid FROM leaves, event" " WHERE event.objid=leaves.rid" " ORDER BY event.mtime DESC"); if( tid==0 ) tid = vid; } if( tid==0 ) { return; } db_begin_transaction(); vfile_check_signature(vid, CKSIG_ENOTFILE); if( !dryRunFlag && !internalUpdate ) undo_begin(); if( load_vfile_from_rid(tid) && !forceMissingFlag ) { fossil_fatal("missing content, unable to update"); }; /* ** The record.fn field is used to match files against each other. The ** FV table contains one row for each each unique filename in ** in the current checkout, the pivot, and the version being merged. */ db_multi_exec( "DROP TABLE IF EXISTS fv;" "CREATE TEMP TABLE fv(" " fn TEXT %s PRIMARY KEY," /* The filename relative to root */ " idv INTEGER," /* VFILE entry for current version */ " idt INTEGER," /* VFILE entry for target version */ " chnged BOOLEAN," /* True if current version has been edited */ " islinkv BOOLEAN," /* True if current file is a link */ " islinkt BOOLEAN," /* True if target file is a link */ " ridv INTEGER," /* Record ID for current version */ " ridt INTEGER," /* Record ID for target */ " isexe BOOLEAN," /* Does target have execute permission? */ " deleted BOOLEAN DEFAULT 0,"/* File marked by "rm" to become unmanaged */ " fnt TEXT %s" /* Filename of same file on target version */ ");", filename_collation(), filename_collation() ); /* Add files found in the current version */ db_multi_exec( "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged,deleted)" " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged, deleted" " FROM vfile WHERE vid=%d", vid ); /* Compute file name changes on V->T. Record name changes in files that ** have changed locally. */ if( vid ) { find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0); if( nChng ) { for(i=0; i<nChng; i++) { db_multi_exec( "UPDATE fv" " SET fnt=(SELECT name FROM filename WHERE fnid=%d)" " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged", aChng[i*2+1], aChng[i*2] ); } fossil_free(aChng); } } /* Add files found in the target version T but missing from the current ** version V. */ db_multi_exec( "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)" " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile" " WHERE vid=%d" " AND pathname %s NOT IN (SELECT fnt FROM fv)", tid, filename_collation() ); /* ** Compute the file version ids for T */ db_multi_exec( "UPDATE fv SET" " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnt=pathname),0)," " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnt=pathname),0)", tid, tid ); /* ** Add islink information */ db_multi_exec( "UPDATE fv SET" " islinkv=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnt=pathname),0)," " islinkt=coalesce((SELECT islink FROM vfile" " WHERE vid=%d AND fnt=pathname),0)", vid, tid ); if( debugFlag ) { db_prepare(&q, "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe," " islinkv, islinkt FROM fv" ); while( db_step(&q)==SQLITE_ROW ) { fossil_print("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d" " islinkv=%d islinkt=%d\n", db_column_int(&q, 0), db_column_int(&q, 4), db_column_int(&q, 5), db_column_int(&q, 3), db_column_int(&q, 6), db_column_int(&q, 7), db_column_int(&q, 8)); fossil_print(" fnv = [%s]\n", db_column_text(&q, 1)); fossil_print(" fnt = [%s]\n", db_column_text(&q, 2)); } db_finalize(&q); } /* If FILES appear on the command-line, remove from the "fv" table ** every entry that is not named on the command-line or which is not ** in a directory named on the command-line. */ if( g.argc>=4 ) { Blob sql; /* SQL statement to purge unwanted entries */ Blob treename; /* Normalized filename */ int i; /* Loop counter */ const char *zSep; /* Term separator */ blob_zero(&sql); blob_append(&sql, "DELETE FROM fv WHERE ", -1); zSep = ""; for(i=3; i<g.argc; i++) { file_tree_name(g.argv[i], &treename, 0, 1); if( file_wd_isdir(g.argv[i])==1 ) { if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ) { blob_append_sql(&sql, "%sfn NOT GLOB '%q/*' ", zSep /*safe-for-%s*/, blob_str(&treename)); } else { blob_reset(&sql); break; } } else { blob_append_sql(&sql, "%sfn<>%Q ", zSep /*safe-for-%s*/, blob_str(&treename)); } zSep = "AND "; blob_reset(&treename); } db_multi_exec("%s", blob_sql_text(&sql)); blob_reset(&sql); } /* ** Alter the content of the checkout so that it conforms with the ** target */ db_prepare(&q, "SELECT fn, idv, ridv, idt, ridt, chnged, fnt," " isexe, islinkv, islinkt, deleted FROM fv ORDER BY 1" ); db_prepare(&mtimeXfer, "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)" " WHERE id=:idt" ); assert( g.zLocalRoot!=0 ); assert( strlen(g.zLocalRoot)>0 ); assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' ); while( db_step(&q)==SQLITE_ROW ) { const char *zName = db_column_text(&q, 0); /* The filename from root */ int idv = db_column_int(&q, 1); /* VFILE entry for current */ int ridv = db_column_int(&q, 2); /* RecordID for current */ int idt = db_column_int(&q, 3); /* VFILE entry for target */ int ridt = db_column_int(&q, 4); /* RecordID for target */ int chnged = db_column_int(&q, 5); /* Current is edited */ const char *zNewName = db_column_text(&q,6);/* New filename */ int isexe = db_column_int(&q, 7); /* EXE perm for new file */ int islinkv = db_column_int(&q, 8); /* Is current file is a link */ int islinkt = db_column_int(&q, 9); /* Is target file is a link */ int deleted = db_column_int(&q, 10); /* Marked for deletion */ char *zFullPath; /* Full pathname of the file */ char *zFullNewPath; /* Full pathname of dest */ char nameChng; /* True if the name changed */ zFullPath = mprintf("%s%s", g.zLocalRoot, zName); zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); nameChng = fossil_strcmp(zName, zNewName); nUpdate++; if( deleted ) { db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt); } if( idv>0 && ridv==0 && idt>0 && ridt>0 ) { /* Conflict. This file has been added to the current checkout ** but also exists in the target checkout. Use the current version. */ fossil_print("CONFLICT %s\n", zName); nConflict++; } else if( idt>0 && idv==0 ) { /* File added in the target. */ if( file_wd_isfile_or_link(zFullPath) ) { fossil_print("ADD %s - overwrites an unmanaged file\n", zName); nOverwrite++; } else { fossil_print("ADD %s\n", zName); } if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ) { /* The file is unedited. Change it to the target version */ if( deleted ) { fossil_print("UPDATE %s - change to unmanaged file\n", zName); } else { fossil_print("UPDATE %s\n", zName); } if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt>0 && idv>0 && !deleted && file_wd_size(zFullPath)<0 ) { /* The file missing from the local check-out. Restore it to the ** version that appears in the target. */ fossil_print("UPDATE %s\n", zName); if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0); } else if( idt==0 && idv>0 ) { if( ridv==0 ) { /* Added in current checkout. Continue to hold the file as ** as an addition */ db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv); } else if( chnged ) { /* Edited locally but deleted from the target. Do not track the ** file but keep the edited version around. */ fossil_print("CONFLICT %s - edited locally but deleted by update\n", zName); nConflict++; } else { fossil_print("REMOVE %s\n", zName); if( !dryRunFlag && !internalUpdate ) undo_save(zName); if( !dryRunFlag ) file_delete(zFullPath); } } else if( idt>0 && idv>0 && ridt!=ridv && chnged ) { /* Merge the changes in the current tree into the target version */ Blob r, t, v; int rc; if( nameChng ) { fossil_print("MERGE %s -> %s\n", zName, zNewName); } else { fossil_print("MERGE %s\n", zName); } if( islinkv || islinkt /* || file_wd_islink(zFullPath) */ ) { fossil_print("***** Cannot merge symlink %s\n", zNewName); nConflict++; } else { unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; if( !dryRunFlag && !internalUpdate ) undo_save(zName); content_get(ridt, &t); content_get(ridv, &v); rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags); if( rc>=0 ) { if( !dryRunFlag ) { blob_write_to_file(&r, zFullNewPath); file_wd_setexe(zFullNewPath, isexe); } if( rc>0 ) { fossil_print("***** %d merge conflicts in %s\n", rc, zNewName); nConflict++; } } else { if( !dryRunFlag ) { blob_write_to_file(&t, zFullNewPath); file_wd_setexe(zFullNewPath, isexe); } fossil_print("***** Cannot merge binary file %s\n", zNewName); nConflict++; } } if( nameChng && !dryRunFlag ) file_delete(zFullPath); blob_reset(&v); blob_reset(&t); blob_reset(&r); } else { nUpdate--; if( chnged ) { if( verboseFlag ) fossil_print("EDITED %s\n", zName); } else { db_bind_int(&mtimeXfer, ":idv", idv); db_bind_int(&mtimeXfer, ":idt", idt); db_step(&mtimeXfer); db_reset(&mtimeXfer); if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName); } } free(zFullPath); free(zFullNewPath); } db_finalize(&q); db_finalize(&mtimeXfer); fossil_print("%.79c\n",'-'); if( nUpdate==0 ) { show_common_info(tid, "checkout:", 1, 0); fossil_print("%-13s None. Already up-to-date\n", "changes:"); } else { show_common_info(tid, "updated-to:", 1, 0); fossil_print("%-13s %d file%s modified.\n", "changes:", nUpdate, nUpdate>1 ? "s" : ""); } /* Report on conflicts */ if( !dryRunFlag ) { Stmt q; int nMerge = 0; db_prepare(&q, "SELECT uuid, id FROM vmerge JOIN blob ON merge=rid" " WHERE id<=0"); while( db_step(&q)==SQLITE_ROW ) { const char *zLabel = "merge"; switch( db_column_int(&q, 1) ) { case -1: zLabel = "cherrypick merge"; break; case -2: zLabel = "backout merge"; break; } fossil_warning("uncommitted %s against %S.", zLabel, db_column_text(&q, 0)); nMerge++; } db_finalize(&q); leaf_ambiguity_warning(tid, tid); if( nConflict ) { if( internalUpdate ) { internalConflictCnt = nConflict; nConflict = 0; } else { fossil_warning("WARNING: %d merge conflicts", nConflict); } } if( nOverwrite ) { fossil_warning("WARNING: %d unmanaged files were overwritten", nOverwrite); } if( nMerge ) { fossil_warning("WARNING: %d uncommitted prior merges", nMerge); } } /* ** Clean up the mid and pid VFILE entries. Then commit the changes. */ if( dryRunFlag ) { db_end_transaction(1); /* With --dry-run, rollback changes */ } else { ensure_empty_dirs_created(); if( g.argc<=3 ) { /* All files updated. Shift the current checkout to the target. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid); checkout_set_all_exe(tid); manifest_to_disk(tid); db_lset_int("checkout", tid); } else { /* A subset of files have been checked out. Keep the current ** checkout unchanged. */ db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); } if( !internalUpdate ) undo_finish(); if( setmtimeFlag ) vfile_check_signature(tid, CKSIG_SETMTIME); db_end_transaction(0); } }
/* ** COMMAND: revert ** ** Usage: %fossil revert ?-r REVISION? ?FILE ...? ** ** Revert to the current repository version of FILE, or to ** the version associated with baseline REVISION if the -r flag ** appears. ** ** If FILE was part of a rename operation, both the original file ** and the renamed file are reverted. ** ** Revert all files if no file name is provided. ** ** If a file is reverted accidently, it can be restored using ** the "fossil undo" command. ** ** Options: ** -r REVISION revert given FILE(s) back to given REVISION ** ** See also: redo, undo, update */ void revert_cmd(void) { const char *zFile; const char *zRevision; Blob record; int i; int errCode; Stmt q; undo_capture_command_line(); zRevision = find_option("revision", "r", 1); verify_all_options(); if( g.argc<2 ) { usage("?OPTIONS? [FILE] ..."); } if( zRevision && g.argc<3 ) { fossil_fatal("the --revision option does not work for the entire tree"); } db_must_be_within_tree(); db_begin_transaction(); undo_begin(); db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);"); if( g.argc>2 ) { for(i=2; i<g.argc; i++) { Blob fname; zFile = mprintf("%/", g.argv[i]); blob_zero(&fname); file_tree_name(zFile, &fname, 0, 1); db_multi_exec( "REPLACE INTO torevert VALUES(%B);" "INSERT OR IGNORE INTO torevert" " SELECT pathname" " FROM vfile" " WHERE origname=%B;", &fname, &fname ); blob_reset(&fname); } } else { int vid; vid = db_lget_int("checkout", 0); vfile_check_signature(vid, 0); db_multi_exec( "DELETE FROM vmerge;" "INSERT OR IGNORE INTO torevert " " SELECT pathname" " FROM vfile " " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;" ); } db_multi_exec( "INSERT OR IGNORE INTO torevert" " SELECT origname" " FROM vfile" " WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);" ); blob_zero(&record); db_prepare(&q, "SELECT name FROM torevert"); if( zRevision==0 ) { int vid = db_lget_int("checkout", 0); zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); } while( db_step(&q)==SQLITE_ROW ) { int isExe = 0; int isLink = 0; char *zFull; zFile = db_column_text(&q, 0); zFull = mprintf("%/%/", g.zLocalRoot, zFile); errCode = historical_version_of_file(zRevision, zFile, &record, &isLink, &isExe, 0, 2); if( errCode==2 ) { if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q", zFile, zFile)==0 ) { fossil_print("UNMANAGE %s\n", zFile); } else { undo_save(zFile); file_delete(zFull); fossil_print("DELETE %s\n", zFile); } db_multi_exec( "UPDATE OR REPLACE vfile" " SET pathname=origname, origname=NULL" " WHERE pathname=%Q AND origname!=pathname;" "DELETE FROM vfile WHERE pathname=%Q", zFile, zFile ); } else { sqlite3_int64 mtime; undo_save(zFile); if( file_wd_size(zFull)>=0 && (isLink || file_wd_islink(0)) ) { file_delete(zFull); } if( isLink ) { symlink_create(blob_str(&record), zFull); } else { blob_write_to_file(&record, zFull); } file_wd_setexe(zFull, isExe); fossil_print("REVERT %s\n", zFile); mtime = file_wd_mtime(zFull); db_multi_exec( "UPDATE vfile" " SET mtime=%lld, chnged=0, deleted=0, isexe=%d, islink=%d,mrid=rid" " WHERE pathname=%Q OR origname=%Q", mtime, isExe, isLink, zFile, zFile ); } blob_reset(&record); free(zFull); } db_finalize(&q); undo_finish(); db_end_transaction(0); }