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