| | @@ -75,69 +75,10 @@ |
| 75 | 75 | if( aC1[2]!=aC2[2] ) return 0; |
| 76 | 76 | if( sameLines(pV1, pV2, aC1[2]) ) return 1; |
| 77 | 77 | return 0; |
| 78 | 78 | } |
| 79 | 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 | 80 | /* |
| 140 | 81 | ** Text of boundary markers for merge conflicts. |
| 141 | 82 | */ |
| 142 | 83 | static const char *const mergeMarker[] = { |
| 143 | 84 | /*123456789 123456789 123456789 123456789 123456789 123456789 123456789*/ |
| | @@ -186,10 +127,489 @@ |
| 186 | 127 | ensure_line_end(pOut, useCrLf); |
| 187 | 128 | blob_append(pOut, mergeMarker[iMark], -1); |
| 188 | 129 | if( ln>0 ) blob_appendf(pOut, " (line %d)", ln); |
| 189 | 130 | ensure_line_end(pOut, useCrLf); |
| 190 | 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 | +} |
| 191 | 611 | |
| 192 | 612 | /* |
| 193 | 613 | ** Do a three-way merge. Initialize pOut to contain the result. |
| 194 | 614 | ** |
| 195 | 615 | ** The merge is an edit against pV2. Both pV1 and pV2 have a |
| | @@ -199,156 +619,112 @@ |
| 199 | 619 | ** The return is 0 upon complete success. If any input file is binary, |
| 200 | 620 | ** -1 is returned and pOut is unmodified. If there are merge |
| 201 | 621 | ** conflicts, the merge proceeds as best as it can and the number |
| 202 | 622 | ** of conflicts is returns |
| 203 | 623 | */ |
| 204 | | -static int blob_merge(Blob *pPivot, Blob *pV1, Blob *pV2, Blob *pOut){ |
| 624 | +int merge_three_blobs(MergeBuilder *p){ |
| 205 | 625 | int *aC1; /* Changes from pPivot to pV1 */ |
| 206 | 626 | int *aC2; /* Changes from pPivot to pV2 */ |
| 207 | 627 | int i1, i2; /* Index into aC1[] and aC2[] */ |
| 208 | 628 | int nCpy, nDel, nIns; /* Number of lines to copy, delete, or insert */ |
| 209 | 629 | int limit1, limit2; /* Sizes of aC1[] and aC2[] */ |
| 210 | 630 | 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 | 631 | DiffConfig DCfg; |
| 214 | 632 | |
| 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 | 633 | /* Compute the edits that occur from pPivot => pV1 (into aC1) |
| 235 | 634 | ** and pPivot => pV2 (into aC2). Each of the aC1 and aC2 arrays is |
| 236 | 635 | ** an array of integer triples. Within each triple, the first integer |
| 237 | 636 | ** is the number of lines of text to copy directly from the pivot, |
| 238 | 637 | ** the second integer is the number of lines of text to omit from the |
| 239 | 638 | ** pivot, and the third integer is the number of lines of text that are |
| 240 | 639 | ** inserted. The edit array ends with a triple of 0,0,0. |
| 241 | 640 | */ |
| 242 | 641 | 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); |
| 245 | 644 | if( aC1==0 || aC2==0 ){ |
| 246 | 645 | free(aC1); |
| 247 | 646 | free(aC2); |
| 248 | 647 | return -1; |
| 249 | 648 | } |
| 250 | 649 | |
| 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); |
| 254 | 653 | |
| 255 | 654 | /* 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 | + } |
| 257 | 661 | 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 | + } |
| 259 | 666 | limit2 = i2; |
| 260 | 667 | |
| 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); |
| 269 | 670 | |
| 270 | 671 | /* Loop over the two edit vectors and use them to compute merged text |
| 271 | 672 | ** which is written into pOut. i1 and i2 are multiples of 3 which are |
| 272 | 673 | ** indices into aC1[] and aC2[] to the edit triple currently being |
| 273 | 674 | ** processed |
| 274 | 675 | */ |
| 275 | 676 | i1 = i2 = 0; |
| 276 | | - ln1 = ln2 = lnPivot = 1; |
| 277 | 677 | 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 | 678 | if( aC1[i1]>0 && aC2[i2]>0 ){ |
| 283 | 679 | /* Output text that is unchanged in both V1 and V2 */ |
| 284 | 680 | 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); |
| 289 | 682 | aC1[i1] -= nCpy; |
| 290 | 683 | aC2[i2] -= nCpy; |
| 291 | 684 | }else |
| 292 | 685 | if( aC1[i1] >= aC2[i2+1] && aC1[i1]>0 && aC2[i2+1]+aC2[i2+2]>0 ){ |
| 293 | 686 | /* Output edits to V2 that occurs within unchanged regions of V1 */ |
| 294 | 687 | nDel = aC2[i2+1]; |
| 295 | 688 | 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); |
| 300 | 690 | aC1[i1] -= nDel; |
| 301 | 691 | i2 += 3; |
| 302 | 692 | }else |
| 303 | 693 | if( aC2[i2] >= aC1[i1+1] && aC2[i2]>0 && aC1[i1+1]+aC1[i1+2]>0 ){ |
| 304 | 694 | /* Output edits to V1 that occur within unchanged regions of V2 */ |
| 305 | 695 | nDel = aC1[i1+1]; |
| 306 | 696 | 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); |
| 311 | 698 | aC2[i2] -= nDel; |
| 312 | 699 | i1 += 3; |
| 313 | 700 | }else |
| 314 | | - if( sameEdit(&aC1[i1], &aC2[i2], pV1, pV2) ){ |
| 701 | + if( sameEdit(&aC1[i1], &aC2[i2], p->pV1, p->pV2) ){ |
| 315 | 702 | /* Output edits that are identical in both V1 and V2. */ |
| 316 | 703 | assert( aC1[i1]==0 ); |
| 317 | 704 | nDel = aC1[i1+1]; |
| 318 | 705 | 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); |
| 323 | 707 | i1 += 3; |
| 324 | 708 | i2 += 3; |
| 325 | 709 | }else |
| 326 | 710 | { |
| 327 | 711 | /* We have found a region where different edits to V1 and V2 overlap. |
| 328 | 712 | ** This is a merge conflict. Find the size of the conflict, then |
| 329 | 713 | ** output both possible edits separated by distinctive marks. |
| 330 | 714 | */ |
| 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 */ |
| 332 | 717 | 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) ){ |
| 334 | 719 | sz++; |
| 335 | 720 | } |
| 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 | + |
| 350 | 726 | /* If we are finished with an edit triple, advance to the next |
| 351 | 727 | ** triple. |
| 352 | 728 | */ |
| 353 | 729 | if( i1<limit1 && aC1[i1]==0 && aC1[i1+1]==0 && aC1[i1+2]==0 ) i1+=3; |
| 354 | 730 | if( i2<limit2 && aC2[i2]==0 && aC2[i2+1]==0 && aC2[i2+2]==0 ) i2+=3; |
| | @@ -356,20 +732,18 @@ |
| 356 | 732 | |
| 357 | 733 | /* When one of the two edit vectors reaches its end, there might still |
| 358 | 734 | ** be an insert in the other edit vector. Output this remaining |
| 359 | 735 | ** insert. |
| 360 | 736 | */ |
| 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 | 737 | 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]); |
| 367 | 739 | }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]); |
| 370 | 741 | } |
| 742 | + |
| 743 | + /* Output footer text */ |
| 744 | + p->xEnd(p); |
| 371 | 745 | |
| 372 | 746 | free(aC1); |
| 373 | 747 | free(aC2); |
| 374 | 748 | return nConflict; |
| 375 | 749 | } |
| | @@ -408,18 +782,105 @@ |
| 408 | 782 | blob_read_from_file(&file, zFullpath, ExtFILE); |
| 409 | 783 | rc = contains_merge_marker(&file); |
| 410 | 784 | blob_reset(&file); |
| 411 | 785 | return rc; |
| 412 | 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 | + |
| 413 | 873 | |
| 414 | 874 | /* |
| 415 | 875 | ** COMMAND: 3-way-merge* |
| 416 | 876 | ** |
| 417 | | -** Usage: %fossil 3-way-merge BASELINE V1 V2 MERGED |
| 877 | +** Usage: %fossil 3-way-merge BASELINE V1 V2 [MERGED] |
| 418 | 878 | ** |
| 419 | 879 | ** 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. |
| 421 | 882 | ** |
| 422 | 883 | ** BASELINE is a common ancestor of two files V1 and V2 that have diverging |
| 423 | 884 | ** edits. The generated output file MERGED is the combination of all |
| 424 | 885 | ** changes in both V1 and V2. |
| 425 | 886 | ** |
| | @@ -436,38 +897,75 @@ |
| 436 | 897 | ** cp Xup.c Xbase.c |
| 437 | 898 | ** # Verify that everything still works |
| 438 | 899 | ** fossil commit |
| 439 | 900 | ** |
| 440 | 901 | */ |
| 441 | | -void delta_3waymerge_cmd(void){ |
| 442 | | - Blob pivot, v1, v2, merged; |
| 902 | +void merge_3way_cmd(void){ |
| 903 | + MergeBuilder s; |
| 443 | 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; |
| 444 | 932 | |
| 445 | 933 | /* We should be done with options.. */ |
| 446 | 934 | verify_all_options(); |
| 447 | 935 | |
| 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]"); |
| 450 | 938 | } |
| 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 ){ |
| 452 | 943 | fossil_fatal("cannot read %s", g.argv[2]); |
| 453 | 944 | } |
| 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 ){ |
| 455 | 946 | fossil_fatal("cannot read %s", g.argv[3]); |
| 456 | 947 | } |
| 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 ){ |
| 458 | 949 | fossil_fatal("cannot read %s", g.argv[4]); |
| 459 | 950 | } |
| 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, "-"); |
| 463 | 958 | } |
| 959 | + s.xDestroy(&s); |
| 464 | 960 | blob_reset(&pivot); |
| 465 | 961 | blob_reset(&v1); |
| 466 | 962 | 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 | + } |
| 469 | 967 | } |
| 470 | 968 | |
| 471 | 969 | /* |
| 472 | 970 | ** aSubst is an array of string pairs. The first element of each pair is |
| 473 | 971 | ** a string that begins with %. The second element is a replacement for that |
| | @@ -516,21 +1014,21 @@ |
| 516 | 1014 | #define MERGE_KEEP_FILES 0x0002 |
| 517 | 1015 | #endif |
| 518 | 1016 | |
| 519 | 1017 | |
| 520 | 1018 | /* |
| 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 |
| 522 | 1020 | ** enhancements: |
| 523 | 1021 | ** |
| 524 | 1022 | ** (1) If the merge-command is defined, then use the external merging |
| 525 | 1023 | ** program specified instead of the built-in blob-merge to do the |
| 526 | 1024 | ** merging. Panic if the external merger fails. |
| 527 | 1025 | ** ** Not currently implemented ** |
| 528 | 1026 | ** |
| 529 | 1027 | ** (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. |
| 532 | 1030 | ** |
| 533 | 1031 | ** (3) If a merge conflict occurs and gmerge-command is not defined, |
| 534 | 1032 | ** then write the pivot, original, and merge-in files to the |
| 535 | 1033 | ** filesystem. |
| 536 | 1034 | */ |
| | @@ -539,30 +1037,37 @@ |
| 539 | 1037 | const char *zV1, /* Name of file for version merging into (mine) */ |
| 540 | 1038 | Blob *pV2, /* Version merging from (yours) */ |
| 541 | 1039 | Blob *pOut, /* Output written here */ |
| 542 | 1040 | unsigned mergeFlags /* Flags that control operation */ |
| 543 | 1041 | ){ |
| 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 */ |
| 546 | 1044 | const char *zGMerge; /* Name of the gmerge command */ |
| 1045 | + MergeBuilder s; /* The merge state */ |
| 547 | 1046 | |
| 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); |
| 550 | 1055 | zGMerge = rc<=0 ? 0 : db_get("gmerge-command", 0); |
| 551 | 1056 | if( (mergeFlags & MERGE_DRYRUN)==0 |
| 552 | 1057 | && ((zGMerge!=0 && zGMerge[0]!=0) |
| 553 | 1058 | || (rc!=0 && (mergeFlags & MERGE_KEEP_FILES)!=0)) ){ |
| 554 | 1059 | char *zPivot; /* Name of the pivot file */ |
| 555 | 1060 | char *zOrig; /* Name of the original content file */ |
| 556 | 1061 | char *zOther; /* Name of the merge file */ |
| 557 | 1062 | |
| 558 | 1063 | zPivot = file_newname(zV1, "baseline", 1); |
| 559 | | - blob_write_to_file(pPivot, zPivot); |
| 1064 | + blob_write_to_file(s.pPivot, zPivot); |
| 560 | 1065 | zOrig = file_newname(zV1, "original", 1); |
| 561 | | - blob_write_to_file(&v1, zOrig); |
| 1066 | + blob_write_to_file(s.pV1, zOrig); |
| 562 | 1067 | zOther = file_newname(zV1, "merge", 1); |
| 563 | | - blob_write_to_file(pV2, zOther); |
| 1068 | + blob_write_to_file(s.pV2, zOther); |
| 564 | 1069 | if( rc>0 ){ |
| 565 | 1070 | if( zGMerge && zGMerge[0] ){ |
| 566 | 1071 | char *zOut; /* Temporary output file */ |
| 567 | 1072 | char *zCmd; /* Command to invoke */ |
| 568 | 1073 | const char *azSubst[8]; /* Strings to be substituted */ |
| | @@ -589,8 +1094,8 @@ |
| 589 | 1094 | } |
| 590 | 1095 | fossil_free(zPivot); |
| 591 | 1096 | fossil_free(zOrig); |
| 592 | 1097 | fossil_free(zOther); |
| 593 | 1098 | } |
| 594 | | - blob_reset(&v1); |
| 1099 | + s.xDestroy(&s); |
| 595 | 1100 | return rc; |
| 596 | 1101 | } |
| 597 | 1102 | |