Fossil SCM
Demonstrate the concept of a generic DiffBuilder object.
Commit
6e8d87b398f52d7c3bfc5e165f5e0b01316aec358d77488b21dbe45c00acda6f
Parent
e696aa705c00a87…
2 files changed
+233
-7
+7
-3
+233
-7
| --- src/diff.c | ||
| +++ src/diff.c | ||
| @@ -44,10 +44,11 @@ | ||
| 44 | 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | 46 | #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */ |
| 47 | 47 | #define DIFF_WEBPAGE (((u64)0x40)<<32) /* Complete webpage */ |
| 48 | 48 | #define DIFF_BROWSER (((u64)0x80)<<32) /* The --browser option */ |
| 49 | +#define DIFF_DEBUG1 (((u64)0x100000)<<32) /* Debugging diff output */ | |
| 49 | 50 | |
| 50 | 51 | /* |
| 51 | 52 | ** These error messages are shared in multiple locations. They are defined |
| 52 | 53 | ** here for consistency. |
| 53 | 54 | */ |
| @@ -279,13 +280,13 @@ | ||
| 279 | 280 | /* |
| 280 | 281 | ** Return true if the regular expression *pRe matches any of the |
| 281 | 282 | ** N dlines |
| 282 | 283 | */ |
| 283 | 284 | static int re_dline_match( |
| 284 | - ReCompiled *pRe, /* The regular expression to be matched */ | |
| 285 | - DLine *aDLine, /* First of N DLines to compare against */ | |
| 286 | - int N /* Number of DLines to check */ | |
| 285 | + ReCompiled *pRe, /* The regular expression to be matched */ | |
| 286 | + const DLine *aDLine, /* First of N DLines to compare against */ | |
| 287 | + int N /* Number of DLines to check */ | |
| 287 | 288 | ){ |
| 288 | 289 | while( N-- ){ |
| 289 | 290 | if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){ |
| 290 | 291 | return 1; |
| 291 | 292 | } |
| @@ -1034,11 +1035,11 @@ | ||
| 1034 | 1035 | ** (1) Remove leading and trailing whitespace. |
| 1035 | 1036 | ** (2) Truncate both strings to at most 250 characters |
| 1036 | 1037 | ** (3) Find the length of the longest common subsequence |
| 1037 | 1038 | ** (4) Longer common subsequences yield lower scores. |
| 1038 | 1039 | */ |
| 1039 | -static int match_dline(DLine *pA, DLine *pB){ | |
| 1040 | +static int match_dline(const DLine *pA, const DLine *pB){ | |
| 1040 | 1041 | const char *zA; /* Left string */ |
| 1041 | 1042 | const char *zB; /* right string */ |
| 1042 | 1043 | int nA; /* Bytes in zA[] */ |
| 1043 | 1044 | int nB; /* Bytes in zB[] */ |
| 1044 | 1045 | int avg; /* Average length of A and B */ |
| @@ -1113,13 +1114,13 @@ | ||
| 1113 | 1114 | ** each other. Insertion and deletion costs are 50. Match costs |
| 1114 | 1115 | ** are between 0 and 100 where 0 is a perfect match 100 is a complete |
| 1115 | 1116 | ** mismatch. |
| 1116 | 1117 | */ |
| 1117 | 1118 | static unsigned char *sbsAlignment( |
| 1118 | - DLine *aLeft, int nLeft, /* Text on the left */ | |
| 1119 | - DLine *aRight, int nRight, /* Text on the right */ | |
| 1120 | - u64 diffFlags /* Flags passed into the original diff */ | |
| 1119 | + const DLine *aLeft, int nLeft, /* Text on the left */ | |
| 1120 | + const DLine *aRight, int nRight, /* Text on the right */ | |
| 1121 | + u64 diffFlags /* Flags passed into the original diff */ | |
| 1121 | 1122 | ){ |
| 1122 | 1123 | int i, j, k; /* Loop counters */ |
| 1123 | 1124 | int *a; /* One row of the Wagner matrix */ |
| 1124 | 1125 | int *pToFree; /* Space that needs to be freed */ |
| 1125 | 1126 | unsigned char *aM; /* Wagner result matrix */ |
| @@ -1233,19 +1234,21 @@ | ||
| 1233 | 1234 | /* Return the result */ |
| 1234 | 1235 | fossil_free(pToFree); |
| 1235 | 1236 | return aM; |
| 1236 | 1237 | } |
| 1237 | 1238 | |
| 1239 | +#if 0 /* not used */ | |
| 1238 | 1240 | /* |
| 1239 | 1241 | ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a |
| 1240 | 1242 | ** pair of adjacent differences. Return true if the gap between these |
| 1241 | 1243 | ** two differences is so small that they should be rendered as a single |
| 1242 | 1244 | ** edit. |
| 1243 | 1245 | */ |
| 1244 | 1246 | static int smallGap(int *R){ |
| 1245 | 1247 | return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8; |
| 1246 | 1248 | } |
| 1249 | +#endif | |
| 1247 | 1250 | |
| 1248 | 1251 | /* |
| 1249 | 1252 | ** Given a diff context in which the aEdit[] array has been filled |
| 1250 | 1253 | ** in, compute a side-by-side diff into pOut. |
| 1251 | 1254 | */ |
| @@ -1395,19 +1398,21 @@ | ||
| 1395 | 1398 | for(i=0; i<nr; i++){ |
| 1396 | 1399 | unsigned char *alignment; |
| 1397 | 1400 | ma = R[r+i*3+1]; /* Lines on left but not on right */ |
| 1398 | 1401 | mb = R[r+i*3+2]; /* Lines on right but not on left */ |
| 1399 | 1402 | |
| 1403 | +#if 0 /* not used */ | |
| 1400 | 1404 | /* If the gap between the current diff and then next diff within the |
| 1401 | 1405 | ** same block is not too great, then render them as if they are a |
| 1402 | 1406 | ** single diff. */ |
| 1403 | 1407 | while( i<nr-1 && smallGap(&R[r+i*3]) ){ |
| 1404 | 1408 | i++; |
| 1405 | 1409 | m = R[r+i*3]; |
| 1406 | 1410 | ma += R[r+i*3+1] + m; |
| 1407 | 1411 | mb += R[r+i*3+2] + m; |
| 1408 | 1412 | } |
| 1413 | +#endif | |
| 1409 | 1414 | |
| 1410 | 1415 | alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags); |
| 1411 | 1416 | for(j=0; ma+mb>0; j++){ |
| 1412 | 1417 | if( alignment[j]==1 ){ |
| 1413 | 1418 | /* Delete one line from the left */ |
| @@ -1505,10 +1510,227 @@ | ||
| 1505 | 1510 | blob_reset(s.apCols[i]); |
| 1506 | 1511 | } |
| 1507 | 1512 | blob_append(pOut, "</tr></table>\n", -1); |
| 1508 | 1513 | } |
| 1509 | 1514 | } |
| 1515 | + | |
| 1516 | +/* | |
| 1517 | +** This is an abstract superclass for an object that accepts difference | |
| 1518 | +** lines and formats them for display. Subclasses of this object format | |
| 1519 | +** the diff output in different ways. | |
| 1520 | +*/ | |
| 1521 | +typedef struct DiffBuilder DiffBuilder; | |
| 1522 | +struct DiffBuilder { | |
| 1523 | + void (*xSkip)(DiffBuilder*, unsigned int); | |
| 1524 | + void (*xCommon)(DiffBuilder*,const DLine*); | |
| 1525 | + void (*xInsert)(DiffBuilder*,const DLine*); | |
| 1526 | + void (*xDelete)(DiffBuilder*,const DLine*); | |
| 1527 | + void (*xEdit)(DiffBuilder*,const DLine*,const DLine*); | |
| 1528 | + void (*xEnd)(DiffBuilder*); | |
| 1529 | + unsigned int lnLeft; /* Lines seen on the left (delete) side */ | |
| 1530 | + unsigned int lnRight; /* Lines seen on the right (insert) side */ | |
| 1531 | + Blob *pOut; /* Output blob */ | |
| 1532 | + /* Subclass add additional fields */ | |
| 1533 | +}; | |
| 1534 | + | |
| 1535 | +/************************* DiffBuilderDebug ********************************/ | |
| 1536 | +static void dfdebugSkip(DiffBuilder *p, unsigned int n){ | |
| 1537 | + fossil_print("SKIP %d (%d..%d left and %d..%d right)\n", | |
| 1538 | + n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n); | |
| 1539 | + p->lnLeft += n; | |
| 1540 | + p->lnRight += n; | |
| 1541 | +} | |
| 1542 | +static void dfdebugCommon(DiffBuilder *p, const DLine *pLine){ | |
| 1543 | + p->lnLeft++; | |
| 1544 | + p->lnRight++; | |
| 1545 | + fossil_print("COMMON %8u %8u %.*s\n", | |
| 1546 | + p->lnLeft, p->lnRight, (int)pLine->n, pLine->z); | |
| 1547 | +} | |
| 1548 | +static void dfdebugInsert(DiffBuilder *p, const DLine *pLine){ | |
| 1549 | + p->lnRight++; | |
| 1550 | + fossil_print("RIGHT %8d %.*s\n", | |
| 1551 | + p->lnRight, (int)pLine->n, pLine->z); | |
| 1552 | +} | |
| 1553 | +static void dfdebugDelete(DiffBuilder *p, const DLine *pLine){ | |
| 1554 | + p->lnLeft++; | |
| 1555 | + fossil_print("LEFT %8u %.*s\n", | |
| 1556 | + p->lnLeft, (int)pLine->n, pLine->z); | |
| 1557 | +} | |
| 1558 | +static void dfdebugEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){ | |
| 1559 | + p->lnLeft++; | |
| 1560 | + p->lnRight++; | |
| 1561 | + fossil_print("EDIT %8u %.*s\n %8u %.*s\n", | |
| 1562 | + p->lnLeft, (int)pX->n, pX->z, p->lnRight, (int)pY->n, pY->z); | |
| 1563 | +} | |
| 1564 | +static void dfdebugEnd(DiffBuilder *p){ | |
| 1565 | + fossil_print("END with %u lines left and %u lines right\n", | |
| 1566 | + p->lnLeft, p->lnRight); | |
| 1567 | + fossil_free(p); | |
| 1568 | +} | |
| 1569 | +static DiffBuilder *dfdebugNew(void){ | |
| 1570 | + DiffBuilder *p = fossil_malloc(sizeof(*p)); | |
| 1571 | + p->xSkip = dfdebugSkip; | |
| 1572 | + p->xCommon = dfdebugCommon; | |
| 1573 | + p->xInsert = dfdebugInsert; | |
| 1574 | + p->xDelete = dfdebugDelete; | |
| 1575 | + p->xEdit = dfdebugEdit; | |
| 1576 | + p->xEnd = dfdebugEnd; | |
| 1577 | + p->lnLeft = p->lnRight = 0; | |
| 1578 | + return p; | |
| 1579 | +} | |
| 1580 | +/****************************************************************************/ | |
| 1581 | + | |
| 1582 | +/* | |
| 1583 | +** Format a diff using a DiffBuilder object | |
| 1584 | +*/ | |
| 1585 | +static void formatDiff( | |
| 1586 | + DContext *p, /* The computed diff */ | |
| 1587 | + ReCompiled *pRe, /* Only show changes that match this regex */ | |
| 1588 | + u64 diffFlags, /* Flags controlling the diff */ | |
| 1589 | + DiffBuilder *pBuilder /* The formatter object */ | |
| 1590 | +){ | |
| 1591 | + const DLine *A; /* Left side of the diff */ | |
| 1592 | + const DLine *B; /* Right side of the diff */ | |
| 1593 | + unsigned int a = 0; /* Index of next line in A[] */ | |
| 1594 | + unsigned int b = 0; /* Index of next line in B[] */ | |
| 1595 | + const int *R; /* Array of COPY/DELETE/INSERT triples */ | |
| 1596 | + unsigned int r; /* Index into R[] */ | |
| 1597 | + unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */ | |
| 1598 | + unsigned int mxr; /* Maximum value for r */ | |
| 1599 | + unsigned int na, nb; /* Number of lines shown from A and B */ | |
| 1600 | + unsigned int i, j; /* Loop counters */ | |
| 1601 | + unsigned int m, ma, mb;/* Number of lines to output */ | |
| 1602 | + unsigned int skip; /* Number of lines to skip */ | |
| 1603 | + unsigned int nContext; /* Lines of context above and below each change */ | |
| 1604 | + | |
| 1605 | + nContext = diff_context_lines(diffFlags); | |
| 1606 | + A = p->aFrom; | |
| 1607 | + B = p->aTo; | |
| 1608 | + R = p->aEdit; | |
| 1609 | + mxr = p->nEdit; | |
| 1610 | + while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } | |
| 1611 | + | |
| 1612 | + for(r=0; r<mxr; r += 3*nr){ | |
| 1613 | + /* Figure out how many triples to show in a single block */ | |
| 1614 | + for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){} | |
| 1615 | + | |
| 1616 | + /* If there is a regex, skip this block (generate no diff output) | |
| 1617 | + ** if the regex matches or does not match both insert and delete. | |
| 1618 | + ** Only display the block if one side matches but the other side does | |
| 1619 | + ** not. | |
| 1620 | + */ | |
| 1621 | + if( pRe ){ | |
| 1622 | + int hideBlock = 1; | |
| 1623 | + int xa = a, xb = b; | |
| 1624 | + for(i=0; hideBlock && i<nr; i++){ | |
| 1625 | + int c1, c2; | |
| 1626 | + xa += R[r+i*3]; | |
| 1627 | + xb += R[r+i*3]; | |
| 1628 | + c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]); | |
| 1629 | + c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]); | |
| 1630 | + hideBlock = c1==c2; | |
| 1631 | + xa += R[r+i*3+1]; | |
| 1632 | + xb += R[r+i*3+2]; | |
| 1633 | + } | |
| 1634 | + if( hideBlock ){ | |
| 1635 | + a = xa; | |
| 1636 | + b = xb; | |
| 1637 | + continue; | |
| 1638 | + } | |
| 1639 | + } | |
| 1640 | + | |
| 1641 | + /* For the current block comprising nr triples, figure out | |
| 1642 | + ** how many lines of A and B are to be displayed | |
| 1643 | + */ | |
| 1644 | + if( R[r]>nContext ){ | |
| 1645 | + na = nb = nContext; | |
| 1646 | + skip = R[r] - nContext; | |
| 1647 | + }else{ | |
| 1648 | + na = nb = R[r]; | |
| 1649 | + skip = 0; | |
| 1650 | + } | |
| 1651 | + for(i=0; i<nr; i++){ | |
| 1652 | + na += R[r+i*3+1]; | |
| 1653 | + nb += R[r+i*3+2]; | |
| 1654 | + } | |
| 1655 | + if( R[r+nr*3]>nContext ){ | |
| 1656 | + na += nContext; | |
| 1657 | + nb += nContext; | |
| 1658 | + }else{ | |
| 1659 | + na += R[r+nr*3]; | |
| 1660 | + nb += R[r+nr*3]; | |
| 1661 | + } | |
| 1662 | + for(i=1; i<nr; i++){ | |
| 1663 | + na += R[r+i*3]; | |
| 1664 | + nb += R[r+i*3]; | |
| 1665 | + } | |
| 1666 | + | |
| 1667 | + if( skip>0 ){ | |
| 1668 | + pBuilder->xSkip(pBuilder, skip); | |
| 1669 | + } | |
| 1670 | + | |
| 1671 | + /* Show the initial common area */ | |
| 1672 | + a += skip; | |
| 1673 | + b += skip; | |
| 1674 | + m = R[r] - skip; | |
| 1675 | + for(j=0; j<m; j++){ | |
| 1676 | + pBuilder->xCommon(pBuilder, &A[a+j]); | |
| 1677 | + } | |
| 1678 | + a += m; | |
| 1679 | + b += m; | |
| 1680 | + | |
| 1681 | + /* Show the differences */ | |
| 1682 | + for(i=0; i<nr; i++){ | |
| 1683 | + unsigned char *alignment; | |
| 1684 | + ma = R[r+i*3+1]; /* Lines on left but not on right */ | |
| 1685 | + mb = R[r+i*3+2]; /* Lines on right but not on left */ | |
| 1686 | + | |
| 1687 | + alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags); | |
| 1688 | + for(j=0; ma+mb>0; j++){ | |
| 1689 | + if( alignment[j]==1 ){ | |
| 1690 | + /* Delete one line from the left */ | |
| 1691 | + pBuilder->xDelete(pBuilder, &A[a]); | |
| 1692 | + ma--; | |
| 1693 | + a++; | |
| 1694 | + }else if( alignment[j]==2 ){ | |
| 1695 | + /* Insert one line on the right */ | |
| 1696 | + pBuilder->xInsert(pBuilder, &B[b]); | |
| 1697 | + assert( mb>0 ); | |
| 1698 | + mb--; | |
| 1699 | + b++; | |
| 1700 | + }else{ | |
| 1701 | + /* The left line is changed into the right line */ | |
| 1702 | + pBuilder->xEdit(pBuilder, &A[a], &B[b]); | |
| 1703 | + assert( ma>0 && mb>0 ); | |
| 1704 | + ma--; | |
| 1705 | + mb--; | |
| 1706 | + a++; | |
| 1707 | + b++; | |
| 1708 | + } | |
| 1709 | + } | |
| 1710 | + fossil_free(alignment); | |
| 1711 | + if( i<nr-1 ){ | |
| 1712 | + m = R[r+i*3+3]; | |
| 1713 | + for(j=0; j<m; j++){ | |
| 1714 | + pBuilder->xCommon(pBuilder, &A[a+j]); | |
| 1715 | + } | |
| 1716 | + b += m; | |
| 1717 | + a += m; | |
| 1718 | + } | |
| 1719 | + } | |
| 1720 | + | |
| 1721 | + /* Show the final common area */ | |
| 1722 | + assert( nr==i ); | |
| 1723 | + m = R[r+nr*3]; | |
| 1724 | + if( m>nContext ) m = nContext; | |
| 1725 | + for(j=0; j<m; j++){ | |
| 1726 | + pBuilder->xCommon(pBuilder, &A[a+j]); | |
| 1727 | + } | |
| 1728 | + } | |
| 1729 | + pBuilder->xEnd(pBuilder); | |
| 1730 | +} | |
| 1731 | + | |
| 1510 | 1732 | |
| 1511 | 1733 | /* |
| 1512 | 1734 | ** Compute the optimal longest common subsequence (LCS) using an |
| 1513 | 1735 | ** exhaustive search. This version of the LCS is only used for |
| 1514 | 1736 | ** shorter input strings since runtime is O(N*N) where N is the |
| @@ -2041,10 +2263,13 @@ | ||
| 2041 | 2263 | g.diffCnt[0]++; |
| 2042 | 2264 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 2043 | 2265 | } |
| 2044 | 2266 | }else if( diffFlags & DIFF_SIDEBYSIDE ){ |
| 2045 | 2267 | sbsDiff(&c, pOut, pRe, diffFlags); |
| 2268 | + }else if( diffFlags & DIFF_DEBUG1 ){ | |
| 2269 | + DiffBuilder *pBuilder = dfdebugNew(); | |
| 2270 | + formatDiff(&c, pRe, diffFlags, pBuilder); | |
| 2046 | 2271 | }else{ |
| 2047 | 2272 | contextDiff(&c, pOut, pRe, diffFlags); |
| 2048 | 2273 | } |
| 2049 | 2274 | fossil_free(c.aFrom); |
| 2050 | 2275 | fossil_free(c.aTo); |
| @@ -2176,10 +2401,11 @@ | ||
| 2176 | 2401 | if( zRe ){ |
| 2177 | 2402 | const char *zErr = re_compile(&pRe, zRe, 0); |
| 2178 | 2403 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 2179 | 2404 | } |
| 2180 | 2405 | diffFlag = diff_options(); |
| 2406 | + if( find_option("debug",0,0)!=0 ) diffFlag |= DIFF_DEBUG1; | |
| 2181 | 2407 | verify_all_options(); |
| 2182 | 2408 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 2183 | 2409 | blob_zero(&out); |
| 2184 | 2410 | diff_begin(diffFlag); |
| 2185 | 2411 | diff_print_filenames(g.argv[2], g.argv[3], diffFlag, &out); |
| 2186 | 2412 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -44,10 +44,11 @@ | |
| 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */ |
| 47 | #define DIFF_WEBPAGE (((u64)0x40)<<32) /* Complete webpage */ |
| 48 | #define DIFF_BROWSER (((u64)0x80)<<32) /* The --browser option */ |
| 49 | |
| 50 | /* |
| 51 | ** These error messages are shared in multiple locations. They are defined |
| 52 | ** here for consistency. |
| 53 | */ |
| @@ -279,13 +280,13 @@ | |
| 279 | /* |
| 280 | ** Return true if the regular expression *pRe matches any of the |
| 281 | ** N dlines |
| 282 | */ |
| 283 | static int re_dline_match( |
| 284 | ReCompiled *pRe, /* The regular expression to be matched */ |
| 285 | DLine *aDLine, /* First of N DLines to compare against */ |
| 286 | int N /* Number of DLines to check */ |
| 287 | ){ |
| 288 | while( N-- ){ |
| 289 | if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){ |
| 290 | return 1; |
| 291 | } |
| @@ -1034,11 +1035,11 @@ | |
| 1034 | ** (1) Remove leading and trailing whitespace. |
| 1035 | ** (2) Truncate both strings to at most 250 characters |
| 1036 | ** (3) Find the length of the longest common subsequence |
| 1037 | ** (4) Longer common subsequences yield lower scores. |
| 1038 | */ |
| 1039 | static int match_dline(DLine *pA, DLine *pB){ |
| 1040 | const char *zA; /* Left string */ |
| 1041 | const char *zB; /* right string */ |
| 1042 | int nA; /* Bytes in zA[] */ |
| 1043 | int nB; /* Bytes in zB[] */ |
| 1044 | int avg; /* Average length of A and B */ |
| @@ -1113,13 +1114,13 @@ | |
| 1113 | ** each other. Insertion and deletion costs are 50. Match costs |
| 1114 | ** are between 0 and 100 where 0 is a perfect match 100 is a complete |
| 1115 | ** mismatch. |
| 1116 | */ |
| 1117 | static unsigned char *sbsAlignment( |
| 1118 | DLine *aLeft, int nLeft, /* Text on the left */ |
| 1119 | DLine *aRight, int nRight, /* Text on the right */ |
| 1120 | u64 diffFlags /* Flags passed into the original diff */ |
| 1121 | ){ |
| 1122 | int i, j, k; /* Loop counters */ |
| 1123 | int *a; /* One row of the Wagner matrix */ |
| 1124 | int *pToFree; /* Space that needs to be freed */ |
| 1125 | unsigned char *aM; /* Wagner result matrix */ |
| @@ -1233,19 +1234,21 @@ | |
| 1233 | /* Return the result */ |
| 1234 | fossil_free(pToFree); |
| 1235 | return aM; |
| 1236 | } |
| 1237 | |
| 1238 | /* |
| 1239 | ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a |
| 1240 | ** pair of adjacent differences. Return true if the gap between these |
| 1241 | ** two differences is so small that they should be rendered as a single |
| 1242 | ** edit. |
| 1243 | */ |
| 1244 | static int smallGap(int *R){ |
| 1245 | return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8; |
| 1246 | } |
| 1247 | |
| 1248 | /* |
| 1249 | ** Given a diff context in which the aEdit[] array has been filled |
| 1250 | ** in, compute a side-by-side diff into pOut. |
| 1251 | */ |
| @@ -1395,19 +1398,21 @@ | |
| 1395 | for(i=0; i<nr; i++){ |
| 1396 | unsigned char *alignment; |
| 1397 | ma = R[r+i*3+1]; /* Lines on left but not on right */ |
| 1398 | mb = R[r+i*3+2]; /* Lines on right but not on left */ |
| 1399 | |
| 1400 | /* If the gap between the current diff and then next diff within the |
| 1401 | ** same block is not too great, then render them as if they are a |
| 1402 | ** single diff. */ |
| 1403 | while( i<nr-1 && smallGap(&R[r+i*3]) ){ |
| 1404 | i++; |
| 1405 | m = R[r+i*3]; |
| 1406 | ma += R[r+i*3+1] + m; |
| 1407 | mb += R[r+i*3+2] + m; |
| 1408 | } |
| 1409 | |
| 1410 | alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags); |
| 1411 | for(j=0; ma+mb>0; j++){ |
| 1412 | if( alignment[j]==1 ){ |
| 1413 | /* Delete one line from the left */ |
| @@ -1505,10 +1510,227 @@ | |
| 1505 | blob_reset(s.apCols[i]); |
| 1506 | } |
| 1507 | blob_append(pOut, "</tr></table>\n", -1); |
| 1508 | } |
| 1509 | } |
| 1510 | |
| 1511 | /* |
| 1512 | ** Compute the optimal longest common subsequence (LCS) using an |
| 1513 | ** exhaustive search. This version of the LCS is only used for |
| 1514 | ** shorter input strings since runtime is O(N*N) where N is the |
| @@ -2041,10 +2263,13 @@ | |
| 2041 | g.diffCnt[0]++; |
| 2042 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 2043 | } |
| 2044 | }else if( diffFlags & DIFF_SIDEBYSIDE ){ |
| 2045 | sbsDiff(&c, pOut, pRe, diffFlags); |
| 2046 | }else{ |
| 2047 | contextDiff(&c, pOut, pRe, diffFlags); |
| 2048 | } |
| 2049 | fossil_free(c.aFrom); |
| 2050 | fossil_free(c.aTo); |
| @@ -2176,10 +2401,11 @@ | |
| 2176 | if( zRe ){ |
| 2177 | const char *zErr = re_compile(&pRe, zRe, 0); |
| 2178 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 2179 | } |
| 2180 | diffFlag = diff_options(); |
| 2181 | verify_all_options(); |
| 2182 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 2183 | blob_zero(&out); |
| 2184 | diff_begin(diffFlag); |
| 2185 | diff_print_filenames(g.argv[2], g.argv[3], diffFlag, &out); |
| 2186 |
| --- src/diff.c | |
| +++ src/diff.c | |
| @@ -44,10 +44,11 @@ | |
| 44 | #define DIFF_NOTTOOBIG (((u64)0x08)<<32) /* Only display if not too big */ |
| 45 | #define DIFF_STRIP_EOLCR (((u64)0x10)<<32) /* Strip trailing CR */ |
| 46 | #define DIFF_SLOW_SBS (((u64)0x20)<<32) /* Better but slower side-by-side */ |
| 47 | #define DIFF_WEBPAGE (((u64)0x40)<<32) /* Complete webpage */ |
| 48 | #define DIFF_BROWSER (((u64)0x80)<<32) /* The --browser option */ |
| 49 | #define DIFF_DEBUG1 (((u64)0x100000)<<32) /* Debugging diff output */ |
| 50 | |
| 51 | /* |
| 52 | ** These error messages are shared in multiple locations. They are defined |
| 53 | ** here for consistency. |
| 54 | */ |
| @@ -279,13 +280,13 @@ | |
| 280 | /* |
| 281 | ** Return true if the regular expression *pRe matches any of the |
| 282 | ** N dlines |
| 283 | */ |
| 284 | static int re_dline_match( |
| 285 | ReCompiled *pRe, /* The regular expression to be matched */ |
| 286 | const DLine *aDLine, /* First of N DLines to compare against */ |
| 287 | int N /* Number of DLines to check */ |
| 288 | ){ |
| 289 | while( N-- ){ |
| 290 | if( re_match(pRe, (const unsigned char *)aDLine->z, LENGTH(aDLine)) ){ |
| 291 | return 1; |
| 292 | } |
| @@ -1034,11 +1035,11 @@ | |
| 1035 | ** (1) Remove leading and trailing whitespace. |
| 1036 | ** (2) Truncate both strings to at most 250 characters |
| 1037 | ** (3) Find the length of the longest common subsequence |
| 1038 | ** (4) Longer common subsequences yield lower scores. |
| 1039 | */ |
| 1040 | static int match_dline(const DLine *pA, const DLine *pB){ |
| 1041 | const char *zA; /* Left string */ |
| 1042 | const char *zB; /* right string */ |
| 1043 | int nA; /* Bytes in zA[] */ |
| 1044 | int nB; /* Bytes in zB[] */ |
| 1045 | int avg; /* Average length of A and B */ |
| @@ -1113,13 +1114,13 @@ | |
| 1114 | ** each other. Insertion and deletion costs are 50. Match costs |
| 1115 | ** are between 0 and 100 where 0 is a perfect match 100 is a complete |
| 1116 | ** mismatch. |
| 1117 | */ |
| 1118 | static unsigned char *sbsAlignment( |
| 1119 | const DLine *aLeft, int nLeft, /* Text on the left */ |
| 1120 | const DLine *aRight, int nRight, /* Text on the right */ |
| 1121 | u64 diffFlags /* Flags passed into the original diff */ |
| 1122 | ){ |
| 1123 | int i, j, k; /* Loop counters */ |
| 1124 | int *a; /* One row of the Wagner matrix */ |
| 1125 | int *pToFree; /* Space that needs to be freed */ |
| 1126 | unsigned char *aM; /* Wagner result matrix */ |
| @@ -1233,19 +1234,21 @@ | |
| 1234 | /* Return the result */ |
| 1235 | fossil_free(pToFree); |
| 1236 | return aM; |
| 1237 | } |
| 1238 | |
| 1239 | #if 0 /* not used */ |
| 1240 | /* |
| 1241 | ** R[] is an array of six integer, two COPY/DELETE/INSERT triples for a |
| 1242 | ** pair of adjacent differences. Return true if the gap between these |
| 1243 | ** two differences is so small that they should be rendered as a single |
| 1244 | ** edit. |
| 1245 | */ |
| 1246 | static int smallGap(int *R){ |
| 1247 | return R[3]<=2 || R[3]<=(R[1]+R[2]+R[4]+R[5])/8; |
| 1248 | } |
| 1249 | #endif |
| 1250 | |
| 1251 | /* |
| 1252 | ** Given a diff context in which the aEdit[] array has been filled |
| 1253 | ** in, compute a side-by-side diff into pOut. |
| 1254 | */ |
| @@ -1395,19 +1398,21 @@ | |
| 1398 | for(i=0; i<nr; i++){ |
| 1399 | unsigned char *alignment; |
| 1400 | ma = R[r+i*3+1]; /* Lines on left but not on right */ |
| 1401 | mb = R[r+i*3+2]; /* Lines on right but not on left */ |
| 1402 | |
| 1403 | #if 0 /* not used */ |
| 1404 | /* If the gap between the current diff and then next diff within the |
| 1405 | ** same block is not too great, then render them as if they are a |
| 1406 | ** single diff. */ |
| 1407 | while( i<nr-1 && smallGap(&R[r+i*3]) ){ |
| 1408 | i++; |
| 1409 | m = R[r+i*3]; |
| 1410 | ma += R[r+i*3+1] + m; |
| 1411 | mb += R[r+i*3+2] + m; |
| 1412 | } |
| 1413 | #endif |
| 1414 | |
| 1415 | alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags); |
| 1416 | for(j=0; ma+mb>0; j++){ |
| 1417 | if( alignment[j]==1 ){ |
| 1418 | /* Delete one line from the left */ |
| @@ -1505,10 +1510,227 @@ | |
| 1510 | blob_reset(s.apCols[i]); |
| 1511 | } |
| 1512 | blob_append(pOut, "</tr></table>\n", -1); |
| 1513 | } |
| 1514 | } |
| 1515 | |
| 1516 | /* |
| 1517 | ** This is an abstract superclass for an object that accepts difference |
| 1518 | ** lines and formats them for display. Subclasses of this object format |
| 1519 | ** the diff output in different ways. |
| 1520 | */ |
| 1521 | typedef struct DiffBuilder DiffBuilder; |
| 1522 | struct DiffBuilder { |
| 1523 | void (*xSkip)(DiffBuilder*, unsigned int); |
| 1524 | void (*xCommon)(DiffBuilder*,const DLine*); |
| 1525 | void (*xInsert)(DiffBuilder*,const DLine*); |
| 1526 | void (*xDelete)(DiffBuilder*,const DLine*); |
| 1527 | void (*xEdit)(DiffBuilder*,const DLine*,const DLine*); |
| 1528 | void (*xEnd)(DiffBuilder*); |
| 1529 | unsigned int lnLeft; /* Lines seen on the left (delete) side */ |
| 1530 | unsigned int lnRight; /* Lines seen on the right (insert) side */ |
| 1531 | Blob *pOut; /* Output blob */ |
| 1532 | /* Subclass add additional fields */ |
| 1533 | }; |
| 1534 | |
| 1535 | /************************* DiffBuilderDebug ********************************/ |
| 1536 | static void dfdebugSkip(DiffBuilder *p, unsigned int n){ |
| 1537 | fossil_print("SKIP %d (%d..%d left and %d..%d right)\n", |
| 1538 | n, p->lnLeft+1, p->lnLeft+n, p->lnRight+1, p->lnRight+n); |
| 1539 | p->lnLeft += n; |
| 1540 | p->lnRight += n; |
| 1541 | } |
| 1542 | static void dfdebugCommon(DiffBuilder *p, const DLine *pLine){ |
| 1543 | p->lnLeft++; |
| 1544 | p->lnRight++; |
| 1545 | fossil_print("COMMON %8u %8u %.*s\n", |
| 1546 | p->lnLeft, p->lnRight, (int)pLine->n, pLine->z); |
| 1547 | } |
| 1548 | static void dfdebugInsert(DiffBuilder *p, const DLine *pLine){ |
| 1549 | p->lnRight++; |
| 1550 | fossil_print("RIGHT %8d %.*s\n", |
| 1551 | p->lnRight, (int)pLine->n, pLine->z); |
| 1552 | } |
| 1553 | static void dfdebugDelete(DiffBuilder *p, const DLine *pLine){ |
| 1554 | p->lnLeft++; |
| 1555 | fossil_print("LEFT %8u %.*s\n", |
| 1556 | p->lnLeft, (int)pLine->n, pLine->z); |
| 1557 | } |
| 1558 | static void dfdebugEdit(DiffBuilder *p, const DLine *pX, const DLine *pY){ |
| 1559 | p->lnLeft++; |
| 1560 | p->lnRight++; |
| 1561 | fossil_print("EDIT %8u %.*s\n %8u %.*s\n", |
| 1562 | p->lnLeft, (int)pX->n, pX->z, p->lnRight, (int)pY->n, pY->z); |
| 1563 | } |
| 1564 | static void dfdebugEnd(DiffBuilder *p){ |
| 1565 | fossil_print("END with %u lines left and %u lines right\n", |
| 1566 | p->lnLeft, p->lnRight); |
| 1567 | fossil_free(p); |
| 1568 | } |
| 1569 | static DiffBuilder *dfdebugNew(void){ |
| 1570 | DiffBuilder *p = fossil_malloc(sizeof(*p)); |
| 1571 | p->xSkip = dfdebugSkip; |
| 1572 | p->xCommon = dfdebugCommon; |
| 1573 | p->xInsert = dfdebugInsert; |
| 1574 | p->xDelete = dfdebugDelete; |
| 1575 | p->xEdit = dfdebugEdit; |
| 1576 | p->xEnd = dfdebugEnd; |
| 1577 | p->lnLeft = p->lnRight = 0; |
| 1578 | return p; |
| 1579 | } |
| 1580 | /****************************************************************************/ |
| 1581 | |
| 1582 | /* |
| 1583 | ** Format a diff using a DiffBuilder object |
| 1584 | */ |
| 1585 | static void formatDiff( |
| 1586 | DContext *p, /* The computed diff */ |
| 1587 | ReCompiled *pRe, /* Only show changes that match this regex */ |
| 1588 | u64 diffFlags, /* Flags controlling the diff */ |
| 1589 | DiffBuilder *pBuilder /* The formatter object */ |
| 1590 | ){ |
| 1591 | const DLine *A; /* Left side of the diff */ |
| 1592 | const DLine *B; /* Right side of the diff */ |
| 1593 | unsigned int a = 0; /* Index of next line in A[] */ |
| 1594 | unsigned int b = 0; /* Index of next line in B[] */ |
| 1595 | const int *R; /* Array of COPY/DELETE/INSERT triples */ |
| 1596 | unsigned int r; /* Index into R[] */ |
| 1597 | unsigned int nr; /* Number of COPY/DELETE/INSERT triples to process */ |
| 1598 | unsigned int mxr; /* Maximum value for r */ |
| 1599 | unsigned int na, nb; /* Number of lines shown from A and B */ |
| 1600 | unsigned int i, j; /* Loop counters */ |
| 1601 | unsigned int m, ma, mb;/* Number of lines to output */ |
| 1602 | unsigned int skip; /* Number of lines to skip */ |
| 1603 | unsigned int nContext; /* Lines of context above and below each change */ |
| 1604 | |
| 1605 | nContext = diff_context_lines(diffFlags); |
| 1606 | A = p->aFrom; |
| 1607 | B = p->aTo; |
| 1608 | R = p->aEdit; |
| 1609 | mxr = p->nEdit; |
| 1610 | while( mxr>2 && R[mxr-1]==0 && R[mxr-2]==0 ){ mxr -= 3; } |
| 1611 | |
| 1612 | for(r=0; r<mxr; r += 3*nr){ |
| 1613 | /* Figure out how many triples to show in a single block */ |
| 1614 | for(nr=1; R[r+nr*3]>0 && R[r+nr*3]<nContext*2; nr++){} |
| 1615 | |
| 1616 | /* If there is a regex, skip this block (generate no diff output) |
| 1617 | ** if the regex matches or does not match both insert and delete. |
| 1618 | ** Only display the block if one side matches but the other side does |
| 1619 | ** not. |
| 1620 | */ |
| 1621 | if( pRe ){ |
| 1622 | int hideBlock = 1; |
| 1623 | int xa = a, xb = b; |
| 1624 | for(i=0; hideBlock && i<nr; i++){ |
| 1625 | int c1, c2; |
| 1626 | xa += R[r+i*3]; |
| 1627 | xb += R[r+i*3]; |
| 1628 | c1 = re_dline_match(pRe, &A[xa], R[r+i*3+1]); |
| 1629 | c2 = re_dline_match(pRe, &B[xb], R[r+i*3+2]); |
| 1630 | hideBlock = c1==c2; |
| 1631 | xa += R[r+i*3+1]; |
| 1632 | xb += R[r+i*3+2]; |
| 1633 | } |
| 1634 | if( hideBlock ){ |
| 1635 | a = xa; |
| 1636 | b = xb; |
| 1637 | continue; |
| 1638 | } |
| 1639 | } |
| 1640 | |
| 1641 | /* For the current block comprising nr triples, figure out |
| 1642 | ** how many lines of A and B are to be displayed |
| 1643 | */ |
| 1644 | if( R[r]>nContext ){ |
| 1645 | na = nb = nContext; |
| 1646 | skip = R[r] - nContext; |
| 1647 | }else{ |
| 1648 | na = nb = R[r]; |
| 1649 | skip = 0; |
| 1650 | } |
| 1651 | for(i=0; i<nr; i++){ |
| 1652 | na += R[r+i*3+1]; |
| 1653 | nb += R[r+i*3+2]; |
| 1654 | } |
| 1655 | if( R[r+nr*3]>nContext ){ |
| 1656 | na += nContext; |
| 1657 | nb += nContext; |
| 1658 | }else{ |
| 1659 | na += R[r+nr*3]; |
| 1660 | nb += R[r+nr*3]; |
| 1661 | } |
| 1662 | for(i=1; i<nr; i++){ |
| 1663 | na += R[r+i*3]; |
| 1664 | nb += R[r+i*3]; |
| 1665 | } |
| 1666 | |
| 1667 | if( skip>0 ){ |
| 1668 | pBuilder->xSkip(pBuilder, skip); |
| 1669 | } |
| 1670 | |
| 1671 | /* Show the initial common area */ |
| 1672 | a += skip; |
| 1673 | b += skip; |
| 1674 | m = R[r] - skip; |
| 1675 | for(j=0; j<m; j++){ |
| 1676 | pBuilder->xCommon(pBuilder, &A[a+j]); |
| 1677 | } |
| 1678 | a += m; |
| 1679 | b += m; |
| 1680 | |
| 1681 | /* Show the differences */ |
| 1682 | for(i=0; i<nr; i++){ |
| 1683 | unsigned char *alignment; |
| 1684 | ma = R[r+i*3+1]; /* Lines on left but not on right */ |
| 1685 | mb = R[r+i*3+2]; /* Lines on right but not on left */ |
| 1686 | |
| 1687 | alignment = sbsAlignment(&A[a], ma, &B[b], mb, diffFlags); |
| 1688 | for(j=0; ma+mb>0; j++){ |
| 1689 | if( alignment[j]==1 ){ |
| 1690 | /* Delete one line from the left */ |
| 1691 | pBuilder->xDelete(pBuilder, &A[a]); |
| 1692 | ma--; |
| 1693 | a++; |
| 1694 | }else if( alignment[j]==2 ){ |
| 1695 | /* Insert one line on the right */ |
| 1696 | pBuilder->xInsert(pBuilder, &B[b]); |
| 1697 | assert( mb>0 ); |
| 1698 | mb--; |
| 1699 | b++; |
| 1700 | }else{ |
| 1701 | /* The left line is changed into the right line */ |
| 1702 | pBuilder->xEdit(pBuilder, &A[a], &B[b]); |
| 1703 | assert( ma>0 && mb>0 ); |
| 1704 | ma--; |
| 1705 | mb--; |
| 1706 | a++; |
| 1707 | b++; |
| 1708 | } |
| 1709 | } |
| 1710 | fossil_free(alignment); |
| 1711 | if( i<nr-1 ){ |
| 1712 | m = R[r+i*3+3]; |
| 1713 | for(j=0; j<m; j++){ |
| 1714 | pBuilder->xCommon(pBuilder, &A[a+j]); |
| 1715 | } |
| 1716 | b += m; |
| 1717 | a += m; |
| 1718 | } |
| 1719 | } |
| 1720 | |
| 1721 | /* Show the final common area */ |
| 1722 | assert( nr==i ); |
| 1723 | m = R[r+nr*3]; |
| 1724 | if( m>nContext ) m = nContext; |
| 1725 | for(j=0; j<m; j++){ |
| 1726 | pBuilder->xCommon(pBuilder, &A[a+j]); |
| 1727 | } |
| 1728 | } |
| 1729 | pBuilder->xEnd(pBuilder); |
| 1730 | } |
| 1731 | |
| 1732 | |
| 1733 | /* |
| 1734 | ** Compute the optimal longest common subsequence (LCS) using an |
| 1735 | ** exhaustive search. This version of the LCS is only used for |
| 1736 | ** shorter input strings since runtime is O(N*N) where N is the |
| @@ -2041,10 +2263,13 @@ | |
| 2263 | g.diffCnt[0]++; |
| 2264 | blob_appendf(pOut, "%10d %10d", nIns, nDel); |
| 2265 | } |
| 2266 | }else if( diffFlags & DIFF_SIDEBYSIDE ){ |
| 2267 | sbsDiff(&c, pOut, pRe, diffFlags); |
| 2268 | }else if( diffFlags & DIFF_DEBUG1 ){ |
| 2269 | DiffBuilder *pBuilder = dfdebugNew(); |
| 2270 | formatDiff(&c, pRe, diffFlags, pBuilder); |
| 2271 | }else{ |
| 2272 | contextDiff(&c, pOut, pRe, diffFlags); |
| 2273 | } |
| 2274 | fossil_free(c.aFrom); |
| 2275 | fossil_free(c.aTo); |
| @@ -2176,10 +2401,11 @@ | |
| 2401 | if( zRe ){ |
| 2402 | const char *zErr = re_compile(&pRe, zRe, 0); |
| 2403 | if( zErr ) fossil_fatal("regex error: %s", zErr); |
| 2404 | } |
| 2405 | diffFlag = diff_options(); |
| 2406 | if( find_option("debug",0,0)!=0 ) diffFlag |= DIFF_DEBUG1; |
| 2407 | verify_all_options(); |
| 2408 | if( g.argc!=4 ) usage("FILE1 FILE2"); |
| 2409 | blob_zero(&out); |
| 2410 | diff_begin(diffFlag); |
| 2411 | diff_print_filenames(g.argv[2], g.argv[3], diffFlag, &out); |
| 2412 |
+7
-3
| --- src/diffcmd.c | ||
| +++ src/diffcmd.c | ||
| @@ -128,14 +128,18 @@ | ||
| 128 | 128 | } |
| 129 | 129 | |
| 130 | 130 | /* |
| 131 | 131 | ** Print the +++/--- filename lines for a diff operation. |
| 132 | 132 | */ |
| 133 | -void diff_print_filenames(const char *zLeft, const char *zRight, | |
| 134 | - u64 diffFlags, Blob *diffBlob){ | |
| 133 | +void diff_print_filenames( | |
| 134 | + const char *zLeft, | |
| 135 | + const char *zRight, | |
| 136 | + u64 diffFlags, | |
| 137 | + Blob *diffBlob | |
| 138 | +){ | |
| 135 | 139 | char *z = 0; |
| 136 | - if( diffFlags & DIFF_BRIEF ){ | |
| 140 | + if( diffFlags & (DIFF_BRIEF|DIFF_DEBUG1) ){ | |
| 137 | 141 | /* no-op */ |
| 138 | 142 | }else if( diffFlags & DIFF_WEBPAGE ){ |
| 139 | 143 | if( fossil_strcmp(zLeft,zRight)==0 ){ |
| 140 | 144 | z = mprintf("<h1>%h</h1>\n", zLeft); |
| 141 | 145 | }else{ |
| 142 | 146 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -128,14 +128,18 @@ | |
| 128 | } |
| 129 | |
| 130 | /* |
| 131 | ** Print the +++/--- filename lines for a diff operation. |
| 132 | */ |
| 133 | void diff_print_filenames(const char *zLeft, const char *zRight, |
| 134 | u64 diffFlags, Blob *diffBlob){ |
| 135 | char *z = 0; |
| 136 | if( diffFlags & DIFF_BRIEF ){ |
| 137 | /* no-op */ |
| 138 | }else if( diffFlags & DIFF_WEBPAGE ){ |
| 139 | if( fossil_strcmp(zLeft,zRight)==0 ){ |
| 140 | z = mprintf("<h1>%h</h1>\n", zLeft); |
| 141 | }else{ |
| 142 |
| --- src/diffcmd.c | |
| +++ src/diffcmd.c | |
| @@ -128,14 +128,18 @@ | |
| 128 | } |
| 129 | |
| 130 | /* |
| 131 | ** Print the +++/--- filename lines for a diff operation. |
| 132 | */ |
| 133 | void diff_print_filenames( |
| 134 | const char *zLeft, |
| 135 | const char *zRight, |
| 136 | u64 diffFlags, |
| 137 | Blob *diffBlob |
| 138 | ){ |
| 139 | char *z = 0; |
| 140 | if( diffFlags & (DIFF_BRIEF|DIFF_DEBUG1) ){ |
| 141 | /* no-op */ |
| 142 | }else if( diffFlags & DIFF_WEBPAGE ){ |
| 143 | if( fossil_strcmp(zLeft,zRight)==0 ){ |
| 144 | z = mprintf("<h1>%h</h1>\n", zLeft); |
| 145 | }else{ |
| 146 |