Fossil SCM

Add the "fossil merge-info" command for improved situational awareness on complex merge operations.

drh 2024-12-04 13:40 trunk merge
Commit aa829657f03d76a377a19f8907ad54d620306dc4cd2ed4000fdc12ec4b9251ba
+1 -1
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
11
# The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
22
# to this file, then runs this file using "tclsh" in order to display the
33
# graphical diff in a separate window. A typical "set fossilcmd" line
44
# looks like this:
55
#
6
-# set fossilcmd {| "./fossil" diff --html -y -i -v}
6
+# set fossilcmd {| "./fossil" diff --tcl -i -v}
77
#
88
# This header comment is stripped off by the "mkbuiltin.c" program.
99
#
1010
set prog {
1111
package require Tk
1212
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
1 # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
2 # to this file, then runs this file using "tclsh" in order to display the
3 # graphical diff in a separate window. A typical "set fossilcmd" line
4 # looks like this:
5 #
6 # set fossilcmd {| "./fossil" diff --html -y -i -v}
7 #
8 # This header comment is stripped off by the "mkbuiltin.c" program.
9 #
10 set prog {
11 package require Tk
12
--- src/diff.tcl
+++ src/diff.tcl
@@ -1,11 +1,11 @@
1 # The "diff --tk" command outputs prepends a "set fossilcmd {...}" line
2 # to this file, then runs this file using "tclsh" in order to display the
3 # graphical diff in a separate window. A typical "set fossilcmd" line
4 # looks like this:
5 #
6 # set fossilcmd {| "./fossil" diff --tcl -i -v}
7 #
8 # This header comment is stripped off by the "mkbuiltin.c" program.
9 #
10 set prog {
11 package require Tk
12
--- src/main.mk
+++ src/main.mk
@@ -248,10 +248,11 @@
248248
$(SRCDIR)/hbmenu.js \
249249
$(SRCDIR)/href.js \
250250
$(SRCDIR)/login.js \
251251
$(SRCDIR)/markdown.md \
252252
$(SRCDIR)/menu.js \
253
+ $(SRCDIR)/merge.tcl \
253254
$(SRCDIR)/scroll.js \
254255
$(SRCDIR)/skin.js \
255256
$(SRCDIR)/sorttable.js \
256257
$(SRCDIR)/sounds/0.wav \
257258
$(SRCDIR)/sounds/1.wav \
258259
--- src/main.mk
+++ src/main.mk
@@ -248,10 +248,11 @@
248 $(SRCDIR)/hbmenu.js \
249 $(SRCDIR)/href.js \
250 $(SRCDIR)/login.js \
251 $(SRCDIR)/markdown.md \
252 $(SRCDIR)/menu.js \
 
253 $(SRCDIR)/scroll.js \
254 $(SRCDIR)/skin.js \
255 $(SRCDIR)/sorttable.js \
256 $(SRCDIR)/sounds/0.wav \
257 $(SRCDIR)/sounds/1.wav \
258
--- src/main.mk
+++ src/main.mk
@@ -248,10 +248,11 @@
248 $(SRCDIR)/hbmenu.js \
249 $(SRCDIR)/href.js \
250 $(SRCDIR)/login.js \
251 $(SRCDIR)/markdown.md \
252 $(SRCDIR)/menu.js \
253 $(SRCDIR)/merge.tcl \
254 $(SRCDIR)/scroll.js \
255 $(SRCDIR)/skin.js \
256 $(SRCDIR)/sorttable.js \
257 $(SRCDIR)/sounds/0.wav \
258 $(SRCDIR)/sounds/1.wav \
259
+456 -6
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,366 @@
2020
*/
2121
#include "config.h"
2222
#include "merge.h"
2323
#include <assert.h>
2424
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
+ char *zCmd;
34
+ const char *zTclsh;
35
+ zTclsh = find_option("tclsh",0,1);
36
+ if( zTclsh==0 ){
37
+ zTclsh = db_get("tclsh",0);
38
+ }
39
+ /* The undocumented --script FILENAME option causes the Tk script to
40
+ ** be written into the FILENAME instead of being run. This is used
41
+ ** for testing and debugging. */
42
+ zTempFile = find_option("script",0,1);
43
+ verify_all_options();
44
+
45
+ blob_zero(&script);
46
+ blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info -tcl }\n",
47
+ g.nameOfExe);
48
+ blob_appendf(&script, "set filelist [list");
49
+ if( g.argc==2 ){
50
+ /* No files named on the command-line. Use every file mentioned
51
+ ** in the MERGESTAT table to generate the file list. */
52
+ Stmt q;
53
+ int cnt;
54
+ db_prepare(&q,
55
+ "SELECT coalesce(fnr,fn), op FROM mergestat %s ORDER BY 1",
56
+ bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/
57
+ );
58
+ while( db_step(&q)==SQLITE_ROW ){
59
+ blob_appendf(&script," %s ", db_column_text(&q,1));
60
+ blob_append_tcl_literal(&script, db_column_text(&q,0),
61
+ db_column_bytes(&q,0));
62
+ cnt++;
63
+ }
64
+ db_finalize(&q);
65
+ if( cnt==0 ){
66
+ fossil_print(
67
+ "No interesting changes in this merge. Use --all to see everything\n"
68
+ );
69
+ return;
70
+ }
71
+ }else{
72
+ /* Use only files named on the command-line in the file list.
73
+ ** But verify each file named is actually found in the MERGESTAT
74
+ ** table first. */
75
+ for(i=2; i<g.argc; i++){
76
+ char *zFile; /* Input filename */
77
+ char *zTreename; /* Name of the file in the tree */
78
+ Blob fname; /* Filename relative to root */
79
+ char *zOp; /* Operation on this file */
80
+ zFile = mprintf("%/", g.argv[i]);
81
+ file_tree_name(zFile, &fname, 0, 1);
82
+ fossil_free(zFile);
83
+ zTreename = blob_str(&fname);
84
+ zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q",
85
+ zTreename, zTreename);
86
+ blob_appendf(&script, " %s ", zOp);
87
+ fossil_free(zOp);
88
+ blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename));
89
+ blob_reset(&fname);
90
+ }
91
+ }
92
+ blob_appendf(&script, "]\n");
93
+ blob_appendf(&script, "set darkmode %d\n", bDark!=0);
94
+ blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
95
+ if( zTempFile ){
96
+ blob_write_to_file(&script, zTempFile);
97
+ fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
98
+ }else{
99
+#if defined(FOSSIL_ENABLE_TCL)
100
+ Th_FossilInit(TH_INIT_DEFAULT);
101
+ if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
102
+ blob_size(&script), 1, 1, 0)==TCL_OK ){
103
+ blob_reset(&script);
104
+ return;
105
+ }
106
+ /*
107
+ * If evaluation of the Tcl script fails, the reason may be that Tk
108
+ * could not be found by the loaded Tcl, or that Tcl cannot be loaded
109
+ * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
110
+ * to using the external "tclsh", if available.
111
+ */
112
+#endif
113
+ zTempFile = write_blob_to_temp_file(&script);
114
+ zCmd = mprintf("%$ %$", zTclsh, zTempFile);
115
+ fossil_system(zCmd);
116
+ file_delete(zTempFile);
117
+ fossil_free(zCmd);
118
+ }
119
+ blob_reset(&script);
120
+}
121
+
122
+/*
123
+** Generate a TCL list on standard output that can be fed into the
124
+** merge.tcl script to show the details of the most recent merge
125
+** command associated with file "zFName". zFName must be the filename
126
+** relative to the root of the check-in - in other words a "tree name".
127
+**
128
+** When this routine is called, we know that the mergestat table
129
+** exists, but we do not know if zFName is mentioned in that table.
130
+*/
131
+static void merge_info_tcl(const char *zFName, int nContext){
132
+ const char *zTreename;/* Name of the file in the tree */
133
+ Stmt q; /* To query the MERGESTAT table */
134
+ MergeBuilder mb; /* The merge builder object */
135
+ Blob pivot,v1,v2,out; /* Blobs for holding content */
136
+ const char *zFN; /* A filename */
137
+ int rid; /* RID value */
138
+ int sz; /* File size value */
139
+
140
+ zTreename = zFName;
141
+ db_prepare(&q,
142
+ /* 0 1 2 3 4 5 6 7 */
143
+ "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
144
+ " FROM mergestat"
145
+ " WHERE fnp=%Q OR fnr=%Q",
146
+ zTreename, zTreename
147
+ );
148
+ if( db_step(&q)!=SQLITE_ROW ){
149
+ db_finalize(&q);
150
+ fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
151
+ return;
152
+ }
153
+ mergebuilder_init_tcl(&mb);
154
+ mb.nContext = nContext;
155
+
156
+ /* Set up the pivot */
157
+ zFN = db_column_text(&q, 0);
158
+ if( zFN==0 ){
159
+ /* No pivot because the file was added */
160
+ mb.zPivot = "(no baseline)";
161
+ blob_zero(&pivot);
162
+ }else{
163
+ mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
164
+ rid = db_column_int(&q, 1);
165
+ content_get(rid, &pivot);
166
+ }
167
+ mb.pPivot = &pivot;
168
+
169
+ /* Set up the merge-in as V1 */
170
+ zFN = db_column_text(&q, 5);
171
+ if( zFN==0 ){
172
+ /* File deleted in the merged-in branch */
173
+ mb.zV1 = "(deleted file)";
174
+ blob_zero(&v1);
175
+ }else{
176
+ mb.zV1 = mprintf("%s (merge-in)", file_tail(zFN));
177
+ rid = db_column_int(&q, 6);
178
+ content_get(rid, &v1);
179
+ }
180
+ mb.pV1 = &v1;
181
+
182
+ /* Set up the local content as V2 */
183
+ zFN = db_column_text(&q, 2);
184
+ if( zFN==0 ){
185
+ /* File added by merge */
186
+ mb.zV2 = "(no original)";
187
+ blob_zero(&v2);
188
+ }else{
189
+ mb.zV2 = mprintf("%s (local)", file_tail(zFN));
190
+ rid = db_column_int(&q, 3);
191
+ sz = db_column_int(&q, 4);
192
+ if( rid==0 && sz>0 ){
193
+ /* The origin file had been edited so we'll have to pull its
194
+ ** original content out of the undo buffer */
195
+ Stmt q2;
196
+ db_prepare(&q2,
197
+ "SELECT content FROM undo"
198
+ " WHERE pathname=%Q AND octet_length(content)=%d",
199
+ zFN, sz
200
+ );
201
+ blob_zero(&v2);
202
+ if( db_step(&q2)==SQLITE_ROW ){
203
+ db_column_blob(&q, 0, &v2);
204
+ }else{
205
+ mb.zV2 = "(local content missing)";
206
+ }
207
+ db_finalize(&q2);
208
+ }else{
209
+ /* The origin file was unchanged when the merge first occurred */
210
+ content_get(rid, &v2);
211
+ }
212
+ }
213
+ mb.pV2 = &v2;
214
+
215
+ /* Set up the output */
216
+ zFN = db_column_text(&q, 7);
217
+ if( zFN==0 ){
218
+ mb.zOut = "(Merge Result)";
219
+ }else{
220
+ mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
221
+ }
222
+ blob_zero(&out);
223
+ mb.pOut = &out;
224
+
225
+ merge_three_blobs(&mb);
226
+ blob_write_to_file(&out, "-");
227
+
228
+ mb.xDestroy(&mb);
229
+ blob_reset(&pivot);
230
+ blob_reset(&v1);
231
+ blob_reset(&v2);
232
+ blob_reset(&out);
233
+ db_finalize(&q);
234
+}
235
+
236
+/*
237
+** COMMAND: merge-info
238
+**
239
+** Usage: %fossil merge-info [OPTIONS]
240
+**
241
+** Display information about the most recent merge operation.
242
+**
243
+** Right now, this command basically just dumps the localdb.mergestat
244
+** table. The plan moving forward is that it can generate data for
245
+** a Tk-based GUI to show the details of the merge. This command is
246
+** a work-in-progress.
247
+**
248
+** Options:
249
+** -a|--all Show all changes. Normally only merges, conflicts,
250
+** and errors are shown.
251
+** -c|--context N Show N lines of context around each change,
252
+** with negative N meaning show all content. Only
253
+** meaningful in combination with --tcl or --tk.
254
+** --dark Use dark mode for the Tcl/Tk-based GUI
255
+** --tcl FILE Generate (to stdout) a TCL list containing
256
+** information needed to display the changes to
257
+** FILE caused by the most recent merge. FILE must
258
+** be a pathname relative to the root of the check-out.
259
+** --tk Bring up a Tcl/Tk GUI that shows the changes
260
+** associated with the most recent merge.
261
+**
262
+*/
263
+void merge_info_cmd(void){
264
+ const char *zCnt;
265
+ const char *zTcl;
266
+ int bTk;
267
+ int bDark;
268
+ int bAll;
269
+ int nContext;
270
+ Stmt q;
271
+ const char *zWhere;
272
+ int cnt = 0;
273
+
274
+ db_must_be_within_tree();
275
+ zTcl = find_option("tcl", 0, 1);
276
+ bTk = find_option("tk", 0, 0)!=0;
277
+ zCnt = find_option("context", "c", 1);
278
+ bDark = find_option("dark", 0, 0)!=0;
279
+ bAll = find_option("all", "a", 0)!=0;
280
+ if( bTk==0 ){
281
+ verify_all_options();
282
+ if( g.argc>2 ){
283
+ usage("[OPTIONS]");
284
+ }
285
+ }
286
+ if( zCnt ){
287
+ nContext = atoi(zCnt);
288
+ if( nContext<0 ) nContext = 0xfffffff;
289
+ }else{
290
+ nContext = 6;
291
+ }
292
+ if( !db_table_exists("localdb","mergestat") ){
293
+ if( zTcl ){
294
+ fossil_print("ERROR {no merge data available}\n");
295
+ }else{
296
+ fossil_print("No merge data is available\n");
297
+ }
298
+ return;
299
+ }
300
+ if( bTk ){
301
+ merge_info_tk(bDark, bAll, nContext);
302
+ return;
303
+ }
304
+ if( zTcl ){
305
+ merge_info_tcl(zTcl, nContext);
306
+ return;
307
+ }
308
+ if( bAll ){
309
+ zWhere = "";
310
+ }else{
311
+ zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')";
312
+ }
313
+ db_prepare(&q,
314
+ /* 0 1 2 */
315
+ "SELECT op, coalesce(fnr,fn), msg"
316
+ " FROM mergestat"
317
+ " %s"
318
+ " ORDER BY coalesce(fnr,fn)",
319
+ zWhere /*safe-for-%s*/
320
+ );
321
+ while( db_step(&q)==SQLITE_ROW ){
322
+ const char *zOp = db_column_text(&q, 0);
323
+ const char *zName = db_column_text(&q, 1);
324
+ const char *zErr = db_column_text(&q, 2);
325
+ if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){
326
+ fossil_print("%-9s %s (%s)\n", zOp, zName, zErr);
327
+ }else{
328
+ fossil_print("%-9s %s\n", zOp, zName);
329
+ }
330
+ cnt++;
331
+ }
332
+ db_finalize(&q);
333
+ if( !bAll && cnt==0 ){
334
+ fossil_print(
335
+ "No interesting change in this merge. Use --all to see everything.\n"
336
+ );
337
+ }
338
+}
339
+
340
+/*
341
+** Erase all information about prior merges. Do this, for example, after
342
+** a commit.
343
+*/
344
+void merge_info_forget(void){
345
+ db_multi_exec("DROP TABLE IF EXISTS localdb.mergestat");
346
+}
347
+
348
+
349
+/*
350
+** Initialize the MERGESTAT table.
351
+**
352
+** Notes about mergestat:
353
+**
354
+** * ridv is a positive integer and sz is NULL if the V file contained
355
+** no local edits prior to the merge. If the V file was modified prior
356
+** to the merge then ridv is NULL and sz is the size of the file prior
357
+** to merge.
358
+**
359
+** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was
360
+** added by merge.
361
+*/
362
+void merge_info_init(void){
363
+ db_multi_exec(
364
+ "DROP TABLE IF EXISTS localdb.mergestat;\n"
365
+ "CREATE TABLE localdb.mergestat(\n"
366
+ " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n"
367
+ " fnp TEXT, -- Name of the pivot file (P)\n"
368
+ " ridp INT, -- RID for the pivot file\n"
369
+ " fn TEXT, -- Name of origin file (V)\n"
370
+ " ridv INT, -- RID for origin file, or NULL if previously edited\n"
371
+ " sz INT, -- Size of origin file in bytes, NULL if unedited\n"
372
+ " fnm TEXT, -- Name of the file being merged in (M)\n"
373
+ " ridm INT, -- RID for the merge-in file\n"
374
+ " fnr TEXT, -- Name of the final output file, after all renaming\n"
375
+ " nc INT DEFAULT 0, -- Number of conflicts\n"
376
+ " msg TEXT -- Error message\n"
377
+ ");"
378
+ );
379
+}
380
+
25381
/*
26382
** Print information about a particular check-in.
27383
*/
28384
void print_checkin_description(int rid, int indent, const char *zLabel){
29385
Stmt q;
@@ -323,11 +679,11 @@
323679
** --force-missing Force the merge even if there is missing content
324680
** --integrate Merged branch will be closed when committing
325681
** -K|--keep-merge-files On merge conflict, retain the temporary files
326682
** used for merging, named *-baseline, *-original,
327683
** and *-merge.
328
-** -n|--dry-run If given, display instead of run actions
684
+** -n|--dry-run Do not actually change files on disk
329685
** --nosync Do not auto-sync prior to merging
330686
** -v|--verbose Show additional details of the merge
331687
*/
332688
void merge_cmd(void){
333689
int vid; /* Current version "V" */
@@ -800,15 +1156,21 @@
8001156
8011157
/************************************************************************
8021158
** All of the information needed to do the merge is now contained in the
8031159
** FV table. Starting here, we begin to actually carry out the merge.
8041160
**
805
- ** First, find files that have changed from P->M but not P->V.
1161
+ ** Begin by constructing the localdb.mergestat table.
1162
+ */
1163
+ merge_info_init();
1164
+
1165
+ /*
1166
+ ** Find files that have changed from P->M but not P->V.
8061167
** Copy the M content over into V.
8071168
*/
8081169
db_prepare(&q,
809
- "SELECT idv, ridm, fn, islinkm FROM fv"
1170
+ /* 0 1 2 3 4 5 6 7 */
1171
+ "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv"
8101172
" WHERE idp>0 AND idv>0 AND idm>0"
8111173
" AND ridm!=ridp AND ridv=ridp AND NOT chnged"
8121174
);
8131175
while( db_step(&q)==SQLITE_ROW ){
8141176
int idv = db_column_int(&q, 0);
@@ -825,10 +1187,21 @@
8251187
" THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
8261188
" WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
8271189
);
8281190
vfile_to_disk(0, idv, 0, 0);
8291191
}
1192
+ db_multi_exec(
1193
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)"
1194
+ "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)",
1195
+ /* fnp */ db_column_text(&q, 4),
1196
+ /* ridp */ db_column_int(&q,5),
1197
+ /* fn */ zName,
1198
+ /* ridv */ db_column_int(&q,6),
1199
+ /* fnm */ db_column_text(&q, 7),
1200
+ /* ridm */ ridm,
1201
+ /* fnr */ zName
1202
+ );
8301203
}
8311204
db_finalize(&q);
8321205
8331206
/*
8341207
** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -836,11 +1209,15 @@
8361209
** Proceed even if the file doesn't exist on P, just like the common ancestor
8371210
** of M and V is an empty file. In this case, merge conflict marks will be
8381211
** added to the file and user will be forced to take a decision.
8391212
*/
8401213
db_prepare(&q,
841
- "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
1214
+ /* 0 1 2 3 4 5 6 7 8 */
1215
+ "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm,"
1216
+ /* 9 10 11 */
1217
+ " fnp, fnm, chnged"
1218
+ " FROM fv"
8421219
" WHERE idv>0 AND idm>0"
8431220
" AND ridm!=ridp AND (ridv!=ridp OR chnged)",
8441221
glob_expr("fv.fn", zBinGlob)
8451222
);
8461223
while( db_step(&q)==SQLITE_ROW ){
@@ -851,12 +1228,14 @@
8511228
int isBinary = db_column_int(&q, 4);
8521229
const char *zName = db_column_text(&q, 5);
8531230
int isExe = db_column_int(&q, 6);
8541231
int islinkv = db_column_int(&q, 7);
8551232
int islinkm = db_column_int(&q, 8);
1233
+ int chnged = db_column_int(&q, 11);
8561234
int rc;
8571235
char *zFullPath;
1236
+ const char *zType = "MERGE";
8581237
Blob m, p, r;
8591238
/* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
8601239
if( verboseFlag ){
8611240
fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
8621241
zName, ridp, ridm, ridv);
@@ -864,13 +1243,29 @@
8641243
fossil_print("MERGE %s\n", zName);
8651244
}
8661245
if( islinkv || islinkm ){
8671246
fossil_print("***** Cannot merge symlink %s\n", zName);
8681247
nConflict++;
1248
+ db_multi_exec(
1249
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)"
1250
+ "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')",
1251
+ /* fnp */ db_column_text(&q, 9),
1252
+ /* ridp */ ridp,
1253
+ /* fn */ zName,
1254
+ /* ridv */ ridv,
1255
+ /* fnm */ db_column_text(&q, 10),
1256
+ /* ridm */ ridm,
1257
+ /* fnr */ zName
1258
+ );
8691259
}else{
1260
+ i64 sz;
1261
+ const char *zErrMsg = 0;
1262
+ int nc = 0;
1263
+
8701264
if( !dryRunFlag ) undo_save(zName);
8711265
zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1266
+ sz = file_size(zFullPath, ExtFILE);
8721267
content_get(ridp, &p);
8731268
content_get(ridm, &m);
8741269
if( isBinary ){
8751270
rc = -1;
8761271
blob_zero(&r);
@@ -887,15 +1282,38 @@
8871282
db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
8881283
if( rc>0 ){
8891284
fossil_print("***** %d merge conflict%s in %s\n",
8901285
rc, rc>1 ? "s" : "", zName);
8911286
nConflict++;
1287
+ nc = rc;
1288
+ zErrMsg = "merge conflicts";
1289
+ zType = "CONFLICT";
8921290
}
8931291
}else{
8941292
fossil_print("***** Cannot merge binary file %s\n", zName);
8951293
nConflict++;
1294
+ nc = 1;
1295
+ zErrMsg = "cannot merge binary file";
1296
+ zType = "ERROR";
8961297
}
1298
+ db_multi_exec(
1299
+ "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
1300
+ "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),%Q,%d,"
1301
+ "%Q,%d,%Q)",
1302
+ /* op */ zType,
1303
+ /* fnp */ db_column_text(&q, 9),
1304
+ /* ridp */ ridp,
1305
+ /* fn */ zName,
1306
+ /* ridv */ chnged==0, ridv,
1307
+ /* sz */ chnged!=0, sz,
1308
+ /* fnm */ db_column_text(&q, 10),
1309
+ /* ridm */ ridm,
1310
+ /* fnr */ zName,
1311
+ /* nc */ nc,
1312
+ /* msg */ zErrMsg
1313
+ );
1314
+ fossil_free(zFullPath);
8971315
blob_reset(&p);
8981316
blob_reset(&m);
8991317
blob_reset(&r);
9001318
}
9011319
vmerge_insert(idv, ridm);
@@ -904,22 +1322,33 @@
9041322
9051323
/*
9061324
** Drop files that are in P and V but not in M
9071325
*/
9081326
db_prepare(&q,
909
- "SELECT idv, fn, chnged FROM fv"
1327
+ "SELECT idv, fn, chnged, ridv FROM fv"
9101328
" WHERE idp>0 AND idv>0 AND idm=0"
9111329
);
9121330
while( db_step(&q)==SQLITE_ROW ){
9131331
int idv = db_column_int(&q, 0);
9141332
const char *zName = db_column_text(&q, 1);
9151333
int chnged = db_column_int(&q, 2);
1334
+ int ridv = db_column_int(&q, 3);
1335
+ int sz = -1;
1336
+ const char *zErrMsg = 0;
1337
+ int nc = 0;
9161338
/* Delete the file idv */
9171339
fossil_print("DELETE %s\n", zName);
9181340
if( chnged ){
1341
+ char *zFullPath;
9191342
fossil_warning("WARNING: local edits lost for %s", zName);
9201343
nConflict++;
1344
+ ridv = 0;
1345
+ nc = 1;
1346
+ zErrMsg = "local edits lost";
1347
+ zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1348
+ sz = file_size(zFullPath, ExtFILE);
1349
+ fossil_free(zFullPath);
9211350
}
9221351
if( !dryRunFlag ) undo_save(zName);
9231352
db_multi_exec(
9241353
"UPDATE vfile SET deleted=1 WHERE id=%d", idv
9251354
);
@@ -926,10 +1355,20 @@
9261355
if( !dryRunFlag ){
9271356
char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
9281357
file_delete(zFullPath);
9291358
free(zFullPath);
9301359
}
1360
+ db_multi_exec(
1361
+ "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)"
1362
+ "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),"
1363
+ "NULL,NULL,%d,%Q)",
1364
+ /* fn */ zName,
1365
+ /* ridv */ chnged==0, ridv,
1366
+ /* sz */ chnged!=0, sz,
1367
+ /* nc */ nc,
1368
+ /* msg */ zErrMsg
1369
+ );
9311370
}
9321371
db_finalize(&q);
9331372
9341373
/* For certain sets of renames (e.g. A -> B and B -> A), a file that is
9351374
** being renamed must first be moved to a temporary location to avoid
@@ -956,10 +1395,14 @@
9561395
const char *zNewName = db_column_text(&q, 2);
9571396
int isExe = db_column_int(&q, 3);
9581397
fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
9591398
if( !dryRunFlag ) undo_save(zOldName);
9601399
if( !dryRunFlag ) undo_save(zNewName);
1400
+ db_multi_exec(
1401
+ "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1402
+ zOldName
1403
+ );
9611404
db_multi_exec(
9621405
"UPDATE vfile SET pathname=NULL, origname=pathname"
9631406
" WHERE vid=%d AND pathname=%Q;"
9641407
"UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
9651408
" WHERE id=%d;",
@@ -1009,11 +1452,11 @@
10091452
10101453
/*
10111454
** Insert into V any files that are not in V or P but are in M.
10121455
*/
10131456
db_prepare(&q,
1014
- "SELECT idm, fnm FROM fv"
1457
+ "SELECT idm, fnm, ridm FROM fv"
10151458
" WHERE idp=0 AND idv=0 AND idm>0"
10161459
);
10171460
while( db_step(&q)==SQLITE_ROW ){
10181461
int idm = db_column_int(&q, 0);
10191462
const char *zName;
@@ -1042,10 +1485,17 @@
10421485
nOverwrite++;
10431486
}else{
10441487
fossil_print("ADDED %s\n", zName);
10451488
}
10461489
fossil_free(zFullName);
1490
+ db_multi_exec(
1491
+ "INSERT INTO mergestat(op,fnm,ridm,fnr)"
1492
+ "VALUES('ADDED',%Q,%d,%Q)",
1493
+ /* fnm */ zName,
1494
+ /* ridm */ db_column_int(&q,2),
1495
+ /* fnr */ zName
1496
+ );
10471497
if( !dryRunFlag ){
10481498
undo_save(zName);
10491499
vfile_to_disk(0, idm, 0, 0);
10501500
}
10511501
}
10521502
10531503
ADDED src/merge.tcl
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,366 @@
20 */
21 #include "config.h"
22 #include "merge.h"
23 #include <assert.h>
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25 /*
26 ** Print information about a particular check-in.
27 */
28 void print_checkin_description(int rid, int indent, const char *zLabel){
29 Stmt q;
@@ -323,11 +679,11 @@
323 ** --force-missing Force the merge even if there is missing content
324 ** --integrate Merged branch will be closed when committing
325 ** -K|--keep-merge-files On merge conflict, retain the temporary files
326 ** used for merging, named *-baseline, *-original,
327 ** and *-merge.
328 ** -n|--dry-run If given, display instead of run actions
329 ** --nosync Do not auto-sync prior to merging
330 ** -v|--verbose Show additional details of the merge
331 */
332 void merge_cmd(void){
333 int vid; /* Current version "V" */
@@ -800,15 +1156,21 @@
800
801 /************************************************************************
802 ** All of the information needed to do the merge is now contained in the
803 ** FV table. Starting here, we begin to actually carry out the merge.
804 **
805 ** First, find files that have changed from P->M but not P->V.
 
 
 
 
 
806 ** Copy the M content over into V.
807 */
808 db_prepare(&q,
809 "SELECT idv, ridm, fn, islinkm FROM fv"
 
810 " WHERE idp>0 AND idv>0 AND idm>0"
811 " AND ridm!=ridp AND ridv=ridp AND NOT chnged"
812 );
813 while( db_step(&q)==SQLITE_ROW ){
814 int idv = db_column_int(&q, 0);
@@ -825,10 +1187,21 @@
825 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
826 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
827 );
828 vfile_to_disk(0, idv, 0, 0);
829 }
 
 
 
 
 
 
 
 
 
 
 
830 }
831 db_finalize(&q);
832
833 /*
834 ** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -836,11 +1209,15 @@
836 ** Proceed even if the file doesn't exist on P, just like the common ancestor
837 ** of M and V is an empty file. In this case, merge conflict marks will be
838 ** added to the file and user will be forced to take a decision.
839 */
840 db_prepare(&q,
841 "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm FROM fv"
 
 
 
 
842 " WHERE idv>0 AND idm>0"
843 " AND ridm!=ridp AND (ridv!=ridp OR chnged)",
844 glob_expr("fv.fn", zBinGlob)
845 );
846 while( db_step(&q)==SQLITE_ROW ){
@@ -851,12 +1228,14 @@
851 int isBinary = db_column_int(&q, 4);
852 const char *zName = db_column_text(&q, 5);
853 int isExe = db_column_int(&q, 6);
854 int islinkv = db_column_int(&q, 7);
855 int islinkm = db_column_int(&q, 8);
 
856 int rc;
857 char *zFullPath;
 
858 Blob m, p, r;
859 /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
860 if( verboseFlag ){
861 fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
862 zName, ridp, ridm, ridv);
@@ -864,13 +1243,29 @@
864 fossil_print("MERGE %s\n", zName);
865 }
866 if( islinkv || islinkm ){
867 fossil_print("***** Cannot merge symlink %s\n", zName);
868 nConflict++;
 
 
 
 
 
 
 
 
 
 
 
869 }else{
 
 
 
 
870 if( !dryRunFlag ) undo_save(zName);
871 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
 
872 content_get(ridp, &p);
873 content_get(ridm, &m);
874 if( isBinary ){
875 rc = -1;
876 blob_zero(&r);
@@ -887,15 +1282,38 @@
887 db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
888 if( rc>0 ){
889 fossil_print("***** %d merge conflict%s in %s\n",
890 rc, rc>1 ? "s" : "", zName);
891 nConflict++;
 
 
 
892 }
893 }else{
894 fossil_print("***** Cannot merge binary file %s\n", zName);
895 nConflict++;
 
 
 
896 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897 blob_reset(&p);
898 blob_reset(&m);
899 blob_reset(&r);
900 }
901 vmerge_insert(idv, ridm);
@@ -904,22 +1322,33 @@
904
905 /*
906 ** Drop files that are in P and V but not in M
907 */
908 db_prepare(&q,
909 "SELECT idv, fn, chnged FROM fv"
910 " WHERE idp>0 AND idv>0 AND idm=0"
911 );
912 while( db_step(&q)==SQLITE_ROW ){
913 int idv = db_column_int(&q, 0);
914 const char *zName = db_column_text(&q, 1);
915 int chnged = db_column_int(&q, 2);
 
 
 
 
916 /* Delete the file idv */
917 fossil_print("DELETE %s\n", zName);
918 if( chnged ){
 
919 fossil_warning("WARNING: local edits lost for %s", zName);
920 nConflict++;
 
 
 
 
 
 
921 }
922 if( !dryRunFlag ) undo_save(zName);
923 db_multi_exec(
924 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
925 );
@@ -926,10 +1355,20 @@
926 if( !dryRunFlag ){
927 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
928 file_delete(zFullPath);
929 free(zFullPath);
930 }
 
 
 
 
 
 
 
 
 
 
931 }
932 db_finalize(&q);
933
934 /* For certain sets of renames (e.g. A -> B and B -> A), a file that is
935 ** being renamed must first be moved to a temporary location to avoid
@@ -956,10 +1395,14 @@
956 const char *zNewName = db_column_text(&q, 2);
957 int isExe = db_column_int(&q, 3);
958 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
959 if( !dryRunFlag ) undo_save(zOldName);
960 if( !dryRunFlag ) undo_save(zNewName);
 
 
 
 
961 db_multi_exec(
962 "UPDATE vfile SET pathname=NULL, origname=pathname"
963 " WHERE vid=%d AND pathname=%Q;"
964 "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
965 " WHERE id=%d;",
@@ -1009,11 +1452,11 @@
1009
1010 /*
1011 ** Insert into V any files that are not in V or P but are in M.
1012 */
1013 db_prepare(&q,
1014 "SELECT idm, fnm FROM fv"
1015 " WHERE idp=0 AND idv=0 AND idm>0"
1016 );
1017 while( db_step(&q)==SQLITE_ROW ){
1018 int idm = db_column_int(&q, 0);
1019 const char *zName;
@@ -1042,10 +1485,17 @@
1042 nOverwrite++;
1043 }else{
1044 fossil_print("ADDED %s\n", zName);
1045 }
1046 fossil_free(zFullName);
 
 
 
 
 
 
 
1047 if( !dryRunFlag ){
1048 undo_save(zName);
1049 vfile_to_disk(0, idm, 0, 0);
1050 }
1051 }
1052
1053 DDED src/merge.tcl
--- src/merge.c
+++ src/merge.c
@@ -20,10 +20,366 @@
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 char *zCmd;
34 const char *zTclsh;
35 zTclsh = find_option("tclsh",0,1);
36 if( zTclsh==0 ){
37 zTclsh = db_get("tclsh",0);
38 }
39 /* The undocumented --script FILENAME option causes the Tk script to
40 ** be written into the FILENAME instead of being run. This is used
41 ** for testing and debugging. */
42 zTempFile = find_option("script",0,1);
43 verify_all_options();
44
45 blob_zero(&script);
46 blob_appendf(&script, "set fossilcmd {| \"%/\" merge-info -tcl }\n",
47 g.nameOfExe);
48 blob_appendf(&script, "set filelist [list");
49 if( g.argc==2 ){
50 /* No files named on the command-line. Use every file mentioned
51 ** in the MERGESTAT table to generate the file list. */
52 Stmt q;
53 int cnt;
54 db_prepare(&q,
55 "SELECT coalesce(fnr,fn), op FROM mergestat %s ORDER BY 1",
56 bAll ? "" : "WHERE op IN ('MERGE','CONFLICT')" /*safe-for-%s*/
57 );
58 while( db_step(&q)==SQLITE_ROW ){
59 blob_appendf(&script," %s ", db_column_text(&q,1));
60 blob_append_tcl_literal(&script, db_column_text(&q,0),
61 db_column_bytes(&q,0));
62 cnt++;
63 }
64 db_finalize(&q);
65 if( cnt==0 ){
66 fossil_print(
67 "No interesting changes in this merge. Use --all to see everything\n"
68 );
69 return;
70 }
71 }else{
72 /* Use only files named on the command-line in the file list.
73 ** But verify each file named is actually found in the MERGESTAT
74 ** table first. */
75 for(i=2; i<g.argc; i++){
76 char *zFile; /* Input filename */
77 char *zTreename; /* Name of the file in the tree */
78 Blob fname; /* Filename relative to root */
79 char *zOp; /* Operation on this file */
80 zFile = mprintf("%/", g.argv[i]);
81 file_tree_name(zFile, &fname, 0, 1);
82 fossil_free(zFile);
83 zTreename = blob_str(&fname);
84 zOp = db_text(0, "SELECT op FROM mergestat WHERE fn=%Q or fnr=%Q",
85 zTreename, zTreename);
86 blob_appendf(&script, " %s ", zOp);
87 fossil_free(zOp);
88 blob_append_tcl_literal(&script, zTreename, (int)strlen(zTreename));
89 blob_reset(&fname);
90 }
91 }
92 blob_appendf(&script, "]\n");
93 blob_appendf(&script, "set darkmode %d\n", bDark!=0);
94 blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
95 if( zTempFile ){
96 blob_write_to_file(&script, zTempFile);
97 fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
98 }else{
99 #if defined(FOSSIL_ENABLE_TCL)
100 Th_FossilInit(TH_INIT_DEFAULT);
101 if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
102 blob_size(&script), 1, 1, 0)==TCL_OK ){
103 blob_reset(&script);
104 return;
105 }
106 /*
107 * If evaluation of the Tcl script fails, the reason may be that Tk
108 * could not be found by the loaded Tcl, or that Tcl cannot be loaded
109 * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
110 * to using the external "tclsh", if available.
111 */
112 #endif
113 zTempFile = write_blob_to_temp_file(&script);
114 zCmd = mprintf("%$ %$", zTclsh, zTempFile);
115 fossil_system(zCmd);
116 file_delete(zTempFile);
117 fossil_free(zCmd);
118 }
119 blob_reset(&script);
120 }
121
122 /*
123 ** Generate a TCL list on standard output that can be fed into the
124 ** merge.tcl script to show the details of the most recent merge
125 ** command associated with file "zFName". zFName must be the filename
126 ** relative to the root of the check-in - in other words a "tree name".
127 **
128 ** When this routine is called, we know that the mergestat table
129 ** exists, but we do not know if zFName is mentioned in that table.
130 */
131 static void merge_info_tcl(const char *zFName, int nContext){
132 const char *zTreename;/* Name of the file in the tree */
133 Stmt q; /* To query the MERGESTAT table */
134 MergeBuilder mb; /* The merge builder object */
135 Blob pivot,v1,v2,out; /* Blobs for holding content */
136 const char *zFN; /* A filename */
137 int rid; /* RID value */
138 int sz; /* File size value */
139
140 zTreename = zFName;
141 db_prepare(&q,
142 /* 0 1 2 3 4 5 6 7 */
143 "SELECT fnp, ridp, fn, ridv, sz, fnm, ridm, fnr"
144 " FROM mergestat"
145 " WHERE fnp=%Q OR fnr=%Q",
146 zTreename, zTreename
147 );
148 if( db_step(&q)!=SQLITE_ROW ){
149 db_finalize(&q);
150 fossil_print("ERROR {don't know anything about file: %s}\n", zTreename);
151 return;
152 }
153 mergebuilder_init_tcl(&mb);
154 mb.nContext = nContext;
155
156 /* Set up the pivot */
157 zFN = db_column_text(&q, 0);
158 if( zFN==0 ){
159 /* No pivot because the file was added */
160 mb.zPivot = "(no baseline)";
161 blob_zero(&pivot);
162 }else{
163 mb.zPivot = mprintf("%s (baseline)", file_tail(zFN));
164 rid = db_column_int(&q, 1);
165 content_get(rid, &pivot);
166 }
167 mb.pPivot = &pivot;
168
169 /* Set up the merge-in as V1 */
170 zFN = db_column_text(&q, 5);
171 if( zFN==0 ){
172 /* File deleted in the merged-in branch */
173 mb.zV1 = "(deleted file)";
174 blob_zero(&v1);
175 }else{
176 mb.zV1 = mprintf("%s (merge-in)", file_tail(zFN));
177 rid = db_column_int(&q, 6);
178 content_get(rid, &v1);
179 }
180 mb.pV1 = &v1;
181
182 /* Set up the local content as V2 */
183 zFN = db_column_text(&q, 2);
184 if( zFN==0 ){
185 /* File added by merge */
186 mb.zV2 = "(no original)";
187 blob_zero(&v2);
188 }else{
189 mb.zV2 = mprintf("%s (local)", file_tail(zFN));
190 rid = db_column_int(&q, 3);
191 sz = db_column_int(&q, 4);
192 if( rid==0 && sz>0 ){
193 /* The origin file had been edited so we'll have to pull its
194 ** original content out of the undo buffer */
195 Stmt q2;
196 db_prepare(&q2,
197 "SELECT content FROM undo"
198 " WHERE pathname=%Q AND octet_length(content)=%d",
199 zFN, sz
200 );
201 blob_zero(&v2);
202 if( db_step(&q2)==SQLITE_ROW ){
203 db_column_blob(&q, 0, &v2);
204 }else{
205 mb.zV2 = "(local content missing)";
206 }
207 db_finalize(&q2);
208 }else{
209 /* The origin file was unchanged when the merge first occurred */
210 content_get(rid, &v2);
211 }
212 }
213 mb.pV2 = &v2;
214
215 /* Set up the output */
216 zFN = db_column_text(&q, 7);
217 if( zFN==0 ){
218 mb.zOut = "(Merge Result)";
219 }else{
220 mb.zOut = mprintf("%s (after merge)", file_tail(zFN));
221 }
222 blob_zero(&out);
223 mb.pOut = &out;
224
225 merge_three_blobs(&mb);
226 blob_write_to_file(&out, "-");
227
228 mb.xDestroy(&mb);
229 blob_reset(&pivot);
230 blob_reset(&v1);
231 blob_reset(&v2);
232 blob_reset(&out);
233 db_finalize(&q);
234 }
235
236 /*
237 ** COMMAND: merge-info
238 **
239 ** Usage: %fossil merge-info [OPTIONS]
240 **
241 ** Display information about the most recent merge operation.
242 **
243 ** Right now, this command basically just dumps the localdb.mergestat
244 ** table. The plan moving forward is that it can generate data for
245 ** a Tk-based GUI to show the details of the merge. This command is
246 ** a work-in-progress.
247 **
248 ** Options:
249 ** -a|--all Show all changes. Normally only merges, conflicts,
250 ** and errors are shown.
251 ** -c|--context N Show N lines of context around each change,
252 ** with negative N meaning show all content. Only
253 ** meaningful in combination with --tcl or --tk.
254 ** --dark Use dark mode for the Tcl/Tk-based GUI
255 ** --tcl FILE Generate (to stdout) a TCL list containing
256 ** information needed to display the changes to
257 ** FILE caused by the most recent merge. FILE must
258 ** be a pathname relative to the root of the check-out.
259 ** --tk Bring up a Tcl/Tk GUI that shows the changes
260 ** associated with the most recent merge.
261 **
262 */
263 void merge_info_cmd(void){
264 const char *zCnt;
265 const char *zTcl;
266 int bTk;
267 int bDark;
268 int bAll;
269 int nContext;
270 Stmt q;
271 const char *zWhere;
272 int cnt = 0;
273
274 db_must_be_within_tree();
275 zTcl = find_option("tcl", 0, 1);
276 bTk = find_option("tk", 0, 0)!=0;
277 zCnt = find_option("context", "c", 1);
278 bDark = find_option("dark", 0, 0)!=0;
279 bAll = find_option("all", "a", 0)!=0;
280 if( bTk==0 ){
281 verify_all_options();
282 if( g.argc>2 ){
283 usage("[OPTIONS]");
284 }
285 }
286 if( zCnt ){
287 nContext = atoi(zCnt);
288 if( nContext<0 ) nContext = 0xfffffff;
289 }else{
290 nContext = 6;
291 }
292 if( !db_table_exists("localdb","mergestat") ){
293 if( zTcl ){
294 fossil_print("ERROR {no merge data available}\n");
295 }else{
296 fossil_print("No merge data is available\n");
297 }
298 return;
299 }
300 if( bTk ){
301 merge_info_tk(bDark, bAll, nContext);
302 return;
303 }
304 if( zTcl ){
305 merge_info_tcl(zTcl, nContext);
306 return;
307 }
308 if( bAll ){
309 zWhere = "";
310 }else{
311 zWhere = "WHERE op IN ('MERGE','CONFLICT','ERROR')";
312 }
313 db_prepare(&q,
314 /* 0 1 2 */
315 "SELECT op, coalesce(fnr,fn), msg"
316 " FROM mergestat"
317 " %s"
318 " ORDER BY coalesce(fnr,fn)",
319 zWhere /*safe-for-%s*/
320 );
321 while( db_step(&q)==SQLITE_ROW ){
322 const char *zOp = db_column_text(&q, 0);
323 const char *zName = db_column_text(&q, 1);
324 const char *zErr = db_column_text(&q, 2);
325 if( zErr && fossil_strcmp(zOp,"CONFLICT")!=0 ){
326 fossil_print("%-9s %s (%s)\n", zOp, zName, zErr);
327 }else{
328 fossil_print("%-9s %s\n", zOp, zName);
329 }
330 cnt++;
331 }
332 db_finalize(&q);
333 if( !bAll && cnt==0 ){
334 fossil_print(
335 "No interesting change in this merge. Use --all to see everything.\n"
336 );
337 }
338 }
339
340 /*
341 ** Erase all information about prior merges. Do this, for example, after
342 ** a commit.
343 */
344 void merge_info_forget(void){
345 db_multi_exec("DROP TABLE IF EXISTS localdb.mergestat");
346 }
347
348
349 /*
350 ** Initialize the MERGESTAT table.
351 **
352 ** Notes about mergestat:
353 **
354 ** * ridv is a positive integer and sz is NULL if the V file contained
355 ** no local edits prior to the merge. If the V file was modified prior
356 ** to the merge then ridv is NULL and sz is the size of the file prior
357 ** to merge.
358 **
359 ** * fnp, ridp, fn, ridv, and sz are all NULL for a file that was
360 ** added by merge.
361 */
362 void merge_info_init(void){
363 db_multi_exec(
364 "DROP TABLE IF EXISTS localdb.mergestat;\n"
365 "CREATE TABLE localdb.mergestat(\n"
366 " op TEXT, -- 'UPDATE', 'ADDED', 'MERGE', etc...\n"
367 " fnp TEXT, -- Name of the pivot file (P)\n"
368 " ridp INT, -- RID for the pivot file\n"
369 " fn TEXT, -- Name of origin file (V)\n"
370 " ridv INT, -- RID for origin file, or NULL if previously edited\n"
371 " sz INT, -- Size of origin file in bytes, NULL if unedited\n"
372 " fnm TEXT, -- Name of the file being merged in (M)\n"
373 " ridm INT, -- RID for the merge-in file\n"
374 " fnr TEXT, -- Name of the final output file, after all renaming\n"
375 " nc INT DEFAULT 0, -- Number of conflicts\n"
376 " msg TEXT -- Error message\n"
377 ");"
378 );
379 }
380
381 /*
382 ** Print information about a particular check-in.
383 */
384 void print_checkin_description(int rid, int indent, const char *zLabel){
385 Stmt q;
@@ -323,11 +679,11 @@
679 ** --force-missing Force the merge even if there is missing content
680 ** --integrate Merged branch will be closed when committing
681 ** -K|--keep-merge-files On merge conflict, retain the temporary files
682 ** used for merging, named *-baseline, *-original,
683 ** and *-merge.
684 ** -n|--dry-run Do not actually change files on disk
685 ** --nosync Do not auto-sync prior to merging
686 ** -v|--verbose Show additional details of the merge
687 */
688 void merge_cmd(void){
689 int vid; /* Current version "V" */
@@ -800,15 +1156,21 @@
1156
1157 /************************************************************************
1158 ** All of the information needed to do the merge is now contained in the
1159 ** FV table. Starting here, we begin to actually carry out the merge.
1160 **
1161 ** Begin by constructing the localdb.mergestat table.
1162 */
1163 merge_info_init();
1164
1165 /*
1166 ** Find files that have changed from P->M but not P->V.
1167 ** Copy the M content over into V.
1168 */
1169 db_prepare(&q,
1170 /* 0 1 2 3 4 5 6 7 */
1171 "SELECT idv, ridm, fn, islinkm, fnp, ridp, ridv, fnm FROM fv"
1172 " WHERE idp>0 AND idv>0 AND idm>0"
1173 " AND ridm!=ridp AND ridv=ridp AND NOT chnged"
1174 );
1175 while( db_step(&q)==SQLITE_ROW ){
1176 int idv = db_column_int(&q, 0);
@@ -825,10 +1187,21 @@
1187 " THEN (SELECT uuid FROM blob WHERE blob.rid=%d) END"
1188 " WHERE id=%d", ridm, integrateFlag?4:2, islinkm, ridm, ridm, idv
1189 );
1190 vfile_to_disk(0, idv, 0, 0);
1191 }
1192 db_multi_exec(
1193 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr)"
1194 "VALUES('UPDATE',%Q,%d,%Q,%d,%Q,%d,%Q)",
1195 /* fnp */ db_column_text(&q, 4),
1196 /* ridp */ db_column_int(&q,5),
1197 /* fn */ zName,
1198 /* ridv */ db_column_int(&q,6),
1199 /* fnm */ db_column_text(&q, 7),
1200 /* ridm */ ridm,
1201 /* fnr */ zName
1202 );
1203 }
1204 db_finalize(&q);
1205
1206 /*
1207 ** Do a three-way merge on files that have changes on both P->M and P->V.
@@ -836,11 +1209,15 @@
1209 ** Proceed even if the file doesn't exist on P, just like the common ancestor
1210 ** of M and V is an empty file. In this case, merge conflict marks will be
1211 ** added to the file and user will be forced to take a decision.
1212 */
1213 db_prepare(&q,
1214 /* 0 1 2 3 4 5 6 7 8 */
1215 "SELECT ridm, idv, ridp, ridv, %s, fn, isexe, islinkv, islinkm,"
1216 /* 9 10 11 */
1217 " fnp, fnm, chnged"
1218 " FROM fv"
1219 " WHERE idv>0 AND idm>0"
1220 " AND ridm!=ridp AND (ridv!=ridp OR chnged)",
1221 glob_expr("fv.fn", zBinGlob)
1222 );
1223 while( db_step(&q)==SQLITE_ROW ){
@@ -851,12 +1228,14 @@
1228 int isBinary = db_column_int(&q, 4);
1229 const char *zName = db_column_text(&q, 5);
1230 int isExe = db_column_int(&q, 6);
1231 int islinkv = db_column_int(&q, 7);
1232 int islinkm = db_column_int(&q, 8);
1233 int chnged = db_column_int(&q, 11);
1234 int rc;
1235 char *zFullPath;
1236 const char *zType = "MERGE";
1237 Blob m, p, r;
1238 /* Do a 3-way merge of idp->idm into idp->idv. The results go into idv. */
1239 if( verboseFlag ){
1240 fossil_print("MERGE %s (pivot=%d v1=%d v2=%d)\n",
1241 zName, ridp, ridm, ridv);
@@ -864,13 +1243,29 @@
1243 fossil_print("MERGE %s\n", zName);
1244 }
1245 if( islinkv || islinkm ){
1246 fossil_print("***** Cannot merge symlink %s\n", zName);
1247 nConflict++;
1248 db_multi_exec(
1249 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,fnm,ridm,fnr,nc,msg)"
1250 "VALUES('ERROR',%Q,%d,%Q,%d,%Q,%d,%Q,1,'cannot merge symlink')",
1251 /* fnp */ db_column_text(&q, 9),
1252 /* ridp */ ridp,
1253 /* fn */ zName,
1254 /* ridv */ ridv,
1255 /* fnm */ db_column_text(&q, 10),
1256 /* ridm */ ridm,
1257 /* fnr */ zName
1258 );
1259 }else{
1260 i64 sz;
1261 const char *zErrMsg = 0;
1262 int nc = 0;
1263
1264 if( !dryRunFlag ) undo_save(zName);
1265 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1266 sz = file_size(zFullPath, ExtFILE);
1267 content_get(ridp, &p);
1268 content_get(ridm, &m);
1269 if( isBinary ){
1270 rc = -1;
1271 blob_zero(&r);
@@ -887,15 +1282,38 @@
1282 db_multi_exec("UPDATE vfile SET mtime=0 WHERE id=%d", idv);
1283 if( rc>0 ){
1284 fossil_print("***** %d merge conflict%s in %s\n",
1285 rc, rc>1 ? "s" : "", zName);
1286 nConflict++;
1287 nc = rc;
1288 zErrMsg = "merge conflicts";
1289 zType = "CONFLICT";
1290 }
1291 }else{
1292 fossil_print("***** Cannot merge binary file %s\n", zName);
1293 nConflict++;
1294 nc = 1;
1295 zErrMsg = "cannot merge binary file";
1296 zType = "ERROR";
1297 }
1298 db_multi_exec(
1299 "INSERT INTO mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,fnr,nc,msg)"
1300 "VALUES(%Q,%Q,%d,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),%Q,%d,"
1301 "%Q,%d,%Q)",
1302 /* op */ zType,
1303 /* fnp */ db_column_text(&q, 9),
1304 /* ridp */ ridp,
1305 /* fn */ zName,
1306 /* ridv */ chnged==0, ridv,
1307 /* sz */ chnged!=0, sz,
1308 /* fnm */ db_column_text(&q, 10),
1309 /* ridm */ ridm,
1310 /* fnr */ zName,
1311 /* nc */ nc,
1312 /* msg */ zErrMsg
1313 );
1314 fossil_free(zFullPath);
1315 blob_reset(&p);
1316 blob_reset(&m);
1317 blob_reset(&r);
1318 }
1319 vmerge_insert(idv, ridm);
@@ -904,22 +1322,33 @@
1322
1323 /*
1324 ** Drop files that are in P and V but not in M
1325 */
1326 db_prepare(&q,
1327 "SELECT idv, fn, chnged, ridv FROM fv"
1328 " WHERE idp>0 AND idv>0 AND idm=0"
1329 );
1330 while( db_step(&q)==SQLITE_ROW ){
1331 int idv = db_column_int(&q, 0);
1332 const char *zName = db_column_text(&q, 1);
1333 int chnged = db_column_int(&q, 2);
1334 int ridv = db_column_int(&q, 3);
1335 int sz = -1;
1336 const char *zErrMsg = 0;
1337 int nc = 0;
1338 /* Delete the file idv */
1339 fossil_print("DELETE %s\n", zName);
1340 if( chnged ){
1341 char *zFullPath;
1342 fossil_warning("WARNING: local edits lost for %s", zName);
1343 nConflict++;
1344 ridv = 0;
1345 nc = 1;
1346 zErrMsg = "local edits lost";
1347 zFullPath = mprintf("%s/%s", g.zLocalRoot, zName);
1348 sz = file_size(zFullPath, ExtFILE);
1349 fossil_free(zFullPath);
1350 }
1351 if( !dryRunFlag ) undo_save(zName);
1352 db_multi_exec(
1353 "UPDATE vfile SET deleted=1 WHERE id=%d", idv
1354 );
@@ -926,10 +1355,20 @@
1355 if( !dryRunFlag ){
1356 char *zFullPath = mprintf("%s%s", g.zLocalRoot, zName);
1357 file_delete(zFullPath);
1358 free(zFullPath);
1359 }
1360 db_multi_exec(
1361 "INSERT INTO localdb.mergestat(op,fnp,ridp,fn,ridv,sz,fnm,ridm,nc,msg)"
1362 "VALUES('DELETE',NULL,NULL,%Q,iif(%d,%d,NULL),iif(%d,%d,NULL),"
1363 "NULL,NULL,%d,%Q)",
1364 /* fn */ zName,
1365 /* ridv */ chnged==0, ridv,
1366 /* sz */ chnged!=0, sz,
1367 /* nc */ nc,
1368 /* msg */ zErrMsg
1369 );
1370 }
1371 db_finalize(&q);
1372
1373 /* For certain sets of renames (e.g. A -> B and B -> A), a file that is
1374 ** being renamed must first be moved to a temporary location to avoid
@@ -956,10 +1395,14 @@
1395 const char *zNewName = db_column_text(&q, 2);
1396 int isExe = db_column_int(&q, 3);
1397 fossil_print("RENAME %s -> %s\n", zOldName, zNewName);
1398 if( !dryRunFlag ) undo_save(zOldName);
1399 if( !dryRunFlag ) undo_save(zNewName);
1400 db_multi_exec(
1401 "UPDATE mergestat SET fnr=fnm WHERE fnp=%Q",
1402 zOldName
1403 );
1404 db_multi_exec(
1405 "UPDATE vfile SET pathname=NULL, origname=pathname"
1406 " WHERE vid=%d AND pathname=%Q;"
1407 "UPDATE vfile SET pathname=%Q, origname=coalesce(origname,pathname)"
1408 " WHERE id=%d;",
@@ -1009,11 +1452,11 @@
1452
1453 /*
1454 ** Insert into V any files that are not in V or P but are in M.
1455 */
1456 db_prepare(&q,
1457 "SELECT idm, fnm, ridm FROM fv"
1458 " WHERE idp=0 AND idv=0 AND idm>0"
1459 );
1460 while( db_step(&q)==SQLITE_ROW ){
1461 int idm = db_column_int(&q, 0);
1462 const char *zName;
@@ -1042,10 +1485,17 @@
1485 nOverwrite++;
1486 }else{
1487 fossil_print("ADDED %s\n", zName);
1488 }
1489 fossil_free(zFullName);
1490 db_multi_exec(
1491 "INSERT INTO mergestat(op,fnm,ridm,fnr)"
1492 "VALUES('ADDED',%Q,%d,%Q)",
1493 /* fnm */ zName,
1494 /* ridm */ db_column_int(&q,2),
1495 /* fnr */ zName
1496 );
1497 if( !dryRunFlag ){
1498 undo_save(zName);
1499 vfile_to_disk(0, idm, 0, 0);
1500 }
1501 }
1502
1503 DDED src/merge.tcl
+287
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,287 @@
1
+# The "--tk" option to various merge commands prepends one or more
2
+# "set fossilcmd(NAME) {...}" lines to this file, then runs this file using
3
+# "tclsh" in order to show a graphical analysis of the merge results.
4
+# A typical "set fossilcmd" line looks like this:
5
+#
6
+# set fossilcmd(file1.txt) {| "./fossil" diff --tcl -i -v}(TITLE)
7
+# . Disable them, to prevent the diff screen from
8
+# disappearing abruptly and unexpectedly when searching for "q".
9
+#
10
+bind . <Control-q> exit
11
+bind . <Control-p> {catch searchPrev; break}
12
+bind . <return. <Escape><Escape> exit
13
+bind . <Destroy> {after 0if {$key4=="N"} {
14
+ hose same keystrokes are pr$last-$first)}]
15
+ foreach side {A B C D} {
16
+ set sb .sbx$side
17
+ set xview [.txt$side xview]
18
+ }
19
+ enableSync xp $fn"}
20
+ tk_optionMenu .bb.files current_file {*}$fnlist
21
+ } else {
22
+ set useOptionMenu 0
23
+ ::ttk::menubutton .bb.files -text $current_file
24
+ if {[tk windowingsystem] eq "win32"} {
25
+ ::ttk::style theme use winnative
26
+ .bb.files configure -padding {20 1 10 2}
27
+ }
28
+ toplevel .wfiles
29
+ wm withdraw .wfiles
30
+ update idletasks
31
+ wm transient .wfiles .
32
+ B B .txtAy {scroll 1 page}
33
+ sveto 0}
34
+ g y {moveto 0}
35
+ End y {moveto 1}
36
+} {
37
+ bind . <$key> "scroll-$axis $args; break"
38
+ bind . <Shift-$key> continue
39
+}
40
+
41
+frame .bb
42
+::ttk::menubutton .bb.diff2 -text {2-way diffs
43
+.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
44
+.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
45
+.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
46
+
47
+# Bring up a separate two-way diff between a pair of co44444
48
+ HR_PAD_TOP 4
49
+ set ::current_file [%W get $ii]
50
+ .bb.files config -text $::current_file
51
+ focus .
52
+ break
53
+ }
54
+ }
55
+ bind .fossilcmd} {
56
+ ptly and unexpectedly when searchinfor "q".
57
+#
58
+bind . <Controlclose $inway
59
+
60
+ $c command {two-way 23"N"} {
61
+ hose same keystrokes are pr$last-$first)}]
62
+ foreach side {A B C D} {
63
+ set sb .sbx$side
64
+ set xview [.txt$side xview]
65
+ }
66
+ enableSync xp $fn"}
67
+ tk_optionMenu .bb.files current_file {*}$fnlist
68
+ } else {
69
+ set useOptionMenu 0
70
+ ::ttk::menubutton .bb.files -text $current_file
71
+ if {[tk windowingsystem] eq "win32"} {
72
+ ::ttk::style theme use winnative
73
+ .bb.files configure -padding {20 1 10 2}
74
+ }
75
+ toplevel .wfiles
76
+ wm withdraw .wfiles
77
+ update idletasks
78
+ wm transient .wfiles .
79
+ B B .txtAy {scroll 1 page}
80
+ sveto 0}
81
+ g y {moveto 0}
82
+ End y {moveto 1}
83
+} {
84
+ bind . <$key> "scroll-$axis $args; break"
85
+ bind . <Shift-$key> continue
86
+}
87
+
88
+frame .bb
89
+::ttk::menubutton .bb.diff2 -text {2-way diffs
90
+.bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
91
+.bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
92
+.bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
93
+
94
+# Bring up a separate two-way diff between a pair of co44444
95
+ HR_PAD_TOP 4
96
+ set ::current_file [%W get $ii]
97
+ .bb.files config -text $::current_file
98
+ focus .
99
+ break
100
+ }
101
+ }
102
+ bind .wfiles.lb <Motion> {
103
+ %W selection clear 0 end
104
+ %W selection set @%x,et NtD insert end [sA 1 end]
105
+ incr lnA $N
106
+ incr lnB $N
107
+ incr lnC $N
108
+ incr lnD $N
109
+ .lnA_platform(os)=="Darwin" || $u.txtA)=="Darwin" || $useOin32"} {$c confi...\n hrln
110
+ {
111
+ .txtB insert end [strepeat . 30]\n hrtxt if {$key3=="."} {
112
+ ...\n hrln
113
+ .txtC insert end [string rangeepeat . 30]\n hrtxt
114
+ if {$key4=="."} {
115
+...\n hrln
116
+ .txtD insert end [string rangeepeat . 30]\n hrtxt2"} {
117
+ ::ttk::style theme use winnative
118
+ .bb.ctx configure -padding {20 1 10 2}
119
+ }
120
+ toplevel .wctx
121
+ wm withdraw .wctx
122
+ upda4=="X"} {set dtag rm} {set dtag -}oplevel .wctx
123
+ wm withdraw .wctx
124
+ update idletasks
125
+ wm transient .wctx .
126
+$dtag
127
+l .txtC insert b -width 0 -height 7 -activestyle none
128
+ .wctx.lb insert end {*}$context_choices
129
+ pack .wctx.lb
130
+ .txtAsert end [string range $C 1A 1 end]\n $# if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} {
131
+# grid $sb
132
+# eval $sb set $xview
133
+# } else {
134
+# grid remove $sb
135
+# } else {
136
+ view
137
+ } else {
138
+ viewDiff [lindex $range 0]
139
+ }
140
+ } else {
141
+ set range [.txtA tag nextrange fn {@0,0 +1c} end]
142
+ if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
143
+ viewDiff fn.first
144
+ } else {
145
+ viewDiff [lindex $range 0]
146
+ }
147
+ }
148
+}
149
+
150
+proc xvis {col} {
151
+ set view [$col xview]
152
+ return [expr {[lindex $view 1]-[lindex $view 0]}]
153
+}
154
+
155
+proc scroll-x {args} {
156
+ set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
157
+ eval $c xview $args
158
+}
159
+
160
+interp alias {} scroll-y {} .txtA yview
161
+
162
+proc noop {argx {col first last} {
163
+ disableSynctk_optionMenu .bb.ctx nc:ncontext [lindex $::context_choices [%W curselection]]
164
+ .bb.ctx config -text $::ncontext
165
+ focus .
166
+ break
167
+ }
168
+ }
169
+ bind .wctx.lb <Motion> {
170
+ %W selection clear 0 end
171
+ %W selection set @%x,%y
172
+ }
173
+}
174
+
175
+foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
176
+ set ln .ln$side
177
+ text $ln -width 6
178
+ $ln tag config - -justify right
179
+
180
+ set txt .txt$side
181
+ text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
182
+ -xscroll ".sbx$side set"
183
+ catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
184
+ foreach tag {add rm chng} {
185
+ $txt tag config $tag -background $CFG([string toupper $tag]_BG)
186
+ $txt tag lower $tag
187
+ }
188
+ $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
189
+ -justify center
190
+ $txt tag config err -foreground $CFG(ERR_FG)
191
+}
192
+text .mkr
193
+
194
+set mxwidth [lindex [wm maxsize .] 0]
195
+while {$CFG(WIDTH)>=40} {
196
+ set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
197
+ if {$wanted<=$mxwidth} break
198
+ incr CFG(WIDTH) -10
199
+ .txtA config -width $CFG(WIDTH)
200
+ .txtB config -width $CFG(WIDTH)
201
+ .txtC config -width -width $CFG(WIDTH)
202
+ .txtD confi[tk windowingsystem] eq "win32"} {set filelist 1]
203
+ get $ii]
204
+ ier 9}}
205
+ $c command {two-way
206
+
207
+ $c command {two-way 23}
208
+
209
+# Bri} {
210
+ set fnlist {}
211
+444
212
+ HR_PAD_TOP 4
213
+ se
214
+ .bb.files co} else {
215
+iles.lb <Motion> {
216
+ %W%y
217
+ }
218
+ } @%x,%y
219
+ text_choices {3 6 }
220
+ toplevel .wfiles
221
+
222
+ dd variable current_fplatform(os)=="Darwin" || $usform(os)=="Darwin" || $useOptionMeset ht [expr
223
+ set keyPrefix [str/2}]
224
+ ::ttk::menubutton .bb.ctx -texngsystem] eq "win32"} {
225
+ ::ttk::style theme use winnative
226
+ ..ctx configure -padding {20 1 set mx 1
227
+444
228
+ HR_PAD_TOP 4
229
+ set :
230
+g -text "Fi:"ystem] eq "win32"} {$c confifn$fnlindex $::filelis[format "%-9s %s" $op $fn]
231
+ }
232
+ context_choices {3 -width $mx
233
+ bind .bb.files <1> {
234
+focus .wfiles.lb
235
+ }
236
+focus .
237
+ }
238
+ }
239
+Motion> {
240
+ndraw .wfiles}
241
+ ge $B 1 en
242
+ ge $B 1 end]\n rm
243
+ } else {
244
+ .txtB inse{$tcl_platform(os)=="Darwin" || $useOptionMenu} {
245
+ tk_optionMenu .bb.ctx ncontext {*}$context_choices
246
+} else {
247
+ ::ttk::menubutton .bb.ctx -text $ncontext
248
+ if {[tk windowingsystem] eq "win32"} {
249
+ ::ttk::style theme use winnative
250
+ .bb.ctx configure -padding {20 1 10 2}
251
+ }
252
+ toplevel .wctx
253
+ wm withdraw .wctx
254
+ update idletasks
255
+ wm transient .wctx .
256
+ wm overrideredirect .wctx 1
257
+ listbox .wctx.lb -width 0 -height 7 -activestyle none
258
+ .wctx.lb insert end {*}$context_choices
259
+ pack .wctx.lb
260
+ bind .bb.ctx <1> {
261
+ set x [winfo rootx %W]
262
+ set y [expr {[winfo rooty %W]+[winfo height %W]}]
263
+ wm geometry .wctx +$x+$y
264
+ wm deiconify .wctx
265
+ focus .wctx.lb
266
+ }
267
+ bin set :
268
+g -text "Fi:"ystem] eq "wi"Files""ystem] eq "win32"} {$c confifn$fnlindex $::filelis[format "%-9s %s" $op $fn]
269
+ }
270
+ context_choices {3 -width $mx
271
+ bind .bb.files <1> {
272
+focus .wfiles.lb
273
+ }
274
+focus .
275
+ }
276
+ }
277
+Motion> {
278
+ndraw .wfiles}
279
+ ge $B 1 en
280
+ ge $B 1 end]\n rm
281
+ } else {
282
+ .txtB insereadMerge "$::fossilcmd [list]" set keyPrefix [str/2}]
283
+ ::ttk::mreadMerge "$fossilcmd [listative
284
+ ..ctx confi]"
285
+} else {
286
+ {20 1 set $fossilcmd
287
+}
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
--- a/src/merge.tcl
+++ b/src/merge.tcl
@@ -0,0 +1,287 @@
1 # The "--tk" option to various merge commands prepends one or more
2 # "set fossilcmd(NAME) {...}" lines to this file, then runs this file using
3 # "tclsh" in order to show a graphical analysis of the merge results.
4 # A typical "set fossilcmd" line looks like this:
5 #
6 # set fossilcmd(file1.txt) {| "./fossil" diff --tcl -i -v}(TITLE)
7 # . Disable them, to prevent the diff screen from
8 # disappearing abruptly and unexpectedly when searching for "q".
9 #
10 bind . <Control-q> exit
11 bind . <Control-p> {catch searchPrev; break}
12 bind . <return. <Escape><Escape> exit
13 bind . <Destroy> {after 0if {$key4=="N"} {
14 hose same keystrokes are pr$last-$first)}]
15 foreach side {A B C D} {
16 set sb .sbx$side
17 set xview [.txt$side xview]
18 }
19 enableSync xp $fn"}
20 tk_optionMenu .bb.files current_file {*}$fnlist
21 } else {
22 set useOptionMenu 0
23 ::ttk::menubutton .bb.files -text $current_file
24 if {[tk windowingsystem] eq "win32"} {
25 ::ttk::style theme use winnative
26 .bb.files configure -padding {20 1 10 2}
27 }
28 toplevel .wfiles
29 wm withdraw .wfiles
30 update idletasks
31 wm transient .wfiles .
32 B B .txtAy {scroll 1 page}
33 sveto 0}
34 g y {moveto 0}
35 End y {moveto 1}
36 } {
37 bind . <$key> "scroll-$axis $args; break"
38 bind . <Shift-$key> continue
39 }
40
41 frame .bb
42 ::ttk::menubutton .bb.diff2 -text {2-way diffs
43 .bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
44 .bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
45 .bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
46
47 # Bring up a separate two-way diff between a pair of co44444
48 HR_PAD_TOP 4
49 set ::current_file [%W get $ii]
50 .bb.files config -text $::current_file
51 focus .
52 break
53 }
54 }
55 bind .fossilcmd} {
56 ptly and unexpectedly when searchinfor "q".
57 #
58 bind . <Controlclose $inway
59
60 $c command {two-way 23"N"} {
61 hose same keystrokes are pr$last-$first)}]
62 foreach side {A B C D} {
63 set sb .sbx$side
64 set xview [.txt$side xview]
65 }
66 enableSync xp $fn"}
67 tk_optionMenu .bb.files current_file {*}$fnlist
68 } else {
69 set useOptionMenu 0
70 ::ttk::menubutton .bb.files -text $current_file
71 if {[tk windowingsystem] eq "win32"} {
72 ::ttk::style theme use winnative
73 .bb.files configure -padding {20 1 10 2}
74 }
75 toplevel .wfiles
76 wm withdraw .wfiles
77 update idletasks
78 wm transient .wfiles .
79 B B .txtAy {scroll 1 page}
80 sveto 0}
81 g y {moveto 0}
82 End y {moveto 1}
83 } {
84 bind . <$key> "scroll-$axis $args; break"
85 bind . <Shift-$key> continue
86 }
87
88 frame .bb
89 ::ttk::menubutton .bb.diff2 -text {2-way diffs
90 .bb.diff2.m add command -label {baseline vs. local} -command {two-way 12}
91 .bb.diff2.m add command -label {baseline vs. merge-in} -command {two-way 13}
92 .bb.diff2.m add command -label {local vs. merge-in} -command {two-way 23}
93
94 # Bring up a separate two-way diff between a pair of co44444
95 HR_PAD_TOP 4
96 set ::current_file [%W get $ii]
97 .bb.files config -text $::current_file
98 focus .
99 break
100 }
101 }
102 bind .wfiles.lb <Motion> {
103 %W selection clear 0 end
104 %W selection set @%x,et NtD insert end [sA 1 end]
105 incr lnA $N
106 incr lnB $N
107 incr lnC $N
108 incr lnD $N
109 .lnA_platform(os)=="Darwin" || $u.txtA)=="Darwin" || $useOin32"} {$c confi...\n hrln
110 {
111 .txtB insert end [strepeat . 30]\n hrtxt if {$key3=="."} {
112 ...\n hrln
113 .txtC insert end [string rangeepeat . 30]\n hrtxt
114 if {$key4=="."} {
115 ...\n hrln
116 .txtD insert end [string rangeepeat . 30]\n hrtxt2"} {
117 ::ttk::style theme use winnative
118 .bb.ctx configure -padding {20 1 10 2}
119 }
120 toplevel .wctx
121 wm withdraw .wctx
122 upda4=="X"} {set dtag rm} {set dtag -}oplevel .wctx
123 wm withdraw .wctx
124 update idletasks
125 wm transient .wctx .
126 $dtag
127 l .txtC insert b -width 0 -height 7 -activestyle none
128 .wctx.lb insert end {*}$context_choices
129 pack .wctx.lb
130 .txtAsert end [string range $C 1A 1 end]\n $# if {[lindex $xview 0] > 0 || [lindex $xview 1] < 1} {
131 # grid $sb
132 # eval $sb set $xview
133 # } else {
134 # grid remove $sb
135 # } else {
136 view
137 } else {
138 viewDiff [lindex $range 0]
139 }
140 } else {
141 set range [.txtA tag nextrange fn {@0,0 +1c} end]
142 if {$range eq "" || [lindex [.txtA yview] 1] == 1} {
143 viewDiff fn.first
144 } else {
145 viewDiff [lindex $range 0]
146 }
147 }
148 }
149
150 proc xvis {col} {
151 set view [$col xview]
152 return [expr {[lindex $view 1]-[lindex $view 0]}]
153 }
154
155 proc scroll-x {args} {
156 set c .txt[expr {[xvis .txtA] < [xvis .txtB] ? "A" : "B"}]
157 eval $c xview $args
158 }
159
160 interp alias {} scroll-y {} .txtA yview
161
162 proc noop {argx {col first last} {
163 disableSynctk_optionMenu .bb.ctx nc:ncontext [lindex $::context_choices [%W curselection]]
164 .bb.ctx config -text $::ncontext
165 focus .
166 break
167 }
168 }
169 bind .wctx.lb <Motion> {
170 %W selection clear 0 end
171 %W selection set @%x,%y
172 }
173 }
174
175 foreach {side syncCol} {A .txtA B .txtB C .txtC D .txtD} {
176 set ln .ln$side
177 text $ln -width 6
178 $ln tag config - -justify right
179
180 set txt .txt$side
181 text $txt -width $CFG(WIDTH) -height $CFG(HEIGHT) -wrap none \
182 -xscroll ".sbx$side set"
183 catch {$txt config -tabstyle wordprocessor} ;# Required for Tk>=8.5
184 foreach tag {add rm chng} {
185 $txt tag config $tag -background $CFG([string toupper $tag]_BG)
186 $txt tag lower $tag
187 }
188 $txt tag config fn -background $CFG(FN_BG) -foreground $CFG(FN_FG) \
189 -justify center
190 $txt tag config err -foreground $CFG(ERR_FG)
191 }
192 text .mkr
193
194 set mxwidth [lindex [wm maxsize .] 0]
195 while {$CFG(WIDTH)>=40} {
196 set wanted [expr {([winfo reqwidth .lnA]+[winfo reqwidth .txtA])*4+30}]
197 if {$wanted<=$mxwidth} break
198 incr CFG(WIDTH) -10
199 .txtA config -width $CFG(WIDTH)
200 .txtB config -width $CFG(WIDTH)
201 .txtC config -width -width $CFG(WIDTH)
202 .txtD confi[tk windowingsystem] eq "win32"} {set filelist 1]
203 get $ii]
204 ier 9}}
205 $c command {two-way
206
207 $c command {two-way 23}
208
209 # Bri} {
210 set fnlist {}
211 444
212 HR_PAD_TOP 4
213 se
214 .bb.files co} else {
215 iles.lb <Motion> {
216 %W%y
217 }
218 } @%x,%y
219 text_choices {3 6 }
220 toplevel .wfiles
221
222 dd variable current_fplatform(os)=="Darwin" || $usform(os)=="Darwin" || $useOptionMeset ht [expr
223 set keyPrefix [str/2}]
224 ::ttk::menubutton .bb.ctx -texngsystem] eq "win32"} {
225 ::ttk::style theme use winnative
226 ..ctx configure -padding {20 1 set mx 1
227 444
228 HR_PAD_TOP 4
229 set :
230 g -text "Fi:"ystem] eq "win32"} {$c confifn$fnlindex $::filelis[format "%-9s %s" $op $fn]
231 }
232 context_choices {3 -width $mx
233 bind .bb.files <1> {
234 focus .wfiles.lb
235 }
236 focus .
237 }
238 }
239 Motion> {
240 ndraw .wfiles}
241 ge $B 1 en
242 ge $B 1 end]\n rm
243 } else {
244 .txtB inse{$tcl_platform(os)=="Darwin" || $useOptionMenu} {
245 tk_optionMenu .bb.ctx ncontext {*}$context_choices
246 } else {
247 ::ttk::menubutton .bb.ctx -text $ncontext
248 if {[tk windowingsystem] eq "win32"} {
249 ::ttk::style theme use winnative
250 .bb.ctx configure -padding {20 1 10 2}
251 }
252 toplevel .wctx
253 wm withdraw .wctx
254 update idletasks
255 wm transient .wctx .
256 wm overrideredirect .wctx 1
257 listbox .wctx.lb -width 0 -height 7 -activestyle none
258 .wctx.lb insert end {*}$context_choices
259 pack .wctx.lb
260 bind .bb.ctx <1> {
261 set x [winfo rootx %W]
262 set y [expr {[winfo rooty %W]+[winfo height %W]}]
263 wm geometry .wctx +$x+$y
264 wm deiconify .wctx
265 focus .wctx.lb
266 }
267 bin set :
268 g -text "Fi:"ystem] eq "wi"Files""ystem] eq "win32"} {$c confifn$fnlindex $::filelis[format "%-9s %s" $op $fn]
269 }
270 context_choices {3 -width $mx
271 bind .bb.files <1> {
272 focus .wfiles.lb
273 }
274 focus .
275 }
276 }
277 Motion> {
278 ndraw .wfiles}
279 ge $B 1 en
280 ge $B 1 end]\n rm
281 } else {
282 .txtB insereadMerge "$::fossilcmd [list]" set keyPrefix [str/2}]
283 ::ttk::mreadMerge "$fossilcmd [listative
284 ..ctx confi]"
285 } else {
286 {20 1 set $fossilcmd
287 }
+671 -166
--- src/merge3.c
+++ src/merge3.c
@@ -75,69 +75,10 @@
7575
if( aC1[2]!=aC2[2] ) return 0;
7676
if( sameLines(pV1, pV2, aC1[2]) ) return 1;
7777
return 0;
7878
}
7979
80
-/*
81
-** The aC[] array contains triples of integers. Within each triple, the
82
-** elements are:
83
-**
84
-** (0) The number of lines to copy
85
-** (1) The number of lines to delete
86
-** (2) The number of liens to insert
87
-**
88
-** Suppose we want to advance over sz lines of the original file. This routine
89
-** returns true if that advance would land us on a copy operation. It
90
-** returns false if the advance would end on a delete.
91
-*/
92
-static int ends_at_CPY(int *aC, int sz){
93
- while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
94
- if( aC[0]>=sz ) return 1;
95
- sz -= aC[0];
96
- if( aC[1]>sz ) return 0;
97
- sz -= aC[1];
98
- aC += 3;
99
- }
100
- return 1;
101
-}
102
-
103
-/*
104
-** pSrc contains an edited file where aC[] describes the edit. Part of
105
-** pSrc has already been output. This routine outputs additional lines
106
-** of pSrc - lines that correspond to the next sz lines of the original
107
-** unedited file.
108
-**
109
-** Note that sz counts the number of lines of text in the original file.
110
-** But text is output from the edited file. So the number of lines transfer
111
-** to pOut might be different from sz. Fewer lines appear in pOut if there
112
-** are deletes. More lines appear if there are inserts.
113
-**
114
-** The aC[] array is updated and the new index into aC[] is returned.
115
-*/
116
-static int output_one_side(
117
- Blob *pOut, /* Write to this blob */
118
- Blob *pSrc, /* The edited file that is to be copied to pOut */
119
- int *aC, /* Array of integer triples describing the edit */
120
- int i, /* Index in aC[] of current location in pSrc */
121
- int sz, /* Number of lines in unedited source to output */
122
- int *pLn /* Line number counter */
123
-){
124
- while( sz>0 ){
125
- if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
126
- if( aC[i]>=sz ){
127
- blob_copy_lines(pOut, pSrc, sz); *pLn += sz;
128
- aC[i] -= sz;
129
- break;
130
- }
131
- blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i];
132
- blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2];
133
- sz -= aC[i] + aC[i+1];
134
- i += 3;
135
- }
136
- return i;
137
-}
138
-
13980
/*
14081
** Text of boundary markers for merge conflicts.
14182
*/
14283
static const char *const mergeMarker[] = {
14384
/*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
@@ -186,10 +127,489 @@
186127
ensure_line_end(pOut, useCrLf);
187128
blob_append(pOut, mergeMarker[iMark], -1);
188129
if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
189130
ensure_line_end(pOut, useCrLf);
190131
}
132
+
133
+#if INTERFACE
134
+/*
135
+** This is an abstract class for constructing a merge.
136
+** Subclasses of this object format the merge output in different ways.
137
+**
138
+** To subclass, create an instance of the MergeBuilder object and fill
139
+** in appropriate method implementations.
140
+*/
141
+struct MergeBuilder {
142
+ void (*xStart)(MergeBuilder*);
143
+ void (*xSame)(MergeBuilder*, unsigned int);
144
+ void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
145
+ void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
146
+ void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
147
+ void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
148
+ void (*xEnd)(MergeBuilder*);
149
+ void (*xDestroy)(MergeBuilder*);
150
+ const char *zPivot; /* Label or name for the pivot */
151
+ const char *zV1; /* Label or name for the V1 file */
152
+ const char *zV2; /* Label or name for the V2 file */
153
+ const char *zOut; /* Label or name for the output */
154
+ Blob *pPivot; /* The common ancestor */
155
+ Blob *pV1; /* First variant */
156
+ Blob *pV2; /* Second variant */
157
+ Blob *pOut; /* Write merge results here */
158
+ int useCrLf; /* Use CRLF line endings */
159
+ int nContext; /* Size of unchanged line boundaries */
160
+ unsigned int mxPivot; /* Number of lines in the pivot */
161
+ unsigned int mxV1; /* Number of lines in V1 */
162
+ unsigned int mxV2; /* Number of lines in V2 */
163
+ unsigned int lnPivot; /* Lines read from pivot */
164
+ unsigned int lnV1; /* Lines read from v1 */
165
+ unsigned int lnV2; /* Lines read from v2 */
166
+ unsigned int lnOut; /* Lines written to out */
167
+ unsigned int nConflict; /* Number of conflicts seen */
168
+};
169
+#endif /* INTERFACE */
170
+
171
+
172
+/************************* Generic MergeBuilder ******************************/
173
+/* These are generic methods for MergeBuilder. They just output debugging
174
+** information. But some of them are useful as base methods for other useful
175
+** implementations of MergeBuilder.
176
+*/
177
+
178
+/* xStart() and xEnd() are called to generate header and fotter information
179
+** in the output. This is a no-op in the generic implementation.
180
+*/
181
+static void dbgStartEnd(MergeBuilder *p){ (void)p; }
182
+
183
+/* The next N lines of PIVOT are unchanged in both V1 and V2
184
+*/
185
+static void dbgSame(MergeBuilder *p, unsigned int N){
186
+ blob_appendf(p->pOut,
187
+ "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
188
+ N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
189
+ p->lnV2+1, p->lnV2+N);
190
+ p->lnPivot += N;
191
+ p->lnV1 += N;
192
+ p->lnV2 += N;
193
+}
194
+
195
+/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
196
+*/
197
+static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
198
+ blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
199
+ nV1, p->lnV1+1, p->lnV1+nV1);
200
+ p->lnPivot += nPivot;
201
+ p->lnV2 += nPivot;
202
+ p->lnV1 += nV1;
203
+}
204
+
205
+/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
206
+*/
207
+static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
208
+ blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
209
+ nV2, p->lnV2+1, p->lnV2+nV2);
210
+ p->lnPivot += nPivot;
211
+ p->lnV1 += nPivot;
212
+ p->lnV2 += nV2;
213
+}
214
+
215
+/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
216
+** V2, which should be the same. In other words, the same change is found
217
+** in both V1 and V2.
218
+*/
219
+static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
220
+ blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
221
+ nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
222
+ p->lnPivot += nPivot;
223
+ p->lnV1 += nV;
224
+ p->lnV2 += nV;
225
+}
226
+
227
+/* V1 and V2 have different and overlapping changes. The next nPivot lines
228
+** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
229
+*/
230
+static void dbgConflict(
231
+ MergeBuilder *p,
232
+ unsigned int nPivot,
233
+ unsigned int nV1,
234
+ unsigned int nV2
235
+){
236
+ blob_appendf(p->pOut,
237
+ "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
238
+ nPivot, nV1, nV2,
239
+ p->lnPivot+1, p->lnPivot+nPivot,
240
+ p->lnV1+1, p->lnV1+nV1,
241
+ p->lnV2+1, p->lnV2+nV2);
242
+ p->lnV1 += nV1;
243
+ p->lnPivot += nPivot;
244
+ p->lnV2 += nV2;
245
+}
246
+
247
+/* Generic destructor for the MergeBuilder object
248
+*/
249
+static void dbgDestroy(MergeBuilder *p){
250
+ memset(p, 0, sizeof(*p));
251
+}
252
+
253
+/* Generic initializer for a MergeBuilder object
254
+*/
255
+static void mergebuilder_init(MergeBuilder *p){
256
+ memset(p, 0, sizeof(*p));
257
+ p->xStart = dbgStartEnd;
258
+ p->xSame = dbgSame;
259
+ p->xChngV1 = dbgChngV1;
260
+ p->xChngV2 = dbgChngV2;
261
+ p->xChngBoth = dbgChngBoth;
262
+ p->xConflict = dbgConflict;
263
+ p->xEnd = dbgStartEnd;
264
+ p->xDestroy = dbgDestroy;
265
+}
266
+
267
+/************************* MergeBuilderText **********************************/
268
+/* This version of MergeBuilder actually performs a merge on file and puts
269
+** the result in pOut
270
+*/
271
+static void txtStart(MergeBuilder *p){
272
+ /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
273
+ ** keep it in the output. This should be secure enough not to cause
274
+ ** unintended changes to the merged file and consistent with what
275
+ ** users are using in their source files.
276
+ */
277
+ if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
278
+ blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
279
+ }
280
+ if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
281
+ p->useCrLf = 1;
282
+ }
283
+}
284
+static void txtSame(MergeBuilder *p, unsigned int N){
285
+ blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N;
286
+ blob_copy_lines(0, p->pV1, N); p->lnV1 += N;
287
+ blob_copy_lines(0, p->pV2, N); p->lnV2 += N;
288
+}
289
+static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
290
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
291
+ blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot;
292
+ blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
293
+}
294
+static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
295
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
296
+ blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot;
297
+ blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
298
+}
299
+static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
300
+ blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
301
+ blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nV;
302
+ blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV;
303
+}
304
+static void txtConflict(
305
+ MergeBuilder *p,
306
+ unsigned int nPivot,
307
+ unsigned int nV1,
308
+ unsigned int nV2
309
+){
310
+ append_merge_mark(p->pOut, 0, p->lnV1, p->useCrLf);
311
+ blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
312
+
313
+ append_merge_mark(p->pOut, 1, p->lnPivot, p->useCrLf);
314
+ blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot;
315
+
316
+ append_merge_mark(p->pOut, 2, p->lnV2, p->useCrLf);
317
+ blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
318
+
319
+ append_merge_mark(p->pOut, 3, -1, p->useCrLf);
320
+}
321
+static void mergebuilder_init_text(MergeBuilder *p){
322
+ mergebuilder_init(p);
323
+ p->xStart = txtStart;
324
+ p->xSame = txtSame;
325
+ p->xChngV1 = txtChngV1;
326
+ p->xChngV2 = txtChngV2;
327
+ p->xChngBoth = txtChngBoth;
328
+ p->xConflict = txtConflict;
329
+}
330
+
331
+/************************* MergeBuilderTcl **********************************/
332
+/* Generate merge output formatted for reading by a TCL script.
333
+**
334
+** The output consists of lines of text, each with 4 tokens. The tokens
335
+** represent the content for one line from baseline, v1, v2, and output
336
+** respectively. The first character of each token provides auxiliary
337
+** information:
338
+**
339
+** . This line is omitted.
340
+** N Name of the file.
341
+** T Literal text follows that should have a \n terminator.
342
+** R Literal text follows that needs a \r\n terminator.
343
+** X Merge conflict. (Column 4 only)
344
+** Z Literal text without a line terminator.
345
+** S Skipped lines in all 4 files.
346
+** 1 Text is a copy of token 1
347
+** 2 Use data from data-token 2
348
+** 3 Use data from data-token 3
349
+*/
350
+
351
+/* Write text that goes into the interior of a double-quoted string in TCL */
352
+static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
353
+ int j;
354
+ for(j=0; j<nIn; j++){
355
+ char c = zIn[j];
356
+ if( c=='\\' ){
357
+ blob_append(pOut, "\\\\", 2);
358
+ }else if( c=='"' ){
359
+ blob_append(pOut, "\\\"", 2);
360
+ }else if( c<' ' || c>0x7e ){
361
+ char z[5];
362
+ z[0] = '\\';
363
+ z[1] = "01234567"[(c>>6)&0x3];
364
+ z[2] = "01234567"[(c>>3)&0x7];
365
+ z[3] = "01234567"[c&0x7];
366
+ z[4] = 0;
367
+ blob_append(pOut, z, 4);
368
+ }else{
369
+ blob_append_char(pOut, c);
370
+ }
371
+ }
372
+}
373
+
374
+/* Copy one line of text from pIn and append to pOut, encoded as TCL */
375
+static void tclLineOfText(Blob *pOut, Blob *pIn){
376
+ int i, k;
377
+ for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
378
+ if( i==pIn->nUsed ){
379
+ blob_append(pOut, "\"Z", 2);
380
+ k = i;
381
+ }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
382
+ blob_append(pOut, "\"R", 2);
383
+ k = i-1;
384
+ i++;
385
+ }else{
386
+ blob_append(pOut, "\"T", 2);
387
+ k = i;
388
+ i++;
389
+ }
390
+ tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
391
+ pIn->iCursor = i;
392
+ blob_append_char(pOut, '"');
393
+}
394
+static void tclStart(MergeBuilder *p){
395
+ Blob *pOut = p->pOut;
396
+ blob_append(pOut, "\"N", 2);
397
+ tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
398
+ blob_append(pOut, "\" \"N", 4);
399
+ tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
400
+ blob_append(pOut, "\" \"N", 4);
401
+ tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
402
+ blob_append(pOut, "\" \"N", 4);
403
+ if( p->zOut ){
404
+ tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
405
+ }else{
406
+ blob_append(pOut, "(Merge Result)", -1);
407
+ }
408
+ blob_append(pOut, "\"\n", 2);
409
+}
410
+static void tclSame(MergeBuilder *p, unsigned int N){
411
+ int i = 0;
412
+ int nSkip;
413
+
414
+ if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
415
+ while( i<N && i<p->nContext ){
416
+ tclLineOfText(p->pOut, p->pPivot);
417
+ blob_append(p->pOut, " 1 1 1\n", 7);
418
+ i++;
419
+ }
420
+ nSkip = N - p->nContext*2;
421
+ }else{
422
+ nSkip = N - p->nContext;
423
+ }
424
+ if( nSkip>0 ){
425
+ blob_appendf(p->pOut, "S%d . . .\n", nSkip);
426
+ blob_copy_lines(0, p->pPivot, nSkip);
427
+ i += nSkip;
428
+ }
429
+
430
+ p->lnPivot += N;
431
+ p->lnV1 += N;
432
+ p->lnV2 += N;
433
+
434
+ if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
435
+ while( i<N ){
436
+ tclLineOfText(p->pOut, p->pPivot);
437
+ blob_append(p->pOut, " 1 1 1\n", 7);
438
+ i++;
439
+ }
440
+ }
441
+
442
+ blob_copy_lines(0, p->pV1, N);
443
+ blob_copy_lines(0, p->pV2, N);
444
+}
445
+static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
446
+ int i;
447
+ for(i=0; i<nPivot && i<nV1; i++){
448
+ tclLineOfText(p->pOut, p->pPivot);
449
+ blob_append_char(p->pOut, ' ');
450
+ tclLineOfText(p->pOut, p->pV1);
451
+ blob_append(p->pOut, " 1 2\n", 5);
452
+ }
453
+ while( i<nPivot ){
454
+ tclLineOfText(p->pOut, p->pPivot);
455
+ blob_append(p->pOut, " . 1 .\n", 7);
456
+ i++;
457
+ }
458
+ while( i<nV1 ){
459
+ blob_append(p->pOut, ". ", 2);
460
+ tclLineOfText(p->pOut, p->pV1);
461
+ blob_append(p->pOut, " . 2\n", 5);
462
+ i++;
463
+ }
464
+ p->lnPivot += nPivot;
465
+ p->lnV1 += nV1;
466
+ p->lnV2 += nPivot;
467
+ blob_copy_lines(0, p->pV2, nPivot);
468
+}
469
+static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
470
+ int i;
471
+ for(i=0; i<nPivot && i<nV2; i++){
472
+ tclLineOfText(p->pOut, p->pPivot);
473
+ blob_append(p->pOut, " 1 ", 3);
474
+ tclLineOfText(p->pOut, p->pV2);
475
+ blob_append(p->pOut, " 3\n", 3);
476
+ }
477
+ while( i<nPivot ){
478
+ tclLineOfText(p->pOut, p->pPivot);
479
+ blob_append(p->pOut, " 1 . .\n", 7);
480
+ i++;
481
+ }
482
+ while( i<nV2 ){
483
+ blob_append(p->pOut, ". . ", 4);
484
+ tclLineOfText(p->pOut, p->pV2);
485
+ blob_append(p->pOut, " 3\n", 3);
486
+ i++;
487
+ }
488
+ p->lnPivot += nPivot;
489
+ p->lnV1 += nPivot;
490
+ p->lnV2 += nV2;
491
+ blob_copy_lines(0, p->pV1, nPivot);
492
+}
493
+static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
494
+ int i;
495
+ for(i=0; i<nPivot && i<nV; i++){
496
+ tclLineOfText(p->pOut, p->pPivot);
497
+ blob_append_char(p->pOut, ' ');
498
+ tclLineOfText(p->pOut, p->pV1);
499
+ blob_append(p->pOut, " 2 2\n", 5);
500
+ }
501
+ while( i<nPivot ){
502
+ tclLineOfText(p->pOut, p->pPivot);
503
+ blob_append(p->pOut, " . . .\n", 7);
504
+ i++;
505
+ }
506
+ while( i<nV ){
507
+ blob_append(p->pOut, ". ", 2);
508
+ tclLineOfText(p->pOut, p->pV1);
509
+ blob_append(p->pOut, " 2 2\n", 5);
510
+ i++;
511
+ }
512
+ p->lnPivot += nPivot;
513
+ p->lnV1 += nV;
514
+ p->lnV2 += nV;
515
+ blob_copy_lines(0, p->pV2, nV);
516
+}
517
+static void tclConflict(
518
+ MergeBuilder *p,
519
+ unsigned int nPivot,
520
+ unsigned int nV1,
521
+ unsigned int nV2
522
+){
523
+ int mx = nPivot;
524
+ int i;
525
+ if( nV1>mx ) mx = nV1;
526
+ if( nV2>mx ) mx = nV2;
527
+ for(i=0; i<mx; i++){
528
+ if( i<nPivot ){
529
+ tclLineOfText(p->pOut, p->pPivot);
530
+ }else{
531
+ blob_append_char(p->pOut, '.');
532
+ }
533
+ blob_append_char(p->pOut, ' ');
534
+ if( i<nV1 ){
535
+ tclLineOfText(p->pOut, p->pV1);
536
+ }else{
537
+ blob_append_char(p->pOut, '.');
538
+ }
539
+ blob_append_char(p->pOut, ' ');
540
+ if( i<nV2 ){
541
+ tclLineOfText(p->pOut, p->pV2);
542
+ }else{
543
+ blob_append_char(p->pOut, '.');
544
+ }
545
+ blob_append(p->pOut, " X\n", 3);
546
+ }
547
+ p->lnPivot += nPivot;
548
+ p->lnV1 += nV1;
549
+ p->lnV2 += nV2;
550
+}
551
+void mergebuilder_init_tcl(MergeBuilder *p){
552
+ mergebuilder_init(p);
553
+ p->xStart = tclStart;
554
+ p->xSame = tclSame;
555
+ p->xChngV1 = tclChngV1;
556
+ p->xChngV2 = tclChngV2;
557
+ p->xChngBoth = tclChngBoth;
558
+ p->xConflict = tclConflict;
559
+}
560
+/*****************************************************************************/
561
+
562
+/*
563
+** The aC[] array contains triples of integers. Within each triple, the
564
+** elements are:
565
+**
566
+** (0) The number of lines to copy
567
+** (1) The number of lines to delete
568
+** (2) The number of liens to insert
569
+**
570
+** Suppose we want to advance over sz lines of the original file. This routine
571
+** returns true if that advance would land us on a copy operation. It
572
+** returns false if the advance would end on a delete.
573
+*/
574
+static int ends_with_copy(int *aC, int sz){
575
+ while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
576
+ if( aC[0]>=sz ) return 1;
577
+ sz -= aC[0];
578
+ if( aC[1]>sz ) return 0;
579
+ sz -= aC[1];
580
+ aC += 3;
581
+ }
582
+ return 1;
583
+}
584
+
585
+/*
586
+** aC[] is an "edit triple" for changes from A to B. Advance through
587
+** this triple to determine the number of lines to bypass on B in order
588
+** to match an advance of sz lines on A.
589
+*/
590
+static int skip_conflict(
591
+ int *aC, /* Array of integer triples describing the edit */
592
+ int i, /* Index in aC[] of current location */
593
+ int sz, /* Lines of A that have been skipped */
594
+ unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */
595
+){
596
+ *pLn = 0;
597
+ while( sz>0 ){
598
+ if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
599
+ if( aC[i]>=sz ){
600
+ aC[i] -= sz;
601
+ *pLn += sz;
602
+ break;
603
+ }
604
+ *pLn += aC[i];
605
+ *pLn += aC[i+2];
606
+ sz -= aC[i] + aC[i+1];
607
+ i += 3;
608
+ }
609
+ return i;
610
+}
191611
192612
/*
193613
** Do a three-way merge. Initialize pOut to contain the result.
194614
**
195615
** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +619,112 @@
199619
** The return is 0 upon complete success. If any input file is binary,
200620
** -1 is returned and pOut is unmodified. If there are merge
201621
** conflicts, the merge proceeds as best as it can and the number
202622
** of conflicts is returns
203623
*/
204
-static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
624
+int merge_three_blobs(MergeBuilder *p){
205625
int *aC1; /* Changes from pPivot to pV1 */
206626
int *aC2; /* Changes from pPivot to pV2 */
207627
int i1, i2; /* Index into aC1[] and aC2[] */
208628
int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
209629
int limit1, limit2; /* Sizes of aC1[] and aC2[] */
210630
int nConflict = 0; /* Number of merge conflicts seen so far */
211
- int useCrLf = 0;
212
- int ln1, ln2, lnPivot; /* Line numbers for all files */
213631
DiffConfig DCfg;
214632
215
- blob_zero(pOut); /* Merge results stored in pOut */
216
-
217
- /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
218
- ** keep it in the output. This should be secure enough not to cause
219
- ** unintended changes to the merged file and consistent with what
220
- ** users are using in their source files.
221
- */
222
- if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
223
- blob_append(pOut, (char*)get_utf8_bom(0), -1);
224
- }
225
-
226
- /* Check once to see if both pV1 and pV2 contains CR/LF endings.
227
- ** If true, CR/LF pair will be used later to append the
228
- ** boundary markers for merge conflicts.
229
- */
230
- if( contains_crlf(pV1) && contains_crlf(pV2) ){
231
- useCrLf = 1;
232
- }
233
-
234633
/* Compute the edits that occur from pPivot => pV1 (into aC1)
235634
** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
236635
** an array of integer triples. Within each triple, the first integer
237636
** is the number of lines of text to copy directly from the pivot,
238637
** the second integer is the number of lines of text to omit from the
239638
** pivot, and the third integer is the number of lines of text that are
240639
** inserted. The edit array ends with a triple of 0,0,0.
241640
*/
242641
diff_config_init(&DCfg, 0);
243
- aC1 = text_diff(pPivot, pV1, 0, &DCfg);
244
- aC2 = text_diff(pPivot, pV2, 0, &DCfg);
642
+ aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
643
+ aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
245644
if( aC1==0 || aC2==0 ){
246645
free(aC1);
247646
free(aC2);
248647
return -1;
249648
}
250649
251
- blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */
252
- blob_rewind(pV2);
253
- blob_rewind(pPivot);
650
+ blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */
651
+ blob_rewind(p->pV2);
652
+ blob_rewind(p->pPivot);
254653
255654
/* Determine the length of the aC1[] and aC2[] change vectors */
256
- for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
655
+ p->mxPivot = 0;
656
+ p->mxV1 = 0;
657
+ for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
658
+ p->mxPivot += aC1[i1] + aC1[i1+1];
659
+ p->mxV1 += aC1[i1] + aC1[i1+2];
660
+ }
257661
limit1 = i1;
258
- for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
662
+ p->mxV2 = 0;
663
+ for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
664
+ p->mxV2 += aC2[i2] + aC2[i2+2];
665
+ }
259666
limit2 = i2;
260667
261
- DEBUG(
262
- for(i1=0; i1<limit1; i1+=3){
263
- printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
264
- }
265
- for(i2=0; i2<limit2; i2+=3){
266
- printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
267
- }
268
- )
668
+ /* Output header text and do any other required initialization */
669
+ p->xStart(p);
269670
270671
/* Loop over the two edit vectors and use them to compute merged text
271672
** which is written into pOut. i1 and i2 are multiples of 3 which are
272673
** indices into aC1[] and aC2[] to the edit triple currently being
273674
** processed
274675
*/
275676
i1 = i2 = 0;
276
- ln1 = ln2 = lnPivot = 1;
277677
while( i1<limit1 && i2<limit2 ){
278
- DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
279
- i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
280
- i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
281
-
282678
if( aC1[i1]>0 && aC2[i2]>0 ){
283679
/* Output text that is unchanged in both V1 and V2 */
284680
nCpy = min(aC1[i1], aC2[i2]);
285
- DEBUG( printf("COPY %d\n", nCpy); )
286
- blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy;
287
- blob_copy_lines(0, pV1, nCpy); ln1 += nCpy;
288
- blob_copy_lines(0, pV2, nCpy); ln2 += nCpy;
681
+ p->xSame(p, nCpy);
289682
aC1[i1] -= nCpy;
290683
aC2[i2] -= nCpy;
291684
}else
292685
if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
293686
/* Output edits to V2 that occurs within unchanged regions of V1 */
294687
nDel = aC2[i2+1];
295688
nIns = aC2[i2+2];
296
- DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
297
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
298
- blob_copy_lines(0, pV1, nDel); ln1 += nDel;
299
- blob_copy_lines(pOut, pV2, nIns); ln2 += nIns;
689
+ p->xChngV2(p, nDel, nIns);
300690
aC1[i1] -= nDel;
301691
i2 += 3;
302692
}else
303693
if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
304694
/* Output edits to V1 that occur within unchanged regions of V2 */
305695
nDel = aC1[i1+1];
306696
nIns = aC1[i1+2];
307
- DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
308
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
309
- blob_copy_lines(0, pV2, nDel); ln2 += nDel;
310
- blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
697
+ p->xChngV1(p, nDel, nIns);
311698
aC2[i2] -= nDel;
312699
i1 += 3;
313700
}else
314
- if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
701
+ if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
315702
/* Output edits that are identical in both V1 and V2. */
316703
assert( aC1[i1]==0 );
317704
nDel = aC1[i1+1];
318705
nIns = aC1[i1+2];
319
- DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
320
- blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
321
- blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
322
- blob_copy_lines(0, pV2, nIns); ln2 += nIns;
706
+ p->xChngBoth(p, nDel, nIns);
323707
i1 += 3;
324708
i2 += 3;
325709
}else
326710
{
327711
/* We have found a region where different edits to V1 and V2 overlap.
328712
** This is a merge conflict. Find the size of the conflict, then
329713
** output both possible edits separated by distinctive marks.
330714
*/
331
- int sz = 1; /* Size of the conflict in lines */
715
+ unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */
716
+ unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */
332717
nConflict++;
333
- while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
718
+ while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
334719
sz++;
335720
}
336
- DEBUG( printf("CONFLICT %d\n", sz); )
337
-
338
- append_merge_mark(pOut, 0, ln1, useCrLf);
339
- i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1);
340
-
341
- append_merge_mark(pOut, 1, lnPivot, useCrLf);
342
- blob_copy_lines(pOut, pPivot, sz); lnPivot += sz;
343
-
344
- append_merge_mark(pOut, 2, ln2, useCrLf);
345
- i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2);
346
-
347
- append_merge_mark(pOut, 3, -1, useCrLf);
348
- }
349
-
721
+ i1 = skip_conflict(aC1, i1, sz, &nV1);
722
+ i2 = skip_conflict(aC2, i2, sz, &nV2);
723
+ p->xConflict(p, sz, nV1, nV2);
724
+ }
725
+
350726
/* If we are finished with an edit triple, advance to the next
351727
** triple.
352728
*/
353729
if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
354730
if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +732,18 @@
356732
357733
/* When one of the two edit vectors reaches its end, there might still
358734
** be an insert in the other edit vector. Output this remaining
359735
** insert.
360736
*/
361
- DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
362
- i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
363
- i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
364737
if( i1<limit1 && aC1[i1+2]>0 ){
365
- DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
366
- blob_copy_lines(pOut, pV1, aC1[i1+2]);
738
+ p->xChngV1(p, 0, aC1[i1+2]);
367739
}else if( i2<limit2 && aC2[i2+2]>0 ){
368
- DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
369
- blob_copy_lines(pOut, pV2, aC2[i2+2]);
740
+ p->xChngV2(p, 0, aC2[i2+2]);
370741
}
742
+
743
+ /* Output footer text */
744
+ p->xEnd(p);
371745
372746
free(aC1);
373747
free(aC2);
374748
return nConflict;
375749
}
@@ -408,18 +782,105 @@
408782
blob_read_from_file(&file, zFullpath, ExtFILE);
409783
rc = contains_merge_marker(&file);
410784
blob_reset(&file);
411785
return rc;
412786
}
787
+
788
+/*
789
+** Show merge output in a Tcl/Tk window, in response to the --tk option
790
+** to the "merge" or "3-way-merge" command.
791
+**
792
+** If fossil has direct access to a Tcl interpreter (either loaded
793
+** dynamically through stubs or linked in statically), we can use it
794
+** directly. Otherwise:
795
+** (1) Write the Tcl/Tk script used for rendering into a temp file.
796
+** (2) Invoke "tclsh" on the temp file using fossil_system().
797
+** (3) Delete the temp file.
798
+*/
799
+void merge_tk(const char *zSubCmd, int firstArg){
800
+ int i;
801
+ Blob script;
802
+ const char *zTempFile = 0;
803
+ char *zCmd;
804
+ const char *zTclsh;
805
+ const char *zCnt;
806
+ int bDarkMode = find_option("dark",0,0)!=0;
807
+ int nContext;
808
+ zCnt = find_option("context", "c", 1);
809
+ if( zCnt==0 ){
810
+ nContext = 6;
811
+ }else{
812
+ nContext = atoi(zCnt);
813
+ if( nContext<0 ) nContext = 0xfffffff;
814
+ }
815
+ blob_zero(&script);
816
+ blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -c %d",
817
+ g.nameOfExe, zSubCmd, nContext);
818
+ find_option("tcl",0,0);
819
+ find_option("debug",0,0);
820
+ zTclsh = find_option("tclsh",0,1);
821
+ if( zTclsh==0 ){
822
+ zTclsh = db_get("tclsh",0);
823
+ }
824
+ /* The undocumented --script FILENAME option causes the Tk script to
825
+ ** be written into the FILENAME instead of being run. This is used
826
+ ** for testing and debugging. */
827
+ zTempFile = find_option("script",0,1);
828
+ verify_all_options();
829
+
830
+ if( (g.argc - firstArg)!=3 ){
831
+ fossil_fatal("Requires 3 filename arguments");
832
+ }
833
+
834
+ for(i=firstArg; i<g.argc; i++){
835
+ const char *z = g.argv[i];
836
+ if( sqlite3_strglob("*}*",z) ){
837
+ blob_appendf(&script, " {%/}", z);
838
+ }else{
839
+ int j;
840
+ blob_append(&script, " ", 1);
841
+ for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
842
+ }
843
+ }
844
+ blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
845
+ blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
846
+ if( zTempFile ){
847
+ blob_write_to_file(&script, zTempFile);
848
+ fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
849
+ }else{
850
+#if defined(FOSSIL_ENABLE_TCL)
851
+ Th_FossilInit(TH_INIT_DEFAULT);
852
+ if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
853
+ blob_size(&script), 1, 1, 0)==TCL_OK ){
854
+ blob_reset(&script);
855
+ return;
856
+ }
857
+ /*
858
+ * If evaluation of the Tcl script fails, the reason may be that Tk
859
+ * could not be found by the loaded Tcl, or that Tcl cannot be loaded
860
+ * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
861
+ * to using the external "tclsh", if available.
862
+ */
863
+#endif
864
+ zTempFile = write_blob_to_temp_file(&script);
865
+ zCmd = mprintf("%$ %$", zTclsh, zTempFile);
866
+ fossil_system(zCmd);
867
+ file_delete(zTempFile);
868
+ fossil_free(zCmd);
869
+ }
870
+ blob_reset(&script);
871
+}
872
+
413873
414874
/*
415875
** COMMAND: 3-way-merge*
416876
**
417
-** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED
877
+** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
418878
**
419879
** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
420
-** as output.
880
+** as output. If no MERGED file is specified, output is sent to
881
+** stdout.
421882
**
422883
** BASELINE is a common ancestor of two files V1 and V2 that have diverging
423884
** edits. The generated output file MERGED is the combination of all
424885
** changes in both V1 and V2.
425886
**
@@ -436,38 +897,75 @@
436897
** cp Xup.c Xbase.c
437898
** # Verify that everything still works
438899
** fossil commit
439900
**
440901
*/
441
-void delta_3waymerge_cmd(void){
442
- Blob pivot, v1, v2, merged;
902
+void merge_3way_cmd(void){
903
+ MergeBuilder s;
443904
int nConflict;
905
+ Blob pivot, v1, v2, out;
906
+ int noWarn = 0;
907
+ const char *zCnt;
908
+
909
+ if( find_option("tk", 0, 0)!=0 ){
910
+ merge_tk("3-way-merge", 2);
911
+ return;
912
+ }
913
+ mergebuilder_init_text(&s);
914
+ if( find_option("debug", 0, 0) ){
915
+ mergebuilder_init(&s);
916
+ }
917
+ if( find_option("tcl", 0, 0) ){
918
+ mergebuilder_init_tcl(&s);
919
+ noWarn = 1;
920
+ }
921
+ zCnt = find_option("context", "c", 1);
922
+ if( zCnt ){
923
+ s.nContext = atoi(zCnt);
924
+ if( s.nContext<0 ) s.nContext = 0xfffffff;
925
+ }else{
926
+ s.nContext = 6;
927
+ }
928
+ blob_zero(&pivot); s.pPivot = &pivot;
929
+ blob_zero(&v1); s.pV1 = &v1;
930
+ blob_zero(&v2); s.pV2 = &v2;
931
+ blob_zero(&out); s.pOut = &out;
444932
445933
/* We should be done with options.. */
446934
verify_all_options();
447935
448
- if( g.argc!=6 ){
449
- usage("PIVOT V1 V2 MERGED");
936
+ if( g.argc!=6 && g.argc!=5 ){
937
+ usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
450938
}
451
- if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){
939
+ s.zPivot = file_tail(g.argv[2]);
940
+ s.zV1 = file_tail(g.argv[3]);
941
+ s.zV2 = file_tail(g.argv[4]);
942
+ if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
452943
fossil_fatal("cannot read %s", g.argv[2]);
453944
}
454
- if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){
945
+ if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
455946
fossil_fatal("cannot read %s", g.argv[3]);
456947
}
457
- if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){
948
+ if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
458949
fossil_fatal("cannot read %s", g.argv[4]);
459950
}
460
- nConflict = blob_merge(&pivot, &v1, &v2, &merged);
461
- if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){
462
- fossil_fatal("cannot write %s", g.argv[4]);
951
+ nConflict = merge_three_blobs(&s);
952
+ if( g.argc==6 ){
953
+ s.zOut = file_tail(g.argv[5]);
954
+ blob_write_to_file(s.pOut, g.argv[5]);
955
+ }else{
956
+ s.zOut = "(Merge Result)";
957
+ blob_write_to_file(s.pOut, "-");
463958
}
959
+ s.xDestroy(&s);
464960
blob_reset(&pivot);
465961
blob_reset(&v1);
466962
blob_reset(&v2);
467
- blob_reset(&merged);
468
- if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict);
963
+ blob_reset(&out);
964
+ if( nConflict>0 && !noWarn ){
965
+ fossil_warning("WARNING: %d merge conflicts", nConflict);
966
+ }
469967
}
470968
471969
/*
472970
** aSubst is an array of string pairs. The first element of each pair is
473971
** a string that begins with %. The second element is a replacement for that
@@ -516,21 +1014,21 @@
5161014
#define MERGE_KEEP_FILES 0x0002
5171015
#endif
5181016
5191017
5201018
/*
521
-** This routine is a wrapper around blob_merge() with the following
1019
+** This routine is a wrapper around merge_three_blobs() with the following
5221020
** enhancements:
5231021
**
5241022
** (1) If the merge-command is defined, then use the external merging
5251023
** program specified instead of the built-in blob-merge to do the
5261024
** merging. Panic if the external merger fails.
5271025
** ** Not currently implemented **
5281026
**
5291027
** (2) If gmerge-command is defined and there are merge conflicts in
530
-** blob_merge() then invoke the external graphical merger to resolve
531
-** the conflicts.
1028
+** merge_three_blobs() then invoke the external graphical merger
1029
+** to resolve the conflicts.
5321030
**
5331031
** (3) If a merge conflict occurs and gmerge-command is not defined,
5341032
** then write the pivot, original, and merge-in files to the
5351033
** filesystem.
5361034
*/
@@ -539,30 +1037,37 @@
5391037
const char *zV1, /* Name of file for version merging into (mine) */
5401038
Blob *pV2, /* Version merging from (yours) */
5411039
Blob *pOut, /* Output written here */
5421040
unsigned mergeFlags /* Flags that control operation */
5431041
){
544
- Blob v1; /* Content of zV1 */
545
- int rc; /* Return code of subroutines and this routine */
1042
+ Blob v1; /* Content of zV1 */
1043
+ int rc; /* Return code of subroutines and this routine */
5461044
const char *zGMerge; /* Name of the gmerge command */
1045
+ MergeBuilder s; /* The merge state */
5471046
548
- blob_read_from_file(&v1, zV1, ExtFILE);
549
- rc = blob_merge(pPivot, &v1, pV2, pOut);
1047
+ mergebuilder_init_text(&s);
1048
+ s.pPivot = pPivot;
1049
+ s.pV1 = &v1;
1050
+ s.pV2 = pV2;
1051
+ blob_zero(pOut);
1052
+ s.pOut = pOut;
1053
+ blob_read_from_file(s.pV1, zV1, ExtFILE);
1054
+ rc = merge_three_blobs(&s);
5501055
zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
5511056
if( (mergeFlags & MERGE_DRYRUN)==0
5521057
&& ((zGMerge!=0 && zGMerge[0]!=0)
5531058
|| (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
5541059
char *zPivot; /* Name of the pivot file */
5551060
char *zOrig; /* Name of the original content file */
5561061
char *zOther; /* Name of the merge file */
5571062
5581063
zPivot = file_newname(zV1, "baseline", 1);
559
- blob_write_to_file(pPivot, zPivot);
1064
+ blob_write_to_file(s.pPivot, zPivot);
5601065
zOrig = file_newname(zV1, "original", 1);
561
- blob_write_to_file(&v1, zOrig);
1066
+ blob_write_to_file(s.pV1, zOrig);
5621067
zOther = file_newname(zV1, "merge", 1);
563
- blob_write_to_file(pV2, zOther);
1068
+ blob_write_to_file(s.pV2, zOther);
5641069
if( rc>0 ){
5651070
if( zGMerge && zGMerge[0] ){
5661071
char *zOut; /* Temporary output file */
5671072
char *zCmd; /* Command to invoke */
5681073
const char *azSubst[8]; /* Strings to be substituted */
@@ -589,8 +1094,8 @@
5891094
}
5901095
fossil_free(zPivot);
5911096
fossil_free(zOrig);
5921097
fossil_free(zOther);
5931098
}
594
- blob_reset(&v1);
1099
+ s.xDestroy(&s);
5951100
return rc;
5961101
}
5971102
--- src/merge3.c
+++ src/merge3.c
@@ -75,69 +75,10 @@
75 if( aC1[2]!=aC2[2] ) return 0;
76 if( sameLines(pV1, pV2, aC1[2]) ) return 1;
77 return 0;
78 }
79
80 /*
81 ** The aC[] array contains triples of integers. Within each triple, the
82 ** elements are:
83 **
84 ** (0) The number of lines to copy
85 ** (1) The number of lines to delete
86 ** (2) The number of liens to insert
87 **
88 ** Suppose we want to advance over sz lines of the original file. This routine
89 ** returns true if that advance would land us on a copy operation. It
90 ** returns false if the advance would end on a delete.
91 */
92 static int ends_at_CPY(int *aC, int sz){
93 while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
94 if( aC[0]>=sz ) return 1;
95 sz -= aC[0];
96 if( aC[1]>sz ) return 0;
97 sz -= aC[1];
98 aC += 3;
99 }
100 return 1;
101 }
102
103 /*
104 ** pSrc contains an edited file where aC[] describes the edit. Part of
105 ** pSrc has already been output. This routine outputs additional lines
106 ** of pSrc - lines that correspond to the next sz lines of the original
107 ** unedited file.
108 **
109 ** Note that sz counts the number of lines of text in the original file.
110 ** But text is output from the edited file. So the number of lines transfer
111 ** to pOut might be different from sz. Fewer lines appear in pOut if there
112 ** are deletes. More lines appear if there are inserts.
113 **
114 ** The aC[] array is updated and the new index into aC[] is returned.
115 */
116 static int output_one_side(
117 Blob *pOut, /* Write to this blob */
118 Blob *pSrc, /* The edited file that is to be copied to pOut */
119 int *aC, /* Array of integer triples describing the edit */
120 int i, /* Index in aC[] of current location in pSrc */
121 int sz, /* Number of lines in unedited source to output */
122 int *pLn /* Line number counter */
123 ){
124 while( sz>0 ){
125 if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
126 if( aC[i]>=sz ){
127 blob_copy_lines(pOut, pSrc, sz); *pLn += sz;
128 aC[i] -= sz;
129 break;
130 }
131 blob_copy_lines(pOut, pSrc, aC[i]); *pLn += aC[i];
132 blob_copy_lines(pOut, pSrc, aC[i+2]); *pLn += aC[i+2];
133 sz -= aC[i] + aC[i+1];
134 i += 3;
135 }
136 return i;
137 }
138
139 /*
140 ** Text of boundary markers for merge conflicts.
141 */
142 static const char *const mergeMarker[] = {
143 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
@@ -186,10 +127,489 @@
186 ensure_line_end(pOut, useCrLf);
187 blob_append(pOut, mergeMarker[iMark], -1);
188 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
189 ensure_line_end(pOut, useCrLf);
190 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
192 /*
193 ** Do a three-way merge. Initialize pOut to contain the result.
194 **
195 ** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +619,112 @@
199 ** The return is 0 upon complete success. If any input file is binary,
200 ** -1 is returned and pOut is unmodified. If there are merge
201 ** conflicts, the merge proceeds as best as it can and the number
202 ** of conflicts is returns
203 */
204 static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){
205 int *aC1; /* Changes from pPivot to pV1 */
206 int *aC2; /* Changes from pPivot to pV2 */
207 int i1, i2; /* Index into aC1[] and aC2[] */
208 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
209 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
210 int nConflict = 0; /* Number of merge conflicts seen so far */
211 int useCrLf = 0;
212 int ln1, ln2, lnPivot; /* Line numbers for all files */
213 DiffConfig DCfg;
214
215 blob_zero(pOut); /* Merge results stored in pOut */
216
217 /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
218 ** keep it in the output. This should be secure enough not to cause
219 ** unintended changes to the merged file and consistent with what
220 ** users are using in their source files.
221 */
222 if( starts_with_utf8_bom(pV1, 0) && starts_with_utf8_bom(pV2, 0) ){
223 blob_append(pOut, (char*)get_utf8_bom(0), -1);
224 }
225
226 /* Check once to see if both pV1 and pV2 contains CR/LF endings.
227 ** If true, CR/LF pair will be used later to append the
228 ** boundary markers for merge conflicts.
229 */
230 if( contains_crlf(pV1) && contains_crlf(pV2) ){
231 useCrLf = 1;
232 }
233
234 /* Compute the edits that occur from pPivot => pV1 (into aC1)
235 ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
236 ** an array of integer triples. Within each triple, the first integer
237 ** is the number of lines of text to copy directly from the pivot,
238 ** the second integer is the number of lines of text to omit from the
239 ** pivot, and the third integer is the number of lines of text that are
240 ** inserted. The edit array ends with a triple of 0,0,0.
241 */
242 diff_config_init(&DCfg, 0);
243 aC1 = text_diff(pPivot, pV1, 0, &DCfg);
244 aC2 = text_diff(pPivot, pV2, 0, &DCfg);
245 if( aC1==0 || aC2==0 ){
246 free(aC1);
247 free(aC2);
248 return -1;
249 }
250
251 blob_rewind(pV1); /* Rewind inputs: Needed to reconstruct output */
252 blob_rewind(pV2);
253 blob_rewind(pPivot);
254
255 /* Determine the length of the aC1[] and aC2[] change vectors */
256 for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){}
 
 
 
 
 
257 limit1 = i1;
258 for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){}
 
 
 
259 limit2 = i2;
260
261 DEBUG(
262 for(i1=0; i1<limit1; i1+=3){
263 printf("c1: %4d %4d %4d\n", aC1[i1], aC1[i1+1], aC1[i1+2]);
264 }
265 for(i2=0; i2<limit2; i2+=3){
266 printf("c2: %4d %4d %4d\n", aC2[i2], aC2[i2+1], aC2[i2+2]);
267 }
268 )
269
270 /* Loop over the two edit vectors and use them to compute merged text
271 ** which is written into pOut. i1 and i2 are multiples of 3 which are
272 ** indices into aC1[] and aC2[] to the edit triple currently being
273 ** processed
274 */
275 i1 = i2 = 0;
276 ln1 = ln2 = lnPivot = 1;
277 while( i1<limit1 && i2<limit2 ){
278 DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
279 i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
280 i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
281
282 if( aC1[i1]>0 && aC2[i2]>0 ){
283 /* Output text that is unchanged in both V1 and V2 */
284 nCpy = min(aC1[i1], aC2[i2]);
285 DEBUG( printf("COPY %d\n", nCpy); )
286 blob_copy_lines(pOut, pPivot, nCpy); lnPivot += nCpy;
287 blob_copy_lines(0, pV1, nCpy); ln1 += nCpy;
288 blob_copy_lines(0, pV2, nCpy); ln2 += nCpy;
289 aC1[i1] -= nCpy;
290 aC2[i2] -= nCpy;
291 }else
292 if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
293 /* Output edits to V2 that occurs within unchanged regions of V1 */
294 nDel = aC2[i2+1];
295 nIns = aC2[i2+2];
296 DEBUG( printf("EDIT -%d+%d left\n", nDel, nIns); )
297 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
298 blob_copy_lines(0, pV1, nDel); ln1 += nDel;
299 blob_copy_lines(pOut, pV2, nIns); ln2 += nIns;
300 aC1[i1] -= nDel;
301 i2 += 3;
302 }else
303 if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
304 /* Output edits to V1 that occur within unchanged regions of V2 */
305 nDel = aC1[i1+1];
306 nIns = aC1[i1+2];
307 DEBUG( printf("EDIT -%d+%d right\n", nDel, nIns); )
308 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
309 blob_copy_lines(0, pV2, nDel); ln2 += nDel;
310 blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
311 aC2[i2] -= nDel;
312 i1 += 3;
313 }else
314 if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){
315 /* Output edits that are identical in both V1 and V2. */
316 assert( aC1[i1]==0 );
317 nDel = aC1[i1+1];
318 nIns = aC1[i1+2];
319 DEBUG( printf("EDIT -%d+%d both\n", nDel, nIns); )
320 blob_copy_lines(0, pPivot, nDel); lnPivot += nDel;
321 blob_copy_lines(pOut, pV1, nIns); ln1 += nIns;
322 blob_copy_lines(0, pV2, nIns); ln2 += nIns;
323 i1 += 3;
324 i2 += 3;
325 }else
326 {
327 /* We have found a region where different edits to V1 and V2 overlap.
328 ** This is a merge conflict. Find the size of the conflict, then
329 ** output both possible edits separated by distinctive marks.
330 */
331 int sz = 1; /* Size of the conflict in lines */
 
332 nConflict++;
333 while( !ends_at_CPY(&aC1[i1], sz) || !ends_at_CPY(&aC2[i2], sz) ){
334 sz++;
335 }
336 DEBUG( printf("CONFLICT %d\n", sz); )
337
338 append_merge_mark(pOut, 0, ln1, useCrLf);
339 i1 = output_one_side(pOut, pV1, aC1, i1, sz, &ln1);
340
341 append_merge_mark(pOut, 1, lnPivot, useCrLf);
342 blob_copy_lines(pOut, pPivot, sz); lnPivot += sz;
343
344 append_merge_mark(pOut, 2, ln2, useCrLf);
345 i2 = output_one_side(pOut, pV2, aC2, i2, sz, &ln2);
346
347 append_merge_mark(pOut, 3, -1, useCrLf);
348 }
349
350 /* If we are finished with an edit triple, advance to the next
351 ** triple.
352 */
353 if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
354 if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +732,18 @@
356
357 /* When one of the two edit vectors reaches its end, there might still
358 ** be an insert in the other edit vector. Output this remaining
359 ** insert.
360 */
361 DEBUG( printf("%d: %2d %2d %2d %d: %2d %2d %2d\n",
362 i1/3, aC1[i1], aC1[i1+1], aC1[i1+2],
363 i2/3, aC2[i2], aC2[i2+1], aC2[i2+2]); )
364 if( i1<limit1 && aC1[i1+2]>0 ){
365 DEBUG( printf("INSERT +%d left\n", aC1[i1+2]); )
366 blob_copy_lines(pOut, pV1, aC1[i1+2]);
367 }else if( i2<limit2 && aC2[i2+2]>0 ){
368 DEBUG( printf("INSERT +%d right\n", aC2[i2+2]); )
369 blob_copy_lines(pOut, pV2, aC2[i2+2]);
370 }
 
 
 
371
372 free(aC1);
373 free(aC2);
374 return nConflict;
375 }
@@ -408,18 +782,105 @@
408 blob_read_from_file(&file, zFullpath, ExtFILE);
409 rc = contains_merge_marker(&file);
410 blob_reset(&file);
411 return rc;
412 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
413
414 /*
415 ** COMMAND: 3-way-merge*
416 **
417 ** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED
418 **
419 ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
420 ** as output.
 
421 **
422 ** BASELINE is a common ancestor of two files V1 and V2 that have diverging
423 ** edits. The generated output file MERGED is the combination of all
424 ** changes in both V1 and V2.
425 **
@@ -436,38 +897,75 @@
436 ** cp Xup.c Xbase.c
437 ** # Verify that everything still works
438 ** fossil commit
439 **
440 */
441 void delta_3waymerge_cmd(void){
442 Blob pivot, v1, v2, merged;
443 int nConflict;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
444
445 /* We should be done with options.. */
446 verify_all_options();
447
448 if( g.argc!=6 ){
449 usage("PIVOT V1 V2 MERGED");
450 }
451 if( blob_read_from_file(&pivot, g.argv[2], ExtFILE)<0 ){
 
 
 
452 fossil_fatal("cannot read %s", g.argv[2]);
453 }
454 if( blob_read_from_file(&v1, g.argv[3], ExtFILE)<0 ){
455 fossil_fatal("cannot read %s", g.argv[3]);
456 }
457 if( blob_read_from_file(&v2, g.argv[4], ExtFILE)<0 ){
458 fossil_fatal("cannot read %s", g.argv[4]);
459 }
460 nConflict = blob_merge(&pivot, &v1, &v2, &merged);
461 if( blob_write_to_file(&merged, g.argv[5])<(int)blob_size(&merged) ){
462 fossil_fatal("cannot write %s", g.argv[4]);
 
 
 
 
463 }
 
464 blob_reset(&pivot);
465 blob_reset(&v1);
466 blob_reset(&v2);
467 blob_reset(&merged);
468 if( nConflict>0 ) fossil_warning("WARNING: %d merge conflicts", nConflict);
 
 
469 }
470
471 /*
472 ** aSubst is an array of string pairs. The first element of each pair is
473 ** a string that begins with %. The second element is a replacement for that
@@ -516,21 +1014,21 @@
516 #define MERGE_KEEP_FILES 0x0002
517 #endif
518
519
520 /*
521 ** This routine is a wrapper around blob_merge() with the following
522 ** enhancements:
523 **
524 ** (1) If the merge-command is defined, then use the external merging
525 ** program specified instead of the built-in blob-merge to do the
526 ** merging. Panic if the external merger fails.
527 ** ** Not currently implemented **
528 **
529 ** (2) If gmerge-command is defined and there are merge conflicts in
530 ** blob_merge() then invoke the external graphical merger to resolve
531 ** the conflicts.
532 **
533 ** (3) If a merge conflict occurs and gmerge-command is not defined,
534 ** then write the pivot, original, and merge-in files to the
535 ** filesystem.
536 */
@@ -539,30 +1037,37 @@
539 const char *zV1, /* Name of file for version merging into (mine) */
540 Blob *pV2, /* Version merging from (yours) */
541 Blob *pOut, /* Output written here */
542 unsigned mergeFlags /* Flags that control operation */
543 ){
544 Blob v1; /* Content of zV1 */
545 int rc; /* Return code of subroutines and this routine */
546 const char *zGMerge; /* Name of the gmerge command */
 
547
548 blob_read_from_file(&v1, zV1, ExtFILE);
549 rc = blob_merge(pPivot, &v1, pV2, pOut);
 
 
 
 
 
 
550 zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
551 if( (mergeFlags & MERGE_DRYRUN)==0
552 && ((zGMerge!=0 && zGMerge[0]!=0)
553 || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
554 char *zPivot; /* Name of the pivot file */
555 char *zOrig; /* Name of the original content file */
556 char *zOther; /* Name of the merge file */
557
558 zPivot = file_newname(zV1, "baseline", 1);
559 blob_write_to_file(pPivot, zPivot);
560 zOrig = file_newname(zV1, "original", 1);
561 blob_write_to_file(&v1, zOrig);
562 zOther = file_newname(zV1, "merge", 1);
563 blob_write_to_file(pV2, zOther);
564 if( rc>0 ){
565 if( zGMerge && zGMerge[0] ){
566 char *zOut; /* Temporary output file */
567 char *zCmd; /* Command to invoke */
568 const char *azSubst[8]; /* Strings to be substituted */
@@ -589,8 +1094,8 @@
589 }
590 fossil_free(zPivot);
591 fossil_free(zOrig);
592 fossil_free(zOther);
593 }
594 blob_reset(&v1);
595 return rc;
596 }
597
--- src/merge3.c
+++ src/merge3.c
@@ -75,69 +75,10 @@
75 if( aC1[2]!=aC2[2] ) return 0;
76 if( sameLines(pV1, pV2, aC1[2]) ) return 1;
77 return 0;
78 }
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80 /*
81 ** Text of boundary markers for merge conflicts.
82 */
83 static const char *const mergeMarker[] = {
84 /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/
@@ -186,10 +127,489 @@
127 ensure_line_end(pOut, useCrLf);
128 blob_append(pOut, mergeMarker[iMark], -1);
129 if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
130 ensure_line_end(pOut, useCrLf);
131 }
132
133 #if INTERFACE
134 /*
135 ** This is an abstract class for constructing a merge.
136 ** Subclasses of this object format the merge output in different ways.
137 **
138 ** To subclass, create an instance of the MergeBuilder object and fill
139 ** in appropriate method implementations.
140 */
141 struct MergeBuilder {
142 void (*xStart)(MergeBuilder*);
143 void (*xSame)(MergeBuilder*, unsigned int);
144 void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
145 void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
146 void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
147 void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
148 void (*xEnd)(MergeBuilder*);
149 void (*xDestroy)(MergeBuilder*);
150 const char *zPivot; /* Label or name for the pivot */
151 const char *zV1; /* Label or name for the V1 file */
152 const char *zV2; /* Label or name for the V2 file */
153 const char *zOut; /* Label or name for the output */
154 Blob *pPivot; /* The common ancestor */
155 Blob *pV1; /* First variant */
156 Blob *pV2; /* Second variant */
157 Blob *pOut; /* Write merge results here */
158 int useCrLf; /* Use CRLF line endings */
159 int nContext; /* Size of unchanged line boundaries */
160 unsigned int mxPivot; /* Number of lines in the pivot */
161 unsigned int mxV1; /* Number of lines in V1 */
162 unsigned int mxV2; /* Number of lines in V2 */
163 unsigned int lnPivot; /* Lines read from pivot */
164 unsigned int lnV1; /* Lines read from v1 */
165 unsigned int lnV2; /* Lines read from v2 */
166 unsigned int lnOut; /* Lines written to out */
167 unsigned int nConflict; /* Number of conflicts seen */
168 };
169 #endif /* INTERFACE */
170
171
172 /************************* Generic MergeBuilder ******************************/
173 /* These are generic methods for MergeBuilder. They just output debugging
174 ** information. But some of them are useful as base methods for other useful
175 ** implementations of MergeBuilder.
176 */
177
178 /* xStart() and xEnd() are called to generate header and fotter information
179 ** in the output. This is a no-op in the generic implementation.
180 */
181 static void dbgStartEnd(MergeBuilder *p){ (void)p; }
182
183 /* The next N lines of PIVOT are unchanged in both V1 and V2
184 */
185 static void dbgSame(MergeBuilder *p, unsigned int N){
186 blob_appendf(p->pOut,
187 "COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
188 N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
189 p->lnV2+1, p->lnV2+N);
190 p->lnPivot += N;
191 p->lnV1 += N;
192 p->lnV2 += N;
193 }
194
195 /* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
196 */
197 static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
198 blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
199 nV1, p->lnV1+1, p->lnV1+nV1);
200 p->lnPivot += nPivot;
201 p->lnV2 += nPivot;
202 p->lnV1 += nV1;
203 }
204
205 /* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
206 */
207 static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
208 blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
209 nV2, p->lnV2+1, p->lnV2+nV2);
210 p->lnPivot += nPivot;
211 p->lnV1 += nPivot;
212 p->lnV2 += nV2;
213 }
214
215 /* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
216 ** V2, which should be the same. In other words, the same change is found
217 ** in both V1 and V2.
218 */
219 static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
220 blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
221 nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
222 p->lnPivot += nPivot;
223 p->lnV1 += nV;
224 p->lnV2 += nV;
225 }
226
227 /* V1 and V2 have different and overlapping changes. The next nPivot lines
228 ** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
229 */
230 static void dbgConflict(
231 MergeBuilder *p,
232 unsigned int nPivot,
233 unsigned int nV1,
234 unsigned int nV2
235 ){
236 blob_appendf(p->pOut,
237 "CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
238 nPivot, nV1, nV2,
239 p->lnPivot+1, p->lnPivot+nPivot,
240 p->lnV1+1, p->lnV1+nV1,
241 p->lnV2+1, p->lnV2+nV2);
242 p->lnV1 += nV1;
243 p->lnPivot += nPivot;
244 p->lnV2 += nV2;
245 }
246
247 /* Generic destructor for the MergeBuilder object
248 */
249 static void dbgDestroy(MergeBuilder *p){
250 memset(p, 0, sizeof(*p));
251 }
252
253 /* Generic initializer for a MergeBuilder object
254 */
255 static void mergebuilder_init(MergeBuilder *p){
256 memset(p, 0, sizeof(*p));
257 p->xStart = dbgStartEnd;
258 p->xSame = dbgSame;
259 p->xChngV1 = dbgChngV1;
260 p->xChngV2 = dbgChngV2;
261 p->xChngBoth = dbgChngBoth;
262 p->xConflict = dbgConflict;
263 p->xEnd = dbgStartEnd;
264 p->xDestroy = dbgDestroy;
265 }
266
267 /************************* MergeBuilderText **********************************/
268 /* This version of MergeBuilder actually performs a merge on file and puts
269 ** the result in pOut
270 */
271 static void txtStart(MergeBuilder *p){
272 /* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
273 ** keep it in the output. This should be secure enough not to cause
274 ** unintended changes to the merged file and consistent with what
275 ** users are using in their source files.
276 */
277 if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
278 blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
279 }
280 if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
281 p->useCrLf = 1;
282 }
283 }
284 static void txtSame(MergeBuilder *p, unsigned int N){
285 blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N;
286 blob_copy_lines(0, p->pV1, N); p->lnV1 += N;
287 blob_copy_lines(0, p->pV2, N); p->lnV2 += N;
288 }
289 static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
290 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
291 blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot;
292 blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
293 }
294 static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
295 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
296 blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot;
297 blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
298 }
299 static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
300 blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
301 blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nV;
302 blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV;
303 }
304 static void txtConflict(
305 MergeBuilder *p,
306 unsigned int nPivot,
307 unsigned int nV1,
308 unsigned int nV2
309 ){
310 append_merge_mark(p->pOut, 0, p->lnV1, p->useCrLf);
311 blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
312
313 append_merge_mark(p->pOut, 1, p->lnPivot, p->useCrLf);
314 blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot;
315
316 append_merge_mark(p->pOut, 2, p->lnV2, p->useCrLf);
317 blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
318
319 append_merge_mark(p->pOut, 3, -1, p->useCrLf);
320 }
321 static void mergebuilder_init_text(MergeBuilder *p){
322 mergebuilder_init(p);
323 p->xStart = txtStart;
324 p->xSame = txtSame;
325 p->xChngV1 = txtChngV1;
326 p->xChngV2 = txtChngV2;
327 p->xChngBoth = txtChngBoth;
328 p->xConflict = txtConflict;
329 }
330
331 /************************* MergeBuilderTcl **********************************/
332 /* Generate merge output formatted for reading by a TCL script.
333 **
334 ** The output consists of lines of text, each with 4 tokens. The tokens
335 ** represent the content for one line from baseline, v1, v2, and output
336 ** respectively. The first character of each token provides auxiliary
337 ** information:
338 **
339 ** . This line is omitted.
340 ** N Name of the file.
341 ** T Literal text follows that should have a \n terminator.
342 ** R Literal text follows that needs a \r\n terminator.
343 ** X Merge conflict. (Column 4 only)
344 ** Z Literal text without a line terminator.
345 ** S Skipped lines in all 4 files.
346 ** 1 Text is a copy of token 1
347 ** 2 Use data from data-token 2
348 ** 3 Use data from data-token 3
349 */
350
351 /* Write text that goes into the interior of a double-quoted string in TCL */
352 static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
353 int j;
354 for(j=0; j<nIn; j++){
355 char c = zIn[j];
356 if( c=='\\' ){
357 blob_append(pOut, "\\\\", 2);
358 }else if( c=='"' ){
359 blob_append(pOut, "\\\"", 2);
360 }else if( c<' ' || c>0x7e ){
361 char z[5];
362 z[0] = '\\';
363 z[1] = "01234567"[(c>>6)&0x3];
364 z[2] = "01234567"[(c>>3)&0x7];
365 z[3] = "01234567"[c&0x7];
366 z[4] = 0;
367 blob_append(pOut, z, 4);
368 }else{
369 blob_append_char(pOut, c);
370 }
371 }
372 }
373
374 /* Copy one line of text from pIn and append to pOut, encoded as TCL */
375 static void tclLineOfText(Blob *pOut, Blob *pIn){
376 int i, k;
377 for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
378 if( i==pIn->nUsed ){
379 blob_append(pOut, "\"Z", 2);
380 k = i;
381 }else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
382 blob_append(pOut, "\"R", 2);
383 k = i-1;
384 i++;
385 }else{
386 blob_append(pOut, "\"T", 2);
387 k = i;
388 i++;
389 }
390 tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
391 pIn->iCursor = i;
392 blob_append_char(pOut, '"');
393 }
394 static void tclStart(MergeBuilder *p){
395 Blob *pOut = p->pOut;
396 blob_append(pOut, "\"N", 2);
397 tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
398 blob_append(pOut, "\" \"N", 4);
399 tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
400 blob_append(pOut, "\" \"N", 4);
401 tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
402 blob_append(pOut, "\" \"N", 4);
403 if( p->zOut ){
404 tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
405 }else{
406 blob_append(pOut, "(Merge Result)", -1);
407 }
408 blob_append(pOut, "\"\n", 2);
409 }
410 static void tclSame(MergeBuilder *p, unsigned int N){
411 int i = 0;
412 int nSkip;
413
414 if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
415 while( i<N && i<p->nContext ){
416 tclLineOfText(p->pOut, p->pPivot);
417 blob_append(p->pOut, " 1 1 1\n", 7);
418 i++;
419 }
420 nSkip = N - p->nContext*2;
421 }else{
422 nSkip = N - p->nContext;
423 }
424 if( nSkip>0 ){
425 blob_appendf(p->pOut, "S%d . . .\n", nSkip);
426 blob_copy_lines(0, p->pPivot, nSkip);
427 i += nSkip;
428 }
429
430 p->lnPivot += N;
431 p->lnV1 += N;
432 p->lnV2 += N;
433
434 if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
435 while( i<N ){
436 tclLineOfText(p->pOut, p->pPivot);
437 blob_append(p->pOut, " 1 1 1\n", 7);
438 i++;
439 }
440 }
441
442 blob_copy_lines(0, p->pV1, N);
443 blob_copy_lines(0, p->pV2, N);
444 }
445 static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
446 int i;
447 for(i=0; i<nPivot && i<nV1; i++){
448 tclLineOfText(p->pOut, p->pPivot);
449 blob_append_char(p->pOut, ' ');
450 tclLineOfText(p->pOut, p->pV1);
451 blob_append(p->pOut, " 1 2\n", 5);
452 }
453 while( i<nPivot ){
454 tclLineOfText(p->pOut, p->pPivot);
455 blob_append(p->pOut, " . 1 .\n", 7);
456 i++;
457 }
458 while( i<nV1 ){
459 blob_append(p->pOut, ". ", 2);
460 tclLineOfText(p->pOut, p->pV1);
461 blob_append(p->pOut, " . 2\n", 5);
462 i++;
463 }
464 p->lnPivot += nPivot;
465 p->lnV1 += nV1;
466 p->lnV2 += nPivot;
467 blob_copy_lines(0, p->pV2, nPivot);
468 }
469 static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
470 int i;
471 for(i=0; i<nPivot && i<nV2; i++){
472 tclLineOfText(p->pOut, p->pPivot);
473 blob_append(p->pOut, " 1 ", 3);
474 tclLineOfText(p->pOut, p->pV2);
475 blob_append(p->pOut, " 3\n", 3);
476 }
477 while( i<nPivot ){
478 tclLineOfText(p->pOut, p->pPivot);
479 blob_append(p->pOut, " 1 . .\n", 7);
480 i++;
481 }
482 while( i<nV2 ){
483 blob_append(p->pOut, ". . ", 4);
484 tclLineOfText(p->pOut, p->pV2);
485 blob_append(p->pOut, " 3\n", 3);
486 i++;
487 }
488 p->lnPivot += nPivot;
489 p->lnV1 += nPivot;
490 p->lnV2 += nV2;
491 blob_copy_lines(0, p->pV1, nPivot);
492 }
493 static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
494 int i;
495 for(i=0; i<nPivot && i<nV; i++){
496 tclLineOfText(p->pOut, p->pPivot);
497 blob_append_char(p->pOut, ' ');
498 tclLineOfText(p->pOut, p->pV1);
499 blob_append(p->pOut, " 2 2\n", 5);
500 }
501 while( i<nPivot ){
502 tclLineOfText(p->pOut, p->pPivot);
503 blob_append(p->pOut, " . . .\n", 7);
504 i++;
505 }
506 while( i<nV ){
507 blob_append(p->pOut, ". ", 2);
508 tclLineOfText(p->pOut, p->pV1);
509 blob_append(p->pOut, " 2 2\n", 5);
510 i++;
511 }
512 p->lnPivot += nPivot;
513 p->lnV1 += nV;
514 p->lnV2 += nV;
515 blob_copy_lines(0, p->pV2, nV);
516 }
517 static void tclConflict(
518 MergeBuilder *p,
519 unsigned int nPivot,
520 unsigned int nV1,
521 unsigned int nV2
522 ){
523 int mx = nPivot;
524 int i;
525 if( nV1>mx ) mx = nV1;
526 if( nV2>mx ) mx = nV2;
527 for(i=0; i<mx; i++){
528 if( i<nPivot ){
529 tclLineOfText(p->pOut, p->pPivot);
530 }else{
531 blob_append_char(p->pOut, '.');
532 }
533 blob_append_char(p->pOut, ' ');
534 if( i<nV1 ){
535 tclLineOfText(p->pOut, p->pV1);
536 }else{
537 blob_append_char(p->pOut, '.');
538 }
539 blob_append_char(p->pOut, ' ');
540 if( i<nV2 ){
541 tclLineOfText(p->pOut, p->pV2);
542 }else{
543 blob_append_char(p->pOut, '.');
544 }
545 blob_append(p->pOut, " X\n", 3);
546 }
547 p->lnPivot += nPivot;
548 p->lnV1 += nV1;
549 p->lnV2 += nV2;
550 }
551 void mergebuilder_init_tcl(MergeBuilder *p){
552 mergebuilder_init(p);
553 p->xStart = tclStart;
554 p->xSame = tclSame;
555 p->xChngV1 = tclChngV1;
556 p->xChngV2 = tclChngV2;
557 p->xChngBoth = tclChngBoth;
558 p->xConflict = tclConflict;
559 }
560 /*****************************************************************************/
561
562 /*
563 ** The aC[] array contains triples of integers. Within each triple, the
564 ** elements are:
565 **
566 ** (0) The number of lines to copy
567 ** (1) The number of lines to delete
568 ** (2) The number of liens to insert
569 **
570 ** Suppose we want to advance over sz lines of the original file. This routine
571 ** returns true if that advance would land us on a copy operation. It
572 ** returns false if the advance would end on a delete.
573 */
574 static int ends_with_copy(int *aC, int sz){
575 while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
576 if( aC[0]>=sz ) return 1;
577 sz -= aC[0];
578 if( aC[1]>sz ) return 0;
579 sz -= aC[1];
580 aC += 3;
581 }
582 return 1;
583 }
584
585 /*
586 ** aC[] is an "edit triple" for changes from A to B. Advance through
587 ** this triple to determine the number of lines to bypass on B in order
588 ** to match an advance of sz lines on A.
589 */
590 static int skip_conflict(
591 int *aC, /* Array of integer triples describing the edit */
592 int i, /* Index in aC[] of current location */
593 int sz, /* Lines of A that have been skipped */
594 unsigned int *pLn /* OUT: Lines of B to skip to keep aligment with A */
595 ){
596 *pLn = 0;
597 while( sz>0 ){
598 if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
599 if( aC[i]>=sz ){
600 aC[i] -= sz;
601 *pLn += sz;
602 break;
603 }
604 *pLn += aC[i];
605 *pLn += aC[i+2];
606 sz -= aC[i] + aC[i+1];
607 i += 3;
608 }
609 return i;
610 }
611
612 /*
613 ** Do a three-way merge. Initialize pOut to contain the result.
614 **
615 ** The merge is an edit against pV2. Both pV1 and pV2 have a
@@ -199,156 +619,112 @@
619 ** The return is 0 upon complete success. If any input file is binary,
620 ** -1 is returned and pOut is unmodified. If there are merge
621 ** conflicts, the merge proceeds as best as it can and the number
622 ** of conflicts is returns
623 */
624 int merge_three_blobs(MergeBuilder *p){
625 int *aC1; /* Changes from pPivot to pV1 */
626 int *aC2; /* Changes from pPivot to pV2 */
627 int i1, i2; /* Index into aC1[] and aC2[] */
628 int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
629 int limit1, limit2; /* Sizes of aC1[] and aC2[] */
630 int nConflict = 0; /* Number of merge conflicts seen so far */
 
 
631 DiffConfig DCfg;
632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633 /* Compute the edits that occur from pPivot => pV1 (into aC1)
634 ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
635 ** an array of integer triples. Within each triple, the first integer
636 ** is the number of lines of text to copy directly from the pivot,
637 ** the second integer is the number of lines of text to omit from the
638 ** pivot, and the third integer is the number of lines of text that are
639 ** inserted. The edit array ends with a triple of 0,0,0.
640 */
641 diff_config_init(&DCfg, 0);
642 aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
643 aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
644 if( aC1==0 || aC2==0 ){
645 free(aC1);
646 free(aC2);
647 return -1;
648 }
649
650 blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */
651 blob_rewind(p->pV2);
652 blob_rewind(p->pPivot);
653
654 /* Determine the length of the aC1[] and aC2[] change vectors */
655 p->mxPivot = 0;
656 p->mxV1 = 0;
657 for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
658 p->mxPivot += aC1[i1] + aC1[i1+1];
659 p->mxV1 += aC1[i1] + aC1[i1+2];
660 }
661 limit1 = i1;
662 p->mxV2 = 0;
663 for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
664 p->mxV2 += aC2[i2] + aC2[i2+2];
665 }
666 limit2 = i2;
667
668 /* Output header text and do any other required initialization */
669 p->xStart(p);
 
 
 
 
 
 
670
671 /* Loop over the two edit vectors and use them to compute merged text
672 ** which is written into pOut. i1 and i2 are multiples of 3 which are
673 ** indices into aC1[] and aC2[] to the edit triple currently being
674 ** processed
675 */
676 i1 = i2 = 0;
 
677 while( i1<limit1 && i2<limit2 ){
 
 
 
 
678 if( aC1[i1]>0 && aC2[i2]>0 ){
679 /* Output text that is unchanged in both V1 and V2 */
680 nCpy = min(aC1[i1], aC2[i2]);
681 p->xSame(p, nCpy);
 
 
 
682 aC1[i1] -= nCpy;
683 aC2[i2] -= nCpy;
684 }else
685 if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
686 /* Output edits to V2 that occurs within unchanged regions of V1 */
687 nDel = aC2[i2+1];
688 nIns = aC2[i2+2];
689 p->xChngV2(p, nDel, nIns);
 
 
 
690 aC1[i1] -= nDel;
691 i2 += 3;
692 }else
693 if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
694 /* Output edits to V1 that occur within unchanged regions of V2 */
695 nDel = aC1[i1+1];
696 nIns = aC1[i1+2];
697 p->xChngV1(p, nDel, nIns);
 
 
 
698 aC2[i2] -= nDel;
699 i1 += 3;
700 }else
701 if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
702 /* Output edits that are identical in both V1 and V2. */
703 assert( aC1[i1]==0 );
704 nDel = aC1[i1+1];
705 nIns = aC1[i1+2];
706 p->xChngBoth(p, nDel, nIns);
 
 
 
707 i1 += 3;
708 i2 += 3;
709 }else
710 {
711 /* We have found a region where different edits to V1 and V2 overlap.
712 ** This is a merge conflict. Find the size of the conflict, then
713 ** output both possible edits separated by distinctive marks.
714 */
715 unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */
716 unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */
717 nConflict++;
718 while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
719 sz++;
720 }
721 i1 = skip_conflict(aC1, i1, sz, &nV1);
722 i2 = skip_conflict(aC2, i2, sz, &nV2);
723 p->xConflict(p, sz, nV1, nV2);
724 }
725
 
 
 
 
 
 
 
 
 
726 /* If we are finished with an edit triple, advance to the next
727 ** triple.
728 */
729 if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
730 if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
@@ -356,20 +732,18 @@
732
733 /* When one of the two edit vectors reaches its end, there might still
734 ** be an insert in the other edit vector. Output this remaining
735 ** insert.
736 */
 
 
 
737 if( i1<limit1 && aC1[i1+2]>0 ){
738 p->xChngV1(p, 0, aC1[i1+2]);
 
739 }else if( i2<limit2 && aC2[i2+2]>0 ){
740 p->xChngV2(p, 0, aC2[i2+2]);
 
741 }
742
743 /* Output footer text */
744 p->xEnd(p);
745
746 free(aC1);
747 free(aC2);
748 return nConflict;
749 }
@@ -408,18 +782,105 @@
782 blob_read_from_file(&file, zFullpath, ExtFILE);
783 rc = contains_merge_marker(&file);
784 blob_reset(&file);
785 return rc;
786 }
787
788 /*
789 ** Show merge output in a Tcl/Tk window, in response to the --tk option
790 ** to the "merge" or "3-way-merge" command.
791 **
792 ** If fossil has direct access to a Tcl interpreter (either loaded
793 ** dynamically through stubs or linked in statically), we can use it
794 ** directly. Otherwise:
795 ** (1) Write the Tcl/Tk script used for rendering into a temp file.
796 ** (2) Invoke "tclsh" on the temp file using fossil_system().
797 ** (3) Delete the temp file.
798 */
799 void merge_tk(const char *zSubCmd, int firstArg){
800 int i;
801 Blob script;
802 const char *zTempFile = 0;
803 char *zCmd;
804 const char *zTclsh;
805 const char *zCnt;
806 int bDarkMode = find_option("dark",0,0)!=0;
807 int nContext;
808 zCnt = find_option("context", "c", 1);
809 if( zCnt==0 ){
810 nContext = 6;
811 }else{
812 nContext = atoi(zCnt);
813 if( nContext<0 ) nContext = 0xfffffff;
814 }
815 blob_zero(&script);
816 blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl -c %d",
817 g.nameOfExe, zSubCmd, nContext);
818 find_option("tcl",0,0);
819 find_option("debug",0,0);
820 zTclsh = find_option("tclsh",0,1);
821 if( zTclsh==0 ){
822 zTclsh = db_get("tclsh",0);
823 }
824 /* The undocumented --script FILENAME option causes the Tk script to
825 ** be written into the FILENAME instead of being run. This is used
826 ** for testing and debugging. */
827 zTempFile = find_option("script",0,1);
828 verify_all_options();
829
830 if( (g.argc - firstArg)!=3 ){
831 fossil_fatal("Requires 3 filename arguments");
832 }
833
834 for(i=firstArg; i<g.argc; i++){
835 const char *z = g.argv[i];
836 if( sqlite3_strglob("*}*",z) ){
837 blob_appendf(&script, " {%/}", z);
838 }else{
839 int j;
840 blob_append(&script, " ", 1);
841 for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
842 }
843 }
844 blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
845 blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
846 if( zTempFile ){
847 blob_write_to_file(&script, zTempFile);
848 fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
849 }else{
850 #if defined(FOSSIL_ENABLE_TCL)
851 Th_FossilInit(TH_INIT_DEFAULT);
852 if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
853 blob_size(&script), 1, 1, 0)==TCL_OK ){
854 blob_reset(&script);
855 return;
856 }
857 /*
858 * If evaluation of the Tcl script fails, the reason may be that Tk
859 * could not be found by the loaded Tcl, or that Tcl cannot be loaded
860 * dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
861 * to using the external "tclsh", if available.
862 */
863 #endif
864 zTempFile = write_blob_to_temp_file(&script);
865 zCmd = mprintf("%$ %$", zTclsh, zTempFile);
866 fossil_system(zCmd);
867 file_delete(zTempFile);
868 fossil_free(zCmd);
869 }
870 blob_reset(&script);
871 }
872
873
874 /*
875 ** COMMAND: 3-way-merge*
876 **
877 ** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
878 **
879 ** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
880 ** as output. If no MERGED file is specified, output is sent to
881 ** stdout.
882 **
883 ** BASELINE is a common ancestor of two files V1 and V2 that have diverging
884 ** edits. The generated output file MERGED is the combination of all
885 ** changes in both V1 and V2.
886 **
@@ -436,38 +897,75 @@
897 ** cp Xup.c Xbase.c
898 ** # Verify that everything still works
899 ** fossil commit
900 **
901 */
902 void merge_3way_cmd(void){
903 MergeBuilder s;
904 int nConflict;
905 Blob pivot, v1, v2, out;
906 int noWarn = 0;
907 const char *zCnt;
908
909 if( find_option("tk", 0, 0)!=0 ){
910 merge_tk("3-way-merge", 2);
911 return;
912 }
913 mergebuilder_init_text(&s);
914 if( find_option("debug", 0, 0) ){
915 mergebuilder_init(&s);
916 }
917 if( find_option("tcl", 0, 0) ){
918 mergebuilder_init_tcl(&s);
919 noWarn = 1;
920 }
921 zCnt = find_option("context", "c", 1);
922 if( zCnt ){
923 s.nContext = atoi(zCnt);
924 if( s.nContext<0 ) s.nContext = 0xfffffff;
925 }else{
926 s.nContext = 6;
927 }
928 blob_zero(&pivot); s.pPivot = &pivot;
929 blob_zero(&v1); s.pV1 = &v1;
930 blob_zero(&v2); s.pV2 = &v2;
931 blob_zero(&out); s.pOut = &out;
932
933 /* We should be done with options.. */
934 verify_all_options();
935
936 if( g.argc!=6 && g.argc!=5 ){
937 usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
938 }
939 s.zPivot = file_tail(g.argv[2]);
940 s.zV1 = file_tail(g.argv[3]);
941 s.zV2 = file_tail(g.argv[4]);
942 if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
943 fossil_fatal("cannot read %s", g.argv[2]);
944 }
945 if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
946 fossil_fatal("cannot read %s", g.argv[3]);
947 }
948 if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
949 fossil_fatal("cannot read %s", g.argv[4]);
950 }
951 nConflict = merge_three_blobs(&s);
952 if( g.argc==6 ){
953 s.zOut = file_tail(g.argv[5]);
954 blob_write_to_file(s.pOut, g.argv[5]);
955 }else{
956 s.zOut = "(Merge Result)";
957 blob_write_to_file(s.pOut, "-");
958 }
959 s.xDestroy(&s);
960 blob_reset(&pivot);
961 blob_reset(&v1);
962 blob_reset(&v2);
963 blob_reset(&out);
964 if( nConflict>0 && !noWarn ){
965 fossil_warning("WARNING: %d merge conflicts", nConflict);
966 }
967 }
968
969 /*
970 ** aSubst is an array of string pairs. The first element of each pair is
971 ** a string that begins with %. The second element is a replacement for that
@@ -516,21 +1014,21 @@
1014 #define MERGE_KEEP_FILES 0x0002
1015 #endif
1016
1017
1018 /*
1019 ** This routine is a wrapper around merge_three_blobs() with the following
1020 ** enhancements:
1021 **
1022 ** (1) If the merge-command is defined, then use the external merging
1023 ** program specified instead of the built-in blob-merge to do the
1024 ** merging. Panic if the external merger fails.
1025 ** ** Not currently implemented **
1026 **
1027 ** (2) If gmerge-command is defined and there are merge conflicts in
1028 ** merge_three_blobs() then invoke the external graphical merger
1029 ** to resolve the conflicts.
1030 **
1031 ** (3) If a merge conflict occurs and gmerge-command is not defined,
1032 ** then write the pivot, original, and merge-in files to the
1033 ** filesystem.
1034 */
@@ -539,30 +1037,37 @@
1037 const char *zV1, /* Name of file for version merging into (mine) */
1038 Blob *pV2, /* Version merging from (yours) */
1039 Blob *pOut, /* Output written here */
1040 unsigned mergeFlags /* Flags that control operation */
1041 ){
1042 Blob v1; /* Content of zV1 */
1043 int rc; /* Return code of subroutines and this routine */
1044 const char *zGMerge; /* Name of the gmerge command */
1045 MergeBuilder s; /* The merge state */
1046
1047 mergebuilder_init_text(&s);
1048 s.pPivot = pPivot;
1049 s.pV1 = &v1;
1050 s.pV2 = pV2;
1051 blob_zero(pOut);
1052 s.pOut = pOut;
1053 blob_read_from_file(s.pV1, zV1, ExtFILE);
1054 rc = merge_three_blobs(&s);
1055 zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
1056 if( (mergeFlags & MERGE_DRYRUN)==0
1057 && ((zGMerge!=0 && zGMerge[0]!=0)
1058 || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
1059 char *zPivot; /* Name of the pivot file */
1060 char *zOrig; /* Name of the original content file */
1061 char *zOther; /* Name of the merge file */
1062
1063 zPivot = file_newname(zV1, "baseline", 1);
1064 blob_write_to_file(s.pPivot, zPivot);
1065 zOrig = file_newname(zV1, "original", 1);
1066 blob_write_to_file(s.pV1, zOrig);
1067 zOther = file_newname(zV1, "merge", 1);
1068 blob_write_to_file(s.pV2, zOther);
1069 if( rc>0 ){
1070 if( zGMerge && zGMerge[0] ){
1071 char *zOut; /* Temporary output file */
1072 char *zCmd; /* Command to invoke */
1073 const char *azSubst[8]; /* Strings to be substituted */
@@ -589,8 +1094,8 @@
1094 }
1095 fossil_free(zPivot);
1096 fossil_free(zOrig);
1097 fossil_free(zOther);
1098 }
1099 s.xDestroy(&s);
1100 return rc;
1101 }
1102
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -209,10 +209,11 @@
209209
# Additional resource files that get built into the executable.
210210
# These paths are all resolved from the src/ directory, so must
211211
# be relative to that.
212212
set extra_files {
213213
diff.tcl
214
+ merge.tcl
214215
markdown.md
215216
wiki.wiki
216217
*.js
217218
default.css
218219
style.*.css
219220
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -209,10 +209,11 @@
209 # Additional resource files that get built into the executable.
210 # These paths are all resolved from the src/ directory, so must
211 # be relative to that.
212 set extra_files {
213 diff.tcl
 
214 markdown.md
215 wiki.wiki
216 *.js
217 default.css
218 style.*.css
219
--- tools/makemake.tcl
+++ tools/makemake.tcl
@@ -209,10 +209,11 @@
209 # Additional resource files that get built into the executable.
210 # These paths are all resolved from the src/ directory, so must
211 # be relative to that.
212 set extra_files {
213 diff.tcl
214 merge.tcl
215 markdown.md
216 wiki.wiki
217 *.js
218 default.css
219 style.*.css
220
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -634,10 +634,11 @@
634634
$(SRCDIR)/hbmenu.js \
635635
$(SRCDIR)/href.js \
636636
$(SRCDIR)/login.js \
637637
$(SRCDIR)/markdown.md \
638638
$(SRCDIR)/menu.js \
639
+ $(SRCDIR)/merge.tcl \
639640
$(SRCDIR)/scroll.js \
640641
$(SRCDIR)/skin.js \
641642
$(SRCDIR)/sorttable.js \
642643
$(SRCDIR)/sounds/0.wav \
643644
$(SRCDIR)/sounds/1.wav \
644645
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -634,10 +634,11 @@
634 $(SRCDIR)/hbmenu.js \
635 $(SRCDIR)/href.js \
636 $(SRCDIR)/login.js \
637 $(SRCDIR)/markdown.md \
638 $(SRCDIR)/menu.js \
 
639 $(SRCDIR)/scroll.js \
640 $(SRCDIR)/skin.js \
641 $(SRCDIR)/sorttable.js \
642 $(SRCDIR)/sounds/0.wav \
643 $(SRCDIR)/sounds/1.wav \
644
--- win/Makefile.mingw
+++ win/Makefile.mingw
@@ -634,10 +634,11 @@
634 $(SRCDIR)/hbmenu.js \
635 $(SRCDIR)/href.js \
636 $(SRCDIR)/login.js \
637 $(SRCDIR)/markdown.md \
638 $(SRCDIR)/menu.js \
639 $(SRCDIR)/merge.tcl \
640 $(SRCDIR)/scroll.js \
641 $(SRCDIR)/skin.js \
642 $(SRCDIR)/sorttable.js \
643 $(SRCDIR)/sounds/0.wav \
644 $(SRCDIR)/sounds/1.wav \
645
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -592,10 +592,11 @@
592592
"$(SRCDIR)\hbmenu.js" \
593593
"$(SRCDIR)\href.js" \
594594
"$(SRCDIR)\login.js" \
595595
"$(SRCDIR)\markdown.md" \
596596
"$(SRCDIR)\menu.js" \
597
+ "$(SRCDIR)\merge.tcl" \
597598
"$(SRCDIR)\scroll.js" \
598599
"$(SRCDIR)\skin.js" \
599600
"$(SRCDIR)\sorttable.js" \
600601
"$(SRCDIR)\sounds\0.wav" \
601602
"$(SRCDIR)\sounds\1.wav" \
@@ -1222,10 +1223,11 @@
12221223
echo "$(SRCDIR)\hbmenu.js" >> $@
12231224
echo "$(SRCDIR)\href.js" >> $@
12241225
echo "$(SRCDIR)\login.js" >> $@
12251226
echo "$(SRCDIR)\markdown.md" >> $@
12261227
echo "$(SRCDIR)\menu.js" >> $@
1228
+ echo "$(SRCDIR)\merge.tcl" >> $@
12271229
echo "$(SRCDIR)\scroll.js" >> $@
12281230
echo "$(SRCDIR)\skin.js" >> $@
12291231
echo "$(SRCDIR)\sorttable.js" >> $@
12301232
echo "$(SRCDIR)\sounds/0.wav" >> $@
12311233
echo "$(SRCDIR)\sounds/1.wav" >> $@
12321234
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -592,10 +592,11 @@
592 "$(SRCDIR)\hbmenu.js" \
593 "$(SRCDIR)\href.js" \
594 "$(SRCDIR)\login.js" \
595 "$(SRCDIR)\markdown.md" \
596 "$(SRCDIR)\menu.js" \
 
597 "$(SRCDIR)\scroll.js" \
598 "$(SRCDIR)\skin.js" \
599 "$(SRCDIR)\sorttable.js" \
600 "$(SRCDIR)\sounds\0.wav" \
601 "$(SRCDIR)\sounds\1.wav" \
@@ -1222,10 +1223,11 @@
1222 echo "$(SRCDIR)\hbmenu.js" >> $@
1223 echo "$(SRCDIR)\href.js" >> $@
1224 echo "$(SRCDIR)\login.js" >> $@
1225 echo "$(SRCDIR)\markdown.md" >> $@
1226 echo "$(SRCDIR)\menu.js" >> $@
 
1227 echo "$(SRCDIR)\scroll.js" >> $@
1228 echo "$(SRCDIR)\skin.js" >> $@
1229 echo "$(SRCDIR)\sorttable.js" >> $@
1230 echo "$(SRCDIR)\sounds/0.wav" >> $@
1231 echo "$(SRCDIR)\sounds/1.wav" >> $@
1232
--- win/Makefile.msc
+++ win/Makefile.msc
@@ -592,10 +592,11 @@
592 "$(SRCDIR)\hbmenu.js" \
593 "$(SRCDIR)\href.js" \
594 "$(SRCDIR)\login.js" \
595 "$(SRCDIR)\markdown.md" \
596 "$(SRCDIR)\menu.js" \
597 "$(SRCDIR)\merge.tcl" \
598 "$(SRCDIR)\scroll.js" \
599 "$(SRCDIR)\skin.js" \
600 "$(SRCDIR)\sorttable.js" \
601 "$(SRCDIR)\sounds\0.wav" \
602 "$(SRCDIR)\sounds\1.wav" \
@@ -1222,10 +1223,11 @@
1223 echo "$(SRCDIR)\hbmenu.js" >> $@
1224 echo "$(SRCDIR)\href.js" >> $@
1225 echo "$(SRCDIR)\login.js" >> $@
1226 echo "$(SRCDIR)\markdown.md" >> $@
1227 echo "$(SRCDIR)\menu.js" >> $@
1228 echo "$(SRCDIR)\merge.tcl" >> $@
1229 echo "$(SRCDIR)\scroll.js" >> $@
1230 echo "$(SRCDIR)\skin.js" >> $@
1231 echo "$(SRCDIR)\sorttable.js" >> $@
1232 echo "$(SRCDIR)\sounds/0.wav" >> $@
1233 echo "$(SRCDIR)\sounds/1.wav" >> $@
1234

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button