/* ** 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 module implements a 3-way merge */ #include "config.h" #include "merge3.h" #if 0 #define DEBUG(X) X #define ISDEBUG 1 #else #define DEBUG(X) #define ISDEBUG 0 #endif /* The minimum of two integers */ #ifndef min # define min(A,B) (A0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){ if( aC[0]>=sz ) return 1; sz -= aC[0]; if( aC[1]>sz ) return 0; sz -= aC[1]; aC += 3; } return 1; } /* ** pSrc contains an edited file where aC[] describes the edit. Part of ** pSrc has already been output. This routine outputs additional lines ** of pSrc - lines that correspond to the next sz lines of the original ** unedited file. ** ** Note that sz counts the number of lines of text in the original file. ** But text is output from the edited file. So the number of lines transfer ** to pOut might be different from sz. Fewer lines appear in pOut if there ** are deletes. More lines appear if there are inserts. ** ** The aC[] array is updated and the new index into aC[] is returned. */ static int output_one_side( Blob *pOut, /* Write to this blob */ Blob *pSrc, /* The edited file that is to be copied to pOut */ int *aC, /* Array of integer triples describing the edit */ int i, /* Index in aC[] of current location in pSrc */ int sz /* Number of lines in unedited source to output */ ){ while( sz>0 ){ if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break; if( aC[i]>=sz ){ blob_copy_lines(pOut, pSrc, sz); aC[i] -= sz; break; } blob_copy_lines(pOut, pSrc, aC[i]); blob_copy_lines(pOut, pSrc, aC[i+2]); sz -= aC[i] + aC[i+1]; i += 3; } return i; } /* ** Text of boundary markers for merge conflicts. */ static const char *const mergeMarker[] = { /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<\n", "======= COMMON ANCESTOR content follows ============================\n", "======= MERGED IN content follows ==================================\n", ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" }; /* ** Do a three-way merge. Initialize pOut to contain the result. ** ** The merge is an edit against pV2. Both pV1 and pV2 have a ** common origin at pPivot. Apply the changes of pPivot ==> pV1 ** to pV2. ** ** The return is 0 upon complete success. If any input file is binary, ** -1 is returned and pOut is unmodified. If there are merge ** conflicts, the merge proceeds as best as it can and the number ** of conflicts is returns */ static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ int *aC1; /* Changes from pPivot to pV1 */ int *aC2; /* Changes from pPivot to pV2 */ int i1, i2; /* Index into aC1[] and aC2[] */ int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ int limit1, limit2; /* Sizes of aC1[] and aC2[] */ int nConflict = 0; /* Number of merge conflicts seen so far */ blob_zero(pOut); /* Merge results stored in pOut */ /* Compute the edits that occur from pPivot => pV1 (into aC1) ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is ** an array of integer triples. Within each triple, the first integer ** is the number of lines of text to copy directly from the pivot, ** the second integer is the number of lines of text to omit from the ** pivot, and the third integer is the number of lines of text that are ** inserted. The edit array ends with a triple of 0,0,0. */ aC1 = text_diff(pPivot, pV1, 0, 0, 0); aC2 = text_diff(pPivot, pV2, 0, 0, 0); if( aC1==0 || aC2==0 ){ free(aC1); free(aC2); return -1; } blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */ blob_rewind(pV2); blob_rewind(pPivot); /* Determine the length of the aC1[] and aC2[] change vectors */ for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){} limit1 = i1; for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){} limit2 = i2; DEBUG( for(i1=0; i10 && aC2[i2]>0 ){ /* Output text that is unchanged in both V1 and V2 */ nCpy = min(aC1[i1], aC2[i2]); DEBUG( printf("COPY %d\n", nCpy); ) blob_copy_lines(pOut, pPivot, nCpy); blob_copy_lines(0, pV1, nCpy); blob_copy_lines(0, pV2, nCpy); aC1[i1] -= nCpy; aC2[i2] -= nCpy; }else if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ /* Output edits to V2 that occurs within unchanged regions of V1 */ nDel = aC2[i2+1]; nIns = aC2[i2+2]; DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); ) blob_copy_lines(0, pPivot, nDel); blob_copy_lines(0, pV1, nDel); blob_copy_lines(pOut, pV2, nIns); aC1[i1] -= nDel; i2 += 3; }else if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ /* Output edits to V1 that occur within unchanged regions of V2 */ nDel = aC1[i1+1]; nIns = aC1[i1+2]; DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); ) blob_copy_lines(0, pPivot, nDel); blob_copy_lines(0, pV2, nDel); blob_copy_lines(pOut, pV1, nIns); aC2[i2] -= nDel; i1 += 3; }else if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){ /* Output edits that are identical in both V1 and V2. */ assert( aC1[i1]==0 ); nDel = aC1[i1+1]; nIns = aC1[i1+2]; DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); ) blob_copy_lines(0, pPivot, nDel); blob_copy_lines(pOut, pV1, nIns); blob_copy_lines(0, pV2, nIns); i1 += 3; i2 += 3; }else { /* We have found a region where different edits to V1 and V2 overlap. ** This is a merge conflict. Find the size of the conflict, then ** output both possible edits separated by distinctive marks. */ int sz = 1; /* Size of the conflict in lines */ nConflict++; while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ sz++; } DEBUG( printf("CONFLICT %d\n", sz); ) blob_append(pOut, mergeMarker[0], -1); i1 = output_one_side(pOut, pV1, aC1, i1, sz); blob_append(pOut, mergeMarker[1], -1); blob_copy_lines(pOut, pPivot, sz); blob_append(pOut, mergeMarker[2], -1); i2 = output_one_side(pOut, pV2, aC2, i2, sz); blob_append(pOut, mergeMarker[3], -1); } /* If we are finished with an edit triple, advance to the next ** triple. */ if( i10 ){ DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); ) blob_copy_lines(pOut, pV1, aC1[i1+2]); }else if( i20 ){ DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); ) blob_copy_lines(pOut, pV2, aC2[i2+2]); } free(aC1); free(aC2); return nConflict; } /* ** Return true if the input string contains a merge marker on a line by ** itself. */ int contains_merge_marker(Blob *p){ int i, j; int len = (int)strlen(mergeMarker[0]); const char *z = blob_buffer(p); int n = blob_size(p) - len + 1; assert( len==(int)strlen(mergeMarker[1]) ); assert( len==(int)strlen(mergeMarker[2]) ); assert( len==(int)strlen(mergeMarker[3]) ); assert( count(mergeMarker)==4 ); for(i=0; i0 ) fossil_warning("WARNING: %d merge conflicts", nConflict); } /* ** aSubst is an array of string pairs. The first element of each pair is ** a string that begins with %. The second element is a replacement for that ** string. ** ** This routine makes a copy of zInput into memory obtained from malloc and ** performance all applicable substitutions on that string. */ char *string_subst(const char *zInput, int nSubst, const char **azSubst){ Blob x; int i, j; blob_zero(&x); while( zInput[0] ){ for(i=0; zInput[i] && zInput[i]!='%'; i++){} if( i>0 ){ blob_append(&x, zInput, i); zInput += i; } if( zInput[0]==0 ) break; for(j=0; j=nSubst ){ blob_append(&x, "%", 1); zInput++; } } return blob_str(&x); } #if INTERFACE /* ** Flags to the 3-way merger */ #define MERGE_DRYRUN 0x0001 #endif /* ** This routine is a wrapper around blob_merge() with the following ** enhancements: ** ** (1) If the merge-command is defined, then use the external merging ** program specified instead of the built-in blob-merge to do the ** merging. Panic if the external merger fails. ** ** Not currently implemented ** ** ** (2) If gmerge-command is defined and there are merge conflicts in ** blob_merge() then invoke the external graphical merger to resolve ** the conflicts. ** ** (3) If a merge conflict occurs and gmerge-command is not defined, ** then write the pivot, original, and merge-in files to the ** filesystem. */ int merge_3way( Blob *pPivot, /* Common ancestor (older) */ const char *zV1, /* Name of file for version merging into (mine) */ Blob *pV2, /* Version merging from (yours) */ 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 */ blob_read_from_file(&v1, zV1, ExtFILE); rc = blob_merge(pPivot, &v1, pV2, pOut); if( rc!=0 && (mergeFlags & MERGE_DRYRUN)==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); blob_write_to_file(pPivot, zPivot); 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; 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); } } fossil_free(zPivot); fossil_free(zOrig); fossil_free(zOther); } blob_reset(&v1); return rc; }