| | @@ -21,10 +21,22 @@ |
| 21 | 21 | #include "config.h" |
| 22 | 22 | #include "diff.h" |
| 23 | 23 | #include <assert.h> |
| 24 | 24 | |
| 25 | 25 | |
| 26 | +#if INTERFACE |
| 27 | +/* |
| 28 | +** Allowed flag parameters to the text_diff() and html_sbsdiff() funtions: |
| 29 | +*/ |
| 30 | +#define DIFF_CONTEXT_MASK 0x0000fff /* Lines of context. Default if 0 */ |
| 31 | +#define DIFF_WIDTH_MASK 0x00ff000 /* side-by-side column width */ |
| 32 | +#define DIFF_IGNORE_EOLWS 0x0100000 /* Ignore end-of-line whitespace */ |
| 33 | +#define DIFF_SIDEBYSIDE 0x0200000 /* Generate a side-by-side diff */ |
| 34 | +#define DIFF_NEWFILE 0x0400000 /* Missing files are as empty files */ |
| 35 | + |
| 36 | +#endif /* INTERFACE */ |
| 37 | + |
| 26 | 38 | /* |
| 27 | 39 | ** Maximum length of a line in a text file. (8192) |
| 28 | 40 | */ |
| 29 | 41 | #define LENGTH_MASK_SZ 13 |
| 30 | 42 | #define LENGTH_MASK ((1<<LENGTH_MASK_SZ)-1) |
| | @@ -285,10 +297,176 @@ |
| 285 | 297 | for(j=0; j<m; j++){ |
| 286 | 298 | appendDiffLine(pOut, " ", &B[b+j]); |
| 287 | 299 | } |
| 288 | 300 | } |
| 289 | 301 | } |
| 302 | + |
| 303 | +/* |
| 304 | +** Append spaces to a blob |
| 305 | +*/ |
| 306 | +static void appendSpace(Blob *pOut, int n){ |
| 307 | + const char z100[101] = |
| 308 | + " " |
| 309 | + " "; |
| 310 | + while( n>100 ){ |
| 311 | + blob_append(pOut, z100, 100); n -= 100; |
| 312 | + } |
| 313 | + if( n>0 ){ |
| 314 | + blob_append(pOut, z100, n); |
| 315 | + } |
| 316 | +} |
| 317 | + |
| 318 | +/* |
| 319 | +** Append text to a sbs diff output |
| 320 | +*/ |
| 321 | +static void appendSbsLine(Blob *pOut, DLine *pLine, int width, int pad){ |
| 322 | + int sz = pLine->h & LENGTH_MASK; |
| 323 | + if( sz<width ){ |
| 324 | + blob_append(pOut, pLine->z, sz); |
| 325 | + if( pad ) appendSpace(pOut, width-sz); |
| 326 | + }else{ |
| 327 | + blob_append(pOut, pLine->z, width); |
| 328 | + } |
| 329 | +} |
| 330 | + |
| 331 | + |
| 332 | +/* |
| 333 | +** Given a diff context in which the aEdit[] array has been filled |
| 334 | +** in, compute a side-by-side diff into pOut. |
| 335 | +*/ |
| 336 | +static void sbsDiff(DContext *p, Blob *pOut, int nContext, int width){ |
| 337 | + DLine *A; /* Left side of the diff */ |
| 338 | + DLine *B; /* Right side of the diff */ |
| 339 | + int a = 0; /* Index of next line in A[] */ |
| 340 | + int b = 0; /* Index of next line in B[] */ |
| 341 | + int *R; /* Array of COPY/DELETE/INSERT triples */ |
| 342 | + int r; /* Index into R[] */ |
| 343 | + int nr; /* Number of COPY/DELETE/INSERT triples to process */ |
| 344 | + int mxr; /* Maximum value for r */ |
| 345 | + int na, nb; /* Number of lines shown from A and B */ |
| 346 | + int i, j; /* Loop counters */ |
| 347 | + int m, ma, mb;/* Number of lines to output */ |
| 348 | + int skip; /* Number of lines to skip */ |
| 349 | + char zFormat[50]; /* Output format */ |
| 350 | + |
| 351 | + sqlite3_snprintf(sizeof(zFormat), zFormat, |
| 352 | + "%%4d %%%d.%ds %%c %%4d %%.%ds\n", |
| 353 | + width, width, width); |
| 354 | + A = p->aFrom; |
| 355 | + B = p->aTo; |
| 356 | + R = p->aEdit; |
| 357 | + mxr = p->nEdit; |
| 358 | + while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } |
| 359 | + for(r=0; r<mxr; r += 3*nr){ |
| 360 | + /* Figure out how many triples to show in a single block */ |
| 361 | + for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){} |
| 362 | + /* printf("r=%d nr=%d\n", r, nr); */ |
| 363 | + |
| 364 | + /* For the current block comprising nr triples, figure out |
| 365 | + ** how many lines of A and B are to be displayed |
| 366 | + */ |
| 367 | + if( R[r]>nContext ){ |
| 368 | + na = nb = nContext; |
| 369 | + skip = R[r] - nContext; |
| 370 | + }else{ |
| 371 | + na = nb = R[r]; |
| 372 | + skip = 0; |
| 373 | + } |
| 374 | + for(i=0; i<nr; i++){ |
| 375 | + na += R[r+i*3+1]; |
| 376 | + nb += R[r+i*3+2]; |
| 377 | + } |
| 378 | + if( R[r+nr*3]>nContext ){ |
| 379 | + na += nContext; |
| 380 | + nb += nContext; |
| 381 | + }else{ |
| 382 | + na += R[r+nr*3]; |
| 383 | + nb += R[r+nr*3]; |
| 384 | + } |
| 385 | + for(i=1; i<nr; i++){ |
| 386 | + na += R[r+i*3]; |
| 387 | + nb += R[r+i*3]; |
| 388 | + } |
| 389 | + /* |
| 390 | + * If the patch changes an empty file or results in an empty file, |
| 391 | + * the block header must use 0,0 as position indicator and not 1,0. |
| 392 | + * Otherwise, patch would be confused and may reject the diff. |
| 393 | + */ |
| 394 | + blob_appendf(pOut,"@@ -%d,%d +%d,%d @@\n", |
| 395 | + na ? a+skip+1 : 0, na, |
| 396 | + nb ? b+skip+1 : 0, nb); |
| 397 | + |
| 398 | + /* Show the initial common area */ |
| 399 | + a += skip; |
| 400 | + b += skip; |
| 401 | + m = R[r] - skip; |
| 402 | + for(j=0; j<m; j++){ |
| 403 | + blob_appendf(pOut, "%6d ", a+j); |
| 404 | + appendSbsLine(pOut, &A[a+j], width, 1); |
| 405 | + blob_appendf(pOut, " %6d ", b+j); |
| 406 | + appendSbsLine(pOut, &B[b+j], width, 0); |
| 407 | + blob_append(pOut, "\n", 1); |
| 408 | + } |
| 409 | + a += m; |
| 410 | + b += m; |
| 411 | + |
| 412 | + /* Show the differences */ |
| 413 | + for(i=0; i<nr; i++){ |
| 414 | + ma = R[r+i*3+1]; |
| 415 | + mb = R[r+i*3+2]; |
| 416 | + m = ma<mb ? ma : mb; |
| 417 | + for(j=0; j<m; j++){ |
| 418 | + blob_appendf(pOut, "%6d ", a+j); |
| 419 | + appendSbsLine(pOut, &A[a+j], width, 1); |
| 420 | + blob_appendf(pOut, " | %6d ", b+j); |
| 421 | + appendSbsLine(pOut, &B[b+j], width, 0); |
| 422 | + blob_append(pOut, "\n", 1); |
| 423 | + } |
| 424 | + a += m; |
| 425 | + b += m; |
| 426 | + ma -= m; |
| 427 | + mb -= m; |
| 428 | + for(j=0; j<ma; j++){ |
| 429 | + blob_appendf(pOut, "%6d ", a+j); |
| 430 | + appendSbsLine(pOut, &A[a+j], width, 1); |
| 431 | + blob_append(pOut, " <\n", 3); |
| 432 | + } |
| 433 | + a += ma; |
| 434 | + for(j=0; j<mb; j++){ |
| 435 | + appendSpace(pOut, width+7); |
| 436 | + blob_appendf(pOut, " > %6d", b+j); |
| 437 | + appendSbsLine(pOut, &B[b+j], width, 0); |
| 438 | + blob_append(pOut, "\n", 1); |
| 439 | + } |
| 440 | + b += mb; |
| 441 | + if( i<nr-1 ){ |
| 442 | + m = R[r+i*3+3]; |
| 443 | + for(j=0; j<m; j++){ |
| 444 | + blob_appendf(pOut, "%6d ", a+j); |
| 445 | + appendSbsLine(pOut, &A[a+j], width, 1); |
| 446 | + blob_appendf(pOut, " %6d ", b+j); |
| 447 | + appendSbsLine(pOut, &B[b+j], width, 0); |
| 448 | + blob_append(pOut, "\n", 1); |
| 449 | + } |
| 450 | + b += m; |
| 451 | + a += m; |
| 452 | + } |
| 453 | + } |
| 454 | + |
| 455 | + /* Show the final common area */ |
| 456 | + assert( nr==i ); |
| 457 | + m = R[r+nr*3]; |
| 458 | + if( m>nContext ) m = nContext; |
| 459 | + for(j=0; j<m; j++){ |
| 460 | + blob_appendf(pOut, "%6d ", a+j); |
| 461 | + appendSbsLine(pOut, &A[a+j], width, 1); |
| 462 | + blob_appendf(pOut, " %6d ", b+j); |
| 463 | + appendSbsLine(pOut, &B[b+j], width, 0); |
| 464 | + blob_append(pOut, "\n", 1); |
| 465 | + } |
| 466 | + } |
| 467 | +} |
| 290 | 468 | |
| 291 | 469 | /* |
| 292 | 470 | ** Compute the optimal longest common subsequence (LCS) using an |
| 293 | 471 | ** exhaustive search. This version of the LCS is only used for |
| 294 | 472 | ** shorter input strings since runtime is O(N*N) where N is the |
| | @@ -538,16 +716,21 @@ |
| 538 | 716 | ** text "cannot compute difference between binary files". |
| 539 | 717 | */ |
| 540 | 718 | int *text_diff( |
| 541 | 719 | Blob *pA_Blob, /* FROM file */ |
| 542 | 720 | Blob *pB_Blob, /* TO file */ |
| 543 | | - Blob *pOut, /* Write unified diff here if not NULL */ |
| 544 | | - int nContext, /* Amount of context to unified diff */ |
| 545 | | - int ignoreEolWs /* Ignore whitespace at the end of lines */ |
| 721 | + Blob *pOut, /* Write diff here if not NULL */ |
| 722 | + int diffFlags /* DIFF_* flags defined above */ |
| 546 | 723 | ){ |
| 724 | + int ignoreEolWs; /* Ignore whitespace at the end of lines */ |
| 725 | + int nContext; /* Amount of context to display */ |
| 547 | 726 | DContext c; |
| 548 | | - |
| 727 | + |
| 728 | + nContext = diffFlags & DIFF_CONTEXT_MASK; |
| 729 | + if( nContext==0 ) nContext = 5; |
| 730 | + ignoreEolWs = (diffFlags & DIFF_IGNORE_EOLWS)!=0; |
| 731 | + |
| 549 | 732 | /* Prepare the input files */ |
| 550 | 733 | memset(&c, 0, sizeof(c)); |
| 551 | 734 | c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob), |
| 552 | 735 | &c.nFrom, ignoreEolWs); |
| 553 | 736 | c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob), |
| | @@ -563,12 +746,18 @@ |
| 563 | 746 | |
| 564 | 747 | /* Compute the difference */ |
| 565 | 748 | diff_all(&c); |
| 566 | 749 | |
| 567 | 750 | if( pOut ){ |
| 568 | | - /* Compute a context diff if requested */ |
| 569 | | - contextDiff(&c, pOut, nContext); |
| 751 | + /* Compute a context or side-by-side diff into pOut */ |
| 752 | + if( diffFlags & DIFF_SIDEBYSIDE ){ |
| 753 | + int width = (diffFlags & DIFF_WIDTH_MASK)/(DIFF_CONTEXT_MASK+1); |
| 754 | + if( width==0 ) width = 80; |
| 755 | + sbsDiff(&c, pOut, nContext, width); |
| 756 | + }else{ |
| 757 | + contextDiff(&c, pOut, nContext); |
| 758 | + } |
| 570 | 759 | free(c.aFrom); |
| 571 | 760 | free(c.aTo); |
| 572 | 761 | free(c.aEdit); |
| 573 | 762 | return 0; |
| 574 | 763 | }else{ |
| | @@ -660,11 +849,11 @@ |
| 660 | 849 | while( i<c.nEdit ){ |
| 661 | 850 | int j; |
| 662 | 851 | /* Copied lines */ |
| 663 | 852 | for( j=0; j<c.aEdit[i]; j++){ |
| 664 | 853 | /* Hide lines which are copied and are further away from block boundaries |
| 665 | | - ** than nConext lines. For each block with hidden lines, show a row |
| 854 | + ** than nContext lines. For each block with hidden lines, show a row |
| 666 | 855 | ** notifying the user about the hidden rows. |
| 667 | 856 | */ |
| 668 | 857 | if( j<nContext || j>c.aEdit[i]-nContext-1 ){ |
| 669 | 858 | @ <tr> |
| 670 | 859 | }else if( j==nContext && j<c.aEdit[i]-nContext-1 ){ |
| | @@ -776,11 +965,11 @@ |
| 776 | 965 | if( g.argc<4 ) usage("FILE1 FILE2 ..."); |
| 777 | 966 | blob_read_from_file(&a, g.argv[2]); |
| 778 | 967 | for(i=3; i<g.argc; i++){ |
| 779 | 968 | if( i>3 ) fossil_print("-------------------------------\n"); |
| 780 | 969 | blob_read_from_file(&b, g.argv[i]); |
| 781 | | - R = text_diff(&a, &b, 0, 0, 0); |
| 970 | + R = text_diff(&a, &b, 0, 0); |
| 782 | 971 | for(r=0; R[r] || R[r+1] || R[r+2]; r += 3){ |
| 783 | 972 | fossil_print(" copy %4d delete %4d insert %4d\n", R[r], R[r+1], R[r+2]); |
| 784 | 973 | } |
| 785 | 974 | /* free(R); */ |
| 786 | 975 | blob_reset(&b); |
| | @@ -790,15 +979,23 @@ |
| 790 | 979 | /* |
| 791 | 980 | ** COMMAND: test-udiff |
| 792 | 981 | */ |
| 793 | 982 | void test_udiff_cmd(void){ |
| 794 | 983 | Blob a, b, out; |
| 795 | | - if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 984 | + int diffFlag = find_option("sbs",0,0)!=0 ? DIFF_SIDEBYSIDE : 0; |
| 985 | + int nContext = 5; |
| 986 | + const char *z; |
| 987 | + if( (z = find_option("context","c",1))!=0 && atoi(z)>0 ){ |
| 988 | + nContext = atoi(z); |
| 989 | + } |
| 990 | + if( nContext<=0 ) nContext = 5; |
| 991 | + if( (nContext&DIFF_CONTEXT_MASK)!=nContext ) nContext = DIFF_CONTEXT_MASK; |
| 992 | + if( g.argc!=4 ) usage("[--sbs] [--context N] FILE1 FILE2"); |
| 796 | 993 | blob_read_from_file(&a, g.argv[2]); |
| 797 | 994 | blob_read_from_file(&b, g.argv[3]); |
| 798 | 995 | blob_zero(&out); |
| 799 | | - text_diff(&a, &b, &out, 3, 0); |
| 996 | + text_diff(&a, &b, &out, nContext | diffFlag); |
| 800 | 997 | blob_write_to_file(&out, "-"); |
| 801 | 998 | } |
| 802 | 999 | |
| 803 | 1000 | /************************************************************************** |
| 804 | 1001 | ** The basic difference engine is above. What follows is the annotation |
| 805 | 1002 | |