Fossil SCM

fossil-scm / src / merge3.c
Blame History Raw 1233 lines
1
/*
2
** Copyright (c) 2007 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This module implements a 3-way merge
19
*/
20
#include "config.h"
21
#include "merge3.h"
22
23
#if 0
24
#define DEBUG(X) X
25
#define ISDEBUG 1
26
#else
27
#define DEBUG(X)
28
#define ISDEBUG 0
29
#endif
30
31
/* The minimum of two integers */
32
#ifndef min
33
# define min(A,B) (A<B?A:B)
34
#endif
35
36
/*
37
** Compare N lines of text from pV1 and pV2. If the lines
38
** are the same, return true. Return false if one or more of the N
39
** lines are different.
40
**
41
** The cursors on both pV1 and pV2 is unchanged by this comparison.
42
*/
43
static int sameLines(Blob *pV1, Blob *pV2, int N){
44
char *z1, *z2;
45
int i;
46
char c;
47
48
if( N==0 ) return 1;
49
z1 = &blob_buffer(pV1)[blob_tell(pV1)];
50
z2 = &blob_buffer(pV2)[blob_tell(pV2)];
51
for(i=0; (c=z1[i])==z2[i]; i++){
52
if( c=='\n' || c==0 ){
53
N--;
54
if( N==0 || c==0 ) return 1;
55
}
56
}
57
return 0;
58
}
59
60
/*
61
** Look at the next edit triple in both aC1 and aC2. (An "edit triple" is
62
** three integers describing the number of copies, deletes, and inserts in
63
** moving from the original to the edited copy of the file.) If the three
64
** integers of the edit triples describe an identical edit, then return 1.
65
** If the edits are different, return 0.
66
*/
67
static int sameEdit(
68
int *aC1, /* Array of edit integers for file 1 */
69
int *aC2, /* Array of edit integers for file 2 */
70
Blob *pV1, /* Text of file 1 */
71
Blob *pV2 /* Text of file 2 */
72
){
73
if( aC1[0]!=aC2[0] ) return 0;
74
if( aC1[1]!=aC2[1] ) return 0;
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*/
85
"<<<<<<< BEGIN MERGE CONFLICT: local copy shown first <<<<<<<<<<<<",
86
"####### SUGGESTED CONFLICT RESOLUTION follows ###################",
87
"||||||| COMMON ANCESTOR content follows |||||||||||||||||||||||||",
88
"======= MERGED IN content follows ===============================",
89
">>>>>>> END MERGE CONFLICT >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"
90
};
91
92
/*
93
** Return true if the input blob contains any CR/LF pairs on the first
94
** ten lines. This should be enough to detect files that use mainly CR/LF
95
** line endings without causing a performance impact for LF only files.
96
*/
97
int contains_crlf(Blob *p){
98
int i;
99
int j = 0;
100
const int maxL = 10; /* Max lines to check */
101
const char *z = blob_buffer(p);
102
int n = blob_size(p)+1;
103
for(i=1; i<n; ){
104
if( z[i-1]=='\r' && z[i]=='\n' ) return 1;
105
while( i<n && z[i]!='\n' ){ i++; }
106
j++;
107
if( j>maxL ) return 0;
108
}
109
return 0;
110
}
111
112
/*
113
** Ensure that the text in pBlob ends with a new line.
114
** If useCrLf is true adds "\r\n" otherwise '\n'.
115
*/
116
void ensure_line_end(Blob *pBlob, int useCrLf){
117
if( pBlob->nUsed<=0 ) return;
118
if( pBlob->aData[pBlob->nUsed-1]!='\n' ){
119
if( useCrLf ) blob_append_char(pBlob, '\r');
120
blob_append_char(pBlob, '\n');
121
}
122
}
123
124
/*
125
** Write out one of the four merge-marks.
126
*/
127
void append_merge_mark(Blob *pOut, int iMark, int ln, int useCrLf){
128
ensure_line_end(pOut, useCrLf);
129
blob_append(pOut, mergeMarker[iMark], -1);
130
if( ln>0 ) blob_appendf(pOut, " (line %d)", ln);
131
ensure_line_end(pOut, useCrLf);
132
}
133
134
#if INTERFACE
135
/*
136
** This is an abstract class for constructing a merge.
137
** Subclasses of this object format the merge output in different ways.
138
**
139
** To subclass, create an instance of the MergeBuilder object and fill
140
** in appropriate method implementations.
141
*/
142
struct MergeBuilder {
143
void (*xStart)(MergeBuilder*);
144
void (*xSame)(MergeBuilder*, unsigned int);
145
void (*xChngV1)(MergeBuilder*, unsigned int, unsigned int);
146
void (*xChngV2)(MergeBuilder*, unsigned int, unsigned int);
147
void (*xChngBoth)(MergeBuilder*, unsigned int, unsigned int);
148
void (*xConflict)(MergeBuilder*, unsigned int, unsigned int, unsigned int);
149
void (*xEnd)(MergeBuilder*);
150
void (*xDestroy)(MergeBuilder*);
151
const char *zPivot; /* Label or name for the pivot */
152
const char *zV1; /* Label or name for the V1 file */
153
const char *zV2; /* Label or name for the V2 file */
154
const char *zOut; /* Label or name for the output */
155
Blob *pPivot; /* The common ancestor */
156
Blob *pV1; /* First variant (local copy) */
157
Blob *pV2; /* Second variant (merged in) */
158
Blob *pOut; /* Write merge results here */
159
int useCrLf; /* Use CRLF line endings */
160
int nContext; /* Size of unchanged line boundaries */
161
unsigned int mxPivot; /* Number of lines in the pivot */
162
unsigned int mxV1; /* Number of lines in V1 */
163
unsigned int mxV2; /* Number of lines in V2 */
164
unsigned int lnPivot; /* Lines read from pivot */
165
unsigned int lnV1; /* Lines read from v1 */
166
unsigned int lnV2; /* Lines read from v2 */
167
unsigned int lnOut; /* Lines written to out */
168
unsigned int nConflict; /* Number of conflicts seen */
169
u64 diffFlags; /* Flags for difference engine */
170
};
171
#endif /* INTERFACE */
172
173
174
/************************* Generic MergeBuilder ******************************/
175
/* These are generic methods for MergeBuilder. They just output debugging
176
** information. But some of them are useful as base methods for other useful
177
** implementations of MergeBuilder.
178
*/
179
180
/* xStart() and xEnd() are called to generate header and footer information
181
** in the output. This is a no-op in the generic implementation.
182
*/
183
static void dbgStartEnd(MergeBuilder *p){ (void)p; }
184
185
/* The next N lines of PIVOT are unchanged in both V1 and V2
186
*/
187
static void dbgSame(MergeBuilder *p, unsigned int N){
188
blob_appendf(p->pOut,
189
"COPY %u from BASELINE(%u..%u) or V1(%u..%u) or V2(%u..%u)\n",
190
N, p->lnPivot+1, p->lnPivot+N, p->lnV1+1, p->lnV1+N,
191
p->lnV2+1, p->lnV2+N);
192
p->lnPivot += N;
193
p->lnV1 += N;
194
p->lnV2 += N;
195
}
196
197
/* The next nPivot lines of the PIVOT are changed into nV1 lines by V1
198
*/
199
static void dbgChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
200
blob_appendf(p->pOut, "COPY %u from V1(%u..%u)\n",
201
nV1, p->lnV1+1, p->lnV1+nV1);
202
p->lnPivot += nPivot;
203
p->lnV2 += nPivot;
204
p->lnV1 += nV1;
205
}
206
207
/* The next nPivot lines of the PIVOT are changed into nV2 lines by V2
208
*/
209
static void dbgChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
210
blob_appendf(p->pOut, "COPY %u lines FROM V2(%u..%u)\n",
211
nV2, p->lnV2+1, p->lnV2+nV2);
212
p->lnPivot += nPivot;
213
p->lnV1 += nPivot;
214
p->lnV2 += nV2;
215
}
216
217
/* The next nPivot lines of the PIVOT are changed into nV lines from V1 and
218
** V2, which should be the same. In other words, the same change is found
219
** in both V1 and V2.
220
*/
221
static void dbgChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
222
blob_appendf(p->pOut, "COPY %u lines from V1(%u..%u) or V2(%u..%u)\n",
223
nV, p->lnV1+1, p->lnV1+nV, p->lnV2+1, p->lnV2+nV);
224
p->lnPivot += nPivot;
225
p->lnV1 += nV;
226
p->lnV2 += nV;
227
}
228
229
/* V1 and V2 have different and overlapping changes. The next nPivot lines
230
** of the PIVOT are converted into nV1 lines of V1 and nV2 lines of V2.
231
*/
232
static void dbgConflict(
233
MergeBuilder *p,
234
unsigned int nPivot,
235
unsigned int nV1,
236
unsigned int nV2
237
){
238
blob_appendf(p->pOut,
239
"CONFLICT %u,%u,%u BASELINE(%u..%u) versus V1(%u..%u) versus V2(%u..%u)\n",
240
nPivot, nV1, nV2,
241
p->lnPivot+1, p->lnPivot+nPivot,
242
p->lnV1+1, p->lnV1+nV1,
243
p->lnV2+1, p->lnV2+nV2);
244
p->lnV1 += nV1;
245
p->lnPivot += nPivot;
246
p->lnV2 += nV2;
247
}
248
249
/* Generic destructor for the MergeBuilder object
250
*/
251
static void dbgDestroy(MergeBuilder *p){
252
memset(p, 0, sizeof(*p));
253
}
254
255
/* Generic initializer for a MergeBuilder object
256
*/
257
static void mergebuilder_init(MergeBuilder *p){
258
memset(p, 0, sizeof(*p));
259
p->xStart = dbgStartEnd;
260
p->xSame = dbgSame;
261
p->xChngV1 = dbgChngV1;
262
p->xChngV2 = dbgChngV2;
263
p->xChngBoth = dbgChngBoth;
264
p->xConflict = dbgConflict;
265
p->xEnd = dbgStartEnd;
266
p->xDestroy = dbgDestroy;
267
}
268
269
/************************* MergeBuilderToken ********************************/
270
/* This version of MergeBuilder actually performs a merge on file that
271
** are broken up into tokens instead of lines, and puts the result in pOut.
272
*/
273
static void tokenSame(MergeBuilder *p, unsigned int N){
274
blob_append(p->pOut, p->pPivot->aData+p->pPivot->iCursor, N);
275
p->pPivot->iCursor += N;
276
p->pV1->iCursor += N;
277
p->pV2->iCursor += N;
278
}
279
static void tokenChngV1(MergeBuilder *p, unsigned int nPivot, unsigned nV1){
280
blob_append(p->pOut, p->pV1->aData+p->pV1->iCursor, nV1);
281
p->pPivot->iCursor += nPivot;
282
p->pV1->iCursor += nV1;
283
p->pV2->iCursor += nPivot;
284
}
285
static void tokenChngV2(MergeBuilder *p, unsigned int nPivot, unsigned nV2){
286
blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
287
p->pPivot->iCursor += nPivot;
288
p->pV1->iCursor += nPivot;
289
p->pV2->iCursor += nV2;
290
}
291
static void tokenChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned nV){
292
blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV);
293
p->pPivot->iCursor += nPivot;
294
p->pV1->iCursor += nV;
295
p->pV2->iCursor += nV;
296
}
297
static void tokenConflict(
298
MergeBuilder *p,
299
unsigned int nPivot,
300
unsigned int nV1,
301
unsigned int nV2
302
){
303
/* For a token-merge conflict, use the text from the merge-in */
304
blob_append(p->pOut, p->pV2->aData+p->pV2->iCursor, nV2);
305
p->pPivot->iCursor += nPivot;
306
p->pV1->iCursor += nV1;
307
p->pV2->iCursor += nV2;
308
}
309
static void mergebuilder_init_token(MergeBuilder *p){
310
mergebuilder_init(p);
311
p->xSame = tokenSame;
312
p->xChngV1 = tokenChngV1;
313
p->xChngV2 = tokenChngV2;
314
p->xChngBoth = tokenChngBoth;
315
p->xConflict = tokenConflict;
316
p->diffFlags = DIFF_BY_TOKEN;
317
}
318
319
/*
320
** Attempt to do a low-level merge on a conflict. The conflict is
321
** described by the first four parameters, which are the same as the
322
** arguments to the xConflict method of the MergeBuilder object.
323
** This routine attempts to resolve the conflict by looking at
324
** elements of the conflict region that are finer grain than complete
325
** lines of text.
326
**
327
** The result is written into Blob pOut. pOut is initialized by this
328
** routine.
329
*/
330
int merge_try_to_resolve_conflict(
331
MergeBuilder *pMB, /* MergeBuilder that encounter conflict */
332
unsigned int nPivot, /* Lines of conflict in the pivot */
333
unsigned int nV1, /* Lines of conflict in V1 */
334
unsigned int nV2, /* Lines of conflict in V2 */
335
Blob *pOut /* Write resolution text here */
336
){
337
int nConflict;
338
MergeBuilder mb;
339
Blob pv, v1, v2;
340
mergebuilder_init_token(&mb);
341
blob_extract_lines(pMB->pPivot, nPivot, &pv);
342
blob_extract_lines(pMB->pV1, nV1, &v1);
343
blob_extract_lines(pMB->pV2, nV2, &v2);
344
blob_zero(pOut);
345
blob_materialize(&pv);
346
blob_materialize(&v1);
347
blob_materialize(&v2);
348
mb.pPivot = &pv;
349
mb.pV1 = &v1;
350
mb.pV2 = &v2;
351
mb.pOut = pOut;
352
nConflict = merge_three_blobs(&mb);
353
blob_reset(&pv);
354
blob_reset(&v1);
355
blob_reset(&v2);
356
/* mb has not allocated any resources, so we do not need to invoke
357
** the xDestroy method. */
358
blob_add_final_newline(pOut);
359
return nConflict;
360
}
361
362
363
/************************* MergeBuilderText **********************************/
364
/* This version of MergeBuilder actually performs a merge on file and puts
365
** the result in pOut
366
*/
367
static void txtStart(MergeBuilder *p){
368
/* If both pV1 and pV2 start with a UTF-8 byte-order-mark (BOM),
369
** keep it in the output. This should be secure enough not to cause
370
** unintended changes to the merged file and consistent with what
371
** users are using in their source files.
372
*/
373
if( starts_with_utf8_bom(p->pV1, 0) && starts_with_utf8_bom(p->pV2, 0) ){
374
blob_append(p->pOut, (char*)get_utf8_bom(0), -1);
375
}
376
if( contains_crlf(p->pV1) && contains_crlf(p->pV2) ){
377
p->useCrLf = 1;
378
}
379
}
380
static void txtSame(MergeBuilder *p, unsigned int N){
381
blob_copy_lines(p->pOut, p->pPivot, N); p->lnPivot += N;
382
blob_copy_lines(0, p->pV1, N); p->lnV1 += N;
383
blob_copy_lines(0, p->pV2, N); p->lnV2 += N;
384
}
385
static void txtChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
386
blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
387
blob_copy_lines(0, p->pV2, nPivot); p->lnV2 += nPivot;
388
blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
389
}
390
static void txtChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
391
blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
392
blob_copy_lines(0, p->pV1, nPivot); p->lnV1 += nPivot;
393
blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
394
}
395
static void txtChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
396
blob_copy_lines(0, p->pPivot, nPivot); p->lnPivot += nPivot;
397
blob_copy_lines(0, p->pV1, nV); p->lnV1 += nV;
398
blob_copy_lines(p->pOut, p->pV2, nV); p->lnV2 += nV;
399
}
400
static void txtConflict(
401
MergeBuilder *p,
402
unsigned int nPivot,
403
unsigned int nV1,
404
unsigned int nV2
405
){
406
int nRes; /* Lines in the computed conflict resolution */
407
Blob res; /* Text of the conflict resolution */
408
409
merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
410
nRes = blob_linecount(&res);
411
412
append_merge_mark(p->pOut, 0, p->lnV1+1, p->useCrLf);
413
blob_copy_lines(p->pOut, p->pV1, nV1); p->lnV1 += nV1;
414
415
if( nRes>0 ){
416
append_merge_mark(p->pOut, 1, 0, p->useCrLf);
417
blob_copy_lines(p->pOut, &res, nRes);
418
}
419
blob_reset(&res);
420
421
append_merge_mark(p->pOut, 2, p->lnPivot+1, p->useCrLf);
422
blob_copy_lines(p->pOut, p->pPivot, nPivot); p->lnPivot += nPivot;
423
424
append_merge_mark(p->pOut, 3, p->lnV2+1, p->useCrLf);
425
blob_copy_lines(p->pOut, p->pV2, nV2); p->lnV2 += nV2;
426
427
append_merge_mark(p->pOut, 4, -1, p->useCrLf);
428
}
429
static void mergebuilder_init_text(MergeBuilder *p){
430
mergebuilder_init(p);
431
p->xStart = txtStart;
432
p->xSame = txtSame;
433
p->xChngV1 = txtChngV1;
434
p->xChngV2 = txtChngV2;
435
p->xChngBoth = txtChngBoth;
436
p->xConflict = txtConflict;
437
}
438
439
/************************* MergeBuilderTcl **********************************/
440
/* Generate merge output formatted for reading by a TCL script.
441
**
442
** The output consists of lines of text, each with 4 tokens. The tokens
443
** represent the content for one line from baseline, v1, v2, and output
444
** respectively. The first character of each token provides auxiliary
445
** information:
446
**
447
** . This line is omitted.
448
** N Name of the file.
449
** T Literal text follows that should have a \n terminator.
450
** R Literal text follows that needs a \r\n terminator.
451
** X Merge conflict.
452
** Z Literal text without a line terminator.
453
** S Skipped lines. Followed by number of lines to skip.
454
** 1 Text is a copy of token 1
455
** 2 Use data from data-token 2
456
** 3 Use data from data-token 3
457
*/
458
459
/* Write text that goes into the interior of a double-quoted string in TCL */
460
static void tclWriteQuotedText(Blob *pOut, const char *zIn, int nIn){
461
int j;
462
for(j=0; j<nIn; j++){
463
char c = zIn[j];
464
if( c=='\\' ){
465
blob_append(pOut, "\\\\", 2);
466
}else if( c=='"' ){
467
blob_append(pOut, "\\\"", 2);
468
}else if( c<' ' || c>0x7e ){
469
char z[5];
470
z[0] = '\\';
471
z[1] = "01234567"[(c>>6)&0x3];
472
z[2] = "01234567"[(c>>3)&0x7];
473
z[3] = "01234567"[c&0x7];
474
z[4] = 0;
475
blob_append(pOut, z, 4);
476
}else{
477
blob_append_char(pOut, c);
478
}
479
}
480
}
481
482
/* Copy one line of text from pIn and append to pOut, encoded as TCL */
483
static void tclLineOfText(Blob *pOut, Blob *pIn, char cType){
484
int i, k;
485
for(i=pIn->iCursor; i<pIn->nUsed && pIn->aData[i]!='\n'; i++){}
486
if( i==pIn->nUsed ){
487
k = i;
488
}else if( i>pIn->iCursor && pIn->aData[i-1]=='\r' ){
489
k = i-1;
490
i++;
491
}else{
492
k = i;
493
i++;
494
}
495
blob_append_char(pOut, '"');
496
blob_append_char(pOut, cType);
497
tclWriteQuotedText(pOut, pIn->aData+pIn->iCursor, k-pIn->iCursor);
498
pIn->iCursor = i;
499
blob_append_char(pOut, '"');
500
}
501
static void tclStart(MergeBuilder *p){
502
Blob *pOut = p->pOut;
503
blob_append(pOut, "\"N", 2);
504
tclWriteQuotedText(pOut, p->zPivot, (int)strlen(p->zPivot));
505
blob_append(pOut, "\" \"N", 4);
506
tclWriteQuotedText(pOut, p->zV1, (int)strlen(p->zV1));
507
blob_append(pOut, "\" \"N", 4);
508
tclWriteQuotedText(pOut, p->zV2, (int)strlen(p->zV2));
509
blob_append(pOut, "\" \"N", 4);
510
if( p->zOut ){
511
tclWriteQuotedText(pOut, p->zOut, (int)strlen(p->zOut));
512
}else{
513
blob_append(pOut, "(Merge Result)", -1);
514
}
515
blob_append(pOut, "\"\n", 2);
516
}
517
static void tclSame(MergeBuilder *p, unsigned int N){
518
int i = 0;
519
int nSkip;
520
521
if( p->lnPivot>=2 || p->lnV1>2 || p->lnV2>2 ){
522
while( i<N && i<p->nContext ){
523
tclLineOfText(p->pOut, p->pPivot, 'T');
524
blob_append(p->pOut, " 1 1 1\n", 7);
525
i++;
526
}
527
nSkip = N - p->nContext*2;
528
}else{
529
nSkip = N - p->nContext;
530
}
531
if( nSkip>0 ){
532
blob_appendf(p->pOut, "\"S%d %d %d %d\" . . .\n",
533
nSkip, nSkip, nSkip, nSkip);
534
blob_copy_lines(0, p->pPivot, nSkip);
535
i += nSkip;
536
}
537
538
p->lnPivot += N;
539
p->lnV1 += N;
540
p->lnV2 += N;
541
542
if( p->lnPivot<p->mxPivot || p->lnV1<p->mxV1 || p->lnV2<p->mxV2 ){
543
while( i<N ){
544
tclLineOfText(p->pOut, p->pPivot, 'T');
545
blob_append(p->pOut, " 1 1 1\n", 7);
546
i++;
547
}
548
}
549
550
blob_copy_lines(0, p->pV1, N);
551
blob_copy_lines(0, p->pV2, N);
552
}
553
static void tclChngV1(MergeBuilder *p, unsigned int nPivot, unsigned int nV1){
554
int i;
555
for(i=0; i<nPivot && i<nV1; i++){
556
tclLineOfText(p->pOut, p->pPivot, 'T');
557
blob_append_char(p->pOut, ' ');
558
tclLineOfText(p->pOut, p->pV1, 'T');
559
blob_append(p->pOut, " 1 2\n", 5);
560
}
561
while( i<nPivot ){
562
tclLineOfText(p->pOut, p->pPivot, 'T');
563
blob_append(p->pOut, " . 1 .\n", 7);
564
i++;
565
}
566
while( i<nV1 ){
567
blob_append(p->pOut, ". ", 2);
568
tclLineOfText(p->pOut, p->pV1, 'T');
569
blob_append(p->pOut, " . 2\n", 5);
570
i++;
571
}
572
p->lnPivot += nPivot;
573
p->lnV1 += nV1;
574
p->lnV2 += nPivot;
575
blob_copy_lines(0, p->pV2, nPivot);
576
}
577
static void tclChngV2(MergeBuilder *p, unsigned int nPivot, unsigned int nV2){
578
int i;
579
for(i=0; i<nPivot && i<nV2; i++){
580
tclLineOfText(p->pOut, p->pPivot, 'T');
581
blob_append(p->pOut, " 1 ", 3);
582
tclLineOfText(p->pOut, p->pV2, 'T');
583
blob_append(p->pOut, " 3\n", 3);
584
}
585
while( i<nPivot ){
586
tclLineOfText(p->pOut, p->pPivot, 'T');
587
blob_append(p->pOut, " 1 . .\n", 7);
588
i++;
589
}
590
while( i<nV2 ){
591
blob_append(p->pOut, ". . ", 4);
592
tclLineOfText(p->pOut, p->pV2, 'T');
593
blob_append(p->pOut, " 3\n", 3);
594
i++;
595
}
596
p->lnPivot += nPivot;
597
p->lnV1 += nPivot;
598
p->lnV2 += nV2;
599
blob_copy_lines(0, p->pV1, nPivot);
600
}
601
static void tclChngBoth(MergeBuilder *p, unsigned int nPivot, unsigned int nV){
602
int i;
603
for(i=0; i<nPivot && i<nV; i++){
604
tclLineOfText(p->pOut, p->pPivot, 'T');
605
blob_append_char(p->pOut, ' ');
606
tclLineOfText(p->pOut, p->pV1, 'T');
607
blob_append(p->pOut, " 2 2\n", 5);
608
}
609
while( i<nPivot ){
610
tclLineOfText(p->pOut, p->pPivot, 'T');
611
blob_append(p->pOut, " . . .\n", 7);
612
i++;
613
}
614
while( i<nV ){
615
blob_append(p->pOut, ". ", 2);
616
tclLineOfText(p->pOut, p->pV1, 'T');
617
blob_append(p->pOut, " 2 2\n", 5);
618
i++;
619
}
620
p->lnPivot += nPivot;
621
p->lnV1 += nV;
622
p->lnV2 += nV;
623
blob_copy_lines(0, p->pV2, nV);
624
}
625
static void tclConflict(
626
MergeBuilder *p,
627
unsigned int nPivot,
628
unsigned int nV1,
629
unsigned int nV2
630
){
631
int mx = nPivot;
632
int i;
633
int nRes;
634
Blob res;
635
636
merge_try_to_resolve_conflict(p, nPivot, nV1, nV2, &res);
637
nRes = blob_linecount(&res);
638
if( nV1>mx ) mx = nV1;
639
if( nV2>mx ) mx = nV2;
640
if( nRes>mx ) mx = nRes;
641
if( nRes>0 ){
642
blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nV2+2);
643
}
644
for(i=0; i<mx; i++){
645
if( i<nPivot ){
646
tclLineOfText(p->pOut, p->pPivot, 'X');
647
}else{
648
blob_append_char(p->pOut, '.');
649
}
650
blob_append_char(p->pOut, ' ');
651
if( i<nV1 ){
652
tclLineOfText(p->pOut, p->pV1, 'X');
653
}else{
654
blob_append_char(p->pOut, '.');
655
}
656
blob_append_char(p->pOut, ' ');
657
if( i<nV2 ){
658
tclLineOfText(p->pOut, p->pV2, 'X');
659
}else{
660
blob_append_char(p->pOut, '.');
661
}
662
if( i<nRes ){
663
blob_append_char(p->pOut, ' ');
664
tclLineOfText(p->pOut, &res, 'X');
665
blob_append_char(p->pOut, '\n');
666
}else{
667
blob_append(p->pOut, " .\n", 3);
668
}
669
if( i==mx-1 ){
670
blob_appendf(p->pOut, "\"S0 0 0 %d\" . . .\n", nPivot+nV1+3);
671
}
672
}
673
blob_reset(&res);
674
p->lnPivot += nPivot;
675
p->lnV1 += nV1;
676
p->lnV2 += nV2;
677
}
678
void mergebuilder_init_tcl(MergeBuilder *p){
679
mergebuilder_init(p);
680
p->xStart = tclStart;
681
p->xSame = tclSame;
682
p->xChngV1 = tclChngV1;
683
p->xChngV2 = tclChngV2;
684
p->xChngBoth = tclChngBoth;
685
p->xConflict = tclConflict;
686
}
687
/*****************************************************************************/
688
689
/*
690
** The aC[] array contains triples of integers. Within each triple, the
691
** elements are:
692
**
693
** (0) The number of lines to copy
694
** (1) The number of lines to delete
695
** (2) The number of liens to insert
696
**
697
** Suppose we want to advance over sz lines of the original file. This routine
698
** returns true if that advance would land us on a copy operation. It
699
** returns false if the advance would end on a delete.
700
*/
701
static int ends_with_copy(int *aC, int sz){
702
while( sz>0 && (aC[0]>0 || aC[1]>0 || aC[2]>0) ){
703
if( aC[0]>=sz ) return 1;
704
sz -= aC[0];
705
if( aC[1]>sz ) return 0;
706
sz -= aC[1];
707
aC += 3;
708
}
709
return 1;
710
}
711
712
/*
713
** aC[] is an "edit triple" for changes from A to B. Advance through
714
** this triple to determine the number of lines to bypass on B in order
715
** to match an advance of sz lines on A.
716
*/
717
static int skip_conflict(
718
int *aC, /* Array of integer triples describing the edit */
719
int i, /* Index in aC[] of current location */
720
int sz, /* Lines of A that have been skipped */
721
unsigned int *pLn /* OUT: Lines of B to skip to keep alignment with A */
722
){
723
*pLn = 0;
724
while( sz>0 ){
725
if( aC[i]==0 && aC[i+1]==0 && aC[i+2]==0 ) break;
726
if( aC[i]>=sz ){
727
aC[i] -= sz;
728
*pLn += sz;
729
break;
730
}
731
*pLn += aC[i];
732
*pLn += aC[i+2];
733
sz -= aC[i] + aC[i+1];
734
i += 3;
735
}
736
return i;
737
}
738
739
/*
740
** Do a three-way merge. Initialize pOut to contain the result.
741
**
742
** The merge is an edit against pV2. Both pV1 and pV2 have a
743
** common origin at pPivot. Apply the changes of pPivot ==> pV1
744
** to pV2.
745
**
746
** The return is 0 upon complete success. If any input file is binary,
747
** -1 is returned and pOut is unmodified. If there are merge
748
** conflicts, the merge proceeds as best as it can and the number
749
** of conflicts is returns
750
*/
751
int merge_three_blobs(MergeBuilder *p){
752
int *aC1; /* Changes from pPivot to pV1 */
753
int *aC2; /* Changes from pPivot to pV2 */
754
int i1, i2; /* Index into aC1[] and aC2[] */
755
int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */
756
int limit1, limit2; /* Sizes of aC1[] and aC2[] */
757
int nConflict = 0; /* Number of merge conflicts seen so far */
758
DiffConfig DCfg;
759
760
/* Compute the edits that occur from pPivot => pV1 (into aC1)
761
** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is
762
** an array of integer triples. Within each triple, the first integer
763
** is the number of lines of text to copy directly from the pivot,
764
** the second integer is the number of lines of text to omit from the
765
** pivot, and the third integer is the number of lines of text that are
766
** inserted. The edit array ends with a triple of 0,0,0.
767
*/
768
diff_config_init(&DCfg, 0);
769
DCfg.diffFlags = p->diffFlags;
770
aC1 = text_diff(p->pPivot, p->pV1, 0, &DCfg);
771
aC2 = text_diff(p->pPivot, p->pV2, 0, &DCfg);
772
if( aC1==0 || aC2==0 ){
773
free(aC1);
774
free(aC2);
775
return -1;
776
}
777
778
blob_rewind(p->pV1); /* Rewind inputs: Needed to reconstruct output */
779
blob_rewind(p->pV2);
780
blob_rewind(p->pPivot);
781
782
/* Determine the length of the aC1[] and aC2[] change vectors */
783
p->mxPivot = 0;
784
p->mxV1 = 0;
785
for(i1=0; aC1[i1] || aC1[i1+1] || aC1[i1+2]; i1+=3){
786
p->mxPivot += aC1[i1] + aC1[i1+1];
787
p->mxV1 += aC1[i1] + aC1[i1+2];
788
}
789
limit1 = i1;
790
p->mxV2 = 0;
791
for(i2=0; aC2[i2] || aC2[i2+1] || aC2[i2+2]; i2+=3){
792
p->mxV2 += aC2[i2] + aC2[i2+2];
793
}
794
limit2 = i2;
795
796
/* Output header text and do any other required initialization */
797
p->xStart(p);
798
799
/* Loop over the two edit vectors and use them to compute merged text
800
** which is written into pOut. i1 and i2 are multiples of 3 which are
801
** indices into aC1[] and aC2[] to the edit triple currently being
802
** processed
803
*/
804
i1 = i2 = 0;
805
while( i1<limit1 && i2<limit2 ){
806
if( aC1[i1]>0 && aC2[i2]>0 ){
807
/* Output text that is unchanged in both V1 and V2 */
808
nCpy = min(aC1[i1], aC2[i2]);
809
p->xSame(p, nCpy);
810
aC1[i1] -= nCpy;
811
aC2[i2] -= nCpy;
812
}else
813
if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){
814
/* Output edits to V2 that occurs within unchanged regions of V1 */
815
nDel = aC2[i2+1];
816
nIns = aC2[i2+2];
817
p->xChngV2(p, nDel, nIns);
818
aC1[i1] -= nDel;
819
i2 += 3;
820
}else
821
if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){
822
/* Output edits to V1 that occur within unchanged regions of V2 */
823
nDel = aC1[i1+1];
824
nIns = aC1[i1+2];
825
p->xChngV1(p, nDel, nIns);
826
aC2[i2] -= nDel;
827
i1 += 3;
828
}else
829
if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){
830
/* Output edits that are identical in both V1 and V2. */
831
assert( aC1[i1]==0 );
832
nDel = aC1[i1+1];
833
nIns = aC1[i1+2];
834
p->xChngBoth(p, nDel, nIns);
835
i1 += 3;
836
i2 += 3;
837
}else
838
{
839
/* We have found a region where different edits to V1 and V2 overlap.
840
** This is a merge conflict. Find the size of the conflict, then
841
** output both possible edits separated by distinctive marks.
842
*/
843
unsigned int sz = 1; /* Size of the conflict in the pivot, in lines */
844
unsigned int nV1, nV2; /* Size of conflict in V1 and V2, in lines */
845
nConflict++;
846
while( !ends_with_copy(&aC1[i1], sz) || !ends_with_copy(&aC2[i2], sz) ){
847
sz++;
848
}
849
i1 = skip_conflict(aC1, i1, sz, &nV1);
850
i2 = skip_conflict(aC2, i2, sz, &nV2);
851
p->xConflict(p, sz, nV1, nV2);
852
}
853
854
/* If we are finished with an edit triple, advance to the next
855
** triple.
856
*/
857
if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3;
858
if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3;
859
}
860
861
/* When one of the two edit vectors reaches its end, there might still
862
** be an insert in the other edit vector. Output this remaining
863
** insert.
864
*/
865
if( i1<limit1 && aC1[i1+2]>0 ){
866
p->xChngV1(p, 0, aC1[i1+2]);
867
}else if( i2<limit2 && aC2[i2+2]>0 ){
868
p->xChngV2(p, 0, aC2[i2+2]);
869
}
870
871
/* Output footer text */
872
p->xEnd(p);
873
874
free(aC1);
875
free(aC2);
876
return nConflict;
877
}
878
879
/*
880
** Return true if the input string contains a merge marker on a line by
881
** itself.
882
*/
883
int contains_merge_marker(Blob *p){
884
int i, j;
885
int len = (int)strlen(mergeMarker[0]);
886
const char *z = blob_buffer(p);
887
int n = blob_size(p) - len + 1;
888
assert( len==(int)strlen(mergeMarker[1]) );
889
assert( len==(int)strlen(mergeMarker[2]) );
890
assert( len==(int)strlen(mergeMarker[3]) );
891
assert( len==(int)strlen(mergeMarker[4]) );
892
assert( count(mergeMarker)==5 );
893
for(i=0; i<n; ){
894
for(j=0; j<4; j++){
895
if( (memcmp(&z[i], mergeMarker[j], len)==0) ){
896
return 1;
897
}
898
}
899
while( i<n && z[i]!='\n' ){ i++; }
900
while( i<n && (z[i]=='\n' || z[i]=='\r') ){ i++; }
901
}
902
return 0;
903
}
904
905
/*
906
** Return true if the named file contains an unresolved merge marker line.
907
*/
908
int file_contains_merge_marker(const char *zFullpath){
909
Blob file;
910
int rc;
911
blob_read_from_file(&file, zFullpath, ExtFILE);
912
rc = contains_merge_marker(&file);
913
blob_reset(&file);
914
return rc;
915
}
916
917
/*
918
** Show merge output in a Tcl/Tk window, in response to the --tk option
919
** to the "merge" or "3-way-merge" command.
920
**
921
** If fossil has direct access to a Tcl interpreter (either loaded
922
** dynamically through stubs or linked in statically), we can use it
923
** directly. Otherwise:
924
** (1) Write the Tcl/Tk script used for rendering into a temp file.
925
** (2) Invoke "tclsh" on the temp file using fossil_system().
926
** (3) Delete the temp file.
927
*/
928
void merge_tk(const char *zSubCmd, int firstArg){
929
int i;
930
Blob script;
931
const char *zTempFile = 0;
932
char *zCmd;
933
const char *zTclsh;
934
const char *zCnt;
935
int bDarkMode = find_option("dark",0,0)!=0;
936
int nContext;
937
zCnt = find_option("context", "c", 1);
938
if( zCnt==0 ){
939
nContext = 6;
940
}else{
941
nContext = atoi(zCnt);
942
if( nContext<0 ) nContext = 0xfffffff;
943
}
944
blob_zero(&script);
945
blob_appendf(&script, "set ncontext %d\n", nContext);
946
blob_appendf(&script, "set fossilcmd {| \"%/\" %s -tcl",
947
g.nameOfExe, zSubCmd);
948
find_option("tcl",0,0);
949
find_option("debug",0,0);
950
zTclsh = find_option("tclsh",0,1);
951
if( zTclsh==0 ){
952
zTclsh = db_get("tclsh",0);
953
}
954
/* The undocumented --script FILENAME option causes the Tk script to
955
** be written into the FILENAME instead of being run. This is used
956
** for testing and debugging. */
957
zTempFile = find_option("script",0,1);
958
verify_all_options();
959
960
if( (g.argc - firstArg)!=3 ){
961
fossil_fatal("Requires 3 filename arguments");
962
}
963
964
for(i=firstArg; i<g.argc; i++){
965
const char *z = g.argv[i];
966
if( sqlite3_strglob("*}*",z) ){
967
blob_appendf(&script, " {%/}", z);
968
}else{
969
int j;
970
blob_append(&script, " ", 1);
971
for(j=0; z[j]; j++) blob_appendf(&script, "\\%03o", (unsigned char)z[j]);
972
}
973
}
974
blob_appendf(&script, "}\nset darkmode %d\n", bDarkMode);
975
blob_appendf(&script, "%s", builtin_file("merge.tcl", 0));
976
if( zTempFile ){
977
blob_write_to_file(&script, zTempFile);
978
fossil_print("To see the merge, run: %s \"%s\"\n", zTclsh, zTempFile);
979
}else{
980
#if defined(FOSSIL_ENABLE_TCL)
981
Th_FossilInit(TH_INIT_DEFAULT);
982
if( evaluateTclWithEvents(g.interp, &g.tcl, blob_str(&script),
983
blob_size(&script), 1, 1, 0)==TCL_OK ){
984
blob_reset(&script);
985
return;
986
}
987
/*
988
* If evaluation of the Tcl script fails, the reason may be that Tk
989
* could not be found by the loaded Tcl, or that Tcl cannot be loaded
990
* dynamically (e.g. x64 Tcl with x86 Fossil). Therefore, fallback
991
* to using the external "tclsh", if available.
992
*/
993
#endif
994
zTempFile = write_blob_to_temp_file(&script);
995
zCmd = mprintf("%$ %$", zTclsh, zTempFile);
996
fossil_system(zCmd);
997
file_delete(zTempFile);
998
fossil_free(zCmd);
999
}
1000
blob_reset(&script);
1001
}
1002
1003
1004
/*
1005
** COMMAND: 3-way-merge*
1006
**
1007
** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED]
1008
**
1009
** Inputs are files BASELINE, V1, and V2. The file MERGED is generated
1010
** as output. If no MERGED file is specified, output is sent to
1011
** stdout.
1012
**
1013
** BASELINE is a common ancestor of two files V1 and V2 that have diverging
1014
** edits. The generated output file MERGED is the combination of all
1015
** changes in both V1 and V2.
1016
**
1017
** This command has no effect on the Fossil repository. It is a utility
1018
** command made available for the convenience of users. This command can
1019
** be used, for example, to help import changes from an upstream project.
1020
**
1021
** Suppose an upstream project has a file named "Xup.c" which is imported
1022
** with modifications to the local project as "Xlocal.c". Suppose further
1023
** that the "Xbase.c" is an exact copy of the last imported "Xup.c".
1024
** Then to import the latest "Xup.c" while preserving all the local changes:
1025
**
1026
** fossil 3-way-merge Xbase.c Xlocal.c Xup.c Xlocal.c
1027
** cp Xup.c Xbase.c
1028
** # Verify that everything still works
1029
** fossil commit
1030
**
1031
*/
1032
void merge_3way_cmd(void){
1033
MergeBuilder s;
1034
int nConflict;
1035
Blob pivot, v1, v2, out;
1036
int noWarn = 0;
1037
const char *zCnt;
1038
1039
if( find_option("tk", 0, 0)!=0 ){
1040
merge_tk("3-way-merge", 2);
1041
return;
1042
}
1043
mergebuilder_init_text(&s);
1044
if( find_option("debug", 0, 0) ){
1045
mergebuilder_init(&s);
1046
}
1047
if( find_option("tcl", 0, 0) ){
1048
mergebuilder_init_tcl(&s);
1049
noWarn = 1;
1050
}
1051
zCnt = find_option("context", "c", 1);
1052
if( zCnt ){
1053
s.nContext = atoi(zCnt);
1054
if( s.nContext<0 ) s.nContext = 0xfffffff;
1055
}else{
1056
s.nContext = 6;
1057
}
1058
blob_zero(&pivot); s.pPivot = &pivot;
1059
blob_zero(&v1); s.pV1 = &v1;
1060
blob_zero(&v2); s.pV2 = &v2;
1061
blob_zero(&out); s.pOut = &out;
1062
1063
/* We should be done with options.. */
1064
verify_all_options();
1065
1066
if( g.argc!=6 && g.argc!=5 ){
1067
usage("[OPTIONS] PIVOT V1 V2 [MERGED]");
1068
}
1069
s.zPivot = file_tail(g.argv[2]);
1070
s.zV1 = file_tail(g.argv[3]);
1071
s.zV2 = file_tail(g.argv[4]);
1072
if( blob_read_from_file(s.pPivot, g.argv[2], ExtFILE)<0 ){
1073
fossil_fatal("cannot read %s", g.argv[2]);
1074
}
1075
if( blob_read_from_file(s.pV1, g.argv[3], ExtFILE)<0 ){
1076
fossil_fatal("cannot read %s", g.argv[3]);
1077
}
1078
if( blob_read_from_file(s.pV2, g.argv[4], ExtFILE)<0 ){
1079
fossil_fatal("cannot read %s", g.argv[4]);
1080
}
1081
nConflict = merge_three_blobs(&s);
1082
if( g.argc==6 ){
1083
s.zOut = file_tail(g.argv[5]);
1084
blob_write_to_file(s.pOut, g.argv[5]);
1085
}else{
1086
s.zOut = "(Merge Result)";
1087
blob_write_to_file(s.pOut, "-");
1088
}
1089
s.xDestroy(&s);
1090
blob_reset(&pivot);
1091
blob_reset(&v1);
1092
blob_reset(&v2);
1093
blob_reset(&out);
1094
if( nConflict>0 && !noWarn ){
1095
fossil_warning("WARNING: %d merge conflicts", nConflict);
1096
}
1097
}
1098
1099
/*
1100
** aSubst is an array of string pairs. The first element of each pair is
1101
** a string that begins with %. The second element is a replacement for that
1102
** string.
1103
**
1104
** This routine makes a copy of zInput into memory obtained from malloc and
1105
** performance all applicable substitutions on that string.
1106
*/
1107
char *string_subst(const char *zInput, int nSubst, const char **azSubst){
1108
Blob x;
1109
int i, j;
1110
blob_zero(&x);
1111
while( zInput[0] ){
1112
for(i=0; zInput[i] && zInput[i]!='%'; i++){}
1113
if( i>0 ){
1114
blob_append(&x, zInput, i);
1115
zInput += i;
1116
}
1117
if( zInput[0]==0 ) break;
1118
for(j=0; j<nSubst; j+=2){
1119
int n = strlen(azSubst[j]);
1120
if( strncmp(zInput, azSubst[j], n)==0 ){
1121
blob_append(&x, azSubst[j+1], -1);
1122
zInput += n;
1123
break;
1124
}
1125
}
1126
if( j>=nSubst ){
1127
blob_append(&x, "%", 1);
1128
zInput++;
1129
}
1130
}
1131
return blob_str(&x);
1132
}
1133
1134
#if INTERFACE
1135
/*
1136
** Flags to the 3-way merger
1137
*/
1138
#define MERGE_DRYRUN 0x0001
1139
/*
1140
** The MERGE_KEEP_FILES flag specifies that merge_3way() should retain
1141
** its temporary files on error. By default they are removed after the
1142
** merge, regardless of success or failure.
1143
*/
1144
#define MERGE_KEEP_FILES 0x0002
1145
#endif
1146
1147
1148
/*
1149
** This routine is a wrapper around merge_three_blobs() with the following
1150
** enhancements:
1151
**
1152
** (1) If the merge-command is defined, then use the external merging
1153
** program specified instead of the built-in blob-merge to do the
1154
** merging. Panic if the external merger fails.
1155
** ** Not currently implemented **
1156
**
1157
** (2) If gmerge-command is defined and there are merge conflicts in
1158
** merge_three_blobs() then invoke the external graphical merger
1159
** to resolve the conflicts.
1160
**
1161
** (3) If a merge conflict occurs and gmerge-command is not defined,
1162
** then write the pivot, original, and merge-in files to the
1163
** filesystem.
1164
*/
1165
int merge_3way(
1166
Blob *pPivot, /* Common ancestor (older) */
1167
const char *zV1, /* Name of file for version merging into (mine) */
1168
Blob *pV2, /* Version merging from (yours) */
1169
Blob *pOut, /* Output written here */
1170
unsigned mergeFlags /* Flags that control operation */
1171
){
1172
Blob v1; /* Content of zV1 */
1173
int rc; /* Return code of subroutines and this routine */
1174
const char *zGMerge; /* Name of the gmerge command */
1175
MergeBuilder s; /* The merge state */
1176
1177
mergebuilder_init_text(&s);
1178
s.pPivot = pPivot;
1179
s.pV1 = &v1;
1180
s.pV2 = pV2;
1181
blob_zero(pOut);
1182
s.pOut = pOut;
1183
blob_read_from_file(s.pV1, zV1, ExtFILE);
1184
rc = merge_three_blobs(&s);
1185
zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0);
1186
if( (mergeFlags & MERGE_DRYRUN)==0
1187
&& ((zGMerge!=0 && zGMerge[0]!=0)
1188
|| (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){
1189
char *zPivot; /* Name of the pivot file */
1190
char *zOrig; /* Name of the original content file */
1191
char *zOther; /* Name of the merge file */
1192
1193
zPivot = file_newname(zV1, "baseline", 1);
1194
blob_write_to_file(s.pPivot, zPivot);
1195
zOrig = file_newname(zV1, "original", 1);
1196
blob_write_to_file(s.pV1, zOrig);
1197
zOther = file_newname(zV1, "merge", 1);
1198
blob_write_to_file(s.pV2, zOther);
1199
if( rc>0 ){
1200
if( zGMerge && zGMerge[0] ){
1201
char *zOut; /* Temporary output file */
1202
char *zCmd; /* Command to invoke */
1203
const char *azSubst[8]; /* Strings to be substituted */
1204
zOut = file_newname(zV1, "output", 1);
1205
azSubst[0] = "%baseline"; azSubst[1] = zPivot;
1206
azSubst[2] = "%original"; azSubst[3] = zOrig;
1207
azSubst[4] = "%merge"; azSubst[5] = zOther;
1208
azSubst[6] = "%output"; azSubst[7] = zOut;
1209
zCmd = string_subst(zGMerge, 8, azSubst);
1210
printf("%s\n", zCmd); fflush(stdout);
1211
fossil_system(zCmd);
1212
if( file_size(zOut, RepoFILE)>=0 ){
1213
blob_read_from_file(pOut, zOut, ExtFILE);
1214
file_delete(zOut);
1215
}
1216
fossil_free(zCmd);
1217
fossil_free(zOut);
1218
}
1219
}
1220
if( (mergeFlags & MERGE_KEEP_FILES)==0 ){
1221
file_delete(zPivot);
1222
file_delete(zOrig);
1223
file_delete(zOther);
1224
}
1225
fossil_free(zPivot);
1226
fossil_free(zOrig);
1227
fossil_free(zOther);
1228
}
1229
blob_reset(&v1);
1230
s.xDestroy(&s);
1231
return rc;
1232
}
1233

Keyboard Shortcuts

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