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