|
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 two or more branches into |
|
19
|
** a single tree. |
|
20
|
*/ |
|
21
|
#include "config.h" |
|
22
|
#include "merge.h" |
|
23
|
#include <assert.h> |
|
24
|
|
|
25
|
|
|
26
|
/* |
|
27
|
** Bring up a Tcl/Tk GUI to show details of the most recent merge. |
|
28
|
*/ |
|
29
|
static void merge_info_tk(int bDark, int bAll, int nContext){ |
|
30
|
int i; |
|
31
|
Blob script; |
|
32
|
const char *zTempFile = 0; |
|
33
|
int bDebug; |
|
34
|
char *zCmd; |
|
35
|
const char *zTclsh; |
|
36
|
zTclsh = find_option("tclsh",0,1); |
|
37
|
if( zTclsh==0 ){ |
|
38
|
zTclsh = db_get("tclsh",0); |
|
39
|
} |
|
40
|
/* The undocumented --script FILENAME option causes the Tk script to |
|
41
|
** be written into the FILENAME instead of being run. This is used |
|
42
|
** for testing and debugging. */ |
|
43
|
zTempFile = find_option("script",0,1); |
|
44
|
bDebug = find_option("tkdebug",0,0)!=0; |
|
45
|
verify_all_options(); |
|
46
|
|
|
47
|
blob_zero(&script); |
|
48
|
blob_appendf(&script, "set ncontext %d\n", nContext); |
|
49
|
blob_appendf(&script, "set fossilexe {\"%/\"}\n", g.nameOfExe); |
|
50
|
blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info}\n", |
|
51
|
g.nameOfExe); |
|
52
|
blob_appendf(&script, "set filelist [list"); |
|
53
|
if( g.argc==2 ){ |
|
54
|
/* No files named on the command-line. Use every file mentioned |
|
55
|
** in the MERGESTAT table to generate the file list. */ |
|
56
|
Stmt q; |
|
57
|
int cnt = 0; |
|
58
|
db_prepare(&q, |
|
59
|
"WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," |
|
60
|
"('MERGE',1),('ADDED',2),('UPDATE',2))" |
|
61
|
"SELECT coalesce(fnr,fn), op FROM mergestat JOIN priority USING(op)" |
|
62
|
" %s ORDER BY pri, 1", |
|
63
|
bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/ |
|
64
|
); |
|
65
|
while( db_step(&q)==SQLITE_ROW ){ |
|
66
|
blob_appendf(&script," %s ", db_column_text(&q,1)); |
|
67
|
blob_append_tcl_literal(&script, db_column_text(&q,0), |
|
68
|
db_column_bytes(&q,0)); |
|
69
|
cnt++; |
|
70
|
} |
|
71
|
db_finalize(&q); |
|
72
|
if( cnt==0 ){ |
|
73
|
fossil_print( |
|
74
|
"No interesting changes in this merge. Use --all to see everything\n" |
|
75
|
); |
|
76
|
return; |
|
77
|
} |
|
78
|
}else{ |
|
79
|
/* Use only files named on the command-line in the file list. |
|
80
|
** But verify each file named is actually found in the MERGESTAT |
|
81
|
** table first. */ |
|
82
|
for(i=2; i<g.argc; i++){ |
|
83
|
char *zFile; /* Input filename */ |
|
84
|
char *zTreename; /* Name of the file in the tree */ |
|
85
|
Blob fname; /* Filename relative to root */ |
|
86
|
char *zOp; /* Operation on this file */ |
|
87
|
zFile = mprintf("%/", g.argv[i]); |
|
88
|
file_tree_name(zFile, &fname, 0, 1); |
|
89
|
fossil_free(zFile); |
|
90
|
zTreename = blob_str(&fname); |
|
91
|
zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q", |
|
92
|
zTreename, zTreename); |
|
93
|
blob_appendf(&script, " %s ", zOp); |
|
94
|
fossil_free(zOp); |
|
95
|
blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename)); |
|
96
|
blob_reset(&fname); |
|
97
|
} |
|
98
|
} |
|
99
|
blob_appendf(&script, "]\n"); |
|
100
|
blob_appendf(&script, "set darkmode %d\n", bDark!=0); |
|
101
|
blob_appendf(&script, "set debug %d\n", bDebug!=0); |
|
102
|
blob_appendf(&script, "%s", builtin_file("merge.tcl", 0)); |
|
103
|
if( zTempFile ){ |
|
104
|
blob_write_to_file(&script, zTempFile); |
|
105
|
fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile); |
|
106
|
}else{ |
|
107
|
#if defined(FOSSIL_ENABLE_TCL) |
|
108
|
Th_FossilInit(TH_INIT_DEFAULT); |
|
109
|
if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script), |
|
110
|
blob_size(&script), 1, 1, 0)==TCL_OK ){ |
|
111
|
blob_reset(&script); |
|
112
|
return; |
|
113
|
} |
|
114
|
/* |
|
115
|
* If evaluation of the Tcl script fails, the reason may be that Tk |
|
116
|
* could not be found by the loaded Tcl, or that Tcl cannot be loaded |
|
117
|
* dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback |
|
118
|
* to using the external "tclsh", if available. |
|
119
|
*/ |
|
120
|
#endif |
|
121
|
zTempFile = write_blob_to_temp_file(&script); |
|
122
|
zCmd = mprintf("%$ %$", zTclsh, zTempFile); |
|
123
|
if( bDebug ){ |
|
124
|
fossil_print("%s\n", zCmd); |
|
125
|
fflush(stdout); |
|
126
|
} |
|
127
|
fossil_system(zCmd); |
|
128
|
file_delete(zTempFile); |
|
129
|
fossil_free(zCmd); |
|
130
|
} |
|
131
|
blob_reset(&script); |
|
132
|
} |
|
133
|
|
|
134
|
/* |
|
135
|
** Generate a TCL list on standard output that can be fed into the |
|
136
|
** merge.tcl script to show the details of the most recent merge |
|
137
|
** command associated with file "zFName". zFName must be the filename |
|
138
|
** relative to the root of the check-in - in other words a "tree name". |
|
139
|
** |
|
140
|
** When this routine is called, we know that the mergestat table |
|
141
|
** exists, but we do not know if zFName is mentioned in that table. |
|
142
|
** |
|
143
|
** The diffMode variable has these values: |
|
144
|
** |
|
145
|
** 0 Standard 3-way diff |
|
146
|
** 12 2-way diff between baseline and local |
|
147
|
** 13 2-way diff between baseline and merge-in |
|
148
|
** 23 2-way diff between local and merge-in |
|
149
|
*/ |
|
150
|
static void merge_info_tcl(const char *zFName, int nContext, int diffMode){ |
|
151
|
const char *zTreename;/* Name of the file in the tree */ |
|
152
|
Stmt q; /* To query the MERGESTAT table */ |
|
153
|
MergeBuilder mb; /* The merge builder object */ |
|
154
|
Blob pivot,v1,v2,out; /* Blobs for holding content */ |
|
155
|
const char *zFN; /* A filename */ |
|
156
|
int rid; /* RID value */ |
|
157
|
int sz; /* File size value */ |
|
158
|
|
|
159
|
zTreename = zFName; |
|
160
|
db_prepare(&q, |
|
161
|
/* 0 1 2 3 4 5 6 7 */ |
|
162
|
"SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr" |
|
163
|
" FROM mergestat" |
|
164
|
" WHERE fnp=%Q OR fnr=%Q", |
|
165
|
zTreename, zTreename |
|
166
|
); |
|
167
|
if( db_step(&q)!=SQLITE_ROW ){ |
|
168
|
db_finalize(&q); |
|
169
|
fossil_print("ERROR {don't know anything about file: %s}\n", zTreename); |
|
170
|
return; |
|
171
|
} |
|
172
|
mergebuilder_init_tcl(&mb); |
|
173
|
mb.nContext = nContext; |
|
174
|
|
|
175
|
blob_zero(&pivot); |
|
176
|
if( diffMode!=23 ){ |
|
177
|
/* Set up the pivot or baseline */ |
|
178
|
zFN = db_column_text(&q, 0); |
|
179
|
if( zFN==0 ){ |
|
180
|
/* No pivot because the file was added */ |
|
181
|
mb.zPivot = "(no baseline)"; |
|
182
|
}else{ |
|
183
|
mb.zPivot = mprintf("%s (baseline)", file_tail(zFN)); |
|
184
|
rid = db_column_int(&q, 1); |
|
185
|
content_get(rid, &pivot); |
|
186
|
} |
|
187
|
mb.pPivot = &pivot; |
|
188
|
} |
|
189
|
|
|
190
|
blob_zero(&v2); |
|
191
|
if( diffMode!=12 ){ |
|
192
|
/* Set up the merge-in as V2 */ |
|
193
|
zFN = db_column_text(&q, 5); |
|
194
|
if( zFN==0 ){ |
|
195
|
/* File deleted in the merged-in branch */ |
|
196
|
mb.zV2 = "(deleted file)"; |
|
197
|
}else{ |
|
198
|
mb.zV2 = mprintf("%s (merge-in)", file_tail(zFN)); |
|
199
|
rid = db_column_int(&q, 6); |
|
200
|
content_get(rid, &v2); |
|
201
|
} |
|
202
|
mb.pV2 = &v2; |
|
203
|
} |
|
204
|
|
|
205
|
blob_zero(&v1); |
|
206
|
if( diffMode!=13 ){ |
|
207
|
/* Set up the local content as V1 */ |
|
208
|
zFN = db_column_text(&q, 2); |
|
209
|
if( zFN==0 ){ |
|
210
|
/* File added by merge */ |
|
211
|
mb.zV1 = "(no original)"; |
|
212
|
}else{ |
|
213
|
mb.zV1 = mprintf("%s (local)", file_tail(zFN)); |
|
214
|
rid = db_column_int(&q, 3); |
|
215
|
sz = db_column_int(&q, 4); |
|
216
|
if( rid==0 && sz>0 ){ |
|
217
|
/* The origin file had been edited so we'll have to pull its |
|
218
|
** original content out of the undo buffer */ |
|
219
|
Stmt q2; |
|
220
|
db_prepare(&q2, |
|
221
|
"SELECT content FROM undo" |
|
222
|
" WHERE pathname=%Q AND octet_length(content)=%d", |
|
223
|
zFN, sz |
|
224
|
); |
|
225
|
blob_zero(&v1); |
|
226
|
if( db_step(&q2)==SQLITE_ROW ){ |
|
227
|
db_column_blob(&q2, 0, &v1); |
|
228
|
}else{ |
|
229
|
mb.zV1 = "(local content missing)"; |
|
230
|
} |
|
231
|
db_finalize(&q2); |
|
232
|
}else{ |
|
233
|
/* The origin file was unchanged when the merge first occurred */ |
|
234
|
content_get(rid, &v1); |
|
235
|
} |
|
236
|
} |
|
237
|
mb.pV1 = &v1; |
|
238
|
} |
|
239
|
|
|
240
|
blob_zero(&out); |
|
241
|
if( diffMode==0 ){ |
|
242
|
/* Set up the output and do a 3-way diff */ |
|
243
|
zFN = db_column_text(&q, 7); |
|
244
|
if( zFN==0 ){ |
|
245
|
mb.zOut = "(Merge Result)"; |
|
246
|
}else{ |
|
247
|
mb.zOut = mprintf("%s (after merge)", file_tail(zFN)); |
|
248
|
} |
|
249
|
mb.pOut = &out; |
|
250
|
merge_three_blobs(&mb); |
|
251
|
}else{ |
|
252
|
/* Set up to do a two-way diff */ |
|
253
|
Blob *pLeft, *pRight; |
|
254
|
const char *zTagLeft, *zTagRight; |
|
255
|
DiffConfig cfg; |
|
256
|
memset(&cfg, 0, sizeof(cfg)); |
|
257
|
cfg.diffFlags = DIFF_TCL; |
|
258
|
cfg.nContext = mb.nContext; |
|
259
|
if( diffMode==12 || diffMode==13 ){ |
|
260
|
pLeft = &pivot; |
|
261
|
zTagLeft = "baseline"; |
|
262
|
}else{ |
|
263
|
pLeft = &v1; |
|
264
|
zTagLeft = "local"; |
|
265
|
} |
|
266
|
if( diffMode==12 ){ |
|
267
|
pRight = &v1; |
|
268
|
zTagRight = "local"; |
|
269
|
}else{ |
|
270
|
pRight = &v2; |
|
271
|
zTagRight = "merge-in"; |
|
272
|
} |
|
273
|
cfg.azLabel[0] = mprintf("%s (%s)", zFName, zTagLeft); |
|
274
|
cfg.azLabel[1] = mprintf("%s (%s)", zFName, zTagRight); |
|
275
|
diff_print_filenames("", "", &cfg, &out); |
|
276
|
text_diff(pLeft, pRight, &out, &cfg); |
|
277
|
fossil_free((char*)cfg.azLabel[0]); |
|
278
|
fossil_free((char*)cfg.azLabel[1]); |
|
279
|
} |
|
280
|
|
|
281
|
blob_write_to_file(&out, "-"); |
|
282
|
mb.xDestroy(&mb); |
|
283
|
blob_reset(&pivot); |
|
284
|
blob_reset(&v1); |
|
285
|
blob_reset(&v2); |
|
286
|
blob_reset(&out); |
|
287
|
db_finalize(&q); |
|
288
|
} |
|
289
|
|
|
290
|
/* |
|
291
|
** COMMAND: merge-info |
|
292
|
** |
|
293
|
** Usage: %fossil merge-info [OPTIONS] |
|
294
|
** |
|
295
|
** Display information about the most recent merge operation. |
|
296
|
** |
|
297
|
** Options: |
|
298
|
** -a|--all Show all file changes that happened because of |
|
299
|
** the merge. Normally only MERGE, CONFLICT, and ERROR |
|
300
|
** lines are shown |
|
301
|
** -c|--context N Show N lines of context around each change, |
|
302
|
** with negative N meaning show all content. Only |
|
303
|
** meaningful in combination with --tcl or --tk. |
|
304
|
** --dark Use dark mode for the Tcl/Tk-based GUI |
|
305
|
** --tk Bring up a Tcl/Tk GUI that shows the changes |
|
306
|
** associated with the most recent merge. |
|
307
|
** |
|
308
|
** Options used internally by --tk: |
|
309
|
** --diff12 FILE Bring up a separate --tk diff for just the baseline |
|
310
|
** and local variants of FILE. |
|
311
|
** --diff13 FILE Like --diff12 but for baseline versus merge-in |
|
312
|
** --diff23 FILE Like --diff12 but for local versus merge-in |
|
313
|
** --tcl FILE Generate (to stdout) a TCL list containing |
|
314
|
** information needed to display the changes to |
|
315
|
** FILE caused by the most recent merge. FILE must |
|
316
|
** be a pathname relative to the root of the check-out. |
|
317
|
** |
|
318
|
** Debugging options available only when --tk is used: |
|
319
|
** --tkdebug Show sub-commands run to implement --tk |
|
320
|
** --script FILE Write script used to implement --tk into FILE |
|
321
|
*/ |
|
322
|
void merge_info_cmd(void){ |
|
323
|
const char *zCnt; |
|
324
|
const char *zTcl; |
|
325
|
int bTk; |
|
326
|
int bDark; |
|
327
|
int bAll; |
|
328
|
int nContext; |
|
329
|
Stmt q; |
|
330
|
const char *zWhere; |
|
331
|
int cnt = 0; |
|
332
|
const char *zDiff2 = 0; |
|
333
|
int diffMode = 0; |
|
334
|
|
|
335
|
db_must_be_within_tree(); |
|
336
|
bTk = find_option("tk", 0, 0)!=0; |
|
337
|
zTcl = find_option("tcl", 0, 1); |
|
338
|
zCnt = find_option("context", "c", 1); |
|
339
|
bDark = find_option("dark", 0, 0)!=0; |
|
340
|
bAll = find_option("all", "a", 0)!=0; |
|
341
|
if( (zDiff2 = find_option("diff12", 0, 1))!=0 ){ |
|
342
|
diffMode = 12; |
|
343
|
}else |
|
344
|
if( (zDiff2 = find_option("diff13", 0, 1))!=0 ){ |
|
345
|
diffMode = 13; |
|
346
|
}else |
|
347
|
if( (zDiff2 = find_option("diff23", 0, 1))!=0 ){ |
|
348
|
diffMode = 23; |
|
349
|
} |
|
350
|
|
|
351
|
if( zCnt ){ |
|
352
|
nContext = atoi(zCnt); |
|
353
|
if( nContext<0 ) nContext = 0xfffffff; |
|
354
|
}else{ |
|
355
|
nContext = 6; |
|
356
|
} |
|
357
|
if( !db_table_exists("localdb","mergestat") ){ |
|
358
|
if( zTcl ){ |
|
359
|
fossil_print("ERROR {no merge data available}\n"); |
|
360
|
}else{ |
|
361
|
fossil_print("No merge data is available\n"); |
|
362
|
} |
|
363
|
return; |
|
364
|
} |
|
365
|
if( bTk ){ |
|
366
|
merge_info_tk(bDark, bAll, nContext); |
|
367
|
return; |
|
368
|
} |
|
369
|
if( zTcl ){ |
|
370
|
if( diffMode ) zTcl = zDiff2; |
|
371
|
merge_info_tcl(zTcl, nContext, diffMode); |
|
372
|
return; |
|
373
|
} |
|
374
|
if( diffMode ){ |
|
375
|
char *zCmd; |
|
376
|
zCmd = mprintf("merge-info --diff%d %!$ -c %d%s", |
|
377
|
diffMode, zDiff2, nContext, bDark ? " --dark" : ""); |
|
378
|
diff_tk(zCmd, g.argc); |
|
379
|
fossil_free(zCmd); |
|
380
|
return; |
|
381
|
} |
|
382
|
|
|
383
|
verify_all_options(); |
|
384
|
if( g.argc>2 ){ |
|
385
|
usage("[OPTIONS]"); |
|
386
|
} |
|
387
|
|
|
388
|
if( bAll ){ |
|
389
|
zWhere = ""; |
|
390
|
}else{ |
|
391
|
zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')"; |
|
392
|
} |
|
393
|
db_prepare(&q, |
|
394
|
"WITH priority(op,pri) AS (VALUES('CONFLICT',0),('ERROR',0)," |
|
395
|
"('MERGE',1),('ADDED',2),('UPDATE',2))" |
|
396
|
|
|
397
|
/* 0 1 2 */ |
|
398
|
"SELECT op, coalesce(fnr,fn), msg" |
|
399
|
" FROM mergestat JOIN priority USING(op)" |
|
400
|
" %s" |
|
401
|
" ORDER BY pri, coalesce(fnr,fn)", |
|
402
|
zWhere /*safe-for-%s*/ |
|
403
|
); |
|
404
|
while( db_step(&q)==SQLITE_ROW ){ |
|
405
|
const char *zOp = db_column_text(&q, 0); |
|
406
|
const char *zName = db_column_text(&q, 1); |
|
407
|
const char *zErr = db_column_text(&q, 2); |
|
408
|
if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){ |
|
409
|
fossil_print("%-9s %s (%s)\n", zOp, zName, zErr); |
|
410
|
}else{ |
|
411
|
fossil_print("%-9s %s\n", zOp, zName); |
|
412
|
} |
|
413
|
cnt++; |
|
414
|
} |
|
415
|
db_finalize(&q); |
|
416
|
if( !bAll && cnt==0 ){ |
|
417
|
fossil_print( |
|
418
|
"No interesting changes in this merge. Use --all to see everything.\n" |
|
419
|
); |
|
420
|
} |
|
421
|
} |
|
422
|
|
|
423
|
/* |
|
424
|
** Erase all information about prior merges. Do this, for example, after |
|
425
|
** a commit. |
|
426
|
*/ |
|
427
|
void merge_info_forget(void){ |
|
428
|
db_multi_exec( |
|
429
|
"DROP TABLE IF EXISTS localdb.mergestat;" |
|
430
|
"DELETE FROM localdb.vvar WHERE name glob 'mergestat-*';" |
|
431
|
); |
|
432
|
} |
|
433
|
|
|
434
|
|
|
435
|
/* |
|
436
|
** Initialize the MERGESTAT table. |
|
437
|
** |
|
438
|
** Notes about mergestat: |
|
439
|
** |
|
440
|
** * ridv is a positive integer and sz is NULL if the V file contained |
|
441
|
** no local edits prior to the merge. If the V file was modified prior |
|
442
|
** to the merge then ridv is NULL and sz is the size of the file prior |
|
443
|
** to merge. |
|
444
|
** |
|
445
|
** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was |
|
446
|
** added by merge. |
|
447
|
*/ |
|
448
|
void merge_info_init(void){ |
|
449
|
merge_info_forget(); |
|
450
|
db_multi_exec( |
|
451
|
"CREATE TABLE localdb.mergestat(\n" |
|
452
|
" op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n" |
|
453
|
" fnp TEXT, -- Name of the pivot file (P)\n" |
|
454
|
" ridp INT, -- RID for the pivot file\n" |
|
455
|
" fn TEXT, -- Name of origin file (V)\n" |
|
456
|
" ridv INT, -- RID for origin file, or NULL if previously edited\n" |
|
457
|
" sz INT, -- Size of origin file in bytes, NULL if unedited\n" |
|
458
|
" fnm TEXT, -- Name of the file being merged in (M)\n" |
|
459
|
" ridm INT, -- RID for the merge-in file\n" |
|
460
|
" fnr TEXT, -- Name of the final output file, after all renaming\n" |
|
461
|
" nc INT DEFAULT 0, -- Number of conflicts\n" |
|
462
|
" msg TEXT -- Error message\n" |
|
463
|
");" |
|
464
|
); |
|
465
|
} |
|
466
|
|
|
467
|
/* |
|
468
|
** Print information about a particular check-in. |
|
469
|
*/ |
|
470
|
void print_checkin_description(int rid, int indent, const char *zLabel){ |
|
471
|
Stmt q; |
|
472
|
db_prepare(&q, |
|
473
|
"SELECT datetime(mtime,toLocal())," |
|
474
|
" coalesce(euser,user), coalesce(ecomment,comment)," |
|
475
|
" (SELECT uuid FROM blob WHERE rid=%d)," |
|
476
|
" (SELECT group_concat(substr(tagname,5), ', ') FROM tag, tagxref" |
|
477
|
" WHERE tagname GLOB 'sym-*' AND tag.tagid=tagxref.tagid" |
|
478
|
" AND tagxref.rid=%d AND tagxref.tagtype>0)" |
|
479
|
" FROM event WHERE objid=%d", rid, rid, rid); |
|
480
|
if( db_step(&q)==SQLITE_ROW ){ |
|
481
|
const char *zTagList = db_column_text(&q, 4); |
|
482
|
char *zCom; |
|
483
|
if( zTagList && zTagList[0] ){ |
|
484
|
zCom = mprintf("%s (%s)", db_column_text(&q, 2), zTagList); |
|
485
|
}else{ |
|
486
|
zCom = fossil_strdup(db_column_text(&q,2)); |
|
487
|
} |
|
488
|
fossil_print("%-*s [%S] by %s on %s\n%*s", |
|
489
|
indent-1, zLabel, |
|
490
|
db_column_text(&q, 3), |
|
491
|
db_column_text(&q, 1), |
|
492
|
db_column_text(&q, 0), |
|
493
|
indent, ""); |
|
494
|
comment_print(zCom, db_column_text(&q,2), indent, -1, get_comment_format()); |
|
495
|
fossil_free(zCom); |
|
496
|
} |
|
497
|
db_finalize(&q); |
|
498
|
} |
|
499
|
|
|
500
|
|
|
501
|
/* Pick the most recent leaf that is (1) not equal to vid and (2) |
|
502
|
** has not already been merged into vid and (3) the leaf is not |
|
503
|
** closed and (4) the leaf is in the same branch as vid. |
|
504
|
** |
|
505
|
** Set vmergeFlag to control whether the vmerge table is checked. |
|
506
|
*/ |
|
507
|
int fossil_find_nearest_fork(int vid, int vmergeFlag){ |
|
508
|
Blob sql; |
|
509
|
Stmt q; |
|
510
|
int rid = 0; |
|
511
|
|
|
512
|
blob_zero(&sql); |
|
513
|
blob_append_sql(&sql, |
|
514
|
"SELECT leaf.rid" |
|
515
|
" FROM leaf, event" |
|
516
|
" WHERE leaf.rid=event.objid" |
|
517
|
" AND leaf.rid!=%d", /* Constraint (1) */ |
|
518
|
vid |
|
519
|
); |
|
520
|
if( vmergeFlag ){ |
|
521
|
blob_append_sql(&sql, |
|
522
|
" AND leaf.rid NOT IN (SELECT merge FROM vmerge)" /* Constraint (2) */ |
|
523
|
); |
|
524
|
} |
|
525
|
blob_append_sql(&sql, |
|
526
|
" AND NOT EXISTS(SELECT 1 FROM tagxref" /* Constraint (3) */ |
|
527
|
" WHERE rid=leaf.rid" |
|
528
|
" AND tagid=%d" |
|
529
|
" AND tagtype>0)" |
|
530
|
" AND (SELECT value FROM tagxref" /* Constraint (4) */ |
|
531
|
" WHERE tagid=%d AND rid=%d AND tagtype>0) =" |
|
532
|
" (SELECT value FROM tagxref" |
|
533
|
" WHERE tagid=%d AND rid=leaf.rid AND tagtype>0)" |
|
534
|
" ORDER BY event.mtime DESC LIMIT 1", |
|
535
|
TAG_CLOSED, TAG_BRANCH, vid, TAG_BRANCH |
|
536
|
); |
|
537
|
db_prepare(&q, "%s", blob_sql_text(&sql)); |
|
538
|
blob_reset(&sql); |
|
539
|
if( db_step(&q)==SQLITE_ROW ){ |
|
540
|
rid = db_column_int(&q, 0); |
|
541
|
} |
|
542
|
db_finalize(&q); |
|
543
|
return rid; |
|
544
|
} |
|
545
|
|
|
546
|
/* |
|
547
|
** Check content that was received with rcvid and return true if any |
|
548
|
** fork was created. |
|
549
|
*/ |
|
550
|
int fossil_any_has_fork(int rcvid){ |
|
551
|
static Stmt q; |
|
552
|
int fForkSeen = 0; |
|
553
|
|
|
554
|
if( rcvid==0 ) return 0; |
|
555
|
db_static_prepare(&q, |
|
556
|
" SELECT pid FROM plink WHERE pid>0 AND isprim" |
|
557
|
" AND cid IN (SELECT blob.rid FROM blob" |
|
558
|
" WHERE rcvid=:rcvid)"); |
|
559
|
db_bind_int(&q, ":rcvid", rcvid); |
|
560
|
while( !fForkSeen && db_step(&q)==SQLITE_ROW ){ |
|
561
|
int pid = db_column_int(&q, 0); |
|
562
|
if( count_nonbranch_children(pid)>1 ){ |
|
563
|
compute_leaves(pid,1); |
|
564
|
if( db_int(0, "SELECT count(*) FROM leaves")>1 ){ |
|
565
|
int rid = db_int(0, "SELECT rid FROM leaves, event" |
|
566
|
" WHERE event.objid=leaves.rid" |
|
567
|
" ORDER BY event.mtime DESC LIMIT 1"); |
|
568
|
fForkSeen = fossil_find_nearest_fork(rid, db_open_local(0))!=0; |
|
569
|
} |
|
570
|
} |
|
571
|
} |
|
572
|
db_finalize(&q); |
|
573
|
return fForkSeen; |
|
574
|
} |
|
575
|
|
|
576
|
/* |
|
577
|
** Add an entry to the FV table for all files renamed between |
|
578
|
** version N and the version specified by vid. |
|
579
|
*/ |
|
580
|
static void add_renames( |
|
581
|
const char *zFnCol, /* The FV column for the filename in vid */ |
|
582
|
int vid, /* The desired version's- RID */ |
|
583
|
int nid, /* The check-in rid for the name pivot */ |
|
584
|
int revOK, /* OK to move backwards (child->parent) if true */ |
|
585
|
const char *zDebug /* Generate trace output if not NULL */ |
|
586
|
){ |
|
587
|
int nChng; /* Number of file name changes */ |
|
588
|
int *aChng; /* An array of file name changes */ |
|
589
|
int i; /* Loop counter */ |
|
590
|
find_filename_changes(nid, vid, revOK, &nChng, &aChng, zDebug); |
|
591
|
if( nChng==0 ) return; |
|
592
|
for(i=0; i<nChng; i++){ |
|
593
|
char *zN, *zV; |
|
594
|
zN = db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i*2]); |
|
595
|
zV = db_text(0, "SELECT name FROM filename WHERE fnid=%d", aChng[i*2+1]); |
|
596
|
db_multi_exec( |
|
597
|
"INSERT OR IGNORE INTO fv(%s,fnn) VALUES(%Q,%Q)", |
|
598
|
zFnCol /*safe-for-%s*/, zV, zN |
|
599
|
); |
|
600
|
if( db_changes()==0 ){ |
|
601
|
db_multi_exec( |
|
602
|
"UPDATE fv SET %s=%Q WHERE fnn=%Q", |
|
603
|
zFnCol /*safe-for-%s*/, zV, zN |
|
604
|
); |
|
605
|
} |
|
606
|
free(zN); |
|
607
|
free(zV); |
|
608
|
} |
|
609
|
free(aChng); |
|
610
|
} |
|
611
|
|
|
612
|
/* Make an entry in the vmerge table for the given id, and rid. |
|
613
|
*/ |
|
614
|
static void vmerge_insert(int id, int rid){ |
|
615
|
db_multi_exec( |
|
616
|
"INSERT OR IGNORE INTO vmerge(id,merge,mhash)" |
|
617
|
"VALUES(%d,%d,(SELECT uuid FROM blob WHERE rid=%d))", |
|
618
|
id, rid, rid |
|
619
|
); |
|
620
|
} |
|
621
|
|
|
622
|
/* |
|
623
|
** Print the contents of the "fv" table on standard output, for debugging |
|
624
|
** purposes. |
|
625
|
** |
|
626
|
** Only show entries where a file has changed, unless showAll is true. |
|
627
|
*/ |
|
628
|
static void debug_fv_dump(int showAll){ |
|
629
|
Stmt q; |
|
630
|
if( showAll ){ |
|
631
|
db_prepare(&q, |
|
632
|
"SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, " |
|
633
|
" isexe, islinkv, islinkm, fnn FROM fv" |
|
634
|
); |
|
635
|
}else{ |
|
636
|
db_prepare(&q, |
|
637
|
"SELECT rowid, fn, fnp, fnm, chnged, ridv, ridp, ridm, " |
|
638
|
" isexe, islinkv, islinkm, fnn FROM fv" |
|
639
|
" WHERE chnged OR (ridv!=ridm AND ridm!=ridp)" |
|
640
|
); |
|
641
|
} |
|
642
|
while( db_step(&q)==SQLITE_ROW ){ |
|
643
|
fossil_print("%3d: ridv=%-4d ridp=%-4d ridm=%-4d chnged=%d isexe=%d " |
|
644
|
" islinkv=%d islinkm=%d\n", |
|
645
|
db_column_int(&q, 0), |
|
646
|
db_column_int(&q, 5), |
|
647
|
db_column_int(&q, 6), |
|
648
|
db_column_int(&q, 7), |
|
649
|
db_column_int(&q, 4), |
|
650
|
db_column_int(&q, 8), |
|
651
|
db_column_int(&q, 9), |
|
652
|
db_column_int(&q, 10)); |
|
653
|
fossil_print(" fn = [%s]\n", db_column_text(&q, 1)); |
|
654
|
fossil_print(" fnp = [%s]\n", db_column_text(&q, 2)); |
|
655
|
fossil_print(" fnm = [%s]\n", db_column_text(&q, 3)); |
|
656
|
fossil_print(" fnn = [%s]\n", db_column_text(&q, 11)); |
|
657
|
} |
|
658
|
db_finalize(&q); |
|
659
|
} |
|
660
|
|
|
661
|
/* |
|
662
|
** Print the content of the VFILE table on standard output, for |
|
663
|
** debugging purposes. |
|
664
|
*/ |
|
665
|
static void debug_show_vfile(void){ |
|
666
|
Stmt q; |
|
667
|
int pvid = -1; |
|
668
|
db_prepare(&q, |
|
669
|
"SELECT vid, id, chnged, deleted, isexe, islink, rid, mrid, mtime," |
|
670
|
" pathname, origname, mhash FROM vfile" |
|
671
|
" ORDER BY vid, pathname" |
|
672
|
); |
|
673
|
while( db_step(&q)==SQLITE_ROW ){ |
|
674
|
int vid = db_column_int(&q, 0); |
|
675
|
int chnged = db_column_int(&q, 2); |
|
676
|
int dltd = db_column_int(&q, 3); |
|
677
|
int isexe = db_column_int(&q, 4); |
|
678
|
int islnk = db_column_int(&q, 5); |
|
679
|
int rid = db_column_int(&q, 6); |
|
680
|
int mrid = db_column_int(&q, 7); |
|
681
|
const char *zPath = db_column_text(&q, 9); |
|
682
|
const char *zOrig = db_column_text(&q, 10); |
|
683
|
if( vid!=pvid ){ |
|
684
|
fossil_print("VFILE vid=%d (%z):\n", vid, |
|
685
|
db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid)); |
|
686
|
pvid = vid; |
|
687
|
} |
|
688
|
fossil_print(" rid %-6d mrid %-6d %4s %3s %3s %3s %s", |
|
689
|
rid, mrid, |
|
690
|
chnged ? "chng" : "", |
|
691
|
dltd ? "del" : "", |
|
692
|
isexe ? "exe" : "", |
|
693
|
islnk ? "lnk" : "", zPath); |
|
694
|
if( zOrig && zOrig[0] ){ |
|
695
|
fossil_print(" <- %s\n", zOrig); |
|
696
|
}else{ |
|
697
|
fossil_print("\n"); |
|
698
|
} |
|
699
|
} |
|
700
|
db_finalize(&q); |
|
701
|
} |
|
702
|
|
|
703
|
/* |
|
704
|
** COMMAND: test-show-vfile |
|
705
|
** Usage: %fossil test-show-vfile |
|
706
|
** |
|
707
|
** Show the content of the VFILE table in a local check-out. |
|
708
|
*/ |
|
709
|
void test_show_vfile_cmd(void){ |
|
710
|
if( g.argc!=2 ){ |
|
711
|
fossil_fatal("unknown arguments to the %s command\n", g.argv[1]); |
|
712
|
} |
|
713
|
verify_all_options(); |
|
714
|
db_must_be_within_tree(); |
|
715
|
debug_show_vfile(); |
|
716
|
} |
|
717
|
|
|
718
|
|
|
719
|
/* |
|
720
|
** COMMAND: merge |
|
721
|
** COMMAND: cherry-pick alias |
|
722
|
** COMMAND: cherrypick |
|
723
|
** |
|
724
|
** Usage: %fossil merge ?OPTIONS? ?VERSION ...? |
|
725
|
** Or: %fossil cherrypick ?OPTIONS? ?VERSION ...? |
|
726
|
** |
|
727
|
** The argument VERSION is a version that should be merged into the |
|
728
|
** current check-out. All changes from VERSION back to the nearest |
|
729
|
** common ancestor are merged. Except, if either of the --cherrypick |
|
730
|
** or --backout options are used only the changes associated with the |
|
731
|
** single check-in VERSION are merged. The --backout option causes |
|
732
|
** the changes associated with VERSION to be removed from the current |
|
733
|
** check-out rather than added. When invoked with the name |
|
734
|
** "cherrypick" instead of "merge", this command works exactly like |
|
735
|
** "merge --cherrypick". |
|
736
|
** |
|
737
|
** Files which are renamed in the merged-in branch will be renamed in |
|
738
|
** the current check-out. |
|
739
|
** |
|
740
|
** If the VERSION argument is omitted, then Fossil attempts to find |
|
741
|
** a recent fork on the current branch to merge. |
|
742
|
** |
|
743
|
** Note that this command does not commit the merge, as that is a |
|
744
|
** separate step. |
|
745
|
** |
|
746
|
** If there are multiple VERSION arguments, then each VERSION is merged |
|
747
|
** (or cherrypicked) in the order that they appear on the command-line. |
|
748
|
** |
|
749
|
** Options: |
|
750
|
** --backout Do a reverse cherrypick merge against VERSION. |
|
751
|
** In other words, back out the changes that were |
|
752
|
** added by VERSION. |
|
753
|
** --baseline BASELINE Use BASELINE as the "pivot" of the merge instead |
|
754
|
** of the nearest common ancestor. This allows |
|
755
|
** a sequence of changes in a branch to be merged |
|
756
|
** without having to merge the entire branch. |
|
757
|
** --binary GLOBPATTERN Treat files that match GLOBPATTERN as binary |
|
758
|
** and do not try to merge parallel changes. This |
|
759
|
** option overrides the "binary-glob" setting. |
|
760
|
** --cherrypick Do a cherrypick merge VERSION into the current |
|
761
|
** check-out. A cherrypick merge pulls in the changes |
|
762
|
** of the single check-in VERSION, rather than all |
|
763
|
** changes back to the nearest common ancestor. |
|
764
|
** -f|--force Force the merge even if it would be a no-op |
|
765
|
** --force-missing Force the merge even if there is missing content |
|
766
|
** --integrate Merged branch will be closed when committing |
|
767
|
** -K|--keep-merge-files On merge conflict, retain the temporary files |
|
768
|
** used for merging, named *-baseline, *-original, |
|
769
|
** and *-merge. |
|
770
|
** -n|--dry-run Do not actually change files on disk |
|
771
|
** --nosync Do not auto-sync prior to merging |
|
772
|
** --noundo Do not record changes in the undo log |
|
773
|
** -v|--verbose Show additional details of the merge |
|
774
|
*/ |
|
775
|
void merge_cmd(void){ |
|
776
|
int vid; /* Current version "V" */ |
|
777
|
int mid; /* Version we are merging from "M" */ |
|
778
|
int pid = 0; /* The pivot version - most recent common ancestor P */ |
|
779
|
int nid = 0; /* The name pivot version "N" */ |
|
780
|
int verboseFlag; /* True if the -v|--verbose option is present */ |
|
781
|
int integrateFlag; /* True if the --integrate option is present */ |
|
782
|
int pickFlag; /* True if the --cherrypick option is present */ |
|
783
|
int backoutFlag; /* True if the --backout option is present */ |
|
784
|
int dryRunFlag; /* True if the --dry-run or -n option is present */ |
|
785
|
int forceFlag; /* True if the --force or -f option is present */ |
|
786
|
int forceMissingFlag; /* True if the --force-missing option is present */ |
|
787
|
const char *zBinGlob; /* The value of --binary */ |
|
788
|
const char *zPivot; /* The value of --baseline */ |
|
789
|
int debugFlag; /* True if --debug is present */ |
|
790
|
int showVfileFlag; /* True if the --show-vfile flag is present */ |
|
791
|
int keepMergeFlag; /* True if --keep-merge-files is present */ |
|
792
|
int nConflict = 0; /* Number of conflicts seen */ |
|
793
|
int nOverwrite = 0; /* Number of unmanaged files overwritten */ |
|
794
|
char vAncestor = 'p'; /* If P is an ancestor of V then 'p', else 'n' */ |
|
795
|
const char *zVersion; /* The VERSION argument */ |
|
796
|
int bMultiMerge = 0; /* True if there are two or more VERSION arguments */ |
|
797
|
int nMerge = 0; /* Number of prior merges processed */ |
|
798
|
int useUndo = 1; /* True to record changes in the undo log */ |
|
799
|
Stmt q; /* SQL statement used for merge processing */ |
|
800
|
|
|
801
|
|
|
802
|
/* Notation: |
|
803
|
** |
|
804
|
** V The current check-out |
|
805
|
** M The version being merged in |
|
806
|
** P The "pivot" - the most recent common ancestor of V and M. |
|
807
|
** N The "name pivot" - for detecting renames |
|
808
|
*/ |
|
809
|
|
|
810
|
undo_capture_command_line(); |
|
811
|
verboseFlag = find_option("verbose","v",0)!=0; |
|
812
|
forceMissingFlag = find_option("force-missing",0,0)!=0; |
|
813
|
if( !verboseFlag ){ |
|
814
|
verboseFlag = find_option("detail",0,0)!=0; /* deprecated */ |
|
815
|
} |
|
816
|
pickFlag = find_option("cherrypick",0,0)!=0; |
|
817
|
if('c'==*g.zCmdName /*called as cherrypick, possibly a short form*/){ |
|
818
|
pickFlag = 1; |
|
819
|
} |
|
820
|
integrateFlag = find_option("integrate",0,0)!=0; |
|
821
|
backoutFlag = find_option("backout",0,0)!=0; |
|
822
|
zBinGlob = find_option("binary",0,1); |
|
823
|
dryRunFlag = find_option("dry-run","n",0)!=0; |
|
824
|
if( !dryRunFlag ){ |
|
825
|
dryRunFlag = find_option("nochange",0,0)!=0; /* deprecated */ |
|
826
|
} |
|
827
|
if( find_option("nosync",0,0) ) g.fNoSync = 1; |
|
828
|
forceFlag = find_option("force","f",0)!=0; |
|
829
|
zPivot = find_option("baseline",0,1); |
|
830
|
keepMergeFlag = find_option("keep-merge-files", "K",0)!=0; |
|
831
|
|
|
832
|
/* Undocumented --debug and --show-vfile options: |
|
833
|
** |
|
834
|
** When included on the command-line, --debug causes lots of state |
|
835
|
** information to be displayed. This option is undocumented as it |
|
836
|
** might change or be eliminated in future releases. |
|
837
|
** |
|
838
|
** The --show-vfile flag does a dump of the VFILE table for reference. |
|
839
|
** |
|
840
|
** Hints: |
|
841
|
** * Combine --debug and --verbose for still more output. |
|
842
|
** * The --dry-run option is also useful in combination with --debug. |
|
843
|
*/ |
|
844
|
debugFlag = find_option("debug",0,0)!=0; |
|
845
|
if( debugFlag && verboseFlag ) debugFlag = 2; |
|
846
|
showVfileFlag = find_option("show-vfile",0,0)!=0; |
|
847
|
useUndo = find_option("noundo",0,0)==0; |
|
848
|
if( dryRunFlag ) useUndo = 0; |
|
849
|
|
|
850
|
verify_all_options(); |
|
851
|
db_must_be_within_tree(); |
|
852
|
if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0); |
|
853
|
vid = db_lget_int("checkout", 0); |
|
854
|
if( vid==0 ){ |
|
855
|
fossil_fatal("nothing is checked out"); |
|
856
|
} |
|
857
|
if( forceFlag==0 && leaf_is_closed(vid) ){ |
|
858
|
fossil_fatal("cannot merge into a closed leaf. Use --force to override"); |
|
859
|
} |
|
860
|
if( !dryRunFlag ){ |
|
861
|
if( autosync_loop(SYNC_PULL + SYNC_VERBOSE*verboseFlag, 1, "merge") ){ |
|
862
|
fossil_fatal("merge abandoned due to sync failure"); |
|
863
|
} |
|
864
|
} |
|
865
|
|
|
866
|
/* |
|
867
|
** A "multi-merge" means two or more other check-ins are being merged |
|
868
|
** into the current check-in. In other words, there are two or more |
|
869
|
** VERSION arguments on the command-line. Multi-merge works by doing |
|
870
|
** the merges one by one, as long as there are no conflicts. At the |
|
871
|
** bottom of this routine, a jump is made back up to this point if there |
|
872
|
** are more merges yet to be done and no errors have yet been seen. |
|
873
|
** |
|
874
|
** Related variables: |
|
875
|
** bMultiMerge True if there are one or more merges yet to do |
|
876
|
** zVersion The name of the current checking being merged in |
|
877
|
** nMerge Number of prior merges |
|
878
|
*/ |
|
879
|
merge_next_child: |
|
880
|
|
|
881
|
/* Find mid, the artifactID of the version to be merged into |
|
882
|
** the current check-out. |
|
883
|
*/ |
|
884
|
if( g.argc>=3 ){ |
|
885
|
int i; |
|
886
|
/* Mid is specified as an argument on the command-line */ |
|
887
|
zVersion = g.argv[2]; |
|
888
|
mid = name_to_typed_rid(zVersion, "ci"); |
|
889
|
if( mid==0 || !is_a_version(mid) ){ |
|
890
|
fossil_fatal("not a version: %s", zVersion); |
|
891
|
} |
|
892
|
bMultiMerge = g.argc>3; |
|
893
|
if( bMultiMerge ){ |
|
894
|
for(i=3; i<g.argc; i++) g.argv[i-1] = g.argv[i]; |
|
895
|
g.argc--; |
|
896
|
} |
|
897
|
}else if( g.argc==2 ){ |
|
898
|
/* No version specified on the command-line so pick the most recent |
|
899
|
** leaf that is (1) not the version currently checked out and (2) |
|
900
|
** has not already been merged into the current check-out and (3) |
|
901
|
** the leaf is not closed and (4) the leaf is in the same branch |
|
902
|
** as the current check-out. |
|
903
|
*/ |
|
904
|
Stmt q; |
|
905
|
if( pickFlag || backoutFlag || integrateFlag){ |
|
906
|
fossil_fatal("cannot use --backout, --cherrypick or --integrate " |
|
907
|
"with a fork merge"); |
|
908
|
} |
|
909
|
mid = fossil_find_nearest_fork(vid, db_open_local(0)); |
|
910
|
if( mid==0 ){ |
|
911
|
fossil_fatal("no unmerged forks of branch \"%s\"", |
|
912
|
db_text(0, "SELECT value FROM tagxref" |
|
913
|
" WHERE tagid=%d AND rid=%d AND tagtype>0", |
|
914
|
TAG_BRANCH, vid) |
|
915
|
); |
|
916
|
} |
|
917
|
db_prepare(&q, |
|
918
|
"SELECT blob.uuid," |
|
919
|
" datetime(event.mtime,toLocal())," |
|
920
|
" coalesce(ecomment, comment)," |
|
921
|
" coalesce(euser, user)" |
|
922
|
" FROM event, blob" |
|
923
|
" WHERE event.objid=%d AND blob.rid=%d", |
|
924
|
mid, mid |
|
925
|
); |
|
926
|
zVersion = 0; |
|
927
|
if( db_step(&q)==SQLITE_ROW ){ |
|
928
|
char *zCom = mprintf("Merging fork [%S] at %s by %s: \"%s\"", |
|
929
|
db_column_text(&q, 0), db_column_text(&q, 1), |
|
930
|
db_column_text(&q, 3), db_column_text(&q, 2)); |
|
931
|
comment_print(zCom, db_column_text(&q,2), 0, -1, get_comment_format()); |
|
932
|
fossil_free(zCom); |
|
933
|
zVersion = mprintf("%S",db_column_text(&q,0)); |
|
934
|
} |
|
935
|
db_finalize(&q); |
|
936
|
}else{ |
|
937
|
usage("?OPTIONS? ?VERSION?"); |
|
938
|
return; |
|
939
|
} |
|
940
|
|
|
941
|
if( zPivot ){ |
|
942
|
pid = name_to_typed_rid(zPivot, "ci"); |
|
943
|
if( pid==0 || !is_a_version(pid) ){ |
|
944
|
fossil_fatal("not a version: %s", zPivot); |
|
945
|
} |
|
946
|
if( pickFlag ){ |
|
947
|
fossil_fatal("incompatible options: --cherrypick and --baseline"); |
|
948
|
} |
|
949
|
} |
|
950
|
if( pickFlag || backoutFlag ){ |
|
951
|
if( integrateFlag ){ |
|
952
|
fossil_fatal("incompatible options: --integrate and --cherrypick " |
|
953
|
"with --backout"); |
|
954
|
} |
|
955
|
pid = db_int(0, "SELECT pid FROM plink WHERE cid=%d AND isprim", mid); |
|
956
|
if( pid<=0 ){ |
|
957
|
fossil_fatal("cannot find an ancestor for %s", zVersion); |
|
958
|
} |
|
959
|
}else{ |
|
960
|
if( !zPivot ){ |
|
961
|
pivot_set_primary(mid); |
|
962
|
pivot_set_secondary(vid); |
|
963
|
db_prepare(&q, "SELECT merge FROM vmerge WHERE id=0"); |
|
964
|
while( db_step(&q)==SQLITE_ROW ){ |
|
965
|
pivot_set_secondary(db_column_int(&q,0)); |
|
966
|
} |
|
967
|
db_finalize(&q); |
|
968
|
pid = pivot_find(0); |
|
969
|
if( pid<=0 ){ |
|
970
|
fossil_fatal("cannot find a common ancestor between the current " |
|
971
|
"check-out and %s", zVersion); |
|
972
|
} |
|
973
|
} |
|
974
|
pivot_set_primary(mid); |
|
975
|
pivot_set_secondary(vid); |
|
976
|
nid = pivot_find(1); |
|
977
|
if( nid!=pid ){ |
|
978
|
pivot_set_primary(nid); |
|
979
|
pivot_set_secondary(pid); |
|
980
|
nid = pivot_find(1); |
|
981
|
} |
|
982
|
} |
|
983
|
if( backoutFlag ){ |
|
984
|
int t = pid; |
|
985
|
pid = mid; |
|
986
|
mid = t; |
|
987
|
} |
|
988
|
if( nid==0 ) nid = pid; |
|
989
|
if( !is_a_version(pid) ){ |
|
990
|
fossil_fatal("not a version: record #%d", pid); |
|
991
|
} |
|
992
|
if( !forceFlag && (mid==pid || mid==vid) ){ |
|
993
|
fossil_print("Merge skipped because it is a no-op. " |
|
994
|
" Use --force to override.\n"); |
|
995
|
return; |
|
996
|
} |
|
997
|
if( integrateFlag && !is_a_leaf(mid)){ |
|
998
|
fossil_warning("ignoring --integrate: %s is not a leaf", zVersion); |
|
999
|
integrateFlag = 0; |
|
1000
|
} |
|
1001
|
if( integrateFlag && content_is_private(mid) ){ |
|
1002
|
fossil_warning( |
|
1003
|
"ignoring --integrate: %s is on a private branch" |
|
1004
|
"\n Use \"fossil amend --close\" (after commit) to close the leaf.", |
|
1005
|
zVersion); |
|
1006
|
integrateFlag = 0; |
|
1007
|
} |
|
1008
|
if( verboseFlag ){ |
|
1009
|
print_checkin_description(mid, 12, |
|
1010
|
integrateFlag ? "integrate:" : "merge-from:"); |
|
1011
|
print_checkin_description(pid, 12, "baseline:"); |
|
1012
|
} |
|
1013
|
vfile_check_signature(vid, CKSIG_ENOTFILE); |
|
1014
|
if( nMerge==0 ) db_begin_transaction(); |
|
1015
|
if( useUndo ) undo_begin(); |
|
1016
|
if( load_vfile_from_rid(mid) && !forceMissingFlag ){ |
|
1017
|
fossil_fatal("missing content, unable to merge"); |
|
1018
|
} |
|
1019
|
if( load_vfile_from_rid(pid) && !forceMissingFlag ){ |
|
1020
|
fossil_fatal("missing content, unable to merge"); |
|
1021
|
} |
|
1022
|
if( zPivot ){ |
|
1023
|
vAncestor = db_exists( |
|
1024
|
"WITH RECURSIVE ancestor(id) AS (" |
|
1025
|
" VALUES(%d)" |
|
1026
|
" UNION" |
|
1027
|
" SELECT pid FROM plink, ancestor" |
|
1028
|
" WHERE cid=ancestor.id AND pid!=%d AND cid!=%d)" |
|
1029
|
"SELECT 1 FROM ancestor WHERE id=%d LIMIT 1", |
|
1030
|
vid, nid, pid, pid |
|
1031
|
) ? 'p' : 'n'; |
|
1032
|
} |
|
1033
|
if( debugFlag ){ |
|
1034
|
char *z; |
|
1035
|
z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", nid); |
|
1036
|
fossil_print("N=%-4d %z (file rename pivot)\n", nid, z); |
|
1037
|
z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", pid); |
|
1038
|
fossil_print("P=%-4d %z (file content pivot)\n", pid, z); |
|
1039
|
z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", mid); |
|
1040
|
fossil_print("M=%-4d %z (merged-in version)\n", mid, z); |
|
1041
|
z = db_text(0, "SELECT uuid FROM blob WHERE rid=%d", vid); |
|
1042
|
fossil_print("V=%-4d %z (current version)\n", vid, z); |
|
1043
|
fossil_print("vAncestor = '%c'\n", vAncestor); |
|
1044
|
} |
|
1045
|
if( showVfileFlag ) debug_show_vfile(); |
|
1046
|
|
|
1047
|
/* |
|
1048
|
** The vfile.pathname field is used to match files against each other. The |
|
1049
|
** FV table contains one row for each unique filename in |
|
1050
|
** in the current check-out, the pivot, and the version being merged. |
|
1051
|
*/ |
|
1052
|
db_multi_exec( |
|
1053
|
"DROP TABLE IF EXISTS fv;" |
|
1054
|
"CREATE TEMP TABLE fv(\n" |
|
1055
|
" fn TEXT UNIQUE %s,\n" /* The filename */ |
|
1056
|
" idv INTEGER DEFAULT 0,\n" /* VFILE entry for current version */ |
|
1057
|
" idp INTEGER DEFAULT 0,\n" /* VFILE entry for the pivot */ |
|
1058
|
" idm INTEGER DEFAULT 0,\n" /* VFILE entry for version merging in */ |
|
1059
|
" chnged BOOLEAN,\n" /* True if current version has been edited */ |
|
1060
|
" ridv INTEGER DEFAULT 0,\n" /* Record ID for current version */ |
|
1061
|
" ridp INTEGER DEFAULT 0,\n" /* Record ID for pivot */ |
|
1062
|
" ridm INTEGER DEFAULT 0,\n" /* Record ID for merge */ |
|
1063
|
" isexe BOOLEAN,\n" /* Execute permission enabled */ |
|
1064
|
" fnp TEXT UNIQUE %s,\n" /* The filename in the pivot */ |
|
1065
|
" fnm TEXT UNIQUE %s,\n" /* The filename in the merged version */ |
|
1066
|
" fnn TEXT UNIQUE %s,\n" /* The filename in the name pivot */ |
|
1067
|
" islinkv BOOLEAN,\n" /* True if current version is a symlink */ |
|
1068
|
" islinkm BOOLEAN\n" /* True if merged version in is a symlink */ |
|
1069
|
");", |
|
1070
|
filename_collation(), filename_collation(), filename_collation(), |
|
1071
|
filename_collation() |
|
1072
|
); |
|
1073
|
|
|
1074
|
/* |
|
1075
|
** Compute name changes from N to V, P, and M |
|
1076
|
*/ |
|
1077
|
add_renames("fn", vid, nid, 0, debugFlag ? "N->V" : 0); |
|
1078
|
add_renames("fnp", pid, nid, 0, debugFlag ? "N->P" : 0); |
|
1079
|
add_renames("fnm", mid, nid, backoutFlag, debugFlag ? "N->M" : 0); |
|
1080
|
if( debugFlag ){ |
|
1081
|
fossil_print("******** FV after name change search *******\n"); |
|
1082
|
debug_fv_dump(1); |
|
1083
|
} |
|
1084
|
if( nid!=pid ){ |
|
1085
|
/* See forum thread https://fossil-scm.org/forum/forumpost/549700437b |
|
1086
|
** |
|
1087
|
** If a filename changes between nid and one of the other check-ins |
|
1088
|
** pid, vid, or mid, then it might not have changed for all of them. |
|
1089
|
** try to fill in the appropriate filename in all slots where the |
|
1090
|
** name is missing. |
|
1091
|
** |
|
1092
|
** This does not work if |
|
1093
|
** (1) The filename changes more than once in between nid and vid/mid |
|
1094
|
** (2) Two or more filenames swap places - for example if A is renamed |
|
1095
|
** to B and B is renamed to A. |
|
1096
|
** The Fossil merge algorithm breaks down in those cases. It will need |
|
1097
|
** to be completely rewritten to handle such complex cases. Such cases |
|
1098
|
** appear to be rare, and also confusing to humans. |
|
1099
|
*/ |
|
1100
|
db_multi_exec( |
|
1101
|
"UPDATE OR IGNORE fv SET fnp=vfile.pathname FROM vfile" |
|
1102
|
" WHERE fnp IS NULL" |
|
1103
|
" AND vfile.pathname = fv.fnn" |
|
1104
|
" AND vfile.vid=%d;", |
|
1105
|
pid |
|
1106
|
); |
|
1107
|
db_multi_exec( |
|
1108
|
"UPDATE OR IGNORE fv SET fn=vfile.pathname FROM vfile" |
|
1109
|
" WHERE fn IS NULL" |
|
1110
|
" AND vfile.pathname = coalesce(fv.fnp,fv.fnn)" |
|
1111
|
" AND vfile.vid=%d;", |
|
1112
|
vid |
|
1113
|
); |
|
1114
|
db_multi_exec( |
|
1115
|
"UPDATE OR IGNORE fv SET fnm=vfile.pathname FROM vfile" |
|
1116
|
" WHERE fnm IS NULL" |
|
1117
|
" AND vfile.pathname = coalesce(fv.fnp,fv.fnn)" |
|
1118
|
" AND vfile.vid=%d;", |
|
1119
|
mid |
|
1120
|
); |
|
1121
|
db_multi_exec( |
|
1122
|
"UPDATE OR IGNORE fv SET fnp=vfile.pathname FROM vfile" |
|
1123
|
" WHERE fnp IS NULL" |
|
1124
|
" AND vfile.pathname IN (fv.fnm,fv.fn)" |
|
1125
|
" AND vfile.vid=%d;", |
|
1126
|
pid |
|
1127
|
); |
|
1128
|
db_multi_exec( |
|
1129
|
"UPDATE OR IGNORE fv SET fn=vfile.pathname FROM vfile" |
|
1130
|
" WHERE fn IS NULL" |
|
1131
|
" AND vfile.pathname = fv.fnm" |
|
1132
|
" AND vfile.vid=%d;", |
|
1133
|
vid |
|
1134
|
); |
|
1135
|
db_multi_exec( |
|
1136
|
"UPDATE OR IGNORE fv SET fnm=vfile.pathname FROM vfile" |
|
1137
|
" WHERE fnm IS NULL" |
|
1138
|
" AND vfile.pathname = fv.fn" |
|
1139
|
" AND vfile.vid=%d;", |
|
1140
|
mid |
|
1141
|
); |
|
1142
|
} |
|
1143
|
if( debugFlag ){ |
|
1144
|
fossil_print("******** FV after name change fill-in *******\n"); |
|
1145
|
debug_fv_dump(1); |
|
1146
|
} |
|
1147
|
|
|
1148
|
/* |
|
1149
|
** Add files found in V |
|
1150
|
*/ |
|
1151
|
db_multi_exec( |
|
1152
|
"UPDATE OR IGNORE fv SET fn=coalesce(fn%c,fnn) WHERE fn IS NULL;" |
|
1153
|
"REPLACE INTO fv(fn,fnp,fnm,fnn,idv,ridv,islinkv,isexe,chnged)" |
|
1154
|
" SELECT pathname, fnp, fnm, fnn, id, rid, islink, vf.isexe, vf.chnged" |
|
1155
|
" FROM vfile vf" |
|
1156
|
" LEFT JOIN fv ON fn=coalesce(origname,pathname)" |
|
1157
|
" AND rid>0 AND vf.chnged NOT IN (3,5)" |
|
1158
|
" WHERE vid=%d;", |
|
1159
|
vAncestor, vid |
|
1160
|
); |
|
1161
|
if( debugFlag>=2 ){ |
|
1162
|
fossil_print("******** FV after adding files in current version *******\n"); |
|
1163
|
debug_fv_dump(1); |
|
1164
|
} |
|
1165
|
|
|
1166
|
/* |
|
1167
|
** Add files found in P |
|
1168
|
*/ |
|
1169
|
db_multi_exec( |
|
1170
|
"UPDATE OR IGNORE fv SET fnp=coalesce(fnn," |
|
1171
|
" (SELECT coalesce(origname,pathname) FROM vfile WHERE id=idv))" |
|
1172
|
" WHERE fnp IS NULL;" |
|
1173
|
"INSERT OR IGNORE INTO fv(fnp)" |
|
1174
|
" SELECT coalesce(origname,pathname) FROM vfile WHERE vid=%d;", |
|
1175
|
pid |
|
1176
|
); |
|
1177
|
if( debugFlag>=2 ){ |
|
1178
|
fossil_print("******** FV after adding pivot files *******\n"); |
|
1179
|
debug_fv_dump(1); |
|
1180
|
} |
|
1181
|
|
|
1182
|
/* |
|
1183
|
** Add files found in M |
|
1184
|
*/ |
|
1185
|
db_multi_exec( |
|
1186
|
"UPDATE OR IGNORE fv SET fnm=fnp WHERE fnm IS NULL;" |
|
1187
|
"INSERT OR IGNORE INTO fv(fnm)" |
|
1188
|
" SELECT pathname FROM vfile WHERE vid=%d;", |
|
1189
|
mid |
|
1190
|
); |
|
1191
|
if( debugFlag>=2 ){ |
|
1192
|
fossil_print("******** FV after adding merge-in files *******\n"); |
|
1193
|
debug_fv_dump(1); |
|
1194
|
} |
|
1195
|
|
|
1196
|
/* |
|
1197
|
** Compute the file version ids for P and M |
|
1198
|
*/ |
|
1199
|
if( pid==vid ){ |
|
1200
|
db_multi_exec( |
|
1201
|
"UPDATE fv SET idp=idv, ridp=ridv WHERE ridv>0 AND chnged NOT IN (3,5)" |
|
1202
|
); |
|
1203
|
}else{ |
|
1204
|
db_multi_exec( |
|
1205
|
"UPDATE fv SET idp=coalesce(vfile.id,0), ridp=coalesce(vfile.rid,0)" |
|
1206
|
" FROM vfile" |
|
1207
|
" WHERE vfile.vid=%d AND fv.fnp=vfile.pathname", |
|
1208
|
pid |
|
1209
|
); |
|
1210
|
} |
|
1211
|
db_multi_exec( |
|
1212
|
"UPDATE fv SET" |
|
1213
|
" idm=coalesce(vfile.id,0)," |
|
1214
|
" ridm=coalesce(vfile.rid,0)," |
|
1215
|
" islinkm=coalesce(vfile.islink,0)," |
|
1216
|
" isexe=coalesce(vfile.isexe,fv.isexe)" |
|
1217
|
" FROM vfile" |
|
1218
|
" WHERE vid=%d AND fnm=pathname", |
|
1219
|
mid |
|
1220
|
); |
|
1221
|
|
|
1222
|
/* |
|
1223
|
** Update the execute bit on files where it's changed from P->M but not P->V |
|
1224
|
*/ |
|
1225
|
db_prepare(&q, |
|
1226
|
"SELECT idv, fn, fv.isexe FROM fv, vfile p, vfile v" |
|
1227
|
" WHERE p.id=idp AND v.id=idv AND fv.isexe!=p.isexe AND v.isexe=p.isexe" |
|
1228
|
); |
|
1229
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1230
|
int idv = db_column_int(&q, 0); |
|
1231
|
const char *zName = db_column_text(&q, 1); |
|
1232
|
int isExe = db_column_int(&q, 2); |
|
1233
|
fossil_print("%s %s\n", isExe ? "EXECUTABLE" : "UNEXEC", zName); |
|
1234
|
if( !dryRunFlag ){ |
|
1235
|
char *zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
|
1236
|
file_setexe(zFullPath, isExe); |
|
1237
|
free(zFullPath); |
|
1238
|
db_multi_exec("UPDATE vfile SET isexe=%d WHERE id=%d", isExe, idv); |
|
1239
|
} |
|
1240
|
} |
|
1241
|
db_finalize(&q); |
|
1242
|
if( debugFlag ){ |
|
1243
|
fossil_print("******** FV final *******\n"); |
|
1244
|
debug_fv_dump( debugFlag>=2 ); |
|
1245
|
} |
|
1246
|
|
|
1247
|
/************************************************************************ |
|
1248
|
** All of the information needed to do the merge is now contained in the |
|
1249
|
** FV table. Starting here, we begin to actually carry out the merge. |
|
1250
|
** |
|
1251
|
** Begin by constructing the localdb.mergestat table. |
|
1252
|
*/ |
|
1253
|
merge_info_init(); |
|
1254
|
|
|
1255
|
/* |
|
1256
|
** Find files that have changed from P->M but not P->V. |
|
1257
|
** Copy the M content over into V. |
|
1258
|
*/ |
|
1259
|
db_prepare(&q, |
|
1260
|
/* 0 1 2 3 4 5 6 7 */ |
|
1261
|
"SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv" |
|
1262
|
" WHERE idp>0 AND idv>0 AND idm>0" |
|
1263
|
" AND ridm!=ridp AND ridv=ridp AND NOT chnged" |
|
1264
|
); |
|
1265
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1266
|
int idv = db_column_int(&q, 0); |
|
1267
|
int ridm = db_column_int(&q, 1); |
|
1268
|
const char *zName = db_column_text(&q, 2); |
|
1269
|
int islinkm = db_column_int(&q, 3); |
|
1270
|
/* Copy content from idm over into idv. Overwrite idv. */ |
|
1271
|
fossil_print("UPDATE %s\n", zName); |
|
1272
|
if( useUndo ) undo_save(zName); |
|
1273
|
if( !dryRunFlag ){ |
|
1274
|
db_multi_exec( |
|
1275
|
"UPDATE vfile SET mtime=0, mrid=%d, chnged=%d, islink=%d," |
|
1276
|
" mhash=CASE WHEN rid<>%d" |
|
1277
|
" THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END" |
|
1278
|
" WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv |
|
1279
|
); |
|
1280
|
vfile_to_disk(0, idv, 0, 0); |
|
1281
|
} |
|
1282
|
db_multi_exec( |
|
1283
|
"INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)" |
|
1284
|
"VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)", |
|
1285
|
/* fnp */ db_column_text(&q, 4), |
|
1286
|
/* ridp */ db_column_int(&q,5), |
|
1287
|
/* fn */ zName, |
|
1288
|
/* ridv */ db_column_int(&q,6), |
|
1289
|
/* fnm */ db_column_text(&q, 7), |
|
1290
|
/* ridm */ ridm, |
|
1291
|
/* fnr */ zName |
|
1292
|
); |
|
1293
|
} |
|
1294
|
db_finalize(&q); |
|
1295
|
|
|
1296
|
/* |
|
1297
|
** Do a three-way merge on files that have changes on both P->M and P->V. |
|
1298
|
** |
|
1299
|
** Proceed even if the file doesn't exist on P, just like the common ancestor |
|
1300
|
** of M and V is an empty file. In this case, merge conflict marks will be |
|
1301
|
** added to the file and user will be forced to take a decision. |
|
1302
|
*/ |
|
1303
|
db_prepare(&q, |
|
1304
|
/* 0 1 2 3 4 5 6 7 8 */ |
|
1305
|
"SELECT ridm, idv, ridp, ridv, %z, fn, isexe, islinkv, islinkm," |
|
1306
|
/* 9 10 11 */ |
|
1307
|
" fnp, fnm, chnged" |
|
1308
|
" FROM fv" |
|
1309
|
" WHERE idv>0 AND idm>0" |
|
1310
|
" AND ridm!=ridp AND (ridv!=ridp OR chnged)", |
|
1311
|
glob_expr("fv.fn", zBinGlob) |
|
1312
|
); |
|
1313
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1314
|
int ridm = db_column_int(&q, 0); |
|
1315
|
int idv = db_column_int(&q, 1); |
|
1316
|
int ridp = db_column_int(&q, 2); |
|
1317
|
int ridv = db_column_int(&q, 3); |
|
1318
|
int isBinary = db_column_int(&q, 4); |
|
1319
|
const char *zName = db_column_text(&q, 5); |
|
1320
|
int isExe = db_column_int(&q, 6); |
|
1321
|
int islinkv = db_column_int(&q, 7); |
|
1322
|
int islinkm = db_column_int(&q, 8); |
|
1323
|
int chnged = db_column_int(&q, 11); |
|
1324
|
int rc; |
|
1325
|
char *zFullPath; |
|
1326
|
const char *zType = "MERGE"; |
|
1327
|
Blob m, p, r; |
|
1328
|
/* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */ |
|
1329
|
if( verboseFlag ){ |
|
1330
|
fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n", |
|
1331
|
zName, ridp, ridm, ridv); |
|
1332
|
}else{ |
|
1333
|
fossil_print("MERGE %s\n", zName); |
|
1334
|
} |
|
1335
|
if( islinkv || islinkm ){ |
|
1336
|
fossil_print("***** Cannot merge symlink %s\n", zName); |
|
1337
|
nConflict++; |
|
1338
|
db_multi_exec( |
|
1339
|
"INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)" |
|
1340
|
"VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')", |
|
1341
|
/* fnp */ db_column_text(&q, 9), |
|
1342
|
/* ridp */ ridp, |
|
1343
|
/* fn */ zName, |
|
1344
|
/* ridv */ ridv, |
|
1345
|
/* fnm */ db_column_text(&q, 10), |
|
1346
|
/* ridm */ ridm, |
|
1347
|
/* fnr */ zName |
|
1348
|
); |
|
1349
|
}else{ |
|
1350
|
i64 sz; |
|
1351
|
const char *zErrMsg = 0; |
|
1352
|
int nc = 0; |
|
1353
|
|
|
1354
|
if( useUndo ) undo_save(zName); |
|
1355
|
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
|
1356
|
sz = file_size(zFullPath, ExtFILE); |
|
1357
|
content_get(ridp, &p); |
|
1358
|
content_get(ridm, &m); |
|
1359
|
if( isBinary ){ |
|
1360
|
rc = -1; |
|
1361
|
blob_zero(&r); |
|
1362
|
}else{ |
|
1363
|
unsigned mergeFlags = dryRunFlag ? MERGE_DRYRUN : 0; |
|
1364
|
if(keepMergeFlag!=0) mergeFlags |= MERGE_KEEP_FILES; |
|
1365
|
rc = merge_3way(&p, zFullPath, &m, &r, mergeFlags); |
|
1366
|
} |
|
1367
|
if( rc>=0 ){ |
|
1368
|
if( !dryRunFlag ){ |
|
1369
|
blob_write_to_file(&r, zFullPath); |
|
1370
|
file_setexe(zFullPath, isExe); |
|
1371
|
} |
|
1372
|
db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv); |
|
1373
|
if( rc>0 ){ |
|
1374
|
fossil_print("***** %d merge conflict%s in %s\n", |
|
1375
|
rc, rc>1 ? "s" : "", zName); |
|
1376
|
nConflict++; |
|
1377
|
nc = rc; |
|
1378
|
zErrMsg = "merge conflicts"; |
|
1379
|
zType = "CONFLICT"; |
|
1380
|
} |
|
1381
|
}else{ |
|
1382
|
fossil_print("***** Cannot merge binary file %s\n", zName); |
|
1383
|
nConflict++; |
|
1384
|
nc = 1; |
|
1385
|
zErrMsg = "cannot merge binary file"; |
|
1386
|
zType = "ERROR"; |
|
1387
|
} |
|
1388
|
db_multi_exec( |
|
1389
|
"INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)" |
|
1390
|
"VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%lld,NULL),%Q,%d," |
|
1391
|
"%Q,%d,%Q)", |
|
1392
|
/* op */ zType, |
|
1393
|
/* fnp */ db_column_text(&q, 9), |
|
1394
|
/* ridp */ ridp, |
|
1395
|
/* fn */ zName, |
|
1396
|
/* ridv */ chnged==0, ridv, |
|
1397
|
/* sz */ chnged!=0, sz, |
|
1398
|
/* fnm */ db_column_text(&q, 10), |
|
1399
|
/* ridm */ ridm, |
|
1400
|
/* fnr */ zName, |
|
1401
|
/* nc */ nc, |
|
1402
|
/* msg */ zErrMsg |
|
1403
|
); |
|
1404
|
fossil_free(zFullPath); |
|
1405
|
blob_reset(&p); |
|
1406
|
blob_reset(&m); |
|
1407
|
blob_reset(&r); |
|
1408
|
} |
|
1409
|
vmerge_insert(idv, ridm); |
|
1410
|
} |
|
1411
|
db_finalize(&q); |
|
1412
|
|
|
1413
|
/* |
|
1414
|
** Drop files that are in P and V but not in M |
|
1415
|
*/ |
|
1416
|
db_prepare(&q, |
|
1417
|
"SELECT idv, fn, chnged, ridv FROM fv" |
|
1418
|
" WHERE idp>0 AND idv>0 AND idm=0" |
|
1419
|
); |
|
1420
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1421
|
int idv = db_column_int(&q, 0); |
|
1422
|
const char *zName = db_column_text(&q, 1); |
|
1423
|
int chnged = db_column_int(&q, 2); |
|
1424
|
int ridv = db_column_int(&q, 3); |
|
1425
|
int sz = -1; |
|
1426
|
const char *zErrMsg = 0; |
|
1427
|
int nc = 0; |
|
1428
|
/* Delete the file idv */ |
|
1429
|
fossil_print("DELETE %s\n", zName); |
|
1430
|
if( chnged ){ |
|
1431
|
char *zFullPath; |
|
1432
|
fossil_warning("WARNING: local edits lost for %s", zName); |
|
1433
|
nConflict++; |
|
1434
|
ridv = 0; |
|
1435
|
nc = 1; |
|
1436
|
zErrMsg = "local edits lost"; |
|
1437
|
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName); |
|
1438
|
sz = file_size(zFullPath, ExtFILE); |
|
1439
|
fossil_free(zFullPath); |
|
1440
|
} |
|
1441
|
if( useUndo ) undo_save(zName); |
|
1442
|
db_multi_exec( |
|
1443
|
"UPDATE vfile SET deleted=1 WHERE id=%d", idv |
|
1444
|
); |
|
1445
|
if( !dryRunFlag ){ |
|
1446
|
char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName); |
|
1447
|
file_delete(zFullPath); |
|
1448
|
free(zFullPath); |
|
1449
|
} |
|
1450
|
db_multi_exec( |
|
1451
|
"INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)" |
|
1452
|
"VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL)," |
|
1453
|
"NULL,NULL,%d,%Q)", |
|
1454
|
/* fn */ zName, |
|
1455
|
/* ridv */ chnged==0, ridv, |
|
1456
|
/* sz */ chnged!=0, sz, |
|
1457
|
/* nc */ nc, |
|
1458
|
/* msg */ zErrMsg |
|
1459
|
); |
|
1460
|
} |
|
1461
|
db_finalize(&q); |
|
1462
|
|
|
1463
|
/* For certain sets of renames (e.g. A -> B and B -> A), a file that is |
|
1464
|
** being renamed must first be moved to a temporary location to avoid |
|
1465
|
** being overwritten by another rename operation. A row is added to the |
|
1466
|
** TMPRN table for each of these temporary renames. |
|
1467
|
*/ |
|
1468
|
db_multi_exec( |
|
1469
|
"DROP TABLE IF EXISTS tmprn;" |
|
1470
|
"CREATE TEMP TABLE tmprn(fn UNIQUE, tmpfn);" |
|
1471
|
); |
|
1472
|
|
|
1473
|
/* |
|
1474
|
** Rename files that have taken a rename on P->M but which keep the same |
|
1475
|
** name on P->V. If a file is renamed on P->V only or on both P->V and |
|
1476
|
** P->M then we retain the V name of the file. |
|
1477
|
*/ |
|
1478
|
db_prepare(&q, |
|
1479
|
"SELECT idv, fnp, fnm, isexe FROM fv" |
|
1480
|
" WHERE idv>0 AND idp>0 AND idm>0 AND fnp=fn AND fnm!=fnp" |
|
1481
|
); |
|
1482
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1483
|
int idv = db_column_int(&q, 0); |
|
1484
|
const char *zOldName = db_column_text(&q, 1); |
|
1485
|
const char *zNewName = db_column_text(&q, 2); |
|
1486
|
int isExe = db_column_int(&q, 3); |
|
1487
|
fossil_print("RENAME %s -> %s\n", zOldName, zNewName); |
|
1488
|
if( useUndo ) undo_save(zOldName); |
|
1489
|
if( useUndo ) undo_save(zNewName); |
|
1490
|
db_multi_exec( |
|
1491
|
"UPDATE mergestat SET fnr=fnm WHERE fnp=%Q", |
|
1492
|
zOldName |
|
1493
|
); |
|
1494
|
db_multi_exec( |
|
1495
|
"UPDATE vfile SET pathname=NULL, origname=pathname" |
|
1496
|
" WHERE vid=%d AND pathname=%Q;" |
|
1497
|
"UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)" |
|
1498
|
" WHERE id=%d;", |
|
1499
|
vid, zNewName, zNewName, idv |
|
1500
|
); |
|
1501
|
if( !dryRunFlag ){ |
|
1502
|
char *zFullOldPath, *zFullNewPath; |
|
1503
|
zFullOldPath = db_text(0,"SELECT tmpfn FROM tmprn WHERE fn=%Q", zOldName); |
|
1504
|
if( !zFullOldPath ){ |
|
1505
|
zFullOldPath = mprintf("%s%s", g.zLocalRoot, zOldName); |
|
1506
|
} |
|
1507
|
zFullNewPath = mprintf("%s%s", g.zLocalRoot, zNewName); |
|
1508
|
if( file_size(zFullNewPath, RepoFILE)>=0 ){ |
|
1509
|
Blob tmpPath; |
|
1510
|
file_tempname(&tmpPath, "", 0); |
|
1511
|
db_multi_exec("INSERT INTO tmprn(fn,tmpfn) VALUES(%Q,%Q)", |
|
1512
|
zNewName, blob_str(&tmpPath)); |
|
1513
|
if( file_islink(zFullNewPath) ){ |
|
1514
|
symlink_copy(zFullNewPath, blob_str(&tmpPath)); |
|
1515
|
}else{ |
|
1516
|
file_copy(zFullNewPath, blob_str(&tmpPath)); |
|
1517
|
} |
|
1518
|
blob_reset(&tmpPath); |
|
1519
|
} |
|
1520
|
if( file_islink(zFullOldPath) ){ |
|
1521
|
symlink_copy(zFullOldPath, zFullNewPath); |
|
1522
|
}else{ |
|
1523
|
file_copy(zFullOldPath, zFullNewPath); |
|
1524
|
} |
|
1525
|
file_setexe(zFullNewPath, isExe); |
|
1526
|
file_delete(zFullOldPath); |
|
1527
|
free(zFullNewPath); |
|
1528
|
free(zFullOldPath); |
|
1529
|
} |
|
1530
|
} |
|
1531
|
db_finalize(&q); |
|
1532
|
|
|
1533
|
/* A file that has been deleted and replaced by a renamed file will have a |
|
1534
|
** NULL pathname. Change it to something that makes the output of "status" |
|
1535
|
** and similar commands make sense for such files and that will (most likely) |
|
1536
|
** not be an actual existing pathname. |
|
1537
|
*/ |
|
1538
|
db_multi_exec( |
|
1539
|
"UPDATE vfile SET pathname=origname || ' (overwritten by rename)'" |
|
1540
|
" WHERE pathname IS NULL" |
|
1541
|
); |
|
1542
|
|
|
1543
|
/* |
|
1544
|
** Insert into V any files that are not in V or P but are in M. |
|
1545
|
*/ |
|
1546
|
db_prepare(&q, |
|
1547
|
"SELECT idm, fnm, ridm FROM fv" |
|
1548
|
" WHERE idp=0 AND idv=0 AND idm>0" |
|
1549
|
); |
|
1550
|
while( db_step(&q)==SQLITE_ROW ){ |
|
1551
|
int idm = db_column_int(&q, 0); |
|
1552
|
const char *zName; |
|
1553
|
char *zFullName; |
|
1554
|
db_multi_exec( |
|
1555
|
"REPLACE INTO vfile(vid,chnged,deleted,rid,mrid," |
|
1556
|
"isexe,islink,pathname,mhash)" |
|
1557
|
" SELECT %d,%d,0,rid,mrid,isexe,islink,pathname," |
|
1558
|
"CASE WHEN rid<>mrid" |
|
1559
|
" THEN (SELECT uuid FROM blob WHERE blob.rid=vfile.mrid) END " |
|
1560
|
"FROM vfile WHERE id=%d", |
|
1561
|
vid, integrateFlag?5:3, idm |
|
1562
|
); |
|
1563
|
zName = db_column_text(&q, 1); |
|
1564
|
zFullName = mprintf("%s%s", g.zLocalRoot, zName); |
|
1565
|
if( file_isfile_or_link(zFullName) |
|
1566
|
&& !db_exists("SELECT 1 FROM fv WHERE fn=%Q", zName) ){ |
|
1567
|
/* Name of backup file with Original content */ |
|
1568
|
char *zOrig = file_newname(zFullName, "original", 1); |
|
1569
|
/* Backup previously unmanaged file before being overwritten */ |
|
1570
|
file_copy(zFullName, zOrig); |
|
1571
|
fossil_free(zOrig); |
|
1572
|
fossil_print("ADDED %s (overwrites an unmanaged file)", zName); |
|
1573
|
if( !dryRunFlag ) fossil_print(", original copy backed up locally"); |
|
1574
|
fossil_print("\n"); |
|
1575
|
nOverwrite++; |
|
1576
|
}else{ |
|
1577
|
fossil_print("ADDED %s\n", zName); |
|
1578
|
} |
|
1579
|
fossil_free(zFullName); |
|
1580
|
db_multi_exec( |
|
1581
|
"INSERT INTO mergestat(op,fnm,ridm,fnr)" |
|
1582
|
"VALUES('ADDED',%Q,%d,%Q)", |
|
1583
|
/* fnm */ zName, |
|
1584
|
/* ridm */ db_column_int(&q,2), |
|
1585
|
/* fnr */ zName |
|
1586
|
); |
|
1587
|
if( useUndo ) undo_save(zName); |
|
1588
|
if( !dryRunFlag ){ |
|
1589
|
vfile_to_disk(0, idm, 0, 0); |
|
1590
|
} |
|
1591
|
} |
|
1592
|
db_finalize(&q); |
|
1593
|
|
|
1594
|
/* Report on conflicts |
|
1595
|
*/ |
|
1596
|
if( nConflict ){ |
|
1597
|
fossil_warning("WARNING: %d merge conflicts", nConflict); |
|
1598
|
if( bMultiMerge ){ |
|
1599
|
int i; |
|
1600
|
Blob msg; |
|
1601
|
blob_init(&msg, 0, 0); |
|
1602
|
blob_appendf(&msg, |
|
1603
|
"The following %ss were not attempted due to prior conflicts:", |
|
1604
|
pickFlag ? "cherrypick" : backoutFlag ? "backout" : "merge" |
|
1605
|
); |
|
1606
|
for(i=2; i<g.argc; i++){ |
|
1607
|
blob_appendf(&msg, " %s", g.argv[i]); |
|
1608
|
} |
|
1609
|
fossil_warning("%s", blob_str(&msg)); |
|
1610
|
blob_zero(&msg); |
|
1611
|
} |
|
1612
|
} |
|
1613
|
if( nOverwrite ){ |
|
1614
|
fossil_warning("WARNING: %d unmanaged files were overwritten", |
|
1615
|
nOverwrite); |
|
1616
|
} |
|
1617
|
if( dryRunFlag && !bMultiMerge ){ |
|
1618
|
fossil_warning("REMINDER: this was a dry run -" |
|
1619
|
" no files were actually changed."); |
|
1620
|
} |
|
1621
|
|
|
1622
|
/* |
|
1623
|
** Clean up the mid and pid VFILE entries. Then commit the changes. |
|
1624
|
*/ |
|
1625
|
db_multi_exec("DELETE FROM vfile WHERE vid!=%d", vid); |
|
1626
|
if( pickFlag ){ |
|
1627
|
vmerge_insert(-1, mid); |
|
1628
|
/* For a cherrypick merge, make the default check-in comment the same |
|
1629
|
** as the check-in comment on the check-in that is being merged in. */ |
|
1630
|
db_multi_exec( |
|
1631
|
"REPLACE INTO vvar(name,value)" |
|
1632
|
" SELECT 'ci-comment', coalesce(ecomment,comment) FROM event" |
|
1633
|
" WHERE type='ci' AND objid=%d", |
|
1634
|
mid |
|
1635
|
); |
|
1636
|
}else if( backoutFlag ){ |
|
1637
|
vmerge_insert(-2, pid); |
|
1638
|
}else if( integrateFlag ){ |
|
1639
|
vmerge_insert(-4, mid); |
|
1640
|
}else{ |
|
1641
|
vmerge_insert(0, mid); |
|
1642
|
} |
|
1643
|
if( bMultiMerge && nConflict==0 ){ |
|
1644
|
nMerge++; |
|
1645
|
goto merge_next_child; |
|
1646
|
} |
|
1647
|
if( useUndo ) undo_finish(); |
|
1648
|
|
|
1649
|
db_end_transaction(dryRunFlag); |
|
1650
|
} |
|
1651
|
|