1 /*
2 ** Copyright (c) 2007 D. Richard Hipp
3 **
4 ** This program is free software; you can redistribute it and/or
5 ** modify it under the terms of the Simplified BSD License (also
6 ** known as the "2-Clause License" or "FreeBSD License".)
7 
8 ** This program is distributed in the hope that it will be useful,
9 ** but without any warranty; without even the implied warranty of
10 ** merchantability or fitness for a particular purpose.
11 **
12 ** Author contact information:
13 **   drh@hwaci.com
14 **   http://www.hwaci.com/drh/
15 **
16 *******************************************************************************
17 **
18 ** This file contains code used to merge the changes in the current
19 ** checkout into a different version and switch to that version.
20 */
21 #include "config.h"
22 #include "update.h"
23 #include <assert.h>
24 
25 /*
26 ** Return true if artifact rid is a version
27 */
is_a_version(int rid)28 int is_a_version(int rid){
29   return db_exists("SELECT 1 FROM event WHERE objid=%d AND type='ci'", rid);
30 }
31 
32 /* This variable is set if we are doing an internal update.  It is clear
33 ** when running the "update" command.
34 */
35 static int internalUpdate = 0;
36 static int internalConflictCnt = 0;
37 
38 /*
39 ** Do an update to version vid.
40 **
41 ** Start an undo session but do not terminate it.  Do not autosync.
42 */
update_to(int vid)43 int update_to(int vid){
44   int savedArgc;
45   char **savedArgv;
46   char *newArgv[3];
47   newArgv[0] = g.argv[0];
48   newArgv[1] = "update";
49   newArgv[2] = 0;
50   savedArgv = g.argv;
51   savedArgc = g.argc;
52   g.argc = 2;
53   g.argv = newArgv;
54   internalUpdate = vid;
55   internalConflictCnt = 0;
56   update_cmd();
57   g.argc = savedArgc;
58   g.argv = savedArgv;
59   return internalConflictCnt;
60 }
61 
62 /*
63 ** COMMAND: update
64 **
65 ** Usage: %fossil update ?OPTIONS? ?VERSION? ?FILES...?
66 **
67 ** Change the version of the current checkout to VERSION.  Any
68 ** uncommitted changes are retained and applied to the new checkout.
69 **
70 ** The VERSION argument can be a specific version or tag or branch
71 ** name.  If the VERSION argument is omitted, then the leaf of the
72 ** subtree that begins at the current version is used, if there is
73 ** only a single leaf.  VERSION can also be "current" to select the
74 ** leaf of the current version or "latest" to select the most recent
75 ** check-in.
76 **
77 ** If one or more FILES are listed after the VERSION then only the
78 ** named files are candidates to be updated, and any updates to them
79 ** will be treated as edits to the current version. Using a directory
80 ** name for one of the FILES arguments is the same as using every
81 ** subdirectory and file beneath that directory.
82 **
83 ** If FILES is omitted, all files in the current checkout are subject
84 ** to being updated and the version of the current checkout is changed
85 ** to VERSION. Any uncommitted changes are retained and applied to the
86 ** new checkout.
87 **
88 ** The -n or --dry-run option causes this command to do a "dry run".
89 ** It prints out what would have happened but does not actually make
90 ** any changes to the current checkout or the repository.
91 **
92 ** The -v or --verbose option prints status information about
93 ** unchanged files in addition to those file that actually do change.
94 **
95 ** Options:
96 **   --case-sensitive BOOL   Override case-sensitive setting
97 **   --debug                 Print debug information on stdout
98 **   -n|--dry-run            If given, display instead of run actions
99 **   --force-missing         Force update if missing content after sync
100 **   -K|--keep-merge-files   On merge conflict, retain the temporary files
101 **                           used for merging, named *-baseline, *-original,
102 **                           and *-merge.
103 **   --latest                Acceptable in place of VERSION, update to
104 **                           latest version
105 **   --nosync                Do not auto-sync prior to update
106 **   --setmtime              Set timestamps of all files to match their
107 **                           SCM-side times (the timestamp of the last
108 **                           checkin which modified them).
109 **   -v|--verbose            Print status information about all files
110 **   -W|--width WIDTH        Width of lines (default is to auto-detect).
111 **                           Must be more than 20 or 0 (= no limit,
112 **                           resulting in a single line per entry).
113 **
114 ** See also: [[revert]]
115 */
update_cmd(void)116 void update_cmd(void){
117   int vid;              /* Current version */
118   int tid=0;            /* Target version - version we are changing to */
119   Stmt q;
120   int latestFlag;       /* --latest.  Pick the latest version if true */
121   int dryRunFlag;       /* -n or --dry-run.  Do a dry run */
122   int verboseFlag;      /* -v or --verbose.  Output extra information */
123   int forceMissingFlag; /* --force-missing.  Continue if missing content */
124   int debugFlag;        /* --debug option */
125   int setmtimeFlag;     /* --setmtime.  Set mtimes on files */
126   int keepMergeFlag;    /* True if --keep-merge-files is present */
127   int nChng;            /* Number of file renames */
128   int *aChng;           /* Array of file renames */
129   int i;                /* Loop counter */
130   int nConflict = 0;    /* Number of merge conflicts */
131   int nOverwrite = 0;   /* Number of unmanaged files overwritten */
132   int nUpdate = 0;      /* Number of changes of any kind */
133   int bNosync = 0;      /* --nosync.  Omit the auto-sync */
134   int width;            /* Width of printed comment lines */
135   Stmt mtimeXfer;       /* Statement to transfer mtimes */
136   const char *zWidth;   /* Width option string value */
137 
138   if( !internalUpdate ){
139     undo_capture_command_line();
140     url_proxy_options();
141   }
142   zWidth = find_option("width","W",1);
143   if( zWidth ){
144     width = atoi(zWidth);
145     if( (width!=0) && (width<=20) ){
146       fossil_fatal("-W|--width value must be >20 or 0");
147     }
148   }else{
149     width = -1;
150   }
151   latestFlag = find_option("latest",0, 0)!=0;
152   dryRunFlag = find_option("dry-run","n",0)!=0;
153   if( !dryRunFlag ){
154     dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */
155   }
156   verboseFlag = find_option("verbose","v",0)!=0;
157   forceMissingFlag = find_option("force-missing",0,0)!=0;
158   debugFlag = find_option("debug",0,0)!=0;
159   setmtimeFlag = find_option("setmtime",0,0)!=0;
160   keepMergeFlag = find_option("keep-merge-files", "K",0)!=0;
161   bNosync = find_option("nosync",0,0)!=0;
162 
163   /* We should be done with options.. */
164   verify_all_options();
165 
166   db_must_be_within_tree();
167   vid = db_lget_int("checkout", 0);
168   user_select();
169   if( !dryRunFlag && !internalUpdate && !bNosync ){
170     if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag,
171                       db_get_int("autosync-tries", 1), 1) ){
172       fossil_fatal("update abandoned due to sync failure");
173     }
174   }
175 
176   /* Create any empty directories now, as well as after the update,
177   ** so changes in settings are reflected now */
178   if( !dryRunFlag ) ensure_empty_dirs_created(0);
179 
180   if( internalUpdate ){
181     tid = internalUpdate;
182   }else if( g.argc>=3 ){
183     if( fossil_strcmp(g.argv[2], "current")==0 ){
184       /* If VERSION is "current", then use the same algorithm to find the
185       ** target as if VERSION were omitted. */
186     }else if( fossil_strcmp(g.argv[2], "latest")==0 ){
187       /* If VERSION is "latest", then use the same algorithm to find the
188       ** target as if VERSION were omitted and the --latest flag is present.
189       */
190       latestFlag = 1;
191     }else{
192       tid = name_to_typed_rid(g.argv[2],"ci");
193        if( tid==0 || !is_a_version(tid) ){
194         fossil_fatal("no such check-in: %s", g.argv[2]);
195       }
196     }
197   }
198 
199   /* If no VERSION is specified on the command-line, then look for a
200   ** descendent of the current version.  If there are multiple descendants,
201   ** look for one from the same branch as the current version.  If there
202   ** are still multiple descendants, show them all and refuse to update
203   ** until the user selects one.
204   */
205   if( tid==0 ){
206     int closeCode = 1;
207     compute_leaves(vid, closeCode);
208     if( !db_exists("SELECT 1 FROM leaves") ){
209       closeCode = 0;
210       compute_leaves(vid, closeCode);
211     }
212     if( !latestFlag && db_int(0, "SELECT count(*) FROM leaves")>1 ){
213       db_multi_exec(
214         "DELETE FROM leaves WHERE rid NOT IN"
215         "   (SELECT leaves.rid FROM leaves, tagxref"
216         "     WHERE leaves.rid=tagxref.rid AND tagxref.tagid=%d"
217         "       AND tagxref.value==(SELECT value FROM tagxref"
218                                    " WHERE tagid=%d AND rid=%d))",
219         TAG_BRANCH, TAG_BRANCH, vid
220       );
221       if( db_int(0, "SELECT count(*) FROM leaves")>1 ){
222         compute_leaves(vid, closeCode);
223         db_prepare(&q,
224           "%s "
225           "   AND event.objid IN leaves"
226           " ORDER BY event.mtime DESC",
227           timeline_query_for_tty()
228         );
229         print_timeline(&q, -100, width, 0, 0);
230         db_finalize(&q);
231         fossil_fatal("Multiple descendants");
232       }
233     }
234     tid = db_int(0, "SELECT rid FROM leaves, event"
235                     " WHERE event.objid=leaves.rid"
236                     " ORDER BY event.mtime DESC");
237     if( tid==0 ) tid = vid;
238   }
239 
240   if( tid==0 ){
241     return;
242   }
243 
244   db_begin_transaction();
245   db_multi_exec(
246      "CREATE TEMP TABLE dir_to_delete(name TEXT %s PRIMARY KEY)WITHOUT ROWID",
247      filename_collation()
248   );
249   vfile_check_signature(vid, CKSIG_ENOTFILE);
250   if( !dryRunFlag && !internalUpdate ) undo_begin();
251   if( load_vfile_from_rid(tid) && !forceMissingFlag ){
252     fossil_fatal("missing content, unable to update");
253   };
254 
255   /*
256   ** The record.fn field is used to match files against each other.  The
257   ** FV table contains one row for each each unique filename in
258   ** in the current checkout, the pivot, and the version being merged.
259   */
260   db_multi_exec(
261     "DROP TABLE IF EXISTS fv;"
262     "CREATE TEMP TABLE fv("
263     "  fn TEXT %s PRIMARY KEY,"   /* The filename relative to root */
264     "  idv INTEGER,"              /* VFILE entry for current version */
265     "  idt INTEGER,"              /* VFILE entry for target version */
266     "  chnged BOOLEAN,"           /* True if current version has been edited */
267     "  islinkv BOOLEAN,"          /* True if current file is a link */
268     "  islinkt BOOLEAN,"          /* True if target file is a link */
269     "  ridv INTEGER,"             /* Record ID for current version */
270     "  ridt INTEGER,"             /* Record ID for target */
271     "  isexe BOOLEAN,"            /* Does target have execute permission? */
272     "  deleted BOOLEAN DEFAULT 0,"/* File marked by "rm" to become unmanaged */
273     "  fnt TEXT %s"               /* Filename of same file on target version */
274     ");",
275     filename_collation(), filename_collation()
276   );
277 
278   /* Add files found in the current version
279   */
280   db_multi_exec(
281     "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged,deleted)"
282     " SELECT pathname, pathname, id, 0, rid, 0, isexe, chnged, deleted"
283     "   FROM vfile WHERE vid=%d",
284     vid
285   );
286 
287   /* Compute file name changes on V->T.  Record name changes in files that
288   ** have changed locally.
289   */
290   if( vid ){
291     find_filename_changes(vid, tid, 1, &nChng, &aChng, debugFlag ? "V->T": 0);
292     if( nChng ){
293       for(i=0; i<nChng; i++){
294         db_multi_exec(
295           "UPDATE fv"
296           "   SET fnt=(SELECT name FROM filename WHERE fnid=%d)"
297           " WHERE fn=(SELECT name FROM filename WHERE fnid=%d) AND chnged",
298           aChng[i*2+1], aChng[i*2]
299         );
300       }
301       fossil_free(aChng);
302     }
303   }
304 
305   /* Add files found in the target version T but missing from the current
306   ** version V.
307   */
308   db_multi_exec(
309     "INSERT OR IGNORE INTO fv(fn,fnt,idv,idt,ridv,ridt,isexe,chnged)"
310     " SELECT pathname, pathname, 0, 0, 0, 0, isexe, 0 FROM vfile"
311     "  WHERE vid=%d"
312     "    AND pathname %s NOT IN (SELECT fnt FROM fv)",
313     tid, filename_collation()
314   );
315 
316   /*
317   ** Compute the file version ids for T
318   */
319   db_multi_exec(
320     "UPDATE fv SET"
321     " idt=coalesce((SELECT id FROM vfile WHERE vid=%d AND fnt=pathname),0),"
322     " ridt=coalesce((SELECT rid FROM vfile WHERE vid=%d AND fnt=pathname),0)",
323     tid, tid
324   );
325 
326   /*
327   ** Add islink information
328   */
329   db_multi_exec(
330     "UPDATE fv SET"
331     " islinkv=coalesce((SELECT islink FROM vfile"
332                        " WHERE vid=%d AND fnt=pathname),0),"
333     " islinkt=coalesce((SELECT islink FROM vfile"
334                        " WHERE vid=%d AND fnt=pathname),0)",
335     vid, tid
336   );
337 
338 
339   if( debugFlag ){
340     db_prepare(&q,
341        "SELECT rowid, fn, fnt, chnged, ridv, ridt, isexe,"
342        "       islinkv, islinkt FROM fv"
343     );
344     while( db_step(&q)==SQLITE_ROW ){
345        fossil_print("%3d: ridv=%-4d ridt=%-4d chnged=%d isexe=%d"
346                     " islinkv=%d  islinkt=%d\n",
347           db_column_int(&q, 0),
348           db_column_int(&q, 4),
349           db_column_int(&q, 5),
350           db_column_int(&q, 3),
351           db_column_int(&q, 6),
352           db_column_int(&q, 7),
353           db_column_int(&q, 8));
354        fossil_print("     fnv = [%s]\n", db_column_text(&q, 1));
355        fossil_print("     fnt = [%s]\n", db_column_text(&q, 2));
356     }
357     db_finalize(&q);
358   }
359 
360   /* If FILES appear on the command-line, remove from the "fv" table
361   ** every entry that is not named on the command-line or which is not
362   ** in a directory named on the command-line.
363   */
364   if( g.argc>=4 ){
365     Blob sql;              /* SQL statement to purge unwanted entries */
366     Blob treename;         /* Normalized filename */
367     int i;                 /* Loop counter */
368     const char *zSep;      /* Term separator */
369 
370     blob_zero(&sql);
371     blob_append(&sql, "DELETE FROM fv WHERE ", -1);
372     zSep = "";
373     for(i=3; i<g.argc; i++){
374       file_tree_name(g.argv[i], &treename, 0, 1);
375       if( file_isdir(g.argv[i], RepoFILE)==1 ){
376         if( blob_size(&treename) != 1 || blob_str(&treename)[0] != '.' ){
377           blob_append_sql(&sql, "%sfn NOT GLOB '%q/*' ",
378                          zSep /*safe-for-%s*/, blob_str(&treename));
379         }else{
380           blob_reset(&sql);
381           break;
382         }
383       }else{
384         blob_append_sql(&sql, "%sfn<>%Q ",
385                         zSep /*safe-for-%s*/, blob_str(&treename));
386       }
387       zSep = "AND ";
388       blob_reset(&treename);
389     }
390     db_multi_exec("%s", blob_sql_text(&sql));
391     blob_reset(&sql);
392   }
393 
394   /*
395   ** Alter the content of the checkout so that it conforms with the
396   ** target
397   */
398   db_prepare(&q,
399     "SELECT fn, idv, ridv, idt, ridt, chnged, fnt,"
400     "       isexe, islinkv, islinkt, deleted FROM fv ORDER BY 1"
401   );
402   db_prepare(&mtimeXfer,
403     "UPDATE vfile SET mtime=(SELECT mtime FROM vfile WHERE id=:idv)"
404     " WHERE id=:idt"
405   );
406   assert( g.zLocalRoot!=0 );
407   assert( strlen(g.zLocalRoot)>0 );
408   assert( g.zLocalRoot[strlen(g.zLocalRoot)-1]=='/' );
409   while( db_step(&q)==SQLITE_ROW ){
410     const char *zName = db_column_text(&q, 0);  /* The filename from root */
411     int idv = db_column_int(&q, 1);             /* VFILE entry for current */
412     int ridv = db_column_int(&q, 2);            /* RecordID for current */
413     int idt = db_column_int(&q, 3);             /* VFILE entry for target */
414     int ridt = db_column_int(&q, 4);            /* RecordID for target */
415     int chnged = db_column_int(&q, 5);          /* Current is edited */
416     const char *zNewName = db_column_text(&q,6);/* New filename */
417     int isexe = db_column_int(&q, 7);           /* EXE perm for new file */
418     int islinkv = db_column_int(&q, 8);         /* Is current file is a link */
419     int islinkt = db_column_int(&q, 9);         /* Is target file is a link */
420     int deleted = db_column_int(&q, 10);        /* Marked for deletion */
421     char *zFullPath;                            /* Full pathname of the file */
422     char *zFullNewPath;                         /* Full pathname of dest */
423     char nameChng;                              /* True if the name changed */
424 
425     zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
426     zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName);
427     nameChng = fossil_strcmp(zName, zNewName);
428     nUpdate++;
429     if( deleted ){
430       db_multi_exec("UPDATE vfile SET deleted=1 WHERE id=%d", idt);
431     }
432     if( idv>0 && ridv==0 && idt>0 && ridt>0 ){
433       /* Conflict.  This file has been added to the current checkout
434       ** but also exists in the target checkout.  Use the current version.
435       */
436       fossil_print("CONFLICT %s\n", zName);
437       nConflict++;
438     }else if( idt>0 && idv==0 ){
439       /* File added in the target. */
440       if( file_isfile_or_link(zFullPath) ){
441         fossil_print("ADD %s - overwrites an unmanaged file\n", zName);
442         nOverwrite++;
443       }else{
444         fossil_print("ADD %s\n", zName);
445       }
446       if( !dryRunFlag && !internalUpdate ) undo_save(zName);
447       if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
448     }else if( idt>0 && idv>0 && ridt!=ridv && (chnged==0 || deleted) ){
449       /* The file is unedited.  Change it to the target version */
450       if( deleted ){
451         fossil_print("UPDATE %s - change to unmanaged file\n", zName);
452       }else{
453         fossil_print("UPDATE %s\n", zName);
454       }
455       if( !dryRunFlag && !internalUpdate ) undo_save(zName);
456       if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
457     }else if( idt>0 && idv>0 && !deleted && file_size(zFullPath, RepoFILE)<0 ){
458       /* The file missing from the local check-out. Restore it to the
459       ** version that appears in the target. */
460       fossil_print("UPDATE %s\n", zName);
461       if( !dryRunFlag && !internalUpdate ) undo_save(zName);
462       if( !dryRunFlag ) vfile_to_disk(0, idt, 0, 0);
463     }else if( idt==0 && idv>0 ){
464       if( ridv==0 ){
465         /* Added in current checkout.  Continue to hold the file as
466         ** as an addition */
467         db_multi_exec("UPDATE vfile SET vid=%d WHERE id=%d", tid, idv);
468       }else if( chnged ){
469         /* Edited locally but deleted from the target.  Do not track the
470         ** file but keep the edited version around. */
471         fossil_print("CONFLICT %s - edited locally but deleted by update\n",
472                      zName);
473         nConflict++;
474       }else{
475         fossil_print("REMOVE %s\n", zName);
476         if( !dryRunFlag && !internalUpdate ) undo_save(zName);
477         if( !dryRunFlag ){
478           char *zDir;
479           file_delete(zFullPath);
480           zDir = file_dirname(zName);
481           while( zDir!=0 ){
482             char *zNext;
483             db_multi_exec("INSERT OR IGNORE INTO dir_to_delete(name)"
484                           "VALUES(%Q)", zDir);
485             zNext = db_changes() ? file_dirname(zDir) : 0;
486             fossil_free(zDir);
487             zDir = zNext;
488           }
489         }
490       }
491     }else if( idt>0 && idv>0 && ridt!=ridv && chnged ){
492       /* Merge the changes in the current tree into the target version */
493       Blob r, t, v;
494       int rc;
495       if( nameChng ){
496         fossil_print("MERGE %s -> %s\n", zName, zNewName);
497       }else{
498         fossil_print("MERGE %s\n", zName);
499       }
500       if( islinkv || islinkt ){
501         fossil_print("***** Cannot merge symlink %s\n", zNewName);
502         nConflict++;
503       }else{
504         unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0;
505         if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES;
506         if( !dryRunFlag && !internalUpdate ) undo_save(zName);
507         content_get(ridt, &t);
508         content_get(ridv, &v);
509         rc = merge_3way(&v, zFullPath, &t, &r, mergeFlags);
510         if( rc>=0 ){
511           if( !dryRunFlag ){
512             blob_write_to_file(&r, zFullNewPath);
513             file_setexe(zFullNewPath, isexe);
514           }
515           if( rc>0 ){
516             fossil_print("***** %d merge conflicts in %s\n", rc, zNewName);
517             nConflict++;
518           }
519         }else{
520           if( !dryRunFlag ){
521             blob_write_to_file(&t, zFullNewPath);
522             file_setexe(zFullNewPath, isexe);
523           }
524           fossil_print("***** Cannot merge binary file %s\n", zNewName);
525           nConflict++;
526         }
527       }
528       if( nameChng && !dryRunFlag ) file_delete(zFullPath);
529       blob_reset(&v);
530       blob_reset(&t);
531       blob_reset(&r);
532     }else{
533       nUpdate--;
534       if( chnged ){
535         if( verboseFlag ) fossil_print("EDITED %s\n", zName);
536       }else{
537         db_bind_int(&mtimeXfer, ":idv", idv);
538         db_bind_int(&mtimeXfer, ":idt", idt);
539         db_step(&mtimeXfer);
540         db_reset(&mtimeXfer);
541         if( verboseFlag ) fossil_print("UNCHANGED %s\n", zName);
542       }
543     }
544     free(zFullPath);
545     free(zFullNewPath);
546   }
547   db_finalize(&q);
548   db_finalize(&mtimeXfer);
549   fossil_print("%.79c\n",'-');
550   if( nUpdate==0 ){
551     show_common_info(tid, "checkout:", 1, 0);
552     fossil_print("%-13s None. Already up-to-date\n", "changes:");
553   }else{
554     show_common_info(tid, "updated-to:", 1, 0);
555     fossil_print("%-13s %d file%s modified.\n", "changes:",
556                  nUpdate, nUpdate>1 ? "s" : "");
557   }
558 
559   /* Report on conflicts
560   */
561   if( !dryRunFlag ){
562     Stmt q;
563     int nMerge = 0;
564     db_prepare(&q, "SELECT mhash, id FROM vmerge WHERE id<=0");
565     while( db_step(&q)==SQLITE_ROW ){
566       const char *zLabel = "merge";
567       switch( db_column_int(&q, 1) ){
568         case -1:  zLabel = "cherrypick merge"; break;
569         case -2:  zLabel = "backout merge";    break;
570       }
571       fossil_warning("uncommitted %s against %S.",
572                      zLabel, db_column_text(&q, 0));
573       nMerge++;
574     }
575     db_finalize(&q);
576     leaf_ambiguity_warning(tid, tid);
577 
578     if( nConflict ){
579       if( internalUpdate ){
580         internalConflictCnt = nConflict;
581         nConflict = 0;
582       }else{
583         fossil_warning("WARNING: %d merge conflicts", nConflict);
584       }
585     }
586     if( nOverwrite ){
587       fossil_warning("WARNING: %d unmanaged files were overwritten",
588                      nOverwrite);
589     }
590     if( nMerge ){
591       fossil_warning("WARNING: %d uncommitted prior merges", nMerge);
592     }
593   }
594 
595   /*
596   ** Clean up the mid and pid VFILE entries.  Then commit the changes.
597   */
598   if( dryRunFlag ){
599     db_end_transaction(1);  /* With --dry-run, rollback changes */
600   }else{
601     char *zPwd;
602     ensure_empty_dirs_created(1);
603     sqlite3_create_function(g.db, "rmdir", 1, SQLITE_UTF8|SQLITE_DIRECTONLY, 0,
604                             file_rmdir_sql_function, 0, 0);
605     zPwd = file_getcwd(0,0);
606     db_multi_exec(
607       "SELECT rmdir(%Q||name) FROM dir_to_delete"
608       " WHERE (%Q||name)<>%Q ORDER BY name DESC",
609       g.zLocalRoot, g.zLocalRoot, zPwd
610     );
611     fossil_free(zPwd);
612     if( g.argc<=3 ){
613       /* All files updated.  Shift the current checkout to the target. */
614       db_multi_exec("DELETE FROM vfile WHERE vid!=%d", tid);
615       checkout_set_all_exe(tid);
616       manifest_to_disk(tid);
617       db_set_checkout(tid);
618     }else{
619       /* A subset of files have been checked out.  Keep the current
620       ** checkout unchanged. */
621       db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid);
622     }
623     if( !internalUpdate ) undo_finish();
624     if( setmtimeFlag ) vfile_check_signature(tid, CKSIG_SETMTIME);
625     db_end_transaction(0);
626   }
627 }
628 
629 /*
630 ** Create empty directories specified by the empty-dirs setting.
631 */
ensure_empty_dirs_created(int clearDirTable)632 void ensure_empty_dirs_created(int clearDirTable){
633   char *zEmptyDirs = db_get("empty-dirs", 0);
634   if( zEmptyDirs!=0 ){
635     int i;
636     Blob dirName;
637     Blob dirsList;
638 
639     zEmptyDirs = fossil_strdup(zEmptyDirs);
640     for(i=0; zEmptyDirs[i]; i++){
641       if( zEmptyDirs[i]==',' ) zEmptyDirs[i] = ' ';
642     }
643     blob_init(&dirsList, zEmptyDirs, -1);
644     while( blob_token(&dirsList, &dirName) ){
645       char *zDir = blob_str(&dirName);
646       char *zPath = mprintf("%s/%s", g.zLocalRoot, zDir);
647       switch( file_isdir(zPath, RepoFILE) ){
648         case 0: { /* doesn't exist */
649           fossil_free(zPath);
650           zPath = mprintf("%s/%s/x", g.zLocalRoot, zDir);
651           if( file_mkfolder(zPath, RepoFILE, 0, 1)!=0 ) {
652             fossil_warning("couldn't create directory %s as "
653                            "required by empty-dirs setting", zDir);
654           }
655           break;
656         }
657         case 1: { /* exists, and is a directory */
658           /* make sure this directory is not on the delete list */
659           if( clearDirTable ){
660             db_multi_exec(
661               "DELETE FROM dir_to_delete WHERE name=%Q", zDir
662             );
663           }
664           break;
665         }
666         case 2: { /* exists, but isn't a directory */
667           fossil_warning("file %s found, but a directory is required "
668                          "by empty-dirs setting", zDir);
669         }
670       }
671       fossil_free(zPath);
672       blob_reset(&dirName);
673     }
674     blob_reset(&dirsList);
675     fossil_free(zEmptyDirs);
676   }
677 }
678 
679 /*
680 ** Get the manifest record for a given revision, or the current checkout if
681 ** zRevision is NULL.
682 */
historical_manifest(const char * zRevision)683 Manifest *historical_manifest(
684   const char *zRevision    /* The check-in to query, or NULL for current */
685 ){
686   int vid;
687   Manifest *pManifest;
688 
689   /* Determine the check-in manifest artifact ID.  Panic on failure. */
690   if( zRevision ){
691     vid = name_to_typed_rid(zRevision, "ci");
692   }else if( !g.localOpen ){
693     vid = name_to_typed_rid(db_get("main-branch", 0), "ci");
694   }else{
695     vid = db_lget_int("checkout", 0);
696     if( !is_a_version(vid) ){
697       if( vid==0 ) return 0;
698       zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
699       if( zRevision ){
700         fossil_fatal("checkout artifact is not a check-in: %s", zRevision);
701       }else{
702         fossil_fatal("invalid checkout artifact ID: %d", vid);
703       }
704     }
705   }
706 
707   /* Parse the manifest, given its artifact ID.  Panic on failure. */
708   if( !(pManifest = manifest_get(vid, CFTYPE_MANIFEST, 0)) ){
709     if( zRevision ){
710       fossil_fatal("could not parse manifest for check-in: %s", zRevision);
711     }else{
712       fossil_fatal("could not parse manifest for current checkout");
713     }
714   }
715 
716   /* Return the manifest pointer.  The caller must use manifest_destroy() to
717    * clean up when finished using the manifest. */
718   return pManifest;
719 }
720 
721 /*
722 ** Get the contents of a file within the check-in "zRevision".  If
723 ** zRevision==NULL then get the file content for the current checkout.
724 */
historical_blob(const char * zRevision,const char * zFile,Blob * pBlob,int fatal)725 int historical_blob(
726   const char *zRevision,   /* The check-in containing the file */
727   const char *zFile,       /* Full treename of the file */
728   Blob *pBlob,             /* Put the content here */
729   int fatal                /* If nonzero, panic if file/artifact not found */
730 ){
731   int result = 0;
732 
733   /* Get the manifest for the requested check-in version.  This call unavoidably
734    * panics on failure even if fatal is not set. */
735   Manifest *pManifest = historical_manifest(zRevision);
736 
737   /* Try to find the file record within the manifest. */
738   ManifestFile *pFile = manifest_file_find(pManifest, zFile);
739 
740   if( !pFile ){
741     /* Process file-not-found errors. */
742     if( fatal ){
743       if( zRevision ){
744         fossil_fatal("file %s does not exist in check-in %s", zFile, zRevision);
745       }else{
746         fossil_fatal("no such file: %s", zFile);
747       }
748     }
749   }else{
750     /* Get the file's contents. */
751     result = content_get(fast_uuid_to_rid(pFile->zUuid), pBlob);
752 
753     /* Process artifact-not-found errors. */
754     if( !result && fatal ){
755       if( zRevision ){
756         fossil_fatal("missing artifact %s for file %s in check-in %s",
757             pFile->zUuid, zFile, zRevision);
758       }else{
759         fossil_fatal("missing artifact %s for file %s", pFile->zUuid, zFile);
760       }
761     }
762   }
763 
764   /* Deallocate the parsed manifest structure. */
765   manifest_destroy(pManifest);
766 
767   /* Return 1 on success and (assuming fatal is not set) 0 if not found. */
768   return result;
769 }
770 
771 /*
772 ** COMMAND: revert
773 **
774 ** Usage: %fossil revert ?OPTIONS? ?FILE ...?
775 **
776 ** Revert to the current repository version of FILE, or to
777 ** the baseline VERSION specified with -r flag.
778 **
779 ** If FILE was part of a rename operation, both the original file
780 ** and the renamed file are reverted.
781 **
782 ** Using a directory name for any of the FILE arguments is the same
783 ** as using every subdirectory and file beneath that directory.
784 **
785 ** Revert all files if no file name is provided.
786 **
787 ** If a file is reverted accidentally, it can be restored using
788 ** the "fossil undo" command.
789 **
790 ** Options:
791 **   -r|--revision VERSION    Revert given FILE(s) back to given
792 **                            VERSION
793 **
794 ** See also: [[redo]], [[undo]], [[checkout]], [[update]]
795 */
revert_cmd(void)796 void revert_cmd(void){
797   Manifest *pCoManifest;          /* Manifest of current checkout */
798   Manifest *pRvManifest;          /* Manifest of selected revert version */
799   ManifestFile *pCoFile;          /* File within current checkout manifest */
800   ManifestFile *pRvFile;          /* File within revert version manifest */
801   const char *zFile;              /* Filename relative to checkout root */
802   const char *zRevision;          /* Selected revert version, NULL if current */
803   Blob record = BLOB_INITIALIZER; /* Contents of each reverted file */
804   int i;
805   Stmt q;
806   int revertAll = 0;
807   int revisionOptNotSupported = 0;
808 
809   undo_capture_command_line();
810   zRevision = find_option("revision", "r", 1);
811   verify_all_options();
812 
813   if( g.argc<2 ){
814     usage("?OPTIONS? [FILE] ...");
815   }
816   if( zRevision && g.argc<3 ){
817     fossil_fatal("directories or the entire tree can only be reverted"
818                  " back to current version");
819   }
820   db_must_be_within_tree();
821 
822   /* Get manifests of revert version and (if different) current checkout. */
823   pRvManifest = historical_manifest(zRevision);
824   pCoManifest = zRevision ? historical_manifest(0) : 0;
825 
826   db_begin_transaction();
827   undo_begin();
828   db_multi_exec("CREATE TEMP TABLE torevert(name UNIQUE);");
829 
830   if( g.argc>2 ){
831     for(i=2; i<g.argc; i++){
832       Blob fname;
833       zFile = mprintf("%/", g.argv[i]);
834       blob_zero(&fname);
835       file_tree_name(zFile, &fname, 0, 1);
836       if( blob_eq(&fname, ".") ){
837         if( zRevision ){
838           revisionOptNotSupported = 1;
839           break;
840         }
841         revertAll = 1;
842         break;
843       }else if( db_exists(
844         "SELECT pathname"
845         "  FROM vfile"
846         " WHERE (substr(pathname,1,length('%q/'))='%q/'"
847         "    OR  substr(origname,1,length('%q/'))='%q/');",
848         blob_str(&fname), blob_str(&fname),
849         blob_str(&fname), blob_str(&fname)) ){
850         int vid;
851         vid = db_lget_int("checkout", 0);
852         vfile_check_signature(vid, 0);
853 
854         if( zRevision ){
855           revisionOptNotSupported = 1;
856           break;
857         }
858         db_multi_exec(
859           "INSERT OR IGNORE INTO torevert"
860           " SELECT pathname"
861           "   FROM vfile"
862           "  WHERE (substr(pathname,1,length('%q/'))='%q/'"
863           "     OR  substr(origname,1,length('%q/'))='%q/')"
864           "    AND (chnged OR deleted OR rid=0 OR pathname!=origname);",
865           blob_str(&fname), blob_str(&fname),
866           blob_str(&fname), blob_str(&fname)
867         );
868       }else{
869         db_multi_exec(
870           "REPLACE INTO torevert VALUES(%B);"
871           "INSERT OR IGNORE INTO torevert"
872           " SELECT pathname"
873           "   FROM vfile"
874           "  WHERE origname=%B;",
875           &fname, &fname
876         );
877       }
878       blob_reset(&fname);
879     }
880   }else{
881     revertAll = 1;
882   }
883 
884   if( revisionOptNotSupported ){
885     fossil_fatal("directories or the entire tree can only be reverted"
886                  " back to current version");
887   }
888 
889   if ( revertAll ){
890     int vid;
891     vid = db_lget_int("checkout", 0);
892     vfile_check_signature(vid, 0);
893     db_multi_exec(
894       "DELETE FROM vmerge;"
895       "INSERT OR IGNORE INTO torevert "
896       " SELECT pathname"
897       "   FROM vfile "
898       "  WHERE chnged OR deleted OR rid=0 OR pathname!=origname;"
899     );
900   }
901 
902   db_multi_exec(
903     "INSERT OR IGNORE INTO torevert"
904     " SELECT origname"
905     "   FROM vfile"
906     "  WHERE origname!=pathname AND pathname IN (SELECT name FROM torevert);"
907   );
908   blob_zero(&record);
909   db_prepare(&q, "SELECT name FROM torevert");
910   if( zRevision==0 ){
911     int vid = db_lget_int("checkout", 0);
912     zRevision = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid);
913   }
914   while( db_step(&q)==SQLITE_ROW ){
915     char *zFull;
916     zFile = db_column_text(&q, 0);
917     zFull = mprintf("%/%/", g.zLocalRoot, zFile);
918     pRvFile = pRvManifest? manifest_file_find(pRvManifest, zFile) : 0;
919     if( !pRvFile ){
920       if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q OR origname=%Q",
921                  zFile, zFile)==0 ){
922         fossil_print("UNMANAGE %s\n", zFile);
923       }else{
924         undo_save(zFile);
925         file_delete(zFull);
926         fossil_print("DELETE   %s\n", zFile);
927       }
928       db_multi_exec(
929         "UPDATE OR REPLACE vfile"
930         "   SET pathname=origname, origname=NULL"
931         " WHERE pathname=%Q AND origname!=pathname;"
932         "DELETE FROM vfile WHERE pathname=%Q",
933         zFile, zFile
934       );
935     }else if( file_unsafe_in_tree_path(zFull) ){
936       /* Ignore this file */
937     }else{
938       sqlite3_int64 mtime;
939       int rvChnged = 0;
940       int rvPerm = manifest_file_mperm(pRvFile);
941 
942       /* Determine if reverted-to file is different than checked out file. */
943       if( pCoManifest && (pCoFile = manifest_file_find(pCoManifest, zFile)) ){
944         rvChnged = manifest_file_mperm(pRvFile)!=rvPerm
945                 || fossil_strcmp(pRvFile->zUuid, pCoFile->zUuid)!=0;
946       }
947 
948       /* Get contents of reverted-to file. */
949       content_get(fast_uuid_to_rid(pRvFile->zUuid), &record);
950 
951       undo_save(zFile);
952       if( file_size(zFull, RepoFILE)>=0
953        && (rvPerm==PERM_LNK || file_islink(0))
954       ){
955         file_delete(zFull);
956       }
957       if( rvPerm==PERM_LNK ){
958         symlink_create(blob_str(&record), zFull);
959       }else{
960         blob_write_to_file(&record, zFull);
961       }
962       file_setexe(zFull, rvPerm==PERM_EXE);
963       fossil_print("REVERT   %s\n", zFile);
964       mtime = file_mtime(zFull, RepoFILE);
965       db_multi_exec(
966          "UPDATE vfile"
967          "   SET mtime=%lld, chnged=%d, deleted=0, isexe=%d, islink=%d,"
968          "       mrid=rid, mhash=NULL"
969          " WHERE pathname=%Q OR origname=%Q",
970          mtime, rvChnged, rvPerm==PERM_EXE, rvPerm==PERM_LNK, zFile, zFile
971       );
972     }
973     blob_reset(&record);
974     free(zFull);
975   }
976   db_finalize(&q);
977   undo_finish();
978   db_end_transaction(0);
979 
980   /* Deallocate parsed manifest structures. */
981   manifest_destroy(pRvManifest);
982   manifest_destroy(pCoManifest);
983 }
984