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 ==============================='