Index: src/checkin.c
==================================================================
--- src/checkin.c
+++ src/checkin.c
@@ -153,11 +153,11 @@
   int cwdRelative = 0;
   db_must_be_within_tree();
   cwdRelative = determine_cwd_relative_option();
   blob_zero(&report);
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid, 0, useSha1sum);
+  vfile_check_signature(vid, useSha1sum ? CKSIG_SHA1 : 0);
   status_report(&report, "", 0, cwdRelative);
   blob_write_to_file(&report, "-");
 }
 
 /*
@@ -191,41 +191,74 @@
   if( vid ){
     show_common_info(vid, "checkout:", 1, 1);
   }
   changes_cmd();
 }
+
+/*
+** Implementation of the checkin_mtime SQL function
+*/
+
 
 /*
 ** COMMAND: ls
 **
-** Usage: %fossil ls [-l]
+** Usage: %fossil ls ?OPTIONS? ?VERSION?
 **
 ** Show the names of all files in the current checkout.  The -l provides
 ** extra information about each file.
+**
+** Options:
+**   -l              Provide extra information about each file.
+**   --age           Show when each file was committed
+**
+** See also: changes, extra, status
 */
 void ls_cmd(void){
   int vid;
   Stmt q;
   int isBrief;
+  int showAge;
+  char *zOrderBy = "pathname";
 
   isBrief = find_option("l","l", 0)==0;
+  showAge = find_option("age",0,0)!=0;
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid, 0, 0);
-  db_prepare(&q,
-     "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
-     "  FROM vfile"
-     " ORDER BY 1"
-  );
+  if( find_option("t","t",0)!=0 ){
+    if( showAge ){
+      zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid);
+    }else{
+      zOrderBy = "mtime DESC";
+    }
+  }
+  verify_all_options();
+  vfile_check_signature(vid, 0);
+  if( showAge ){
+    db_prepare(&q,
+       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0),"
+       "       datetime(checkin_mtime(%d,rid),'unixepoch','localtime')"
+       "  FROM vfile"
+       " ORDER BY %s", vid, zOrderBy
+    );
+  }else{
+    db_prepare(&q,
+       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
+       "  FROM vfile"
+       " ORDER BY %s", zOrderBy
+    );
+  }
   while( db_step(&q)==SQLITE_ROW ){
     const char *zPathname = db_column_text(&q,0);
     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);
     char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
-    if( isBrief ){
+    if( showAge ){
+      fossil_print("%s  %s\n", db_column_text(&q, 5), zPathname);
+    }else if( isBrief ){
       fossil_print("%s\n", zPathname);
     }else if( isNew ){
       fossil_print("ADDED      %s\n", zPathname);
     }else if( isDeleted ){
       fossil_print("DELETED    %s\n", zPathname);

Index: src/checkout.c
==================================================================
--- src/checkout.c
+++ src/checkout.c
@@ -33,11 +33,11 @@
 int unsaved_changes(void){
   int vid;
   db_must_be_within_tree();
   vid = db_lget_int("checkout",0);
   if( vid==0 ) return 2;
-  vfile_check_signature(vid, 1, 0);
+  vfile_check_signature(vid, CKSIG_ENOTFILE);
   return db_exists("SELECT 1 FROM vfile WHERE chnged"
                    " OR coalesce(origname!=pathname,0)");
 }
 
 /*

Index: src/content.c
==================================================================
--- src/content.c
+++ src/content.c
@@ -269,10 +269,13 @@
     n = 1;
     while( !bag_find(&contentCache.inCache, nextRid)
         && (nextRid = findSrcid(nextRid))>0 ){
       n++;
       if( n>=nAlloc ){
+        if( n>db_int(0, "SELECT max(rid) FROM blob") ){
+          fossil_panic("infinite loop in DELTA table");
+        }
         nAlloc = nAlloc*2 + 10;
         a = fossil_realloc(a, nAlloc*sizeof(a[0]));
       }
       a[n] = nextRid;
     }

Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -627,10 +627,26 @@
   sqlite3_value **argv
 ){
   sqlite3_result_int64(context, time(0));
 }
 
+/*
+** Function to return the check-in time for a file.
+*/
+void db_checkin_mtime_function(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  i64 mtime;
+  int rc = mtime_of_manifest_file(sqlite3_value_int(argv[0]),
+                                  sqlite3_value_int(argv[1]), &mtime);
+  if( rc==0 ){
+    sqlite3_result_int64(context, mtime);
+  }
+}
+
 
 /*
 ** Open a database file.  Return a pointer to the new database
 ** connection.  An error results in process abort.
 */
@@ -649,20 +665,22 @@
     db_err(sqlite3_errmsg(db));
   }
   sqlite3_busy_timeout(db, 5000); 
   sqlite3_wal_autocheckpoint(db, 1);  /* Set to checkpoint frequently */
   sqlite3_create_function(db, "now", 0, SQLITE_ANY, 0, db_now_function, 0, 0);
+  sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_ANY, 0,
+                          db_checkin_mtime_function, 0, 0);
   return db;
 }
 
 
 /*
 ** zDbName is the name of a database file.  If no other database
 ** file is open, then open this one.  If another database file is
 ** already open, then attach zDbName using the name zLabel.
 */
-static void db_open_or_attach(const char *zDbName, const char *zLabel){
+void db_open_or_attach(const char *zDbName, const char *zLabel){
   if( !g.db ){
     g.db = openDatabase(zDbName);
     g.zMainDbType = zLabel;
     db_connection_init();
   }else{

Index: src/descendants.c
==================================================================
--- src/descendants.c
+++ src/descendants.c
@@ -156,11 +156,11 @@
 
 /*
 ** Load the record ID rid and up to N-1 closest ancestors into
 ** the "ok" table.
 */
-void compute_ancestors(int rid, int N){
+void compute_ancestors(int rid, int N, int directOnly){
   Bag seen;
   PQueue queue;
   Stmt ins;
   Stmt q;
   bag_init(&seen);
@@ -168,11 +168,12 @@
   bag_insert(&seen, rid);
   pqueue_insert(&queue, rid, 0.0, 0);
   db_prepare(&ins, "INSERT OR IGNORE INTO ok VALUES(:rid)");
   db_prepare(&q,
     "SELECT a.pid, b.mtime FROM plink a LEFT JOIN plink b ON b.cid=a.pid"
-    " WHERE a.cid=:rid"
+    " WHERE a.cid=:rid %s",
+    directOnly ? " AND a.isprim" : ""
   );
   while( (N--)>0 && (rid = pqueue_extract(&queue, 0))!=0 ){
     db_bind_int(&ins, ":rid", rid);
     db_step(&ins);
     db_reset(&ins);
@@ -202,11 +203,13 @@
 void compute_direct_ancestors(int rid, int N){
   Stmt ins;
   Stmt q;
   int gen = 0;
   db_multi_exec(
-    "CREATE TEMP TABLE ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
+    "CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER,"
+                                            " generation INTEGER PRIMARY KEY);"
+    "DELETE FROM ancestor;"
     "INSERT INTO ancestor VALUES(%d, 0);", rid
   );
   db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
   db_prepare(&q, 
     "SELECT pid FROM plink"
@@ -224,10 +227,44 @@
     db_reset(&ins);
   }
   db_finalize(&ins);
   db_finalize(&q);
 }
+
+/*
+** Compute the "mtime" of the file given whose blob.rid is "fid" that
+** is part of check-in "vid".  The mtime will be the mtime on vid or
+** some ancestor of vid where fid first appears.
+*/
+int mtime_of_manifest_file(
+  int vid,       /* The check-in that contains fid */
+  int fid,       /* The id of the file whose check-in time is sought */
+  i64 *pMTime    /* Write result here */
+){
+  static int prevVid = -1;
+  static Stmt q;
+
+  if( prevVid!=vid ){
+    prevVid = vid;
+    db_multi_exec("DROP TABLE IF EXISTS temp.ok;"
+                  "CREATE TEMP TABLE ok(x INTEGER PRIMARY KEY);");
+    compute_ancestors(vid, 100000000, 1);
+  }
+  db_static_prepare(&q,
+    "SELECT (max(event.mtime)-2440587.5)*86400 FROM mlink, event"
+    " WHERE mlink.mid=event.objid"
+    "   AND +mlink.mid IN ok"
+    "   AND mlink.fid=:fid");
+  db_bind_int(&q, ":fid", fid);
+  if( db_step(&q)!=SQLITE_ROW ){
+    db_reset(&q);
+    return 1;
+  }
+  *pMTime = db_column_int64(&q, 0);
+  db_reset(&q);
+  return 0;
+}
 
 /*
 ** Load the record ID rid and up to N-1 closest descendants into
 ** the "ok" table.
 */

Index: src/diffcmd.c
==================================================================
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -212,11 +212,11 @@
   int asNewFile;            /* Treat non-existant files as empty files */
 
   ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
   asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid, 1, 0);
+  vfile_check_signature(vid, CKSIG_ENOTFILE);
   blob_zero(&sql);
   db_begin_transaction();
   if( zFrom ){
     int rid = name_to_typed_rid(zFrom, "ci");
     if( !is_a_version(rid) ){

Index: src/file.c
==================================================================
--- src/file.c
+++ src/file.c
@@ -23,10 +23,16 @@
 #include <unistd.h>
 #include <string.h>
 #include <errno.h>
 #include "file.h"
 
+#ifdef _WIN32
+# include <sys/utime.h>
+#else
+# include <sys/time.h>
+#endif
+
 /*
 ** The file status information from the most recent stat() call.
 **
 ** Use _stati64 rather than stat on windows, in order to handle files
 ** larger than 2GB.
@@ -215,10 +221,53 @@
     }
   }
 #endif /* _WIN32 */
   return rc;
 }
+
+/*
+** Set the mtime for a file.
+*/
+void file_set_mtime(const char *zFilename, i64 newMTime){
+#if !defined(_WIN32)
+  struct timeval tv[2];
+  memset(tv, 0, sizeof(tv[0])*2);
+  tv[0].tv_sec = newMTime;
+  tv[1].tv_sec = newMTime;
+  utimes(zFilename, tv);
+#else
+  struct utimbuf tb;
+  char *zMbcs = fossil_utf8_to_mbcs(zFilename);
+  tb.actime = newMTime;
+  tb.modtime = newMTime;
+  _utime(zMbcs, &tb);
+  fossil_mbcs_free(zMbcs);
+#endif
+}
+
+/*
+** COMMAND: test-set-mtime
+**
+** Usage: %fossil test-set-mtime FILENAME DATE/TIME
+**
+** Sets the mtime of the named file to the date/time shown.
+*/
+void test_set_mtime(void){
+  const char *zFile;
+  char *zDate;
+  i64 iMTime;
+  if( g.argc!=4 ){
+    usage("test-set-mtime FILENAME DATE/TIME");
+  }
+  db_open_or_attach(":memory:", "mem");
+  iMTime = db_int64(0, "SELECT strftime('%%s',%Q)", g.argv[3]);
+  zFile = g.argv[2];
+  file_set_mtime(zFile, iMTime);
+  iMTime = file_mtime(zFile);
+  zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMTime);
+  fossil_print("Set mtime of \"%s\" to %s (%lld)\n", zFile, zDate, iMTime);
+}
 
 /*
 ** Delete a file.
 */
 void file_delete(const char *zFilename){

Index: src/finfo.c
==================================================================
--- src/finfo.c
+++ src/finfo.c
@@ -51,11 +51,11 @@
     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);
+    vfile_check_signature(vid, CKSIG_ENOTFILE);
     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", &fname);
     blob_zero(&line);

Index: src/merge.c
==================================================================
--- src/merge.c
+++ src/merge.c
@@ -143,11 +143,11 @@
     mid = t;
   }
   if( !is_a_version(pid) ){
     fossil_fatal("not a version: record #%d", pid);
   }
-  vfile_check_signature(vid, 1, 0);
+  vfile_check_signature(vid, CKSIG_ENOTFILE);
   db_begin_transaction();
   if( !nochangeFlag ) undo_begin();
   load_vfile_from_rid(mid);
   load_vfile_from_rid(pid);
 

Index: src/stash.c
==================================================================
--- src/stash.c
+++ src/stash.c
@@ -144,11 +144,11 @@
   zComment = find_option("comment", "m", 1);
   verify_all_options();
   stashid = db_lget_int("stash-next", 1);
   db_lset_int("stash-next", stashid+1);
   vid = db_lget_int("checkout", 0);
-  vfile_check_signature(vid, 0, 0);
+  vfile_check_signature(vid, 0);
   db_multi_exec(
     "INSERT INTO stash(stashid,vid,comment,ctime)"
     "VALUES(%d,%d,%Q,julianday('now'))",
     stashid, vid, zComment
   );

Index: src/timeline.c
==================================================================
--- src/timeline.c
+++ src/timeline.c
@@ -986,11 +986,11 @@
       }
       if( useDividers ) timeline_add_dividers(0, d_rid);
       db_multi_exec("DELETE FROM ok");
     }
     if( p_rid ){
-      compute_ancestors(p_rid, nEntry+1);
+      compute_ancestors(p_rid, nEntry+1, 0);
       np = db_int(0, "SELECT count(*)-1 FROM ok");
       if( np>0 ){
         if( nd>0 ) blob_appendf(&desc, " and ");
         blob_appendf(&desc, "%d ancestors", np);
         db_multi_exec("%s", blob_str(&sql));
@@ -1507,11 +1507,11 @@
   if( mode==3 || mode==4 ){
     db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
     if( mode==3 ){
       compute_descendants(objid, n);
     }else{
-      compute_ancestors(objid, n);
+      compute_ancestors(objid, n, 0);
     }
     blob_appendf(&sql, " AND blob.rid IN ok");
   }
   if( zType && (zType[0]!='a') ){
     blob_appendf(&sql, " AND event.type=%Q ", zType);

Index: src/update.c
==================================================================
--- src/update.c
+++ src/update.c
@@ -92,10 +92,11 @@
   Stmt q;
   int latestFlag;       /* --latest.  Pick the latest version if true */
   int nochangeFlag;     /* -n or --nochange.  Do a dry run */
   int verboseFlag;      /* -v or --verbose.  Output extra information */
   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 */
   Stmt mtimeXfer;       /* Statment to transfer mtimes */
@@ -106,10 +107,11 @@
   }
   latestFlag = find_option("latest",0, 0)!=0;
   nochangeFlag = find_option("nochange","n",0)!=0;
   verboseFlag = find_option("verbose","v",0)!=0;
   debugFlag = find_option("debug",0,0)!=0;
+  setmtimeFlag = find_option("setmtime",0,0)!=0;
   db_must_be_within_tree();
   vid = db_lget_int("checkout", 0);
   if( vid==0 ){
     fossil_fatal("cannot find current version");
   }
@@ -186,11 +188,11 @@
   if( tid==0 ){
     fossil_panic("Internal Error: unable to find a version to update to.");
   }
 
   db_begin_transaction();
-  vfile_check_signature(vid, 1, 0);
+  vfile_check_signature(vid, CKSIG_ENOTFILE);
   if( !nochangeFlag && !internalUpdate ) undo_begin();
   load_vfile_from_rid(tid);
 
   /*
   ** The record.fn field is used to match files against each other.  The
@@ -205,19 +207,20 @@
     "  idt INTEGER,"              /* VFILE entry for target version */
     "  chnged BOOLEAN,"           /* True if current version has been edited */
     "  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 marke by "rm" to become unmanaged */
     "  fnt TEXT"                  /* Filename of same file on target version */
     ");"
   );
 
   /* Add files found in the current version
   */
   db_multi_exec(
-    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
-    " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged"
+    "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
@@ -309,11 +312,12 @@
   /*
   ** 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 FROM fv ORDER BY 1"
+    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt,"
+    "       isexe, deleted FROM fv ORDER BY 1"
   );
   db_prepare(&mtimeXfer,
     "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
     " WHERE id=:idt"
   );
@@ -327,17 +331,21 @@
     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 deleted = db_column_int(&q, 8);         /* 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);
+    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);
@@ -345,19 +353,24 @@
     }else if( idt>0 && idv==0 ){
       /* File added in the target. */
       fossil_print("ADD %s\n", zName);
       undo_save(zName);
       if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
-    }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
+    }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){
       /* The file is unedited.  Change it to the target version */
       undo_save(zName);
-      fossil_print("UPDATE %s\n", zName);
+      if( deleted ){
+        fossil_print("UPDATE %s - change to unmanged file\n", zName);
+      }else{
+        fossil_print("UPDATE %s\n", zName);
+      }
       if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
     }else if( idt>0 && idv>0 && file_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);
+      fossil_print("UPDATE %s%s\n", zName,
+                    deleted?" - change to unmanaged file":"");
       undo_save(zName);
       if( !nochangeFlag ) 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
@@ -456,10 +469,11 @@
       /* 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);
   }
 }
 
 /*
@@ -610,11 +624,11 @@
       blob_reset(&fname);
     }
   }else{
     int vid;
     vid = db_lget_int("checkout", 0);
-    vfile_check_signature(vid, 0, 0);
+    vfile_check_signature(vid, 0);
     db_multi_exec(
       "DELETE FROM vmerge;"
       "INSERT INTO torevert "
       "SELECT pathname"
       "  FROM vfile "

Index: src/vfile.c
==================================================================
--- src/vfile.c
+++ src/vfile.c
@@ -122,31 +122,53 @@
   db_finalize(&ins);
   manifest_destroy(p);
   db_end_transaction(0);
 }
 
-/*
-** Check the file signature of the disk image for every VFILE of vid.
-**
-** Set the VFILE.CHNGED field on every file that has changed.  Also 
-** set VFILE.CHNGED on every folder that contains a file or folder 
-** that has changed.
-**
-** If VFILE.DELETED is null or if VFILE.RID is zero, then we can assume
-** the file has changed without having the check the on-disk image.
-**
-** If the size of the file has changed, then we assume that it has
-** changed.  If the mtime of the file has not changed and useSha1sum is false
-** and the mtime-changes setting is true (the default) then we assume that
-** the file has not changed.  If the mtime has changed, we go ahead and
-** double-check that the file has changed by looking at its SHA1 sum.
+#if INTERFACE
+/*
+** The cksigFlags parameter to vfile_check_signature() is an OR-ed
+** combination of the following bits:
+*/
+#define CKSIG_ENOTFILE  0x001   /* non-file FS objects throw an error */
+#define CKSIG_SHA1      0x002   /* Verify file content using sha1sum */
+#define CKSIG_SETMTIME  0x004   /* Set mtime to last check-out time */
+
+#endif /* INTERFACE */
+
+/*
+** Look at every VFILE entry with the given vid and update
+** VFILE.CHNGED field according to whether or not
+** the file has changed.  0 means no change.  1 means edited.  2 means
+** the file has changed due to a merge.  3 means the file was added
+** by a merge.
+**
+** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either
+** removed from configuration management via "fossil rm" or added via
+** "fossil add", respectively, and in both cases we always know that 
+** the file has changed without having the check the size, mtime,
+** or on-disk content.
+**
+** If the size of the file has changed, then we always know that the file
+** changed without having to look at the mtime or on-disk content.
+**
+** The mtime of the file is only a factor if the mtime-changes setting
+** is false and the useSha1sum flag is false.  If the mtime-changes
+** setting is true (or undefined - it defaults to true) or if useSha1sum
+** is true, then we do not trust the mtime and will examine the on-disk
+** content to determine if a file really is the same.
+**
+** If the mtime is used, it is used only to determine if files are the same.
+** If the mtime of a file has changed, we still examine the on-disk content
+** to see whether or not the edit was a null-edit.
 */
-void vfile_check_signature(int vid, int notFileIsFatal, int useSha1sum){
+void vfile_check_signature(int vid, unsigned int cksigFlags){
   int nErr = 0;
   Stmt q;
   Blob fileCksum, origCksum;
-  int checkMtime = useSha1sum==0 && db_get_boolean("mtime-changes", 1);
+  int useMtime = (cksigFlags & CKSIG_SHA1)==0
+                    && db_get_boolean("mtime-changes", 1);
 
   db_begin_transaction();
   db_prepare(&q, "SELECT id, %Q || pathname,"
                  "       vfile.mrid, deleted, chnged, uuid, size, mtime"
                  "  FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid"
@@ -156,55 +178,78 @@
     const char *zName;
     int chnged = 0;
     int oldChnged;
     i64 oldMtime;
     i64 currentMtime;
+    i64 origSize;
+    i64 currentSize;
 
     id = db_column_int(&q, 0);
     zName = db_column_text(&q, 1);
     rid = db_column_int(&q, 2);
     isDeleted = db_column_int(&q, 3);
-    oldChnged = db_column_int(&q, 4);
+    oldChnged = chnged = db_column_int(&q, 4);
     oldMtime = db_column_int64(&q, 7);
-    if( isDeleted ){
+    currentSize = file_size(zName);
+    origSize = db_column_int64(&q, 6);
+    currentMtime = file_mtime(0);
+    if( chnged==0 && (isDeleted || rid==0) ){
+      /* "fossil rm" or "fossil add" always change the file */
       chnged = 1;
-    }else if( !file_isfile(zName) && file_size(0)>=0 ){
-      if( notFileIsFatal ){
+    }else if( !file_isfile(0) && currentSize>=0 ){
+      if( cksigFlags & CKSIG_ENOTFILE ){
         fossil_warning("not an ordinary file: %s", zName);
         nErr++;
       }
       chnged = 1;
-    }else if( oldChnged>=2 ){
-      chnged = oldChnged;
-    }else if( rid==0 ){
-      chnged = 1;
     }
-    if( chnged!=1 ){
-      i64 origSize = db_column_int64(&q, 6);
-      currentMtime = file_mtime(0);
-      if( origSize!=file_size(0) ){
+    if( origSize!=currentSize ){
+      if( chnged!=1 ){
         /* A file size change is definitive - the file has changed.  No
-        ** need to check the sha1sum */
+        ** need to check the mtime or sha1sum */
         chnged = 1;
       }
-    }
-    if( chnged!=1 && (checkMtime==0 || currentMtime!=oldMtime) ){
+    }else if( chnged==1 && rid!=0 && !isDeleted ){
+      /* File is believed to have changed but it is the same size.
+      ** Double check that it really has changed by looking at content. */
+      assert( origSize==currentSize );
+      db_ephemeral_blob(&q, 5, &origCksum);
+      if( sha1sum_file(zName, &fileCksum) ){
+        blob_zero(&fileCksum);
+      }
+      if( blob_compare(&fileCksum, &origCksum)==0 ) chnged = 0;
+      blob_reset(&origCksum);
+      blob_reset(&fileCksum);
+    }else if( (chnged==0 || chnged==2)
+           && (useMtime==0 || currentMtime!=oldMtime) ){
+      /* For files that were formerly believed to be unchanged or that were
+      ** changed by merging, if their mtime changes, or unconditionally
+      ** if --sha1sum is used, check to see if they have been edited by
+      ** looking at their SHA1 sum */
+      assert( origSize==currentSize );
       db_ephemeral_blob(&q, 5, &origCksum);
       if( sha1sum_file(zName, &fileCksum) ){
         blob_zero(&fileCksum);
       }
       if( blob_compare(&fileCksum, &origCksum) ){
         chnged = 1;
-      }else if( currentMtime!=oldMtime ){
-        db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
-                      currentMtime, id);
       }
       blob_reset(&origCksum);
       blob_reset(&fileCksum);
     }
-    if( chnged!=oldChnged && (chnged || !checkMtime) ){
-      db_multi_exec("UPDATE vfile SET chnged=%d WHERE id=%d", chnged, id);
+    if( (cksigFlags & CKSIG_SETMTIME) && (chnged==0 || chnged==2) ){
+      i64 desiredMtime;
+      if( mtime_of_manifest_file(vid,rid,&desiredMtime)==0 ){
+        if( currentMtime!=desiredMtime ){
+          file_set_mtime(zName, desiredMtime);
+          currentMtime = file_mtime(zName);
+        }
+      }
+    }
+    if( currentMtime!=oldMtime || chnged!=oldChnged ){
+      db_multi_exec("UPDATE vfile SET mtime=%lld, chnged=%d WHERE id=%d",
+                    currentMtime, chnged, id);
     }
   }
   db_finalize(&q);
   if( nErr ) fossil_fatal("abort due to prior errors");
   db_end_transaction(0);

Index: src/xfer.c
==================================================================
--- src/xfer.c
+++ src/xfer.c
@@ -268,19 +268,15 @@
   Blob *pUuid             /* The UUID of the file to send */
 ){
   static const char *azQuery[] = {
     "SELECT pid FROM plink x"
     " WHERE cid=%d"
-    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
-    "   AND NOT EXISTS(SELECT 1 FROM plink y"
-                      " WHERE y.pid=x.cid AND y.cid=x.pid)",
-
-    "SELECT pid FROM mlink x"
+    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",
+    
+    "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
     " WHERE fid=%d"
     "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
-    "   AND NOT EXISTS(SELECT 1 FROM mlink y"
-                     "  WHERE y.pid=x.fid AND y.fid=x.pid)"
   };
   int i;
   Blob src, delta;
   int size = 0;
   int srcId = 0;
@@ -994,11 +990,11 @@
           }else{
             send_file(&xfer, seqno, 0, 1);
           }
           seqno++;
         }
-        if( seqno>=max ) seqno = 0;
+        if( seqno>max ) seqno = 0;
         @ clone_seqno %d(seqno)
       }else{
         isClone = 1;
         isPull = 1;
         deltaFlag = 1;

ADDED   test/update-test-1.sh
Index: test/update-test-1.sh
==================================================================
--- test/update-test-1.sh
+++ test/update-test-1.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Run this script in an empty directory.  A single argument is the full
+# pathname of the fossil binary.  Example:
+#
+#     sh update-test-1.sh /home/drh/fossil/m1/fossil
+#
+export FOSSIL=$1
+rm -rf aaa bbb update-test-1.fossil
+
+# Create a test repository
+$FOSSIL new update-test-1.fossil
+
+# In checkout aaa, add file one.txt
+mkdir aaa
+cd aaa
+$FOSSIL open ../update-test-1.fossil
+echo one >one.txt
+$FOSSIL add one.txt
+$FOSSIL commit -m add-one --tag add-one
+
+# Open checkout bbb.
+mkdir ../bbb
+cd ../bbb
+$FOSSIL open ../update-test-1.fossil
+
+# Back in aaa, add file two.txt
+cd ../aaa
+echo two >two.txt
+$FOSSIL add two.txt
+$FOSSIL commit -m add-two --tag add-two
+
+# In bbb, delete file one.txt.  Then update the change from aaa that
+# adds file two.  Verify that one.txt says deleted.
+cd ../bbb
+$FOSSIL rm one.txt
+$FOSSIL changes
+echo '========================================================================'
+$FOSSIL update
+echo '======== The previous should show "ADD two.txt" ========================'
+$FOSSIL changes
+echo '======== The previous should show "DELETE one.txt" ====================='
+$FOSSIL commit --test -m check-in
+echo '======== Only file two.txt is checked in ==============================='

ADDED   test/update-test-2.sh
Index: test/update-test-2.sh
==================================================================
--- test/update-test-2.sh
+++ test/update-test-2.sh
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# Run this script in an empty directory.  A single argument is the full
+# pathname of the fossil binary.  Example:
+#
+#     sh update-test-2.sh /home/drh/fossil/m1/fossil
+#
+export FOSSIL=$1
+rm -rf aaa bbb update-test-2.fossil
+
+# Create a test repository
+$FOSSIL new update-test-2.fossil
+
+# In checkout aaa, add file one.txt.
+mkdir aaa
+cd aaa
+$FOSSIL open ../update-test-2.fossil
+echo one >one.txt
+$FOSSIL add one.txt
+$FOSSIL commit -m add-one --tag add-one
+
+# Create checkout bbb.
+mkdir ../bbb
+cd ../bbb
+$FOSSIL open ../update-test-2.fossil
+
+# Back in aaa, make changes to one.txt.  Add file two.txt.
+cd ../aaa
+echo change >>one.txt
+echo two >two.txt
+$FOSSIL add two.txt
+$FOSSIL commit -m 'chng one and add two' --tag add-two
+
+# In bbb, remove one.txt, then update.
+cd ../bbb
+$FOSSIL rm one.txt
+$FOSSIL changes
+echo '========================================================================'
+$FOSSIL update
+echo '======== Previous should show "ADD two.txt" and conflict on one.txt ===='
+$FOSSIL changes
+echo '======== The previous should show "DELETE one.txt" ====================='
+$FOSSIL commit --test -m 'check-in'
+echo '======== Only file two.txt is checked in ==============================='