Changes On Branch diff-enhancements
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Changes In Branch diff-enhancements Excluding Merge-Ins

This is equivalent to a diff from a75e2d2504 to a505abccc6

2011-10-21
21:55
Merge the side-by-side diff spacing bug fix into trunk. check-in: 54e730c339 user: drh tags: trunk
21:50
Fix a spacing bug in the display of side-by-side diffs. Closed-Leaf check-in: a505abccc6 user: drh tags: diff-enhancements
21:34
Merge the diff enhancements into trunk. check-in: c244605862 user: drh tags: trunk
21:31
Add support for side-by-side diff from the command-line "diff" command. check-in: ac81759f65 user: drh tags: diff-enhancements
20:24
Begin improvement efforts on the "diff" functions by adding the --context option to the "diff" command. check-in: 3bbbbdfd7d user: drh tags: diff-enhancements
12:52
Version 1.20. check-in: a75e2d2504 user: drh tags: trunk, release, version-1.20
2011-10-20
17:10
minor 1.20 changelog tweak. check-in: 1d12fcc416 user: stephan tags: trunk

Changes to src/diff.c.

    19     19   ** text files.
    20     20   */
    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)
    31     43   
    32     44   /*
................................................................................
   283    295       m = R[r+nr*3];
   284    296       if( m>nContext ) m = nContext;
   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
   295    473   ** input string length.
   296    474   */
................................................................................
   536    714   ** This diff utility does not work on binary files.  If a binary
   537    715   ** file is encountered, 0 is returned and pOut is written with
   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),
   554    737                              &c.nTo, ignoreEolWs);
   555    738     if( c.aFrom==0 || c.aTo==0 ){
................................................................................
   561    744       return 0;
   562    745     }
   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{
   575    764       /* If a context diff is not requested, then return the
   576    765       ** array of COPY/DELETE/INSERT triples.
................................................................................
   658    847     iFrom=iTo=0;
   659    848     i=0;
   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 ){
   671    860           @ <tr>
   672    861           @ <td class="meta" colspan="5" style="white-space: nowrap;">
................................................................................
   774    963     int i;
   775    964     int *R;
   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);
   787    976     }
   788    977   }
   789    978   
   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   ** engine.  Both are in the same file since they share many components.
   806   1003   */

Changes to src/diffcmd.c.

    17     17   **
    18     18   ** This file contains code used to implement the "diff" command
    19     19   */
    20     20   #include "config.h"
    21     21   #include "diffcmd.h"
    22     22   #include <assert.h>
    23     23   
    24         -/*
    25         -** Diff option flags
    26         -*/
    27         -#define DIFF_NEWFILE  0x01    /* Treat non-existing fails as empty files */
    28         -#define DIFF_NOEOLWS  0x02    /* Ignore whitespace at the end of lines */
    29         -
    30     24   /*
    31     25   ** Output the results of a diff.  Output goes to stdout for command-line
    32     26   ** or to the CGI/HTTP result buffer for web pages.
    33     27   */
    34     28   static void diff_printf(const char *zFormat, ...){
    35     29     va_list ap;
    36     30     va_start(ap, zFormat);
................................................................................
    60     54   ** command zDiffCmd to do the diffing.
    61     55   */
    62     56   void diff_file(
    63     57     Blob *pFile1,             /* In memory content to compare from */
    64     58     const char *zFile2,       /* On disk content to compare to */
    65     59     const char *zName,        /* Display name of the file */
    66     60     const char *zDiffCmd,     /* Command for comparison */
    67         -  int ignoreEolWs           /* Ignore whitespace at end of line */
           61  +  int diffFlags             /* Flags to control the diff */
    68     62   ){
    69     63     if( zDiffCmd==0 ){
    70     64       Blob out;                 /* Diff output text */
    71     65       Blob file2;               /* Content of zFile2 */
    72     66       const char *zName2;       /* Name of zFile2 for display */
    73     67   
    74     68       /* Read content of zFile2 into memory */
................................................................................
    82     76           blob_read_from_file(&file2, zFile2);
    83     77         }
    84     78         zName2 = zName;
    85     79       }
    86     80   
    87     81       /* Compute and output the differences */
    88     82       blob_zero(&out);
    89         -    text_diff(pFile1, &file2, &out, 5, ignoreEolWs);
           83  +    text_diff(pFile1, &file2, &out, diffFlags);
    90     84       if( blob_size(&out) ){
    91     85         diff_printf("--- %s\n+++ %s\n", zName, zName2);
    92     86         diff_printf("%s\n", blob_str(&out));
    93     87       }
    94     88   
    95     89       /* Release memory resources */
    96     90       blob_reset(&file2);
................................................................................
   136    130   ** command zDiffCmd to do the diffing.
   137    131   */
   138    132   void diff_file_mem(
   139    133     Blob *pFile1,             /* In memory content to compare from */
   140    134     Blob *pFile2,             /* In memory content to compare to */
   141    135     const char *zName,        /* Display name of the file */
   142    136     const char *zDiffCmd,     /* Command for comparison */
   143         -  int ignoreEolWs           /* Ignore whitespace at end of lines */
          137  +  int diffFlags             /* Diff flags */
   144    138   ){
   145    139     if( zDiffCmd==0 ){
   146    140       Blob out;      /* Diff output text */
   147    141   
   148    142       blob_zero(&out);
   149         -    text_diff(pFile1, pFile2, &out, 5, ignoreEolWs);
          143  +    text_diff(pFile1, pFile2, &out, diffFlags);
   150    144       diff_printf("--- %s\n+++ %s\n", zName, zName);
   151    145       diff_printf("%s\n", blob_str(&out));
   152    146   
   153    147       /* Release memory resources */
   154    148       blob_reset(&out);
   155    149     }else{
   156    150       Blob cmd;
................................................................................
   183    177   /*
   184    178   ** Do a diff against a single file named in zFileTreeName from version zFrom
   185    179   ** against the same file on disk.
   186    180   */
   187    181   static void diff_one_against_disk(
   188    182     const char *zFrom,        /* Name of file */
   189    183     const char *zDiffCmd,     /* Use this "diff" command */
   190         -  int ignoreEolWs,          /* Ignore whitespace changes at end of lines */
          184  +  int diffFlags,            /* Diff control flags */
   191    185     const char *zFileTreeName
   192    186   ){
   193    187     Blob fname;
   194    188     Blob content;
   195    189     int isLink;
   196    190     file_tree_name(zFileTreeName, &fname, 1);
   197    191     historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
   198    192     if( !isLink != !file_wd_islink(zFrom) ){
   199    193       diff_printf("cannot compute difference between symlink and regular file\n");
   200    194     }else{
   201         -    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, ignoreEolWs);
          195  +    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
   202    196     }
   203    197     blob_reset(&content);
   204    198     blob_reset(&fname);
   205    199   }
   206    200   
   207    201   /*
   208    202   ** Run a diff between the version zFrom and files on disk.  zFrom might
................................................................................
   213    207     const char *zFrom,        /* Version to difference from */
   214    208     const char *zDiffCmd,     /* Use this diff command.  NULL for built-in */
   215    209     int diffFlags             /* Flags controlling diff output */
   216    210   ){
   217    211     int vid;
   218    212     Blob sql;
   219    213     Stmt q;
   220         -  int ignoreEolWs;          /* Ignore end-of-line whitespace */
   221    214     int asNewFile;            /* Treat non-existant files as empty files */
   222    215   
   223         -  ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0;
   224    216     asNewFile = (diffFlags & DIFF_NEWFILE)!=0;
   225    217     vid = db_lget_int("checkout", 0);
   226    218     vfile_check_signature(vid, 1, 0);
   227    219     blob_zero(&sql);
   228    220     db_begin_transaction();
   229    221     if( zFrom ){
   230    222       int rid = name_to_typed_rid(zFrom, "ci");
................................................................................
   298    290         }
   299    291         if( srcid>0 ){
   300    292           content_get(srcid, &content);
   301    293         }else{
   302    294           blob_zero(&content);
   303    295         }
   304    296         diff_print_index(zPathname);
   305         -      diff_file(&content, zFullName, zPathname, zDiffCmd, ignoreEolWs);
          297  +      diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
   306    298         blob_reset(&content);
   307    299       }
   308    300       free(zToFree);
   309    301     }
   310    302     db_finalize(&q);
   311    303     db_end_transaction(1);  /* ROLLBACK */
   312    304   }
................................................................................
   315    307   ** Output the differences between two versions of a single file.
   316    308   ** zFrom and zTo are the check-ins containing the two file versions.
   317    309   */
   318    310   static void diff_one_two_versions(
   319    311     const char *zFrom,
   320    312     const char *zTo,
   321    313     const char *zDiffCmd,
   322         -  int ignoreEolWs,
          314  +  int diffFlags,
   323    315     const char *zFileTreeName
   324    316   ){
   325    317     char *zName;
   326    318     Blob fname;
   327    319     Blob v1, v2;
   328    320     int isLink1, isLink2;
   329    321     file_tree_name(zFileTreeName, &fname, 1);
................................................................................
   330    322     zName = blob_str(&fname);
   331    323     historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
   332    324     historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
   333    325     if( isLink1 != isLink2 ){
   334    326       diff_printf("--- %s\n+++ %s\n", zName, zName);
   335    327       diff_printf("cannot compute difference between symlink and regular file\n");
   336    328     }else{
   337         -    diff_file_mem(&v1, &v2, zName, zDiffCmd, ignoreEolWs);
          329  +    diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
   338    330     }
   339    331     blob_reset(&v1);
   340    332     blob_reset(&v2);
   341    333     blob_reset(&fname);
   342    334   }
   343    335   
   344    336   /*
................................................................................
   345    337   ** Show the difference between two files identified by ManifestFile
   346    338   ** entries.
   347    339   */
   348    340   static void diff_manifest_entry(
   349    341     struct ManifestFile *pFrom,
   350    342     struct ManifestFile *pTo,
   351    343     const char *zDiffCmd,
   352         -  int ignoreEolWs
          344  +  int diffFlags
   353    345   ){
   354    346     Blob f1, f2;
   355    347     int rid;
   356    348     const char *zName =  pFrom ? pFrom->zName : pTo->zName;
   357    349     diff_print_index(zName);
   358    350     if( pFrom ){
   359    351       rid = uuid_to_rid(pFrom->zUuid, 0);
................................................................................
   363    355     }
   364    356     if( pTo ){
   365    357       rid = uuid_to_rid(pTo->zUuid, 0);
   366    358       content_get(rid, &f2);
   367    359     }else{
   368    360       blob_zero(&f2);
   369    361     }
   370         -  diff_file_mem(&f1, &f2, zName, zDiffCmd, ignoreEolWs);
          362  +  diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
   371    363     blob_reset(&f1);
   372    364     blob_reset(&f2);
   373    365   }
   374    366   
   375    367   /*
   376    368   ** Output the differences between two check-ins.
   377    369   */
................................................................................
   379    371     const char *zFrom,
   380    372     const char *zTo,
   381    373     const char *zDiffCmd,
   382    374     int diffFlags
   383    375   ){
   384    376     Manifest *pFrom, *pTo;
   385    377     ManifestFile *pFromFile, *pToFile;
   386         -  int ignoreEolWs = (diffFlags & DIFF_NOEOLWS)!=0 ? 1 : 0;
   387    378     int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
   388    379   
   389    380     pFrom = manifest_get_by_name(zFrom, 0);
   390    381     manifest_file_rewind(pFrom);
   391    382     pFromFile = manifest_file_next(pFrom,0);
   392    383     pTo = manifest_get_by_name(zTo, 0);
   393    384     manifest_file_rewind(pTo);
................................................................................
   401    392         cmp = -1;
   402    393       }else{
   403    394         cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
   404    395       }
   405    396       if( cmp<0 ){
   406    397         diff_printf("DELETED %s\n", pFromFile->zName);
   407    398         if( asNewFlag ){
   408         -        diff_manifest_entry(pFromFile, 0, zDiffCmd, ignoreEolWs);
          399  +        diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
   409    400         }
   410    401         pFromFile = manifest_file_next(pFrom,0);
   411    402       }else if( cmp>0 ){
   412    403         diff_printf("ADDED   %s\n", pToFile->zName);
   413    404         if( asNewFlag ){
   414         -        diff_manifest_entry(0, pToFile, zDiffCmd, ignoreEolWs);
          405  +        diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
   415    406         }
   416    407         pToFile = manifest_file_next(pTo,0);
   417    408       }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
   418    409         /* No changes */
   419    410         pFromFile = manifest_file_next(pFrom,0);
   420    411         pToFile = manifest_file_next(pTo,0);
   421    412       }else{
   422    413         /* diff_printf("CHANGED %s\n", pFromFile->zName); */
   423         -      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, ignoreEolWs);
          414  +      diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
   424    415         pFromFile = manifest_file_next(pFrom,0);
   425    416         pToFile = manifest_file_next(pTo,0);
   426    417       }
   427    418     }
   428    419     manifest_destroy(pFrom);
   429    420     manifest_destroy(pTo);
   430    421   }
................................................................................
   454    445   ** the "setting" command.  If no external diff program is configured, then
   455    446   ** the "-i" option is a no-op.  The "-i" option converts "gdiff" into "diff".
   456    447   **
   457    448   ** The "-N" or "--new-file" option causes the complete text of added or
   458    449   ** deleted files to be displayed.
   459    450   **
   460    451   ** Options:
          452  +**   --context|-c N      Use N lines of context 
   461    453   **   --from|-r VERSION   select VERSION as source for the diff
   462    454   **   --new-file|-N       output complete text of added or deleted files
   463    455   **   -i                  use internal diff logic
   464    456   **   --to VERSION        select VERSION as target for the diff
          457  +**   --side-by-side|-y   side-by-side diff
          458  +**   --width|-W N        Width of lines in side-by-side diff 
   465    459   */
   466    460   void diff_cmd(void){
   467    461     int isGDiff;               /* True for gdiff.  False for normal diff */
   468    462     int isInternDiff;          /* True for internal diff */
   469    463     int hasNFlag;              /* True if -N or --new-file flag is used */
   470    464     const char *zFrom;         /* Source version number */
   471    465     const char *zTo;           /* Target version number */
   472    466     const char *zDiffCmd = 0;  /* External diff command. NULL for internal diff */
   473    467     int diffFlags = 0;         /* Flags to control the DIFF */
          468  +  const char *z;
   474    469     int f;
   475    470   
   476    471     isGDiff = g.argv[1][0]=='g';
   477    472     isInternDiff = find_option("internal","i",0)!=0;
   478    473     zFrom = find_option("from", "r", 1);
   479    474     zTo = find_option("to", 0, 1);
   480    475     hasNFlag = find_option("new-file","N",0)!=0;
          476  +  if( find_option("side-by-side","y",0)!=0 ) diffFlags |= DIFF_SIDEBYSIDE;
          477  +  if( (z = find_option("context","c",1))!=0 && (f = atoi(z))>0 ){
          478  +    if( f > DIFF_CONTEXT_MASK ) f = DIFF_CONTEXT_MASK;
          479  +    diffFlags |= f;
          480  +  }
          481  +  if( (z = find_option("width","W",1))!=0 && (f = atoi(z))>0 ){
          482  +    f *= DIFF_CONTEXT_MASK+1;
          483  +    if( f > DIFF_WIDTH_MASK ) f = DIFF_CONTEXT_MASK;
          484  +    diffFlags |= f;
          485  +  }
   481    486   
   482    487   
   483    488     if( hasNFlag ) diffFlags |= DIFF_NEWFILE;
   484    489     if( zTo==0 ){
   485    490       db_must_be_within_tree();
   486    491       verify_all_options();
   487    492       if( !isInternDiff ){

Changes to src/info.c.

   268    268     if( zTo ){
   269    269       toid = uuid_to_rid(zTo, 0);
   270    270       content_get(toid, &to);
   271    271     }else{
   272    272       blob_zero(&to);
   273    273     }
   274    274     blob_zero(&out);
   275         -  text_diff(&from, &to, &out, 5, 1);
          275  +  text_diff(&from, &to, &out, DIFF_IGNORE_EOLWS | 5);
   276    276     @ %h(blob_str(&out))
   277    277     blob_reset(&from);
   278    278     blob_reset(&to);
   279    279     blob_reset(&out);  
   280    280   }
   281    281   
   282    282   
................................................................................
  1089   1089     }else{
  1090   1090       blob_zero(&diff);
  1091   1091       pOut = &diff;
  1092   1092     }
  1093   1093     if( !sideBySide || isPatch ){
  1094   1094       content_get(v1, &c1);
  1095   1095       content_get(v2, &c2);
  1096         -    text_diff(&c1, &c2, pOut, 4, 1);
         1096  +    text_diff(&c1, &c2, pOut, 4 | 0);
  1097   1097       blob_reset(&c1);
  1098   1098       blob_reset(&c2);
  1099   1099     }
  1100   1100     if( !isPatch ){
  1101   1101       style_header("Diff");
  1102   1102       style_submenu_element("Patch", "Patch", "%s/fdiff?v1=%T&v2=%T&patch",
  1103   1103                             g.zTop, P("v1"), P("v2"));

Changes to src/merge3.c.

   169    169     ** and pPivot => pV2 (into aC2).  Each of the aC1 and aC2 arrays is
   170    170     ** an array of integer triples.  Within each triple, the first integer
   171    171     ** is the number of lines of text to copy directly from the pivot,
   172    172     ** the second integer is the number of lines of text to omit from the
   173    173     ** pivot, and the third integer is the number of lines of text that are
   174    174     ** inserted.  The edit array ends with a triple of 0,0,0.
   175    175     */
   176         -  aC1 = text_diff(pPivot, pV1, 0, 0, 0);
   177         -  aC2 = text_diff(pPivot, pV2, 0, 0, 0);
          176  +  aC1 = text_diff(pPivot, pV1, 0, 0);
          177  +  aC2 = text_diff(pPivot, pV2, 0, 0);
   178    178     if( aC1==0 || aC2==0 ){
   179    179       free(aC1);
   180    180       free(aC2);
   181    181       return -1;
   182    182     }
   183    183   
   184    184     blob_rewind(pV1);        /* Rewind inputs:  Needed to reconstruct output */

Changes to src/wiki.c.

   619    619     if( pW1==0 ) fossil_redirect_home();
   620    620     blob_init(&w1, pW1->zWiki, -1);
   621    621     blob_zero(&w2);
   622    622     if( rid2 && (pW2 = manifest_get(rid2, CFTYPE_WIKI))!=0 ){
   623    623       blob_init(&w2, pW2->zWiki, -1);
   624    624     }
   625    625     blob_zero(&d);
   626         -  text_diff(&w2, &w1, &d, 5, 1);
          626  +  text_diff(&w2, &w1, &d, 5 | DIFF_IGNORE_EOLWS);
   627    627     @ <pre>
   628    628     @ %h(blob_str(&d))
   629    629     @ </pre>
   630    630     manifest_destroy(pW1);
   631    631     manifest_destroy(pW2);
   632    632     style_footer();
   633    633   }