Index: src/checkin.c ================================================================== --- src/checkin.c +++ src/checkin.c @@ -87,11 +87,15 @@ }else if( isChnged==2 ){ blob_appendf(report, "UPDATED_BY_MERGE %s\n", zDisplayName); }else if( isChnged==3 ){ blob_appendf(report, "ADDED_BY_MERGE %s\n", zDisplayName); }else if( isChnged==1 ){ - blob_appendf(report, "EDITED %s\n", zDisplayName); + if( file_contains_merge_marker(zFullName) ){ + blob_appendf(report, "CONFLICT %s\n", zDisplayName); + }else{ + blob_appendf(report, "EDITED %s\n", zDisplayName); + } }else if( isRenamed ){ blob_appendf(report, "RENAMED %s\n", zDisplayName); } free(zFullName); } @@ -976,10 +980,11 @@ ** --force|-f allow forking with this commit ** --message-file|-M FILE read the commit comment from given file ** --nosign do not attempt to sign this commit with gpg ** --private do not sync changes and their descendants ** --tag TAG-NAME assign given tag TAG-NAME to the checkin +** --conflict allow unresolved merge conflicts ** ** See also: branch, changes, checkout, extra, sync */ void commit_cmd(void){ int hasChanges; /* True if unsaved changes exist */ @@ -993,10 +998,11 @@ int noSign = 0; /* True to omit signing the manifest using GPG */ int isAMerge = 0; /* True if checking in a merge */ int forceFlag = 0; /* Force a fork */ int forceDelta = 0; /* Force a delta-manifest */ int forceBaseline = 0; /* Force a baseline-manifest */ + int allowConflict = 0; /* Allow unresolve merge conflicts */ char *zManifestFile; /* Name of the manifest file */ int useCksum; /* True if checksums should be computed and verified */ int outputManifest; /* True to output "manifest" and "manifest.uuid" */ int testRun; /* True for a test run. Debugging only */ const char *zBranch; /* Create a new branch with this name */ @@ -1012,10 +1018,11 @@ Blob muuid; /* Manifest uuid */ Blob cksum1, cksum2; /* Before and after commit checksums */ Blob cksum1b; /* Checksum recorded in the manifest */ int szD; /* Size of the delta manifest */ int szB; /* Size of the baseline manifest */ + int nConflict = 0; /* Number of unresolved merge conflicts */ url_proxy_options(); noSign = find_option("nosign",0,0)!=0; forceDelta = find_option("delta",0,0)!=0; forceBaseline = find_option("baseline",0,0)!=0; @@ -1040,10 +1047,11 @@ if( zBranch==0 ) zBranch = "private"; if( zBrClr==0 && zColor==0 ) zBrClr = "#fec084"; /* Orange */ } zDateOvrd = find_option("date-override",0,1); zUserOvrd = find_option("user-override",0,1); + allowConflict = find_option("conflict",0,0)!=0; db_must_be_within_tree(); noSign = db_get_boolean("omitsign", 0)|noSign; if( db_get_boolean("clearsign", 0)==0 ){ noSign = 1; } useCksum = db_get_boolean("repo-cksum", 1); outputManifest = db_get_boolean("manifest", 0); @@ -1194,42 +1202,51 @@ /* Step 1: Insert records for all modified files into the blob ** table. If there were arguments passed to this command, only ** the identified fils are inserted (if they have been modified). */ db_prepare(&q, - "SELECT id, %Q || pathname, mrid, %s FROM vfile " + "SELECT id, %Q || pathname, mrid, %s, chnged FROM vfile " "WHERE chnged==1 AND NOT deleted AND is_selected(id)", g.zLocalRoot, glob_expr("pathname", db_get("crnl-glob","")) ); while( db_step(&q)==SQLITE_ROW ){ int id, rid; const char *zFullname; Blob content; - int crnlOk; + int crnlOk, chnged; id = db_column_int(&q, 0); zFullname = db_column_text(&q, 1); rid = db_column_int(&q, 2); crnlOk = db_column_int(&q, 3); + chnged = db_column_int(&q, 4); blob_zero(&content); if( file_wd_islink(zFullname) ){ /* Instead of file content, put link destination path */ blob_read_link(&content, zFullname); }else{ blob_read_from_file(&content, zFullname); } if( !crnlOk ) cr_warning(&content, zFullname); + if( chnged==1 && contains_merge_marker(&content) ){ + nConflict++; + fossil_print("possible unresolved merge conflict in %s\n", + zFullname+strlen(g.zLocalRoot)); + } nrid = content_put(&content); blob_reset(&content); if( rid>0 ){ content_deltify(rid, nrid, 0); } db_multi_exec("UPDATE vfile SET mrid=%d, rid=%d WHERE id=%d", nrid,nrid,id); db_multi_exec("INSERT OR IGNORE INTO unsent VALUES(%d)", nrid); } db_finalize(&q); + if( nConflict && !allowConflict ){ + fossil_fatal("abort due to unresolve merge conflicts"); + } /* Create the new manifest */ if( blob_size(&comment)==0 ){ blob_append(&comment, "(no comment)", -1); } Index: src/finfo.c ================================================================== --- src/finfo.c +++ src/finfo.c @@ -289,14 +289,14 @@ if( (n = atoi(PD("n","0")))>0 ){ blob_appendf(&sql, " LIMIT %d", n); url_add_parameter(&url, "n", P("n")); } if( firstChngOnly ){ - style_submenu_element("Full", "Show all changes", + style_submenu_element("Full", "Show all changes","%s", url_render(&url, "fco", "0", 0, 0)); }else{ - style_submenu_element("Simplified", "Show only first use of a change", + style_submenu_element("Simplified", "Show only first use of a change","%s", url_render(&url, "fco", "1", 0, 0)); } db_prepare(&q, blob_str(&sql)); blob_reset(&sql); blob_zero(&title); Index: src/merge3.c ================================================================== --- src/merge3.c +++ src/merge3.c @@ -133,10 +133,20 @@ i += 3; } return i; } +/* +** Text of boundary markers for merge conflicts. +*/ +static char const * 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. ** @@ -154,18 +164,10 @@ 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 */ - static const char zBegin[] = - "<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<<<<\n"; - static const char zMid1[] = - "======= COMMON ANCESTOR content follows ============================\n"; - static const char zMid2[] = - "======= MERGED IN content follows ==================================\n"; - static const char zEnd[] = - ">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"; 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 @@ -266,17 +268,17 @@ nConflict++; while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){ sz++; } DEBUG( printf("CONFLICT %d\n", sz); ) - blob_appendf(pOut, zBegin); + blob_appendf(pOut, mergeMarker[0]); i1 = output_one_side(pOut, pV1, aC1, i1, sz); - blob_appendf(pOut, zMid1); + blob_appendf(pOut, mergeMarker[1]); blob_copy_lines(pOut, pPivot, sz); - blob_appendf(pOut, zMid2); + blob_appendf(pOut, mergeMarker[2]); i2 = output_one_side(pOut, pV2, aC2, i2, sz); - blob_appendf(pOut, zEnd); + blob_appendf(pOut, mergeMarker[3]); } /* If we are finished with an edit triple, advance to the next ** triple. */ @@ -301,10 +303,45 @@ 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( sizeof(mergeMarker)/sizeof(mergeMarker[0])==4 ); + for(i=0; i<n; ){ + for(j=0; j<4; j++){ + if( memcmp(&z[i], mergeMarker[j], len)==0 ) return 1; + } + while( i<n && z[i]!='\n' ){ i++; } + while( i<n && z[i]=='\n' ){ i++; } + } + return 0; +} + +/* +** Return true if the named file contains an unresolved merge marker line. +*/ +int file_contains_merge_marker(const char *zFullpath){ + Blob file; + int rc; + blob_read_from_file(&file, zFullpath); + rc = contains_merge_marker(&file); + blob_reset(&file); + return rc; +} /* ** COMMAND: test-3-way-merge ** ** Usage: %fossil test-3-way-merge PIVOT V1 V2 MERGED