Changes On Branch branch-1.19
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch branch-1.19 Excluding Merge-Ins

This is equivalent to a diff from 6517b5c857 to 285eeba64f

2012-11-17
19:22
Prevent delta loops on sync operations that might otherwise occur if a sequence of file changes ends with a file back to its original state after a sequence of two or more intermediate states. The is a backport/cherrypick of check-in [141b990722ea81e10e5] Leaf check-in: 285eeba64f user: drh tags: branch-1.19
2012-11-14
20:28
Detect infinite loops in the DELTA table and abort out of content_get() when they are found. Fix an off-by-one error in the version-3 clone protocol. This error might cause an incomplete and corrupt clone if a transfer block fills up just before sending the very last blob. Backport of fixes from 2012-08-23 21:15:36 check-in: 99053ab141 user: drh tags: branch-1.19
2012-10-11
19:45
Cherrypick changes [0c37874941c8972], [9ba8a393fcc569b], and [ae092ec605eed11] in order to backport the --setmtime option of "fossil update" and the --age and -t options of "fossil ls" to version 1.19. check-in: 773c6c5f2c user: drh tags: branch-1.19
2011-09-01
20:23
Stop publishing x64 binaries for linux. x86 binaries are sufficient. check-in: bd04a48925 user: drh tags: trunk
18:25
Version 1.19. check-in: 6517b5c857 user: drh tags: trunk, release, version-1.19
17:45
Merging the unwanted two trunk leaves. check-in: a22c381757 user: viriketo tags: trunk

Changes to src/checkin.c.

   151    151     int vid;
   152    152     int useSha1sum = find_option("sha1sum", 0, 0)!=0;
   153    153     int cwdRelative = 0;
   154    154     db_must_be_within_tree();
   155    155     cwdRelative = determine_cwd_relative_option();
   156    156     blob_zero(&report);
   157    157     vid = db_lget_int("checkout", 0);
   158         -  vfile_check_signature(vid, 0, useSha1sum);
          158  +  vfile_check_signature(vid, useSha1sum ? CKSIG_SHA1 : 0);
   159    159     status_report(&report, "", 0, cwdRelative);
   160    160     blob_write_to_file(&report, "-");
   161    161   }
   162    162   
   163    163   /*
   164    164   ** COMMAND: status
   165    165   **
................................................................................
   189    189     fossil_print("server-code:  %s\n", db_get("server-code", ""));
   190    190     vid = db_lget_int("checkout", 0);
   191    191     if( vid ){
   192    192       show_common_info(vid, "checkout:", 1, 1);
   193    193     }
   194    194     changes_cmd();
   195    195   }
          196  +
          197  +/*
          198  +** Implementation of the checkin_mtime SQL function
          199  +*/
          200  +
   196    201   
   197    202   /*
   198    203   ** COMMAND: ls
   199    204   **
   200         -** Usage: %fossil ls [-l]
          205  +** Usage: %fossil ls ?OPTIONS? ?VERSION?
   201    206   **
   202    207   ** Show the names of all files in the current checkout.  The -l provides
   203    208   ** extra information about each file.
          209  +**
          210  +** Options:
          211  +**   -l              Provide extra information about each file.
          212  +**   --age           Show when each file was committed
          213  +**
          214  +** See also: changes, extra, status
   204    215   */
   205    216   void ls_cmd(void){
   206    217     int vid;
   207    218     Stmt q;
   208    219     int isBrief;
          220  +  int showAge;
          221  +  char *zOrderBy = "pathname";
   209    222   
   210    223     isBrief = find_option("l","l", 0)==0;
          224  +  showAge = find_option("age",0,0)!=0;
   211    225     db_must_be_within_tree();
   212    226     vid = db_lget_int("checkout", 0);
   213         -  vfile_check_signature(vid, 0, 0);
   214         -  db_prepare(&q,
   215         -     "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
   216         -     "  FROM vfile"
   217         -     " ORDER BY 1"
   218         -  );
          227  +  if( find_option("t","t",0)!=0 ){
          228  +    if( showAge ){
          229  +      zOrderBy = mprintf("checkin_mtime(%d,rid) DESC", vid);
          230  +    }else{
          231  +      zOrderBy = "mtime DESC";
          232  +    }
          233  +  }
          234  +  verify_all_options();
          235  +  vfile_check_signature(vid, 0);
          236  +  if( showAge ){
          237  +    db_prepare(&q,
          238  +       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0),"
          239  +       "       datetime(checkin_mtime(%d,rid),'unixepoch','localtime')"
          240  +       "  FROM vfile"
          241  +       " ORDER BY %s", vid, zOrderBy
          242  +    );
          243  +  }else{
          244  +    db_prepare(&q,
          245  +       "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
          246  +       "  FROM vfile"
          247  +       " ORDER BY %s", zOrderBy
          248  +    );
          249  +  }
   219    250     while( db_step(&q)==SQLITE_ROW ){
   220    251       const char *zPathname = db_column_text(&q,0);
   221    252       int isDeleted = db_column_int(&q, 1);
   222    253       int isNew = db_column_int(&q,2)==0;
   223    254       int chnged = db_column_int(&q,3);
   224    255       int renamed = db_column_int(&q,4);
   225    256       char *zFullName = mprintf("%s%s", g.zLocalRoot, zPathname);
   226         -    if( isBrief ){
          257  +    if( showAge ){
          258  +      fossil_print("%s  %s\n", db_column_text(&q, 5), zPathname);
          259  +    }else if( isBrief ){
   227    260         fossil_print("%s\n", zPathname);
   228    261       }else if( isNew ){
   229    262         fossil_print("ADDED      %s\n", zPathname);
   230    263       }else if( isDeleted ){
   231    264         fossil_print("DELETED    %s\n", zPathname);
   232    265       }else if( !file_isfile(zFullName) ){
   233    266         if( file_access(zFullName, 0)==0 ){

Changes to src/checkout.c.

    31     31   **     2:   There is no existing checkout
    32     32   */
    33     33   int unsaved_changes(void){
    34     34     int vid;
    35     35     db_must_be_within_tree();
    36     36     vid = db_lget_int("checkout",0);
    37     37     if( vid==0 ) return 2;
    38         -  vfile_check_signature(vid, 1, 0);
           38  +  vfile_check_signature(vid, CKSIG_ENOTFILE);
    39     39     return db_exists("SELECT 1 FROM vfile WHERE chnged"
    40     40                      " OR coalesce(origname!=pathname,0)");
    41     41   }
    42     42   
    43     43   /*
    44     44   ** Undo the current check-out.  Unlink all files from the disk.
    45     45   ** Clear the VFILE table.

Changes to src/content.c.

   267    267       a[0] = rid;
   268    268       a[1] = nextRid;
   269    269       n = 1;
   270    270       while( !bag_find(&contentCache.inCache, nextRid)
   271    271           && (nextRid = findSrcid(nextRid))>0 ){
   272    272         n++;
   273    273         if( n>=nAlloc ){
          274  +        if( n>db_int(0, "SELECT max(rid) FROM blob") ){
          275  +          fossil_panic("infinite loop in DELTA table");
          276  +        }
   274    277           nAlloc = nAlloc*2 + 10;
   275    278           a = fossil_realloc(a, nAlloc*sizeof(a[0]));
   276    279         }
   277    280         a[n] = nextRid;
   278    281       }
   279    282       mx = n;
   280    283       rc = content_get(a[n], pBlob);

Changes to src/db.c.

   625    625     sqlite3_context *context,
   626    626     int argc,
   627    627     sqlite3_value **argv
   628    628   ){
   629    629     sqlite3_result_int64(context, time(0));
   630    630   }
   631    631   
          632  +/*
          633  +** Function to return the check-in time for a file.
          634  +*/
          635  +void db_checkin_mtime_function(
          636  +  sqlite3_context *context,
          637  +  int argc,
          638  +  sqlite3_value **argv
          639  +){
          640  +  i64 mtime;
          641  +  int rc = mtime_of_manifest_file(sqlite3_value_int(argv[0]),
          642  +                                  sqlite3_value_int(argv[1]), &mtime);
          643  +  if( rc==0 ){
          644  +    sqlite3_result_int64(context, mtime);
          645  +  }
          646  +}
          647  +
   632    648   
   633    649   /*
   634    650   ** Open a database file.  Return a pointer to the new database
   635    651   ** connection.  An error results in process abort.
   636    652   */
   637    653   static sqlite3 *openDatabase(const char *zDbName){
   638    654     int rc;
................................................................................
   647    663     );
   648    664     if( rc!=SQLITE_OK ){
   649    665       db_err(sqlite3_errmsg(db));
   650    666     }
   651    667     sqlite3_busy_timeout(db, 5000); 
   652    668     sqlite3_wal_autocheckpoint(db, 1);  /* Set to checkpoint frequently */
   653    669     sqlite3_create_function(db, "now", 0, SQLITE_ANY, 0, db_now_function, 0, 0);
          670  +  sqlite3_create_function(db, "checkin_mtime", 2, SQLITE_ANY, 0,
          671  +                          db_checkin_mtime_function, 0, 0);
   654    672     return db;
   655    673   }
   656    674   
   657    675   
   658    676   /*
   659    677   ** zDbName is the name of a database file.  If no other database
   660    678   ** file is open, then open this one.  If another database file is
   661    679   ** already open, then attach zDbName using the name zLabel.
   662    680   */
   663         -static void db_open_or_attach(const char *zDbName, const char *zLabel){
          681  +void db_open_or_attach(const char *zDbName, const char *zLabel){
   664    682     if( !g.db ){
   665    683       g.db = openDatabase(zDbName);
   666    684       g.zMainDbType = zLabel;
   667    685       db_connection_init();
   668    686     }else{
   669    687       db_multi_exec("ATTACH DATABASE %Q AS %s", zDbName, zLabel);
   670    688     }

Changes to src/descendants.c.

   154    154     }
   155    155   }
   156    156   
   157    157   /*
   158    158   ** Load the record ID rid and up to N-1 closest ancestors into
   159    159   ** the "ok" table.
   160    160   */
   161         -void compute_ancestors(int rid, int N){
          161  +void compute_ancestors(int rid, int N, int directOnly){
   162    162     Bag seen;
   163    163     PQueue queue;
   164    164     Stmt ins;
   165    165     Stmt q;
   166    166     bag_init(&seen);
   167    167     pqueue_init(&queue);
   168    168     bag_insert(&seen, rid);
   169    169     pqueue_insert(&queue, rid, 0.0, 0);
   170    170     db_prepare(&ins, "INSERT OR IGNORE INTO ok VALUES(:rid)");
   171    171     db_prepare(&q,
   172    172       "SELECT a.pid, b.mtime FROM plink a LEFT JOIN plink b ON b.cid=a.pid"
   173         -    " WHERE a.cid=:rid"
          173  +    " WHERE a.cid=:rid %s",
          174  +    directOnly ? " AND a.isprim" : ""
   174    175     );
   175    176     while( (N--)>0 && (rid = pqueue_extract(&queue, 0))!=0 ){
   176    177       db_bind_int(&ins, ":rid", rid);
   177    178       db_step(&ins);
   178    179       db_reset(&ins);
   179    180       db_bind_int(&q, ":rid", rid);
   180    181       while( db_step(&q)==SQLITE_ROW ){
................................................................................
   200    201   ** direct ancestor as the largest generation number.
   201    202   */
   202    203   void compute_direct_ancestors(int rid, int N){
   203    204     Stmt ins;
   204    205     Stmt q;
   205    206     int gen = 0;
   206    207     db_multi_exec(
   207         -    "CREATE TEMP TABLE ancestor(rid INTEGER, generation INTEGER PRIMARY KEY);"
          208  +    "CREATE TEMP TABLE IF NOT EXISTS ancestor(rid INTEGER,"
          209  +                                            " generation INTEGER PRIMARY KEY);"
          210  +    "DELETE FROM ancestor;"
   208    211       "INSERT INTO ancestor VALUES(%d, 0);", rid
   209    212     );
   210    213     db_prepare(&ins, "INSERT INTO ancestor VALUES(:rid, :gen)");
   211    214     db_prepare(&q, 
   212    215       "SELECT pid FROM plink"
   213    216       " WHERE cid=:rid AND isprim"
   214    217     );
................................................................................
   222    225       db_bind_int(&ins, ":gen", gen);
   223    226       db_step(&ins);
   224    227       db_reset(&ins);
   225    228     }
   226    229     db_finalize(&ins);
   227    230     db_finalize(&q);
   228    231   }
          232  +
          233  +/*
          234  +** Compute the "mtime" of the file given whose blob.rid is "fid" that
          235  +** is part of check-in "vid".  The mtime will be the mtime on vid or
          236  +** some ancestor of vid where fid first appears.
          237  +*/
          238  +int mtime_of_manifest_file(
          239  +  int vid,       /* The check-in that contains fid */
          240  +  int fid,       /* The id of the file whose check-in time is sought */
          241  +  i64 *pMTime    /* Write result here */
          242  +){
          243  +  static int prevVid = -1;
          244  +  static Stmt q;
          245  +
          246  +  if( prevVid!=vid ){
          247  +    prevVid = vid;
          248  +    db_multi_exec("DROP TABLE IF EXISTS temp.ok;"
          249  +                  "CREATE TEMP TABLE ok(x INTEGER PRIMARY KEY);");
          250  +    compute_ancestors(vid, 100000000, 1);
          251  +  }
          252  +  db_static_prepare(&q,
          253  +    "SELECT (max(event.mtime)-2440587.5)*86400 FROM mlink, event"
          254  +    " WHERE mlink.mid=event.objid"
          255  +    "   AND +mlink.mid IN ok"
          256  +    "   AND mlink.fid=:fid");
          257  +  db_bind_int(&q, ":fid", fid);
          258  +  if( db_step(&q)!=SQLITE_ROW ){
          259  +    db_reset(&q);
          260  +    return 1;
          261  +  }
          262  +  *pMTime = db_column_int64(&q, 0);
          263  +  db_reset(&q);
          264  +  return 0;
          265  +}
   229    266   
   230    267   /*
   231    268   ** Load the record ID rid and up to N-1 closest descendants into
   232    269   ** the "ok" table.
   233    270   */
   234    271   void compute_descendants(int rid, int N){
   235    272     Bag seen;

Changes to src/diffcmd.c.

   210    210     Stmt q;
   211    211     int ignoreEolWs;          /* Ignore end-of-line whitespace */
   212    212     int asNewFile;            /* Treat non-existant files as empty files */
   213    213   
   214    214     ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
   215    215     asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
   216    216     vid = db_lget_int("checkout", 0);
   217         -  vfile_check_signature(vid, 1, 0);
          217  +  vfile_check_signature(vid, CKSIG_ENOTFILE);
   218    218     blob_zero(&sql);
   219    219     db_begin_transaction();
   220    220     if( zFrom ){
   221    221       int rid = name_to_typed_rid(zFrom, "ci");
   222    222       if( !is_a_version(rid) ){
   223    223         fossil_fatal("no such check-in: %s", zFrom);
   224    224       }

Changes to src/file.c.

    21     21   #include <sys/types.h>
    22     22   #include <sys/stat.h>
    23     23   #include <unistd.h>
    24     24   #include <string.h>
    25     25   #include <errno.h>
    26     26   #include "file.h"
    27     27   
           28  +#ifdef _WIN32
           29  +# include <sys/utime.h>
           30  +#else
           31  +# include <sys/time.h>
           32  +#endif
           33  +
    28     34   /*
    29     35   ** The file status information from the most recent stat() call.
    30     36   **
    31     37   ** Use _stati64 rather than stat on windows, in order to handle files
    32     38   ** larger than 2GB.
    33     39   */
    34     40   #if defined(_WIN32) && defined(__MSVCRT__)
................................................................................
   213    219         chmod(zFilename, buf.st_mode & ~0111);
   214    220         rc = 1;
   215    221       }
   216    222     }
   217    223   #endif /* _WIN32 */
   218    224     return rc;
   219    225   }
          226  +
          227  +/*
          228  +** Set the mtime for a file.
          229  +*/
          230  +void file_set_mtime(const char *zFilename, i64 newMTime){
          231  +#if !defined(_WIN32)
          232  +  struct timeval tv[2];
          233  +  memset(tv, 0, sizeof(tv[0])*2);
          234  +  tv[0].tv_sec = newMTime;
          235  +  tv[1].tv_sec = newMTime;
          236  +  utimes(zFilename, tv);
          237  +#else
          238  +  struct utimbuf tb;
          239  +  char *zMbcs = fossil_utf8_to_mbcs(zFilename);
          240  +  tb.actime = newMTime;
          241  +  tb.modtime = newMTime;
          242  +  _utime(zMbcs, &tb);
          243  +  fossil_mbcs_free(zMbcs);
          244  +#endif
          245  +}
          246  +
          247  +/*
          248  +** COMMAND: test-set-mtime
          249  +**
          250  +** Usage: %fossil test-set-mtime FILENAME DATE/TIME
          251  +**
          252  +** Sets the mtime of the named file to the date/time shown.
          253  +*/
          254  +void test_set_mtime(void){
          255  +  const char *zFile;
          256  +  char *zDate;
          257  +  i64 iMTime;
          258  +  if( g.argc!=4 ){
          259  +    usage("test-set-mtime FILENAME DATE/TIME");
          260  +  }
          261  +  db_open_or_attach(":memory:", "mem");
          262  +  iMTime = db_int64(0, "SELECT strftime('%%s',%Q)", g.argv[3]);
          263  +  zFile = g.argv[2];
          264  +  file_set_mtime(zFile, iMTime);
          265  +  iMTime = file_mtime(zFile);
          266  +  zDate = db_text(0, "SELECT datetime(%lld, 'unixepoch')", iMTime);
          267  +  fossil_print("Set mtime of \"%s\" to %s (%lld)\n", zFile, zDate, iMTime);
          268  +}
   220    269   
   221    270   /*
   222    271   ** Delete a file.
   223    272   */
   224    273   void file_delete(const char *zFilename){
   225    274     char *z = fossil_utf8_to_mbcs(zFilename);
   226    275     unlink(z);

Changes to src/finfo.c.

    49     49       int vid;
    50     50   
    51     51       if( g.argc!=3 ) usage("-s|--status FILENAME");
    52     52       vid = db_lget_int("checkout", 0);
    53     53       if( vid==0 ){
    54     54         fossil_panic("no checkout to finfo files in");
    55     55       }
    56         -    vfile_check_signature(vid, 1, 0);
           56  +    vfile_check_signature(vid, CKSIG_ENOTFILE);
    57     57       file_tree_name(g.argv[2], &fname, 1);
    58     58       db_prepare(&q,
    59     59           "SELECT pathname, deleted, rid, chnged, coalesce(origname!=pathname,0)"
    60     60           "  FROM vfile WHERE vfile.pathname=%B", &fname);
    61     61       blob_zero(&line);
    62     62       if ( db_step(&q)==SQLITE_ROW ) {
    63     63         Blob uuid;

Changes to src/merge.c.

   141    141       int t = pid;
   142    142       pid = mid;
   143    143       mid = t;
   144    144     }
   145    145     if( !is_a_version(pid) ){
   146    146       fossil_fatal("not a version: record #%d", pid);
   147    147     }
   148         -  vfile_check_signature(vid, 1, 0);
          148  +  vfile_check_signature(vid, CKSIG_ENOTFILE);
   149    149     db_begin_transaction();
   150    150     if( !nochangeFlag ) undo_begin();
   151    151     load_vfile_from_rid(mid);
   152    152     load_vfile_from_rid(pid);
   153    153   
   154    154     /*
   155    155     ** The vfile.pathname field is used to match files against each other.  The

Changes to src/stash.c.

   142    142     int vid;                           /* Current checkout */
   143    143   
   144    144     zComment = find_option("comment", "m", 1);
   145    145     verify_all_options();
   146    146     stashid = db_lget_int("stash-next", 1);
   147    147     db_lset_int("stash-next", stashid+1);
   148    148     vid = db_lget_int("checkout", 0);
   149         -  vfile_check_signature(vid, 0, 0);
          149  +  vfile_check_signature(vid, 0);
   150    150     db_multi_exec(
   151    151       "INSERT INTO stash(stashid,vid,comment,ctime)"
   152    152       "VALUES(%d,%d,%Q,julianday('now'))",
   153    153       stashid, vid, zComment
   154    154     );
   155    155     if( g.argc>3 ){
   156    156       int i;

Changes to src/timeline.c.

   984    984           db_multi_exec("%s", blob_str(&sql));
   985    985           blob_appendf(&desc, "%d descendant%s", nd,(1==nd)?"":"s");
   986    986         }
   987    987         if( useDividers ) timeline_add_dividers(0, d_rid);
   988    988         db_multi_exec("DELETE FROM ok");
   989    989       }
   990    990       if( p_rid ){
   991         -      compute_ancestors(p_rid, nEntry+1);
          991  +      compute_ancestors(p_rid, nEntry+1, 0);
   992    992         np = db_int(0, "SELECT count(*)-1 FROM ok");
   993    993         if( np>0 ){
   994    994           if( nd>0 ) blob_appendf(&desc, " and ");
   995    995           blob_appendf(&desc, "%d ancestors", np);
   996    996           db_multi_exec("%s", blob_str(&sql));
   997    997         }
   998    998         if( d_rid==0 && useDividers ) timeline_add_dividers(0, p_rid);
................................................................................
  1505   1505     );
  1506   1506   
  1507   1507     if( mode==3 || mode==4 ){
  1508   1508       db_multi_exec("CREATE TEMP TABLE ok(rid INTEGER PRIMARY KEY)");
  1509   1509       if( mode==3 ){
  1510   1510         compute_descendants(objid, n);
  1511   1511       }else{
  1512         -      compute_ancestors(objid, n);
         1512  +      compute_ancestors(objid, n, 0);
  1513   1513       }
  1514   1514       blob_appendf(&sql, " AND blob.rid IN ok");
  1515   1515     }
  1516   1516     if( zType && (zType[0]!='a') ){
  1517   1517       blob_appendf(&sql, " AND event.type=%Q ", zType);
  1518   1518     }
  1519   1519   

Changes to src/update.c.

    90     90     int vid;              /* Current version */
    91     91     int tid=0;            /* Target version - version we are changing to */
    92     92     Stmt q;
    93     93     int latestFlag;       /* --latest.  Pick the latest version if true */
    94     94     int nochangeFlag;     /* -n or --nochange.  Do a dry run */
    95     95     int verboseFlag;      /* -v or --verbose.  Output extra information */
    96     96     int debugFlag;        /* --debug option */
           97  +  int setmtimeFlag;     /* --setmtime.  Set mtimes on files */
    97     98     int nChng;            /* Number of file renames */
    98     99     int *aChng;           /* Array of file renames */
    99    100     int i;                /* Loop counter */
   100    101     int nConflict = 0;    /* Number of merge conflicts */
   101    102     Stmt mtimeXfer;       /* Statment to transfer mtimes */
   102    103   
   103    104     if( !internalUpdate ){
................................................................................
   104    105       undo_capture_command_line();
   105    106       url_proxy_options();
   106    107     }
   107    108     latestFlag = find_option("latest",0, 0)!=0;
   108    109     nochangeFlag = find_option("nochange","n",0)!=0;
   109    110     verboseFlag = find_option("verbose","v",0)!=0;
   110    111     debugFlag = find_option("debug",0,0)!=0;
          112  +  setmtimeFlag = find_option("setmtime",0,0)!=0;
   111    113     db_must_be_within_tree();
   112    114     vid = db_lget_int("checkout", 0);
   113    115     if( vid==0 ){
   114    116       fossil_fatal("cannot find current version");
   115    117     }
   116    118     if( !nochangeFlag && db_exists("SELECT 1 FROM vmerge") ){
   117    119       fossil_fatal("cannot update an uncommitted merge");
................................................................................
   184    186     }
   185    187   
   186    188     if( tid==0 ){
   187    189       fossil_panic("Internal Error: unable to find a version to update to.");
   188    190     }
   189    191   
   190    192     db_begin_transaction();
   191         -  vfile_check_signature(vid, 1, 0);
          193  +  vfile_check_signature(vid, CKSIG_ENOTFILE);
   192    194     if( !nochangeFlag && !internalUpdate ) undo_begin();
   193    195     load_vfile_from_rid(tid);
   194    196   
   195    197     /*
   196    198     ** The record.fn field is used to match files against each other.  The
   197    199     ** FV table contains one row for each each unique filename in
   198    200     ** in the current checkout, the pivot, and the version being merged.
................................................................................
   203    205       "  fn TEXT PRIMARY KEY,"      /* The filename relative to root */
   204    206       "  idv INTEGER,"              /* VFILE entry for current version */
   205    207       "  idt INTEGER,"              /* VFILE entry for target version */
   206    208       "  chnged BOOLEAN,"           /* True if current version has been edited */
   207    209       "  ridv INTEGER,"             /* Record ID for current version */
   208    210       "  ridt INTEGER,"             /* Record ID for target */
   209    211       "  isexe BOOLEAN,"            /* Does target have execute permission? */
          212  +    "  deleted BOOLEAN DEFAULT 0,"/* File marke by "rm" to become unmanaged */
   210    213       "  fnt TEXT"                  /* Filename of same file on target version */
   211    214       ");"
   212    215     );
   213    216   
   214    217     /* Add files found in the current version
   215    218     */
   216    219     db_multi_exec(
   217         -    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
   218         -    " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged"
          220  +    "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged,deleted)"
          221  +    " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged, deleted"
   219    222       "   FROM vfile WHERE vid=%d",
   220    223       vid
   221    224     );
   222    225   
   223    226     /* Compute file name changes on V->T.  Record name changes in files that
   224    227     ** have changed locally.
   225    228     */
................................................................................
   307    310     }
   308    311   
   309    312     /*
   310    313     ** Alter the content of the checkout so that it conforms with the
   311    314     ** target
   312    315     */
   313    316     db_prepare(&q, 
   314         -    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt, isexe FROM fv ORDER BY 1"
          317  +    "SELECT fn, idv, ridv, idt, ridt, chnged, fnt,"
          318  +    "       isexe, deleted FROM fv ORDER BY 1"
   315    319     );
   316    320     db_prepare(&mtimeXfer,
   317    321       "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
   318    322       " WHERE id=:idt"
   319    323     );
   320    324     assert( g.zLocalRoot!=0 );
   321    325     assert( strlen(g.zLocalRoot)>1 );
................................................................................
   325    329       int idv = db_column_int(&q, 1);             /* VFILE entry for current */
   326    330       int ridv = db_column_int(&q, 2);            /* RecordID for current */
   327    331       int idt = db_column_int(&q, 3);             /* VFILE entry for target */
   328    332       int ridt = db_column_int(&q, 4);            /* RecordID for target */
   329    333       int chnged = db_column_int(&q, 5);          /* Current is edited */
   330    334       const char *zNewName = db_column_text(&q,6);/* New filename */
   331    335       int isexe = db_column_int(&q, 7);           /* EXE perm for new file */
          336  +    int deleted = db_column_int(&q, 8);         /* Marked for deletion */
   332    337       char *zFullPath;                            /* Full pathname of the file */
   333    338       char *zFullNewPath;                         /* Full pathname of dest */
   334    339       char nameChng;                              /* True if the name changed */
   335    340   
   336    341       zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
   337    342       zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
   338    343       nameChng = fossil_strcmp(zName, zNewName);
          344  +    if( deleted ){
          345  +      db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
          346  +    }
   339    347       if( idv>0 && ridv==0 && idt>0 && ridt>0 ){
   340    348         /* Conflict.  This file has been added to the current checkout
   341    349         ** but also exists in the target checkout.  Use the current version.
   342    350         */
   343    351         fossil_print("CONFLICT %s\n", zName);
   344    352         nConflict++;
   345    353       }else if( idt>0 && idv==0 ){
   346    354         /* File added in the target. */
   347    355         fossil_print("ADD %s\n", zName);
   348    356         undo_save(zName);
   349    357         if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
   350         -    }else if( idt>0 && idv>0 && ridt!=ridv && chnged==0 ){
          358  +    }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){
   351    359         /* The file is unedited.  Change it to the target version */
   352    360         undo_save(zName);
   353         -      fossil_print("UPDATE %s\n", zName);
          361  +      if( deleted ){
          362  +        fossil_print("UPDATE %s - change to unmanged file\n", zName);
          363  +      }else{
          364  +        fossil_print("UPDATE %s\n", zName);
          365  +      }
   354    366         if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
   355    367       }else if( idt>0 && idv>0 && file_size(zFullPath)<0 ){
   356    368         /* The file missing from the local check-out. Restore it to the
   357    369         ** version that appears in the target. */
   358         -      fossil_print("UPDATE %s\n", zName);
          370  +      fossil_print("UPDATE %s%s\n", zName,
          371  +                    deleted?" - change to unmanaged file":"");
   359    372         undo_save(zName);
   360    373         if( !nochangeFlag ) vfile_to_disk(0, idt, 0, 0);
   361    374       }else if( idt==0 && idv>0 ){
   362    375         if( ridv==0 ){
   363    376           /* Added in current checkout.  Continue to hold the file as
   364    377           ** as an addition */
   365    378           db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
................................................................................
   454    467         db_lset_int("checkout", tid);
   455    468       }else{
   456    469         /* A subset of files have been checked out.  Keep the current
   457    470         ** checkout unchanged. */
   458    471         db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
   459    472       }
   460    473       if( !internalUpdate ) undo_finish();
          474  +    if( setmtimeFlag ) vfile_check_signature(tid, CKSIG_SETMTIME);
   461    475       db_end_transaction(0);
   462    476     }
   463    477   }
   464    478   
   465    479   /*
   466    480   ** Make sure empty directories are created
   467    481   */
................................................................................
   608    622         file_tree_name(zFile, &fname, 1);
   609    623         db_multi_exec("REPLACE INTO torevert VALUES(%B)", &fname);
   610    624         blob_reset(&fname);
   611    625       }
   612    626     }else{
   613    627       int vid;
   614    628       vid = db_lget_int("checkout", 0);
   615         -    vfile_check_signature(vid, 0, 0);
          629  +    vfile_check_signature(vid, 0);
   616    630       db_multi_exec(
   617    631         "DELETE FROM vmerge;"
   618    632         "INSERT INTO torevert "
   619    633         "SELECT pathname"
   620    634         "  FROM vfile "
   621    635         " WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
   622    636       );

Changes to src/vfile.c.

   120    120     }
   121    121     db_finalize(&ridq);
   122    122     db_finalize(&ins);
   123    123     manifest_destroy(p);
   124    124     db_end_transaction(0);
   125    125   }
   126    126   
   127         -/*
   128         -** Check the file signature of the disk image for every VFILE of vid.
   129         -**
   130         -** Set the VFILE.CHNGED field on every file that has changed.  Also 
   131         -** set VFILE.CHNGED on every folder that contains a file or folder 
   132         -** that has changed.
   133         -**
   134         -** If VFILE.DELETED is null or if VFILE.RID is zero, then we can assume
   135         -** the file has changed without having the check the on-disk image.
   136         -**
   137         -** If the size of the file has changed, then we assume that it has
   138         -** changed.  If the mtime of the file has not changed and useSha1sum is false
   139         -** and the mtime-changes setting is true (the default) then we assume that
   140         -** the file has not changed.  If the mtime has changed, we go ahead and
   141         -** double-check that the file has changed by looking at its SHA1 sum.
          127  +#if INTERFACE
          128  +/*
          129  +** The cksigFlags parameter to vfile_check_signature() is an OR-ed
          130  +** combination of the following bits:
          131  +*/
          132  +#define CKSIG_ENOTFILE  0x001   /* non-file FS objects throw an error */
          133  +#define CKSIG_SHA1      0x002   /* Verify file content using sha1sum */
          134  +#define CKSIG_SETMTIME  0x004   /* Set mtime to last check-out time */
          135  +
          136  +#endif /* INTERFACE */
          137  +
          138  +/*
          139  +** Look at every VFILE entry with the given vid and update
          140  +** VFILE.CHNGED field according to whether or not
          141  +** the file has changed.  0 means no change.  1 means edited.  2 means
          142  +** the file has changed due to a merge.  3 means the file was added
          143  +** by a merge.
          144  +**
          145  +** If VFILE.DELETED is true or if VFILE.RID is zero, then the file was either
          146  +** removed from configuration management via "fossil rm" or added via
          147  +** "fossil add", respectively, and in both cases we always know that 
          148  +** the file has changed without having the check the size, mtime,
          149  +** or on-disk content.
          150  +**
          151  +** If the size of the file has changed, then we always know that the file
          152  +** changed without having to look at the mtime or on-disk content.
          153  +**
          154  +** The mtime of the file is only a factor if the mtime-changes setting
          155  +** is false and the useSha1sum flag is false.  If the mtime-changes
          156  +** setting is true (or undefined - it defaults to true) or if useSha1sum
          157  +** is true, then we do not trust the mtime and will examine the on-disk
          158  +** content to determine if a file really is the same.
          159  +**
          160  +** If the mtime is used, it is used only to determine if files are the same.
          161  +** If the mtime of a file has changed, we still examine the on-disk content
          162  +** to see whether or not the edit was a null-edit.
   142    163   */
   143         -void vfile_check_signature(int vid, int notFileIsFatal, int useSha1sum){
          164  +void vfile_check_signature(int vid, unsigned int cksigFlags){
   144    165     int nErr = 0;
   145    166     Stmt q;
   146    167     Blob fileCksum, origCksum;
   147         -  int checkMtime = useSha1sum==0 && db_get_boolean("mtime-changes", 1);
          168  +  int useMtime = (cksigFlags & CKSIG_SHA1)==0
          169  +                    && db_get_boolean("mtime-changes", 1);
   148    170   
   149    171     db_begin_transaction();
   150    172     db_prepare(&q, "SELECT id, %Q || pathname,"
   151    173                    "       vfile.mrid, deleted, chnged, uuid, size, mtime"
   152    174                    "  FROM vfile LEFT JOIN blob ON vfile.mrid=blob.rid"
   153    175                    " WHERE vid=%d ", g.zLocalRoot, vid);
   154    176     while( db_step(&q)==SQLITE_ROW ){
   155    177       int id, rid, isDeleted;
   156    178       const char *zName;
   157    179       int chnged = 0;
   158    180       int oldChnged;
   159    181       i64 oldMtime;
   160    182       i64 currentMtime;
          183  +    i64 origSize;
          184  +    i64 currentSize;
   161    185   
   162    186       id = db_column_int(&q, 0);
   163    187       zName = db_column_text(&q, 1);
   164    188       rid = db_column_int(&q, 2);
   165    189       isDeleted = db_column_int(&q, 3);
   166         -    oldChnged = db_column_int(&q, 4);
          190  +    oldChnged = chnged = db_column_int(&q, 4);
   167    191       oldMtime = db_column_int64(&q, 7);
   168         -    if( isDeleted ){
          192  +    currentSize = file_size(zName);
          193  +    origSize = db_column_int64(&q, 6);
          194  +    currentMtime = file_mtime(0);
          195  +    if( chnged==0 && (isDeleted || rid==0) ){
          196  +      /* "fossil rm" or "fossil add" always change the file */
   169    197         chnged = 1;
   170         -    }else if( !file_isfile(zName) && file_size(0)>=0 ){
   171         -      if( notFileIsFatal ){
          198  +    }else if( !file_isfile(0) && currentSize>=0 ){
          199  +      if( cksigFlags & CKSIG_ENOTFILE ){
   172    200           fossil_warning("not an ordinary file: %s", zName);
   173    201           nErr++;
   174    202         }
   175    203         chnged = 1;
   176         -    }else if( oldChnged>=2 ){
   177         -      chnged = oldChnged;
   178         -    }else if( rid==0 ){
   179         -      chnged = 1;
   180    204       }
   181         -    if( chnged!=1 ){
   182         -      i64 origSize = db_column_int64(&q, 6);
   183         -      currentMtime = file_mtime(0);
   184         -      if( origSize!=file_size(0) ){
          205  +    if( origSize!=currentSize ){
          206  +      if( chnged!=1 ){
   185    207           /* A file size change is definitive - the file has changed.  No
   186         -        ** need to check the sha1sum */
          208  +        ** need to check the mtime or sha1sum */
   187    209           chnged = 1;
   188    210         }
   189         -    }
   190         -    if( chnged!=1 && (checkMtime==0 || currentMtime!=oldMtime) ){
          211  +    }else if( chnged==1 && rid!=0 && !isDeleted ){
          212  +      /* File is believed to have changed but it is the same size.
          213  +      ** Double check that it really has changed by looking at content. */
          214  +      assert( origSize==currentSize );
          215  +      db_ephemeral_blob(&q, 5, &origCksum);
          216  +      if( sha1sum_file(zName, &fileCksum) ){
          217  +        blob_zero(&fileCksum);
          218  +      }
          219  +      if( blob_compare(&fileCksum, &origCksum)==0 ) chnged = 0;
          220  +      blob_reset(&origCksum);
          221  +      blob_reset(&fileCksum);
          222  +    }else if( (chnged==0 || chnged==2)
          223  +           && (useMtime==0 || currentMtime!=oldMtime) ){
          224  +      /* For files that were formerly believed to be unchanged or that were
          225  +      ** changed by merging, if their mtime changes, or unconditionally
          226  +      ** if --sha1sum is used, check to see if they have been edited by
          227  +      ** looking at their SHA1 sum */
          228  +      assert( origSize==currentSize );
   191    229         db_ephemeral_blob(&q, 5, &origCksum);
   192    230         if( sha1sum_file(zName, &fileCksum) ){
   193    231           blob_zero(&fileCksum);
   194    232         }
   195    233         if( blob_compare(&fileCksum, &origCksum) ){
   196    234           chnged = 1;
   197         -      }else if( currentMtime!=oldMtime ){
   198         -        db_multi_exec("UPDATE vfile SET mtime=%lld WHERE id=%d",
   199         -                      currentMtime, id);
   200    235         }
   201    236         blob_reset(&origCksum);
   202    237         blob_reset(&fileCksum);
   203    238       }
   204         -    if( chnged!=oldChnged && (chnged || !checkMtime) ){
   205         -      db_multi_exec("UPDATE vfile SET chnged=%d WHERE id=%d", chnged, id);
          239  +    if( (cksigFlags & CKSIG_SETMTIME) && (chnged==0 || chnged==2) ){
          240  +      i64 desiredMtime;
          241  +      if( mtime_of_manifest_file(vid,rid,&desiredMtime)==0 ){
          242  +        if( currentMtime!=desiredMtime ){
          243  +          file_set_mtime(zName, desiredMtime);
          244  +          currentMtime = file_mtime(zName);
          245  +        }
          246  +      }
          247  +    }
          248  +    if( currentMtime!=oldMtime || chnged!=oldChnged ){
          249  +      db_multi_exec("UPDATE vfile SET mtime=%lld, chnged=%d WHERE id=%d",
          250  +                    currentMtime, chnged, id);
   206    251       }
   207    252     }
   208    253     db_finalize(&q);
   209    254     if( nErr ) fossil_fatal("abort due to prior errors");
   210    255     db_end_transaction(0);
   211    256   }
   212    257   

Changes to src/xfer.c.

   266    266     int isPrivate,          /* True if rid is a private artifact */
   267    267     Blob *pContent,         /* The content of the file to send */
   268    268     Blob *pUuid             /* The UUID of the file to send */
   269    269   ){
   270    270     static const char *azQuery[] = {
   271    271       "SELECT pid FROM plink x"
   272    272       " WHERE cid=%d"
   273         -    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
   274         -    "   AND NOT EXISTS(SELECT 1 FROM plink y"
   275         -                      " WHERE y.pid=x.cid AND y.cid=x.pid)",
   276         -
   277         -    "SELECT pid FROM mlink x"
          273  +    "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)",
          274  +    
          275  +    "SELECT pid, min(mtime) FROM mlink, event ON mlink.mid=event.objid"
   278    276       " WHERE fid=%d"
   279    277       "   AND NOT EXISTS(SELECT 1 FROM phantom WHERE rid=pid)"
   280         -    "   AND NOT EXISTS(SELECT 1 FROM mlink y"
   281         -                     "  WHERE y.pid=x.fid AND y.fid=x.pid)"
   282    278     };
   283    279     int i;
   284    280     Blob src, delta;
   285    281     int size = 0;
   286    282     int srcId = 0;
   287    283   
   288    284     for(i=0; srcId==0 && i<count(azQuery); i++){
................................................................................
   992    988             if( iVers>=3 ){
   993    989               send_compressed_file(&xfer, seqno);
   994    990             }else{
   995    991               send_file(&xfer, seqno, 0, 1);
   996    992             }
   997    993             seqno++;
   998    994           }
   999         -        if( seqno>=max ) seqno = 0;
          995  +        if( seqno>max ) seqno = 0;
  1000    996           @ clone_seqno %d(seqno)
  1001    997         }else{
  1002    998           isClone = 1;
  1003    999           isPull = 1;
  1004   1000           deltaFlag = 1;
  1005   1001         }
  1006   1002         @ push %s(db_get("server-code", "x")) %s(db_get("project-code", "x"))

Added test/update-test-1.sh.

            1  +#!/bin/sh
            2  +#
            3  +# Run this script in an empty directory.  A single argument is the full
            4  +# pathname of the fossil binary.  Example:
            5  +#
            6  +#     sh update-test-1.sh /home/drh/fossil/m1/fossil
            7  +#
            8  +export FOSSIL=$1
            9  +rm -rf aaa bbb update-test-1.fossil
           10  +
           11  +# Create a test repository
           12  +$FOSSIL new update-test-1.fossil
           13  +
           14  +# In checkout aaa, add file one.txt
           15  +mkdir aaa
           16  +cd aaa
           17  +$FOSSIL open ../update-test-1.fossil
           18  +echo one >one.txt
           19  +$FOSSIL add one.txt
           20  +$FOSSIL commit -m add-one --tag add-one
           21  +
           22  +# Open checkout bbb.
           23  +mkdir ../bbb
           24  +cd ../bbb
           25  +$FOSSIL open ../update-test-1.fossil
           26  +
           27  +# Back in aaa, add file two.txt
           28  +cd ../aaa
           29  +echo two >two.txt
           30  +$FOSSIL add two.txt
           31  +$FOSSIL commit -m add-two --tag add-two
           32  +
           33  +# In bbb, delete file one.txt.  Then update the change from aaa that
           34  +# adds file two.  Verify that one.txt says deleted.
           35  +cd ../bbb
           36  +$FOSSIL rm one.txt
           37  +$FOSSIL changes
           38  +echo '========================================================================'
           39  +$FOSSIL update
           40  +echo '======== The previous should show "ADD two.txt" ========================'
           41  +$FOSSIL changes
           42  +echo '======== The previous should show "DELETE one.txt" ====================='
           43  +$FOSSIL commit --test -m check-in
           44  +echo '======== Only file two.txt is checked in ==============================='

Added test/update-test-2.sh.

            1  +#!/bin/sh
            2  +#
            3  +# Run this script in an empty directory.  A single argument is the full
            4  +# pathname of the fossil binary.  Example:
            5  +#
            6  +#     sh update-test-2.sh /home/drh/fossil/m1/fossil
            7  +#
            8  +export FOSSIL=$1
            9  +rm -rf aaa bbb update-test-2.fossil
           10  +
           11  +# Create a test repository
           12  +$FOSSIL new update-test-2.fossil
           13  +
           14  +# In checkout aaa, add file one.txt.
           15  +mkdir aaa
           16  +cd aaa
           17  +$FOSSIL open ../update-test-2.fossil
           18  +echo one >one.txt
           19  +$FOSSIL add one.txt
           20  +$FOSSIL commit -m add-one --tag add-one
           21  +
           22  +# Create checkout bbb.
           23  +mkdir ../bbb
           24  +cd ../bbb
           25  +$FOSSIL open ../update-test-2.fossil
           26  +
           27  +# Back in aaa, make changes to one.txt.  Add file two.txt.
           28  +cd ../aaa
           29  +echo change >>one.txt
           30  +echo two >two.txt
           31  +$FOSSIL add two.txt
           32  +$FOSSIL commit -m 'chng one and add two' --tag add-two
           33  +
           34  +# In bbb, remove one.txt, then update.
           35  +cd ../bbb
           36  +$FOSSIL rm one.txt
           37  +$FOSSIL changes
           38  +echo '========================================================================'
           39  +$FOSSIL update
           40  +echo '======== Previous should show "ADD two.txt" and conflict on one.txt ===='
           41  +$FOSSIL changes
           42  +echo '======== The previous should show "DELETE one.txt" ====================='
           43  +$FOSSIL commit --test -m 'check-in'
           44  +echo '======== Only file two.txt is checked in ==============================='