Changes On Branch diffBinExternal
Not logged in

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

Changes In Branch diffBinExternal Excluding Merge-Ins

This is equivalent to a diff from 97f68e61da to 064afb225a

2012-10-02
22:59
Merge the enhancements that adds the "diff-binary" setting for passing binary files to the external diff program. check-in: f8339c2743 user: drh tags: trunk
2012-09-30
09:29
The 'binary-glob' setting is a versionable project setting and should be listed with the others. check-in: 6ef41eeffe user: mistachkin tags: trunk
07:39
Make sure the new command line options are always processed; however, wait until after the settings are available. Closed-Leaf check-in: 064afb225a user: mistachkin tags: diffBinExternal
06:25
Add support for skipping binary files when using an external diff program. The existing 'binary-glob' setting and new 'diff-binary' boolean setting control this feature. check-in: c50eb50718 user: mistachkin tags: diffBinExternal
01:43
Fix typo in comment. check-in: 97f68e61da user: mistachkin tags: trunk
2012-09-29
14:08
Make the "--tk" option to the "diff" command also imply the "-i" option to force the use of the internal diff engine. check-in: 5a4fbb0a28 user: drh tags: trunk

Changes to src/db.c.

  2012   2012     { "auto-shun",     0,                0, 0, "on"                  },
  2013   2013     { "autosync",      0,                0, 0, "on"                  },
  2014   2014     { "binary-glob",   0,               32, 1, ""                    },
  2015   2015     { "clearsign",     0,                0, 0, "off"                 },
  2016   2016     { "case-sensitive",0,                0, 0, "on"                  },
  2017   2017     { "crnl-glob",     0,               16, 1, ""                    },
  2018   2018     { "default-perms", 0,               16, 0, "u"                   },
         2019  +  { "diff-binary",   0,                0, 0, "on"                  },
  2019   2020     { "diff-command",  0,               16, 0, ""                    },
  2020   2021     { "dont-push",     0,                0, 0, "off"                 },
  2021   2022     { "editor",        0,               16, 0, ""                    },
  2022   2023     { "gdiff-command", 0,               16, 0, "gdiff"               },
  2023   2024     { "gmerge-command",0,               40, 0, ""                    },
  2024   2025     { "https-login",   0,                0, 0, "off"                 },
  2025   2026     { "ignore-glob",   0,               40, 1, ""                    },
................................................................................
  2104   2105   **    crnl-glob        A comma or newline-separated list of GLOB patterns for
  2105   2106   **     (versionable)   text files in which it is ok to have CR+NL line endings.
  2106   2107   **                     Set to "*" to disable CR+NL checking.
  2107   2108   **
  2108   2109   **    default-perms    Permissions given automatically to new users.  For more
  2109   2110   **                     information on permissions see Users page in Server
  2110   2111   **                     Administration of the HTTP UI. Default: u.
         2112  +**
         2113  +**    diff-binary      If TRUE (the default), permit files that may be binary
         2114  +**                     or that match the "binary-glob" setting to be used with
         2115  +**                     external diff programs.  If FALSE, skip these files.
  2111   2116   **
  2112   2117   **    diff-command     External command to run when performing a diff.
  2113   2118   **                     If undefined, the internal text diff will be used.
  2114   2119   **
  2115   2120   **    dont-push        Prevent this repository from pushing from client to
  2116   2121   **                     server.  Useful when setting up a private branch.
  2117   2122   **

Changes to src/diff.c.

    36     36   #define DIFF_INLINE       ((u64)0x00000000) /* Inline (not side-by-side) diff */
    37     37   #define DIFF_HTML         ((u64)0x10000000) /* Render for HTML */
    38     38   #define DIFF_LINENO       ((u64)0x20000000) /* Show line numbers */
    39     39   #define DIFF_WS_WARNING   ((u64)0x40000000) /* Warn about whitespace */
    40     40   #define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
    41     41   #define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
    42     42   
           43  +/*
           44  +** These error messages are shared in multiple locations.  They are defined
           45  +** here for consistency.
           46  +*/
           47  +#define DIFF_CANNOT_COMPUTE_BINARY \
           48  +    "cannot compute difference between binary files\n"
           49  +
           50  +#define DIFF_CANNOT_COMPUTE_SYMLINK \
           51  +    "cannot compute difference between symlink and regular file\n"
           52  +
    43     53   #endif /* INTERFACE */
    44     54   
    45     55   /*
    46     56   ** Maximum length of a line in a text file.  (8192)
    47     57   */
    48     58   #define LENGTH_MASK_SZ  13
    49     59   #define LENGTH_MASK     ((1<<LENGTH_MASK_SZ)-1)
................................................................................
   155    165       z += j+1;
   156    166     }
   157    167   
   158    168     /* Return results */
   159    169     *pnLine = nLine;
   160    170     return a;
   161    171   }
          172  +
          173  +/*
          174  +** Returns non-zero if the specified content appears to be binary or
          175  +** contains a line that is too long.
          176  +*/
          177  +int looks_like_binary(const char *z, int n){
          178  +  int nLine;
          179  +  DLine *aContent = break_into_lines(z, n, &nLine, 0);
          180  +  if( aContent ){
          181  +    fossil_free(aContent);
          182  +    return 0;
          183  +  }else{
          184  +    return 1;
          185  +  }
          186  +}
   162    187   
   163    188   /*
   164    189   ** Return true if two DLine elements are identical.
   165    190   */
   166    191   static int same_dline(DLine *pA, DLine *pB){
   167    192     return pA->h==pB->h && memcmp(pA->z,pB->z,pA->h & LENGTH_MASK)==0;
   168    193   }
................................................................................
  1497   1522     /* Prepare the input files */
  1498   1523     memset(&c, 0, sizeof(c));
  1499   1524     c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
  1500   1525                                &c.nFrom, ignoreEolWs);
  1501   1526     c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
  1502   1527                              &c.nTo, ignoreEolWs);
  1503   1528     if( c.aFrom==0 || c.aTo==0 ){
  1504         -    free(c.aFrom);
  1505         -    free(c.aTo);
         1529  +    fossil_free(c.aFrom);
         1530  +    fossil_free(c.aTo);
  1506   1531       if( pOut ){
  1507         -      blob_appendf(pOut, "cannot compute difference between binary files\n");
         1532  +      blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY);
  1508   1533       }
  1509   1534       return 0;
  1510   1535     }
  1511   1536   
  1512   1537     /* Compute the difference */
  1513   1538     diff_all(&c);
  1514   1539     if( (diffFlags & DIFF_NOOPT)==0 ) diff_optimize(&c);
................................................................................
  1519   1544       if( diffFlags & DIFF_SIDEBYSIDE ){
  1520   1545         int width = diff_width(diffFlags);
  1521   1546         sbsDiff(&c, pOut, nContext, width, escHtml);
  1522   1547       }else{
  1523   1548         int showLn = (diffFlags & DIFF_LINENO)!=0;
  1524   1549         contextDiff(&c, pOut, nContext, showLn, escHtml);
  1525   1550       }
  1526         -    free(c.aFrom);
  1527         -    free(c.aTo);
  1528         -    free(c.aEdit);
         1551  +    fossil_free(c.aFrom);
         1552  +    fossil_free(c.aTo);
         1553  +    fossil_free(c.aEdit);
  1529   1554       return 0;
  1530   1555     }else{
  1531   1556       /* If a context diff is not requested, then return the
  1532   1557       ** array of COPY/DELETE/INSERT triples.
  1533   1558       */
  1534   1559       free(c.aFrom);
  1535   1560       free(c.aTo);
................................................................................
  1700   1725            x->iLevel = iThisLevel;
  1701   1726         }
  1702   1727       }
  1703   1728       lnTo += p->c.aEdit[i+2];
  1704   1729     }
  1705   1730   
  1706   1731     /* Clear out the diff results */
  1707         -  free(p->c.aEdit);
         1732  +  fossil_free(p->c.aEdit);
  1708   1733     p->c.aEdit = 0;
  1709   1734     p->c.nEdit = 0;
  1710   1735     p->c.nEditAlloc = 0;
  1711   1736   
  1712   1737     /* Clear out the from file */
  1713   1738     free(p->c.aFrom);    
  1714   1739   

Changes to src/diffcmd.c.

    67     67   ** Show the difference between two files, one in memory and one on disk.
    68     68   **
    69     69   ** The difference is the set of edits needed to transform pFile1 into
    70     70   ** zFile2.  The content of pFile1 is in memory.  zFile2 exists on disk.
    71     71   **
    72     72   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
    73     73   ** command zDiffCmd to do the diffing.
           74  +**
           75  +** When using an external diff program, zBinGlob contains the GLOB patterns
           76  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
           77  +** will be skipped in addition to files that may contain binary content.
    74     78   */
    75     79   void diff_file(
    76     80     Blob *pFile1,             /* In memory content to compare from */
           81  +  int isBin1,               /* Does the 'from' content appear to be binary */
    77     82     const char *zFile2,       /* On disk content to compare to */
    78     83     const char *zName,        /* Display name of the file */
    79     84     const char *zDiffCmd,     /* Command for comparison */
           85  +  const char *zBinGlob,     /* Treat file names matching this as binary */
           86  +  int fIncludeBinary,       /* Include binary files for external diff */
    80     87     u64 diffFlags             /* Flags to control the diff */
    81     88   ){
    82     89     if( zDiffCmd==0 ){
    83     90       Blob out;                 /* Diff output text */
    84     91       Blob file2;               /* Content of zFile2 */
    85     92       const char *zName2;       /* Name of zFile2 for display */
    86     93   
................................................................................
   114    121   
   115    122       /* Release memory resources */
   116    123       blob_reset(&file2);
   117    124     }else{
   118    125       int cnt = 0;
   119    126       Blob nameFile1;    /* Name of temporary file to old pFile1 content */
   120    127       Blob cmd;          /* Text of command to run */
          128  +
          129  +    if( !fIncludeBinary ){
          130  +      Blob file2;
          131  +      if( isBin1 ){
          132  +        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
          133  +        return;
          134  +      }
          135  +      if( zBinGlob ){
          136  +        Glob *pBinary = glob_create(zBinGlob);
          137  +        if( glob_match(pBinary, zName) ){
          138  +          fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
          139  +          glob_free(pBinary);
          140  +          return;
          141  +        }
          142  +        glob_free(pBinary);
          143  +      }
          144  +      blob_zero(&file2);
          145  +      if( file_wd_size(zFile2)>=0 ){
          146  +        if( file_wd_islink(zFile2) ){
          147  +          blob_read_link(&file2, zFile2);
          148  +        }else{
          149  +          blob_read_from_file(&file2, zFile2);
          150  +        }
          151  +      }
          152  +      if( looks_like_binary(blob_str(&file2), blob_size(&file2)) ){
          153  +        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
          154  +        blob_reset(&file2);
          155  +        return;
          156  +      }
          157  +      blob_reset(&file2);
          158  +    }
   121    159   
   122    160       /* Construct a temporary file to hold pFile1 based on the name of
   123    161       ** zFile2 */
   124    162       blob_zero(&nameFile1);
   125    163       do{
   126    164         blob_reset(&nameFile1);
   127    165         blob_appendf(&nameFile1, "%s~%d", zFile2, cnt++);
................................................................................
   149    187   ** Show the difference between two files, both in memory.
   150    188   **
   151    189   ** The difference is the set of edits needed to transform pFile1 into
   152    190   ** pFile2.
   153    191   **
   154    192   ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
   155    193   ** command zDiffCmd to do the diffing.
          194  +**
          195  +** When using an external diff program, zBinGlob contains the GLOB patterns
          196  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          197  +** will be skipped in addition to files that may contain binary content.
   156    198   */
   157    199   void diff_file_mem(
   158    200     Blob *pFile1,             /* In memory content to compare from */
   159    201     Blob *pFile2,             /* In memory content to compare to */
          202  +  int isBin1,               /* Does the 'from' content appear to be binary */
          203  +  int isBin2,               /* Does the 'to' content appear to be binary */
   160    204     const char *zName,        /* Display name of the file */
   161    205     const char *zDiffCmd,     /* Command for comparison */
          206  +  const char *zBinGlob,     /* Treat file names matching this as binary */
          207  +  int fIncludeBinary,       /* Include binary files for external diff */
   162    208     u64 diffFlags             /* Diff flags */
   163    209   ){
   164    210     if( diffFlags & DIFF_BRIEF ) return;
   165    211     if( zDiffCmd==0 ){
   166    212       Blob out;      /* Diff output text */
   167    213   
   168    214       blob_zero(&out);
................................................................................
   172    218   
   173    219       /* Release memory resources */
   174    220       blob_reset(&out);
   175    221     }else{
   176    222       Blob cmd;
   177    223       char zTemp1[300];
   178    224       char zTemp2[300];
          225  +
          226  +    if( !fIncludeBinary ){
          227  +      if( isBin1 || isBin2 ){
          228  +        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
          229  +        return;
          230  +      }
          231  +      if( zBinGlob ){
          232  +        Glob *pBinary = glob_create(zBinGlob);
          233  +        if( glob_match(pBinary, zName) ){
          234  +          fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
          235  +          glob_free(pBinary);
          236  +          return;
          237  +        }
          238  +        glob_free(pBinary);
          239  +      }
          240  +    }
   179    241   
   180    242       /* Construct a temporary file names */
   181    243       file_tempname(sizeof(zTemp1), zTemp1);
   182    244       file_tempname(sizeof(zTemp2), zTemp2);
   183    245       blob_write_to_file(pFile1, zTemp1);
   184    246       blob_write_to_file(pFile2, zTemp2);
   185    247   
................................................................................
   199    261       blob_reset(&cmd);
   200    262     }
   201    263   }
   202    264   
   203    265   /*
   204    266   ** Do a diff against a single file named in zFileTreeName from version zFrom
   205    267   ** against the same file on disk.
          268  +**
          269  +** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
          270  +** command zDiffCmd to do the diffing.
          271  +**
          272  +** When using an external diff program, zBinGlob contains the GLOB patterns
          273  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          274  +** will be skipped in addition to files that may contain binary content.
   206    275   */
   207    276   static void diff_one_against_disk(
   208    277     const char *zFrom,        /* Name of file */
   209    278     const char *zDiffCmd,     /* Use this "diff" command */
          279  +  const char *zBinGlob,     /* Treat file names matching this as binary */
          280  +  int fIncludeBinary,       /* Include binary files for external diff */
   210    281     u64 diffFlags,            /* Diff control flags */
   211    282     const char *zFileTreeName
   212    283   ){
   213    284     Blob fname;
   214    285     Blob content;
   215    286     int isLink;
          287  +  int isBin;
   216    288     file_tree_name(zFileTreeName, &fname, 1);
   217         -  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
          289  +  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0,
          290  +                             fIncludeBinary ? 0 : &isBin, 0);
   218    291     if( !isLink != !file_wd_islink(zFrom) ){
   219         -    fossil_print("cannot compute difference between "
   220         -                 "symlink and regular file\n");
          292  +    fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
   221    293     }else{
   222         -    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
          294  +    diff_file(&content, isBin, zFileTreeName, zFileTreeName,
          295  +              zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   223    296     }
   224    297     blob_reset(&content);
   225    298     blob_reset(&fname);
   226    299   }
   227    300   
   228    301   /*
   229    302   ** Run a diff between the version zFrom and files on disk.  zFrom might
   230    303   ** be NULL which means to simply show the difference between the edited
   231    304   ** files on disk and the check-out on which they are based.
          305  +**
          306  +** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
          307  +** command zDiffCmd to do the diffing.
          308  +**
          309  +** When using an external diff program, zBinGlob contains the GLOB patterns
          310  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          311  +** will be skipped in addition to files that may contain binary content.
   232    312   */
   233    313   static void diff_all_against_disk(
   234    314     const char *zFrom,        /* Version to difference from */
   235    315     const char *zDiffCmd,     /* Use this diff command.  NULL for built-in */
          316  +  const char *zBinGlob,     /* Treat file names matching this as binary */
          317  +  int fIncludeBinary,       /* Treat file names matching this as binary */
   236    318     u64 diffFlags             /* Flags controlling diff output */
   237    319   ){
   238    320     int vid;
   239    321     Blob sql;
   240    322     Stmt q;
   241    323     int asNewFile;            /* Treat non-existant files as empty files */
   242    324   
................................................................................
   305    387       }else if( isChnged==3 ){
   306    388         fossil_print("ADDED_BY_MERGE %s\n", zPathname);
   307    389         srcid = 0;
   308    390         if( !asNewFile ){ showDiff = 0; }
   309    391       }
   310    392       if( showDiff ){
   311    393         Blob content;
          394  +      int isBin;
   312    395         if( !isLink != !file_wd_islink(zFullName) ){
   313    396           diff_print_index(zPathname, diffFlags);
   314    397           diff_print_filenames(zPathname, zPathname, diffFlags);
   315         -        fossil_print("cannot compute difference between "
   316         -                     "symlink and regular file\n");
          398  +        fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
   317    399           continue;
   318    400         }
   319    401         if( srcid>0 ){
   320    402           content_get(srcid, &content);
   321    403         }else{
   322    404           blob_zero(&content);
   323    405         }
          406  +      isBin = fIncludeBinary ? 0 : looks_like_binary(blob_str(&content),
          407  +                                                     blob_size(&content));
   324    408         diff_print_index(zPathname, diffFlags);
   325         -      diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
          409  +      diff_file(&content, isBin, zFullName, zPathname, zDiffCmd,
          410  +                zBinGlob, fIncludeBinary, diffFlags);
   326    411         blob_reset(&content);
   327    412       }
   328    413       free(zToFree);
   329    414     }
   330    415     db_finalize(&q);
   331    416     db_end_transaction(1);  /* ROLLBACK */
   332    417   }
   333    418   
   334    419   /*
   335    420   ** Output the differences between two versions of a single file.
   336    421   ** zFrom and zTo are the check-ins containing the two file versions.
          422  +**
          423  +** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
          424  +** command zDiffCmd to do the diffing.
          425  +**
          426  +** When using an external diff program, zBinGlob contains the GLOB patterns
          427  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          428  +** will be skipped in addition to files that may contain binary content.
   337    429   */
   338    430   static void diff_one_two_versions(
   339    431     const char *zFrom,
   340    432     const char *zTo,
   341    433     const char *zDiffCmd,
          434  +  const char *zBinGlob,
          435  +  int fIncludeBinary,
   342    436     u64 diffFlags,
   343    437     const char *zFileTreeName
   344    438   ){
   345    439     char *zName;
   346    440     Blob fname;
   347    441     Blob v1, v2;
   348    442     int isLink1, isLink2;
          443  +  int isBin1, isBin2;
   349    444     if( diffFlags & DIFF_BRIEF ) return;
   350    445     file_tree_name(zFileTreeName, &fname, 1);
   351    446     zName = blob_str(&fname);
   352         -  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
   353         -  historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
          447  +  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0,
          448  +                             fIncludeBinary ? 0 : &isBin1, 0);
          449  +  historical_version_of_file(zTo, zName, &v2, &isLink2, 0,
          450  +                             fIncludeBinary ? 0 : &isBin2, 0);
   354    451     if( isLink1 != isLink2 ){
   355    452       diff_print_filenames(zName, zName, diffFlags);
   356         -    fossil_print("cannot compute difference "
   357         -                 " between symlink and regular file\n");
          453  +    fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
   358    454     }else{
   359         -    diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
          455  +    diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd,
          456  +                  zBinGlob, fIncludeBinary, diffFlags);
   360    457     }
   361    458     blob_reset(&v1);
   362    459     blob_reset(&v2);
   363    460     blob_reset(&fname);
   364    461   }
   365    462   
   366    463   /*
   367    464   ** Show the difference between two files identified by ManifestFile
   368    465   ** entries.
          466  +**
          467  +** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
          468  +** command zDiffCmd to do the diffing.
          469  +**
          470  +** When using an external diff program, zBinGlob contains the GLOB patterns
          471  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          472  +** will be skipped in addition to files that may contain binary content.
   369    473   */
   370    474   static void diff_manifest_entry(
   371    475     struct ManifestFile *pFrom,
   372    476     struct ManifestFile *pTo,
   373    477     const char *zDiffCmd,
          478  +  const char *zBinGlob,
          479  +  int fIncludeBinary,
   374    480     u64 diffFlags
   375    481   ){
   376    482     Blob f1, f2;
          483  +  int isBin1, isBin2;
   377    484     int rid;
   378    485     const char *zName =  pFrom ? pFrom->zName : pTo->zName;
   379    486     if( diffFlags & DIFF_BRIEF ) return;
   380    487     diff_print_index(zName, diffFlags);
   381    488     if( pFrom ){
   382    489       rid = uuid_to_rid(pFrom->zUuid, 0);
   383    490       content_get(rid, &f1);
................................................................................
   386    493     }
   387    494     if( pTo ){
   388    495       rid = uuid_to_rid(pTo->zUuid, 0);
   389    496       content_get(rid, &f2);
   390    497     }else{
   391    498       blob_zero(&f2);
   392    499     }
   393         -  diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
          500  +  isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&f1),
          501  +                                                  blob_size(&f1));
          502  +  isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&f2),
          503  +                                                  blob_size(&f2));
          504  +  diff_file_mem(&f1, &f2, isBin1, isBin2, zName, zDiffCmd,
          505  +                zBinGlob, fIncludeBinary, diffFlags);
   394    506     blob_reset(&f1);
   395    507     blob_reset(&f2);
   396    508   }
   397    509   
   398    510   /*
   399    511   ** Output the differences between two check-ins.
          512  +**
          513  +** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
          514  +** command zDiffCmd to do the diffing.
          515  +**
          516  +** When using an external diff program, zBinGlob contains the GLOB patterns
          517  +** for file names to treat as binary.  If fIncludeBinary is zero, these files
          518  +** will be skipped in addition to files that may contain binary content.
   400    519   */
   401    520   static void diff_all_two_versions(
   402    521     const char *zFrom,
   403    522     const char *zTo,
   404    523     const char *zDiffCmd,
          524  +  const char *zBinGlob,
          525  +  int fIncludeBinary,
   405    526     u64 diffFlags
   406    527   ){
   407    528     Manifest *pFrom, *pTo;
   408    529     ManifestFile *pFromFile, *pToFile;
   409    530     int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
   410    531   
   411    532     pFrom = manifest_get_by_name(zFrom, 0);
................................................................................
   423    544         cmp = -1;
   424    545       }else{
   425    546         cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
   426    547       }
   427    548       if( cmp<0 ){
   428    549         fossil_print("DELETED %s\n", pFromFile->zName);
   429    550         if( asNewFlag ){
   430         -        diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
          551  +        diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
          552  +                            fIncludeBinary, diffFlags);
   431    553         }
   432    554         pFromFile = manifest_file_next(pFrom,0);
   433    555       }else if( cmp>0 ){
   434    556         fossil_print("ADDED   %s\n", pToFile->zName);
   435    557         if( asNewFlag ){
   436         -        diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
          558  +        diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
          559  +                            fIncludeBinary, diffFlags);
   437    560         }
   438    561         pToFile = manifest_file_next(pTo,0);
   439    562       }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
   440    563         /* No changes */
   441    564         pFromFile = manifest_file_next(pFrom,0);
   442    565         pToFile = manifest_file_next(pTo,0);
   443    566       }else{
   444    567         if( diffFlags & DIFF_BRIEF ){
   445    568           fossil_print("CHANGED %s\n", pFromFile->zName);
   446    569         }else{
   447         -        diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
          570  +        diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob,
          571  +                            fIncludeBinary, diffFlags);
   448    572         }
   449    573         pFromFile = manifest_file_next(pFrom,0);
   450    574         pToFile = manifest_file_next(pTo,0);
   451    575       }
   452    576     }
   453    577     manifest_destroy(pFrom);
   454    578     manifest_destroy(pTo);
................................................................................
   542    666     }
   543    667     blob_appendf(&script, "}\n%s", zDiffScript);
   544    668     zTempFile = write_blob_to_temp_file(&script);
   545    669     zCmd = mprintf("tclsh \"%s\"", zTempFile);
   546    670     fossil_system(zCmd);
   547    671     file_delete(zTempFile);
   548    672   }
          673  +
          674  +/*
          675  +** Returns non-zero if files that may be binary should be used with external
          676  +** diff programs.
          677  +*/
          678  +int diff_include_binary_files(void){
          679  +  if( is_truth(find_option("diff-binary", 0, 1)) ){
          680  +    return 1;
          681  +  }
          682  +  if( db_get_boolean("diff-binary", 1) ){
          683  +    return 1;
          684  +  }
          685  +  return 0;
          686  +}
          687  +
          688  +/*
          689  +** Returns the GLOB pattern for file names that should be treated as binary
          690  +** by the diff subsystem, if any.
          691  +*/
          692  +const char *diff_get_binary_glob(void){
          693  +  const char *zBinGlob = find_option("binary", 0, 1);
          694  +  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
          695  +  return zBinGlob;
          696  +}
   549    697   
   550    698   /*
   551    699   ** COMMAND: diff
   552    700   ** COMMAND: gdiff
   553    701   **
   554    702   ** Usage: %fossil diff|gdiff ?OPTIONS? ?FILE1? ?FILE2 ...?
   555    703   **
................................................................................
   571    719   ** rather than any external diff program that might be configured using
   572    720   ** the "setting" command.  If no external diff program is configured, then
   573    721   ** the "-i" option is a no-op.  The "-i" option converts "gdiff" into "diff".
   574    722   **
   575    723   ** The "-N" or "--new-file" option causes the complete text of added or
   576    724   ** deleted files to be displayed.
   577    725   **
          726  +** The "--diff-binary" option enables or disables the inclusion of binary files
          727  +** when using an external diff program.
          728  +**
          729  +** The "--binary" option causes files matching the glob PATTERN to be treated
          730  +** as binary when considering if they should be used with external diff program.
          731  +** This option overrides the "binary-glob" setting.
          732  +**
   578    733   ** Options:
   579    734   **   --branch BRANCH     Show diff of all changes on BRANCH
   580    735   **   --brief             Show filenames only
   581    736   **   --context|-c N      Use N lines of context 
   582    737   **   --from|-r VERSION   select VERSION as source for the diff
   583    738   **   -i                  use internal diff logic
   584    739   **   --new-file|-N       output complete text of added or deleted files
   585    740   **   --tk                Launch a Tcl/Tk GUI for display
   586    741   **   --to VERSION        select VERSION as target for the diff
   587    742   **   --side-by-side|-y   side-by-side diff
   588    743   **   --unified           unified diff
   589    744   **   --width|-W N        Width of lines in side-by-side diff 
          745  +**   --diff-binary BOOL  Include binary files when using external commands
          746  +**   --binary PATTERN    Treat files that match the glob PATTERN as binary
   590    747   */
   591    748   void diff_cmd(void){
   592    749     int isGDiff;               /* True for gdiff.  False for normal diff */
   593    750     int isInternDiff;          /* True for internal diff */
   594    751     int hasNFlag;              /* True if -N or --new-file flag is used */
   595    752     const char *zFrom;         /* Source version number */
   596    753     const char *zTo;           /* Target version number */
   597    754     const char *zBranch;       /* Branch to diff */
   598    755     const char *zDiffCmd = 0;  /* External diff command. NULL for internal diff */
          756  +  const char *zBinGlob = 0;  /* Treat file names matching this as binary */
          757  +  int fIncludeBinary = 0;    /* Include binary files for external diff */
   599    758     u64 diffFlags = 0;         /* Flags to control the DIFF */
   600    759     int f;
   601    760   
   602    761     if( find_option("tk",0,0)!=0 ){
   603    762       diff_tk();
   604    763       return;
   605    764     }
................................................................................
   617    776         fossil_fatal("cannot use --from or --to with --branch");
   618    777       }
   619    778       zTo = zBranch;
   620    779       zFrom = mprintf("root:%s", zBranch);
   621    780     }
   622    781     if( zTo==0 ){
   623    782       db_must_be_within_tree();
   624         -    verify_all_options();
   625    783       if( !isInternDiff ){
   626    784         zDiffCmd = diff_command_external(isGDiff);
   627    785       }
          786  +    zBinGlob = diff_get_binary_glob();
          787  +    fIncludeBinary = diff_include_binary_files();
          788  +    verify_all_options();
   628    789       if( g.argc>=3 ){
   629    790         for(f=2; f<g.argc; ++f){
   630         -        diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]);
          791  +        diff_one_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
          792  +                              diffFlags, g.argv[f]);
   631    793         }
   632    794       }else{
   633         -      diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
          795  +      diff_all_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
          796  +                            diffFlags);
   634    797       }
   635    798     }else if( zFrom==0 ){
   636    799       fossil_fatal("must use --from if --to is present");
   637    800     }else{
   638    801       db_find_and_open_repository(0, 0);
   639         -    verify_all_options();
   640    802       if( !isInternDiff ){
   641    803         zDiffCmd = diff_command_external(isGDiff);
   642    804       }
          805  +    zBinGlob = diff_get_binary_glob();
          806  +    fIncludeBinary = diff_include_binary_files();
          807  +    verify_all_options();
   643    808       if( g.argc>=3 ){
   644    809         for(f=2; f<g.argc; ++f){
   645         -        diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]);        
          810  +        diff_one_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
          811  +                              diffFlags, g.argv[f]);
   646    812         }
   647    813       }else{
   648         -      diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
          814  +      diff_all_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
          815  +                            diffFlags);
   649    816       }
   650    817     }
   651    818   }
   652    819   
   653    820   /*
   654    821   ** WEBPAGE: vpatch
   655    822   ** URL vpatch?from=UUID&to=UUID
................................................................................
   658    825     const char *zFrom = P("from");
   659    826     const char *zTo = P("to");
   660    827     login_check_credentials();
   661    828     if( !g.perm.Read ){ login_needed(); return; }
   662    829     if( zFrom==0 || zTo==0 ) fossil_redirect_home();
   663    830   
   664    831     cgi_set_content_type("text/plain");
   665         -  diff_all_two_versions(zFrom, zTo, 0, DIFF_NEWFILE);
          832  +  diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_NEWFILE);
   666    833   }

Changes to src/finfo.c.

   113    113     }else if( find_option("print","p",0) ){
   114    114       Blob record;
   115    115       Blob fname;
   116    116       const char *zRevision = find_option("revision", "r", 1);
   117    117   
   118    118       file_tree_name(g.argv[2], &fname, 1);
   119    119       if( zRevision ){
   120         -      historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0);
          120  +      historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0);
   121    121       }else{
   122    122         int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
   123    123                          &fname, filename_collation());
   124    124         if( rid==0 ){
   125    125           fossil_fatal("no history for file: %b", &fname);
   126    126         }
   127    127         content_get(rid, &record);

Changes to src/stash.c.

   265    265         nConflict);
   266    266     }
   267    267   }
   268    268   
   269    269   /*
   270    270   ** Show the diffs associate with a single stash.
   271    271   */
   272         -static void stash_diff(int stashid, const char *zDiffCmd, u64 diffFlags){
          272  +static void stash_diff(
          273  +  int stashid,
          274  +  const char *zDiffCmd,
          275  +  const char *zBinGlob,
          276  +  int fIncludeBinary,
          277  +  u64 diffFlags
          278  +){
   273    279     Stmt q;
   274    280     Blob empty;
   275    281     blob_zero(&empty);
   276    282     db_prepare(&q,
   277    283        "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
   278    284        "  FROM stashfile WHERE stashid=%d",
   279    285        stashid
   280    286     );
   281    287     while( db_step(&q)==SQLITE_ROW ){
   282    288       int rid = db_column_int(&q, 0);
   283    289       int isRemoved = db_column_int(&q, 1);
   284    290       int isLink = db_column_int(&q, 3);
          291  +    int isBin1, isBin2;
   285    292       const char *zOrig = db_column_text(&q, 4);
   286    293       const char *zNew = db_column_text(&q, 5);
   287    294       char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
   288    295       Blob delta;
   289    296       if( rid==0 ){
   290    297         db_ephemeral_blob(&q, 6, &delta);
   291    298         fossil_print("ADDED %s\n", zNew);
   292    299         diff_print_index(zNew, diffFlags);
   293         -      diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags);
          300  +      isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&empty),
          301  +                                                      blob_size(&empty));
          302  +      isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&delta),
          303  +                                                      blob_size(&delta));
          304  +      diff_file_mem(&empty, &delta, isBin1, isBin2, zNew, zDiffCmd,
          305  +                    zBinGlob, fIncludeBinary, diffFlags);
   294    306       }else if( isRemoved ){
   295    307         fossil_print("DELETE %s\n", zOrig);
   296    308         if( file_wd_islink(zOPath) ){
   297    309           blob_read_link(&delta, zOPath);
   298    310         }else{
   299    311           blob_read_from_file(&delta, zOPath);
   300    312         }
   301    313         diff_print_index(zNew, diffFlags);
   302         -      diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags);
          314  +      isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&delta),
          315  +                                                      blob_size(&delta));
          316  +      isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&empty),
          317  +                                                      blob_size(&empty));
          318  +      diff_file_mem(&delta, &empty, isBin1, isBin2, zOrig, zDiffCmd,
          319  +                    zBinGlob, fIncludeBinary, diffFlags);
   303    320       }else{
   304    321         Blob a, b, disk;
   305    322         int isOrigLink = file_wd_islink(zOPath);
   306    323         db_ephemeral_blob(&q, 6, &delta);
   307    324         if( isOrigLink ){
   308    325           blob_read_link(&disk, zOPath);
   309    326         }else{
   310    327           blob_read_from_file(&disk, zOPath);        
   311    328         }
   312    329         fossil_print("CHANGED %s\n", zNew);
   313    330         if( !isOrigLink != !isLink ){
   314    331           diff_print_index(zNew, diffFlags);
   315    332           diff_print_filenames(zOrig, zNew, diffFlags);
   316         -        printf("cannot compute difference between symlink and regular file\n");
          333  +        printf(DIFF_CANNOT_COMPUTE_SYMLINK);
   317    334         }else{
   318    335           content_get(rid, &a);
   319    336           blob_delta_apply(&a, &delta, &b);
   320         -        diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags);
          337  +        isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&disk),
          338  +                                                        blob_size(&disk));
          339  +        isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&b),
          340  +                                                        blob_size(&b));
          341  +        diff_file_mem(&disk, &b, isBin1, isBin2, zNew, zDiffCmd,
          342  +                      zBinGlob, fIncludeBinary, diffFlags);
   321    343           blob_reset(&a);
   322    344           blob_reset(&b);
   323    345         }
   324    346         blob_reset(&disk);
   325    347       }
   326    348       blob_reset(&delta);
   327    349    }
................................................................................
   411    433     const char *zDb;
   412    434     const char *zCmd;
   413    435     int nCmd;
   414    436     int stashid;
   415    437   
   416    438     undo_capture_command_line();
   417    439     db_must_be_within_tree();
          440  +  db_open_config(0);
   418    441     db_begin_transaction();
   419    442     zDb = db_name("localdb");
   420    443     db_multi_exec(zStashInit, zDb, zDb);
   421    444     if( g.argc<=2 ){
   422    445       zCmd = "save";
   423    446     }else{
   424    447       zCmd = g.argv[2];
................................................................................
   548    571       db_multi_exec("UPDATE vfile SET mtime=0 WHERE pathname IN "
   549    572                     "(SELECT origname FROM stashfile WHERE stashid=%d)",
   550    573                     stashid);
   551    574       undo_finish();
   552    575     }else
   553    576     if( memcmp(zCmd, "diff", nCmd)==0 ){
   554    577       const char *zDiffCmd = diff_command_external(0);
          578  +    const char *zBinGlob = 0;
          579  +    int fIncludeBinary = 0;
   555    580       u64 diffFlags = diff_options();
   556    581       if( g.argc>4 ) usage("diff STASHID");
          582  +    if( zDiffCmd ){
          583  +      zBinGlob = diff_get_binary_glob();
          584  +      fIncludeBinary = diff_include_binary_files();
          585  +    }
   557    586       stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
   558         -    stash_diff(stashid, zDiffCmd, diffFlags);
          587  +    stash_diff(stashid, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   559    588     }else
   560    589     if( memcmp(zCmd, "gdiff", nCmd)==0 ){
   561    590       const char *zDiffCmd = diff_command_external(1);
          591  +    const char *zBinGlob = 0;
          592  +    int fIncludeBinary = 0;
   562    593       u64 diffFlags = diff_options();
   563    594       if( g.argc>4 ) usage("gdiff STASHID");
          595  +    if( zDiffCmd ){
          596  +      zBinGlob = diff_get_binary_glob();
          597  +      fIncludeBinary = diff_include_binary_files();
          598  +    }
   564    599       stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
   565         -    stash_diff(stashid, zDiffCmd, diffFlags);
          600  +    stash_diff(stashid, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   566    601     }else
   567    602     if( memcmp(zCmd, "help", nCmd)==0 ){
   568    603       g.argv[1] = "help";
   569    604       g.argv[2] = "stash";
   570    605       g.argc = 3;
   571    606       help_cmd();
   572    607     }else
   573    608     {
   574    609       usage("SUBCOMMAND ARGS...");
   575    610     }
   576    611     db_end_transaction(0);
   577    612   }

Changes to src/update.c.

   593    593   ** Get the contents of a file within the checking "revision".  If
   594    594   ** revision==NULL then get the file content for the current checkout.
   595    595   */
   596    596   int historical_version_of_file(
   597    597     const char *revision,    /* The checkin containing the file */
   598    598     const char *file,        /* Full treename of the file */
   599    599     Blob *content,           /* Put the content here */
   600         -  int *pIsLink,             /* Set to true if file is link. */
          600  +  int *pIsLink,            /* Set to true if file is link. */
   601    601     int *pIsExe,             /* Set to true if file is executable */
          602  +  int *pIsBin,             /* Set to true if file is binary */
   602    603     int errCode              /* Error code if file not found.  Panic if 0. */
   603    604   ){
   604    605     Manifest *pManifest;
   605    606     ManifestFile *pFile;
   606    607     int rid=0;
   607    608     
   608    609     if( revision ){
................................................................................
   615    616       fossil_fatal("no such checkin: %s", revision);
   616    617     }
   617    618     pManifest = manifest_get(rid, CFTYPE_MANIFEST);
   618    619     
   619    620     if( pManifest ){
   620    621       pFile = manifest_file_find(pManifest, file);
   621    622       if( pFile ){
          623  +      int rc;
   622    624         rid = uuid_to_rid(pFile->zUuid, 0);
   623    625         if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE );
   624    626         if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK );
   625    627         manifest_destroy(pManifest);
   626         -      return content_get(rid, content);
          628  +      rc = content_get(rid, content);
          629  +      if( rc && pIsBin ){
          630  +        *pIsBin = looks_like_binary(blob_str(content), blob_size(content));
          631  +      }
          632  +      return rc;
   627    633       }
   628    634       manifest_destroy(pManifest);
   629    635       if( errCode<=0 ){
   630    636         fossil_fatal("file %s does not exist in checkin: %s", file, revision);
   631    637       }
   632    638     }else if( errCode<=0 ){
   633    639       if( revision==0 ){
................................................................................
   710    716     while( db_step(&q)==SQLITE_ROW ){
   711    717       int isExe = 0;
   712    718       int isLink = 0;
   713    719       char *zFull;
   714    720       zFile = db_column_text(&q, 0);
   715    721       zFull = mprintf("%/%/", g.zLocalRoot, zFile);
   716    722       errCode = historical_version_of_file(zRevision, zFile, &record,
   717         -                                         &isLink, &isExe,2);
          723  +                                         &isLink, &isExe, 0, 2);
   718    724       if( errCode==2 ){
   719    725         if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){
   720    726           fossil_print("UNMANAGE: %s\n", zFile);
   721    727         }else{
   722    728           undo_save(zFile);
   723    729           file_delete(zFull);
   724    730           fossil_print("DELETE: %s\n", zFile);