Index: src/merge.c ================================================================== --- src/merge.c +++ src/merge.c @@ -224,10 +224,14 @@ ** -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. +** +** -K|--keep-merge-files On merge conflict, retain the temporary files +** used for merging, named *-baseline, *-original, +** and *-merge. ** ** -n|--dry-run If given, display instead of run actions ** ** -v|--verbose Show additional details of the merge */ @@ -244,10 +248,11 @@ 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 keepMergeFlag; /* True if --keep-merge-files is present */ int nConflict = 0; /* Number of conflicts seen */ int nOverwrite = 0; /* Number of unmanaged files overwritten */ char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ Stmt q; @@ -275,10 +280,12 @@ if( !dryRunFlag ){ dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ } forceFlag = find_option("force","f",0)!=0; zPivot = find_option("baseline",0,1); + keepMergeFlag = find_option("keep-merge-files", "K",0)!=0; + 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 ){ @@ -665,10 +672,11 @@ if( isBinary ){ rc = -1; blob_zero(&r); }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; + if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags); } if( rc>=0 ){ if( !dryRunFlag ){ blob_write_to_file(&r, zFullPath); Index: src/merge3.c ================================================================== --- src/merge3.c +++ src/merge3.c @@ -436,10 +436,16 @@ #if INTERFACE /* ** Flags to the 3-way merger */ #define MERGE_DRYRUN 0x0001 +/* +** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain +** its temporary files on error. By default they are removed after the +** merge, regardless of success or failure. +*/ +#define MERGE_KEEP_FILES 0x0002 #endif /* ** This routine is a wrapper around blob_merge() with the following @@ -465,14 +471,18 @@ Blob *pOut, /* Output written here */ unsigned mergeFlags /* Flags that control operation */ ){ Blob v1; /* Content of zV1 */ int rc; /* Return code of subroutines and this routine */ + const char *zGMerge; /* Name of the gmerge command */ blob_read_from_file(&v1, zV1, ExtFILE); rc = blob_merge(pPivot, &v1, pV2, pOut); - if( rc!=0 && (mergeFlags & MERGE_DRYRUN)==0 ){ + zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); + if( (mergeFlags & MERGE_DRYRUN)==0 + && ((zGMerge!=0 && zGMerge[0]!=0) + || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ char *zPivot; /* Name of the pivot file */ char *zOrig; /* Name of the original content file */ char *zOther; /* Name of the merge file */ zPivot = file_newname(zV1, "baseline", 1); @@ -480,18 +490,14 @@ zOrig = file_newname(zV1, "original", 1); blob_write_to_file(&v1, zOrig); zOther = file_newname(zV1, "merge", 1); blob_write_to_file(pV2, zOther); if( rc>0 ){ - const char *zGMerge; /* Name of the gmerge command */ - - zGMerge = db_get("gmerge-command", 0); if( zGMerge && zGMerge[0] ){ char *zOut; /* Temporary output file */ char *zCmd; /* Command to invoke */ const char *azSubst[8]; /* Strings to be substituted */ - zOut = file_newname(zV1, "output", 1); azSubst[0] = "%baseline"; azSubst[1] = zPivot; azSubst[2] = "%original"; azSubst[3] = zOrig; azSubst[4] = "%merge"; azSubst[5] = zOther; azSubst[6] = "%output"; azSubst[7] = zOut; @@ -498,21 +504,23 @@ zCmd = string_subst(zGMerge, 8, azSubst); printf("%s\n", zCmd); fflush(stdout); fossil_system(zCmd); if( file_size(zOut, RepoFILE)>=0 ){ blob_read_from_file(pOut, zOut, ExtFILE); - file_delete(zPivot); - file_delete(zOrig); - file_delete(zOther); file_delete(zOut); } fossil_free(zCmd); fossil_free(zOut); } + } + if( (mergeFlags & MERGE_KEEP_FILES)==0 ){ + file_delete(zPivot); + file_delete(zOrig); + file_delete(zOther); } fossil_free(zPivot); fossil_free(zOrig); fossil_free(zOther); } blob_reset(&v1); return rc; } Index: src/stash.c ================================================================== --- src/stash.c +++ src/stash.c @@ -359,11 +359,11 @@ if( isLink || isNewLink ){ rc = -1; blob_zero(&b); /* because we reset it later */ fossil_print("***** Cannot merge symlink %s\n", zNew); }else{ - rc = merge_3way(&a, zOPath, &b, &out, 0); + rc = merge_3way(&a, zOPath, &b, &out, MERGE_KEEP_FILES); blob_write_to_file(&out, zNPath); blob_reset(&out); file_setexe(zNPath, isExec); } if( rc ){ Index: src/update.c ================================================================== --- src/update.c +++ src/update.c @@ -103,10 +103,14 @@ ** or 0 (= no limit, resulting in a single line per entry). ** --setmtime Set timestamps of all files to match their SCM-side ** times (the timestamp of the last checkin which modified ** them). ** +** -K|--keep-merge-files On merge conflict, retain the temporary files +** used for merging, named *-baseline, *-original, +** and *-merge. +** ** See also: revert */ void update_cmd(void){ int vid; /* Current version */ int tid=0; /* Target version - version we are changing to */ @@ -115,10 +119,11 @@ 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 keepMergeFlag; /* True if --keep-merge-files is present */ 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 */ @@ -147,10 +152,11 @@ } 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; + keepMergeFlag = find_option("keep-merge-files", "K",0)!=0; /* We should be done with options.. */ verify_all_options(); db_must_be_within_tree(); @@ -490,10 +496,11 @@ if( islinkv || islinkt ){ fossil_print("***** Cannot merge symlink %s\n", zNewName); nConflict++; }else{ unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; + if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; 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 ){ Index: www/changes.wiki ================================================================== --- www/changes.wiki +++ www/changes.wiki @@ -21,10 +21,14 @@ of the form "/doc/$CURRENT/..." the "$CURRENT" text is translated into the check-in hash for the document currently being viewed. * Proactive security: Fossil now assumes that the schema of every database it opens has been tampered with by an adversary and takes extra precautions to ensure that such tampering is harmless. + * Merge conflicts caused via the [/help?cmd=merge|merge] and + [/help?cmd=update|update] commands no longer leave temporary + files behind unless the new --keep-merge-file flag + is used. * Bug fix: the "fossil git export" command is now working on Windows * Bug fix: display Technote items on the timeline correctly * Bug fix: fix the capability summary matrix of the Security Audit page so that it does not add "anonymous" capabilities to the "nobody" user.