Index: src/db.c
==================================================================
--- src/db.c
+++ src/db.c
@@ -2014,10 +2014,11 @@
   { "binary-glob",   0,               32, 1, ""                    },
   { "clearsign",     0,                0, 0, "off"                 },
   { "case-sensitive",0,                0, 0, "on"                  },
   { "crnl-glob",     0,               16, 1, ""                    },
   { "default-perms", 0,               16, 0, "u"                   },
+  { "diff-binary",   0,                0, 0, "on"                  },
   { "diff-command",  0,               16, 0, ""                    },
   { "dont-push",     0,                0, 0, "off"                 },
   { "editor",        0,               16, 0, ""                    },
   { "gdiff-command", 0,               16, 0, "gdiff"               },
   { "gmerge-command",0,               40, 0, ""                    },
@@ -2106,10 +2107,14 @@
 **                     Set to "*" to disable CR+NL checking.
 **
 **    default-perms    Permissions given automatically to new users.  For more
 **                     information on permissions see Users page in Server
 **                     Administration of the HTTP UI. Default: u.
+**
+**    diff-binary      If TRUE (the default), permit files that may be binary
+**                     or that match the "binary-glob" setting to be used with
+**                     external diff programs.  If FALSE, skip these files.
 **
 **    diff-command     External command to run when performing a diff.
 **                     If undefined, the internal text diff will be used.
 **
 **    dont-push        Prevent this repository from pushing from client to

Index: src/diff.c
==================================================================
--- src/diff.c
+++ src/diff.c
@@ -38,10 +38,20 @@
 #define DIFF_LINENO       ((u64)0x20000000) /* Show line numbers */
 #define DIFF_WS_WARNING   ((u64)0x40000000) /* Warn about whitespace */
 #define DIFF_NOOPT        (((u64)0x01)<<32) /* Suppress optimizations (debug) */
 #define DIFF_INVERT       (((u64)0x02)<<32) /* Invert the diff (debug) */
 
+/*
+** These error messages are shared in multiple locations.  They are defined
+** here for consistency.
+*/
+#define DIFF_CANNOT_COMPUTE_BINARY \
+    "cannot compute difference between binary files\n"
+
+#define DIFF_CANNOT_COMPUTE_SYMLINK \
+    "cannot compute difference between symlink and regular file\n"
+
 #endif /* INTERFACE */
 
 /*
 ** Maximum length of a line in a text file.  (8192)
 */
@@ -157,10 +167,25 @@
 
   /* Return results */
   *pnLine = nLine;
   return a;
 }
+
+/*
+** Returns non-zero if the specified content appears to be binary or
+** contains a line that is too long.
+*/
+int looks_like_binary(const char *z, int n){
+  int nLine;
+  DLine *aContent = break_into_lines(z, n, &nLine, 0);
+  if( aContent ){
+    fossil_free(aContent);
+    return 0;
+  }else{
+    return 1;
+  }
+}
 
 /*
 ** Return true if two DLine elements are identical.
 */
 static int same_dline(DLine *pA, DLine *pB){
@@ -1499,14 +1524,14 @@
   c.aFrom = break_into_lines(blob_str(pA_Blob), blob_size(pA_Blob),
                              &c.nFrom, ignoreEolWs);
   c.aTo = break_into_lines(blob_str(pB_Blob), blob_size(pB_Blob),
                            &c.nTo, ignoreEolWs);
   if( c.aFrom==0 || c.aTo==0 ){
-    free(c.aFrom);
-    free(c.aTo);
+    fossil_free(c.aFrom);
+    fossil_free(c.aTo);
     if( pOut ){
-      blob_appendf(pOut, "cannot compute difference between binary files\n");
+      blob_appendf(pOut, DIFF_CANNOT_COMPUTE_BINARY);
     }
     return 0;
   }
 
   /* Compute the difference */
@@ -1521,13 +1546,13 @@
       sbsDiff(&c, pOut, nContext, width, escHtml);
     }else{
       int showLn = (diffFlags & DIFF_LINENO)!=0;
       contextDiff(&c, pOut, nContext, showLn, escHtml);
     }
-    free(c.aFrom);
-    free(c.aTo);
-    free(c.aEdit);
+    fossil_free(c.aFrom);
+    fossil_free(c.aTo);
+    fossil_free(c.aEdit);
     return 0;
   }else{
     /* If a context diff is not requested, then return the
     ** array of COPY/DELETE/INSERT triples.
     */
@@ -1702,11 +1727,11 @@
     }
     lnTo += p->c.aEdit[i+2];
   }
 
   /* Clear out the diff results */
-  free(p->c.aEdit);
+  fossil_free(p->c.aEdit);
   p->c.aEdit = 0;
   p->c.nEdit = 0;
   p->c.nEditAlloc = 0;
 
   /* Clear out the from file */

Index: src/diffcmd.c
==================================================================
--- src/diffcmd.c
+++ src/diffcmd.c
@@ -69,16 +69,23 @@
 ** The difference is the set of edits needed to transform pFile1 into
 ** zFile2.  The content of pFile1 is in memory.  zFile2 exists on disk.
 **
 ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
 ** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 void diff_file(
   Blob *pFile1,             /* In memory content to compare from */
+  int isBin1,               /* Does the 'from' content appear to be binary */
   const char *zFile2,       /* On disk content to compare to */
   const char *zName,        /* Display name of the file */
   const char *zDiffCmd,     /* Command for comparison */
+  const char *zBinGlob,     /* Treat file names matching this as binary */
+  int fIncludeBinary,       /* Include binary files for external diff */
   u64 diffFlags             /* Flags to control the diff */
 ){
   if( zDiffCmd==0 ){
     Blob out;                 /* Diff output text */
     Blob file2;               /* Content of zFile2 */
@@ -116,10 +123,41 @@
     blob_reset(&file2);
   }else{
     int cnt = 0;
     Blob nameFile1;    /* Name of temporary file to old pFile1 content */
     Blob cmd;          /* Text of command to run */
+
+    if( !fIncludeBinary ){
+      Blob file2;
+      if( isBin1 ){
+        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
+        return;
+      }
+      if( zBinGlob ){
+        Glob *pBinary = glob_create(zBinGlob);
+        if( glob_match(pBinary, zName) ){
+          fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
+          glob_free(pBinary);
+          return;
+        }
+        glob_free(pBinary);
+      }
+      blob_zero(&file2);
+      if( file_wd_size(zFile2)>=0 ){
+        if( file_wd_islink(zFile2) ){
+          blob_read_link(&file2, zFile2);
+        }else{
+          blob_read_from_file(&file2, zFile2);
+        }
+      }
+      if( looks_like_binary(blob_str(&file2), blob_size(&file2)) ){
+        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
+        blob_reset(&file2);
+        return;
+      }
+      blob_reset(&file2);
+    }
 
     /* Construct a temporary file to hold pFile1 based on the name of
     ** zFile2 */
     blob_zero(&nameFile1);
     do{
@@ -151,16 +189,24 @@
 ** The difference is the set of edits needed to transform pFile1 into
 ** pFile2.
 **
 ** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
 ** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 void diff_file_mem(
   Blob *pFile1,             /* In memory content to compare from */
   Blob *pFile2,             /* In memory content to compare to */
+  int isBin1,               /* Does the 'from' content appear to be binary */
+  int isBin2,               /* Does the 'to' content appear to be binary */
   const char *zName,        /* Display name of the file */
   const char *zDiffCmd,     /* Command for comparison */
+  const char *zBinGlob,     /* Treat file names matching this as binary */
+  int fIncludeBinary,       /* Include binary files for external diff */
   u64 diffFlags             /* Diff flags */
 ){
   if( diffFlags & DIFF_BRIEF ) return;
   if( zDiffCmd==0 ){
     Blob out;      /* Diff output text */
@@ -174,10 +220,26 @@
     blob_reset(&out);
   }else{
     Blob cmd;
     char zTemp1[300];
     char zTemp2[300];
+
+    if( !fIncludeBinary ){
+      if( isBin1 || isBin2 ){
+        fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
+        return;
+      }
+      if( zBinGlob ){
+        Glob *pBinary = glob_create(zBinGlob);
+        if( glob_match(pBinary, zName) ){
+          fossil_print(DIFF_CANNOT_COMPUTE_BINARY);
+          glob_free(pBinary);
+          return;
+        }
+        glob_free(pBinary);
+      }
+    }
 
     /* Construct a temporary file names */
     file_tempname(sizeof(zTemp1), zTemp1);
     file_tempname(sizeof(zTemp2), zTemp2);
     blob_write_to_file(pFile1, zTemp1);
@@ -201,40 +263,60 @@
 }
 
 /*
 ** Do a diff against a single file named in zFileTreeName from version zFrom
 ** against the same file on disk.
+**
+** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
+** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 static void diff_one_against_disk(
   const char *zFrom,        /* Name of file */
   const char *zDiffCmd,     /* Use this "diff" command */
+  const char *zBinGlob,     /* Treat file names matching this as binary */
+  int fIncludeBinary,       /* Include binary files for external diff */
   u64 diffFlags,            /* Diff control flags */
   const char *zFileTreeName
 ){
   Blob fname;
   Blob content;
   int isLink;
+  int isBin;
   file_tree_name(zFileTreeName, &fname, 1);
-  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0, 0);
+  historical_version_of_file(zFrom, blob_str(&fname), &content, &isLink, 0,
+                             fIncludeBinary ? 0 : &isBin, 0);
   if( !isLink != !file_wd_islink(zFrom) ){
-    fossil_print("cannot compute difference between "
-                 "symlink and regular file\n");
+    fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
   }else{
-    diff_file(&content, zFileTreeName, zFileTreeName, zDiffCmd, diffFlags);
+    diff_file(&content, isBin, zFileTreeName, zFileTreeName,
+              zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   }
   blob_reset(&content);
   blob_reset(&fname);
 }
 
 /*
 ** Run a diff between the version zFrom and files on disk.  zFrom might
 ** be NULL which means to simply show the difference between the edited
 ** files on disk and the check-out on which they are based.
+**
+** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
+** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 static void diff_all_against_disk(
   const char *zFrom,        /* Version to difference from */
   const char *zDiffCmd,     /* Use this diff command.  NULL for built-in */
+  const char *zBinGlob,     /* Treat file names matching this as binary */
+  int fIncludeBinary,       /* Treat file names matching this as binary */
   u64 diffFlags             /* Flags controlling diff output */
 ){
   int vid;
   Blob sql;
   Stmt q;
@@ -307,24 +389,27 @@
       srcid = 0;
       if( !asNewFile ){ showDiff = 0; }
     }
     if( showDiff ){
       Blob content;
+      int isBin;
       if( !isLink != !file_wd_islink(zFullName) ){
         diff_print_index(zPathname, diffFlags);
         diff_print_filenames(zPathname, zPathname, diffFlags);
-        fossil_print("cannot compute difference between "
-                     "symlink and regular file\n");
+        fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
         continue;
       }
       if( srcid>0 ){
         content_get(srcid, &content);
       }else{
         blob_zero(&content);
       }
+      isBin = fIncludeBinary ? 0 : looks_like_binary(blob_str(&content),
+                                                     blob_size(&content));
       diff_print_index(zPathname, diffFlags);
-      diff_file(&content, zFullName, zPathname, zDiffCmd, diffFlags);
+      diff_file(&content, isBin, zFullName, zPathname, zDiffCmd,
+                zBinGlob, fIncludeBinary, diffFlags);
       blob_reset(&content);
     }
     free(zToFree);
   }
   db_finalize(&q);
@@ -332,50 +417,72 @@
 }
 
 /*
 ** Output the differences between two versions of a single file.
 ** zFrom and zTo are the check-ins containing the two file versions.
+**
+** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
+** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 static void diff_one_two_versions(
   const char *zFrom,
   const char *zTo,
   const char *zDiffCmd,
+  const char *zBinGlob,
+  int fIncludeBinary,
   u64 diffFlags,
   const char *zFileTreeName
 ){
   char *zName;
   Blob fname;
   Blob v1, v2;
   int isLink1, isLink2;
+  int isBin1, isBin2;
   if( diffFlags & DIFF_BRIEF ) return;
   file_tree_name(zFileTreeName, &fname, 1);
   zName = blob_str(&fname);
-  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0, 0);
-  historical_version_of_file(zTo, zName, &v2, &isLink2, 0, 0);
+  historical_version_of_file(zFrom, zName, &v1, &isLink1, 0,
+                             fIncludeBinary ? 0 : &isBin1, 0);
+  historical_version_of_file(zTo, zName, &v2, &isLink2, 0,
+                             fIncludeBinary ? 0 : &isBin2, 0);
   if( isLink1 != isLink2 ){
     diff_print_filenames(zName, zName, diffFlags);
-    fossil_print("cannot compute difference "
-                 " between symlink and regular file\n");
+    fossil_print(DIFF_CANNOT_COMPUTE_SYMLINK);
   }else{
-    diff_file_mem(&v1, &v2, zName, zDiffCmd, diffFlags);
+    diff_file_mem(&v1, &v2, isBin1, isBin2, zName, zDiffCmd,
+                  zBinGlob, fIncludeBinary, diffFlags);
   }
   blob_reset(&v1);
   blob_reset(&v2);
   blob_reset(&fname);
 }
 
 /*
 ** Show the difference between two files identified by ManifestFile
 ** entries.
+**
+** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
+** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 static void diff_manifest_entry(
   struct ManifestFile *pFrom,
   struct ManifestFile *pTo,
   const char *zDiffCmd,
+  const char *zBinGlob,
+  int fIncludeBinary,
   u64 diffFlags
 ){
   Blob f1, f2;
+  int isBin1, isBin2;
   int rid;
   const char *zName =  pFrom ? pFrom->zName : pTo->zName;
   if( diffFlags & DIFF_BRIEF ) return;
   diff_print_index(zName, diffFlags);
   if( pFrom ){
@@ -388,22 +495,36 @@
     rid = uuid_to_rid(pTo->zUuid, 0);
     content_get(rid, &f2);
   }else{
     blob_zero(&f2);
   }
-  diff_file_mem(&f1, &f2, zName, zDiffCmd, diffFlags);
+  isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&f1),
+                                                  blob_size(&f1));
+  isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&f2),
+                                                  blob_size(&f2));
+  diff_file_mem(&f1, &f2, isBin1, isBin2, zName, zDiffCmd,
+                zBinGlob, fIncludeBinary, diffFlags);
   blob_reset(&f1);
   blob_reset(&f2);
 }
 
 /*
 ** Output the differences between two check-ins.
+**
+** Use the internal diff logic if zDiffCmd is NULL.  Otherwise call the
+** command zDiffCmd to do the diffing.
+**
+** When using an external diff program, zBinGlob contains the GLOB patterns
+** for file names to treat as binary.  If fIncludeBinary is zero, these files
+** will be skipped in addition to files that may contain binary content.
 */
 static void diff_all_two_versions(
   const char *zFrom,
   const char *zTo,
   const char *zDiffCmd,
+  const char *zBinGlob,
+  int fIncludeBinary,
   u64 diffFlags
 ){
   Manifest *pFrom, *pTo;
   ManifestFile *pFromFile, *pToFile;
   int asNewFlag = (diffFlags & DIFF_NEWFILE)!=0 ? 1 : 0;
@@ -425,17 +546,19 @@
       cmp = fossil_strcmp(pFromFile->zName, pToFile->zName);
     }
     if( cmp<0 ){
       fossil_print("DELETED %s\n", pFromFile->zName);
       if( asNewFlag ){
-        diff_manifest_entry(pFromFile, 0, zDiffCmd, diffFlags);
+        diff_manifest_entry(pFromFile, 0, zDiffCmd, zBinGlob,
+                            fIncludeBinary, diffFlags);
       }
       pFromFile = manifest_file_next(pFrom,0);
     }else if( cmp>0 ){
       fossil_print("ADDED   %s\n", pToFile->zName);
       if( asNewFlag ){
-        diff_manifest_entry(0, pToFile, zDiffCmd, diffFlags);
+        diff_manifest_entry(0, pToFile, zDiffCmd, zBinGlob,
+                            fIncludeBinary, diffFlags);
       }
       pToFile = manifest_file_next(pTo,0);
     }else if( fossil_strcmp(pFromFile->zUuid, pToFile->zUuid)==0 ){
       /* No changes */
       pFromFile = manifest_file_next(pFrom,0);
@@ -442,11 +565,12 @@
       pToFile = manifest_file_next(pTo,0);
     }else{
       if( diffFlags & DIFF_BRIEF ){
         fossil_print("CHANGED %s\n", pFromFile->zName);
       }else{
-        diff_manifest_entry(pFromFile, pToFile, zDiffCmd, diffFlags);
+        diff_manifest_entry(pFromFile, pToFile, zDiffCmd, zBinGlob,
+                            fIncludeBinary, diffFlags);
       }
       pFromFile = manifest_file_next(pFrom,0);
       pToFile = manifest_file_next(pTo,0);
     }
   }
@@ -544,10 +668,34 @@
   zTempFile = write_blob_to_temp_file(&script);
   zCmd = mprintf("tclsh \"%s\"", zTempFile);
   fossil_system(zCmd);
   file_delete(zTempFile);
 }
+
+/*
+** Returns non-zero if files that may be binary should be used with external
+** diff programs.
+*/
+int diff_include_binary_files(void){
+  if( is_truth(find_option("diff-binary", 0, 1)) ){
+    return 1;
+  }
+  if( db_get_boolean("diff-binary", 1) ){
+    return 1;
+  }
+  return 0;
+}
+
+/*
+** Returns the GLOB pattern for file names that should be treated as binary
+** by the diff subsystem, if any.
+*/
+const char *diff_get_binary_glob(void){
+  const char *zBinGlob = find_option("binary", 0, 1);
+  if( zBinGlob==0 ) zBinGlob = db_get("binary-glob",0);
+  return zBinGlob;
+}
 
 /*
 ** COMMAND: diff
 ** COMMAND: gdiff
 **
@@ -573,10 +721,17 @@
 ** the "-i" option is a no-op.  The "-i" option converts "gdiff" into "diff".
 **
 ** The "-N" or "--new-file" option causes the complete text of added or
 ** deleted files to be displayed.
 **
+** The "--diff-binary" option enables or disables the inclusion of binary files
+** when using an external diff program.
+**
+** The "--binary" option causes files matching the glob PATTERN to be treated
+** as binary when considering if they should be used with external diff program.
+** This option overrides the "binary-glob" setting.
+**
 ** Options:
 **   --branch BRANCH     Show diff of all changes on BRANCH
 **   --brief             Show filenames only
 **   --context|-c N      Use N lines of context 
 **   --from|-r VERSION   select VERSION as source for the diff
@@ -585,19 +740,23 @@
 **   --tk                Launch a Tcl/Tk GUI for display
 **   --to VERSION        select VERSION as target for the diff
 **   --side-by-side|-y   side-by-side diff
 **   --unified           unified diff
 **   --width|-W N        Width of lines in side-by-side diff 
+**   --diff-binary BOOL  Include binary files when using external commands
+**   --binary PATTERN    Treat files that match the glob PATTERN as binary
 */
 void diff_cmd(void){
   int isGDiff;               /* True for gdiff.  False for normal diff */
   int isInternDiff;          /* True for internal diff */
   int hasNFlag;              /* True if -N or --new-file flag is used */
   const char *zFrom;         /* Source version number */
   const char *zTo;           /* Target version number */
   const char *zBranch;       /* Branch to diff */
   const char *zDiffCmd = 0;  /* External diff command. NULL for internal diff */
+  const char *zBinGlob = 0;  /* Treat file names matching this as binary */
+  int fIncludeBinary = 0;    /* Include binary files for external diff */
   u64 diffFlags = 0;         /* Flags to control the DIFF */
   int f;
 
   if( find_option("tk",0,0)!=0 ){
     diff_tk();
@@ -619,35 +778,43 @@
     zTo = zBranch;
     zFrom = mprintf("root:%s", zBranch);
   }
   if( zTo==0 ){
     db_must_be_within_tree();
-    verify_all_options();
     if( !isInternDiff ){
       zDiffCmd = diff_command_external(isGDiff);
     }
+    zBinGlob = diff_get_binary_glob();
+    fIncludeBinary = diff_include_binary_files();
+    verify_all_options();
     if( g.argc>=3 ){
       for(f=2; f<g.argc; ++f){
-        diff_one_against_disk(zFrom, zDiffCmd, diffFlags, g.argv[f]);
+        diff_one_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
+                              diffFlags, g.argv[f]);
       }
     }else{
-      diff_all_against_disk(zFrom, zDiffCmd, diffFlags);
+      diff_all_against_disk(zFrom, zDiffCmd, zBinGlob, fIncludeBinary,
+                            diffFlags);
     }
   }else if( zFrom==0 ){
     fossil_fatal("must use --from if --to is present");
   }else{
     db_find_and_open_repository(0, 0);
-    verify_all_options();
     if( !isInternDiff ){
       zDiffCmd = diff_command_external(isGDiff);
     }
+    zBinGlob = diff_get_binary_glob();
+    fIncludeBinary = diff_include_binary_files();
+    verify_all_options();
     if( g.argc>=3 ){
       for(f=2; f<g.argc; ++f){
-        diff_one_two_versions(zFrom, zTo, zDiffCmd, diffFlags, g.argv[f]);        
+        diff_one_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
+                              diffFlags, g.argv[f]);
       }
     }else{
-      diff_all_two_versions(zFrom, zTo, zDiffCmd, diffFlags);
+      diff_all_two_versions(zFrom, zTo, zDiffCmd, zBinGlob, fIncludeBinary,
+                            diffFlags);
     }
   }
 }
 
 /*
@@ -660,7 +827,7 @@
   login_check_credentials();
   if( !g.perm.Read ){ login_needed(); return; }
   if( zFrom==0 || zTo==0 ) fossil_redirect_home();
 
   cgi_set_content_type("text/plain");
-  diff_all_two_versions(zFrom, zTo, 0, DIFF_NEWFILE);
+  diff_all_two_versions(zFrom, zTo, 0, 0, 0, DIFF_NEWFILE);
 }

Index: src/finfo.c
==================================================================
--- src/finfo.c
+++ src/finfo.c
@@ -115,11 +115,11 @@
     Blob fname;
     const char *zRevision = find_option("revision", "r", 1);
 
     file_tree_name(g.argv[2], &fname, 1);
     if( zRevision ){
-      historical_version_of_file(zRevision, blob_str(&fname), &record, 0, 0, 0);
+      historical_version_of_file(zRevision, blob_str(&fname), &record, 0,0,0,0);
     }else{
       int rid = db_int(0, "SELECT rid FROM vfile WHERE pathname=%B %s",
                        &fname, filename_collation());
       if( rid==0 ){
         fossil_fatal("no history for file: %b", &fname);

Index: src/stash.c
==================================================================
--- src/stash.c
+++ src/stash.c
@@ -267,11 +267,17 @@
 }
 
 /*
 ** Show the diffs associate with a single stash.
 */
-static void stash_diff(int stashid, const char *zDiffCmd, u64 diffFlags){
+static void stash_diff(
+  int stashid,
+  const char *zDiffCmd,
+  const char *zBinGlob,
+  int fIncludeBinary,
+  u64 diffFlags
+){
   Stmt q;
   Blob empty;
   blob_zero(&empty);
   db_prepare(&q,
      "SELECT rid, isRemoved, isExec, isLink, origname, newname, delta"
@@ -280,28 +286,39 @@
   );
   while( db_step(&q)==SQLITE_ROW ){
     int rid = db_column_int(&q, 0);
     int isRemoved = db_column_int(&q, 1);
     int isLink = db_column_int(&q, 3);
+    int isBin1, isBin2;
     const char *zOrig = db_column_text(&q, 4);
     const char *zNew = db_column_text(&q, 5);
     char *zOPath = mprintf("%s%s", g.zLocalRoot, zOrig);
     Blob delta;
     if( rid==0 ){
       db_ephemeral_blob(&q, 6, &delta);
       fossil_print("ADDED %s\n", zNew);
       diff_print_index(zNew, diffFlags);
-      diff_file_mem(&empty, &delta, zNew, zDiffCmd, diffFlags);
+      isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&empty),
+                                                      blob_size(&empty));
+      isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&delta),
+                                                      blob_size(&delta));
+      diff_file_mem(&empty, &delta, isBin1, isBin2, zNew, zDiffCmd,
+                    zBinGlob, fIncludeBinary, diffFlags);
     }else if( isRemoved ){
       fossil_print("DELETE %s\n", zOrig);
       if( file_wd_islink(zOPath) ){
         blob_read_link(&delta, zOPath);
       }else{
         blob_read_from_file(&delta, zOPath);
       }
       diff_print_index(zNew, diffFlags);
-      diff_file_mem(&delta, &empty, zOrig, zDiffCmd, diffFlags);
+      isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&delta),
+                                                      blob_size(&delta));
+      isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&empty),
+                                                      blob_size(&empty));
+      diff_file_mem(&delta, &empty, isBin1, isBin2, zOrig, zDiffCmd,
+                    zBinGlob, fIncludeBinary, diffFlags);
     }else{
       Blob a, b, disk;
       int isOrigLink = file_wd_islink(zOPath);
       db_ephemeral_blob(&q, 6, &delta);
       if( isOrigLink ){
@@ -311,15 +328,20 @@
       }
       fossil_print("CHANGED %s\n", zNew);
       if( !isOrigLink != !isLink ){
         diff_print_index(zNew, diffFlags);
         diff_print_filenames(zOrig, zNew, diffFlags);
-        printf("cannot compute difference between symlink and regular file\n");
+        printf(DIFF_CANNOT_COMPUTE_SYMLINK);
       }else{
         content_get(rid, &a);
         blob_delta_apply(&a, &delta, &b);
-        diff_file_mem(&disk, &b, zNew, zDiffCmd, diffFlags);
+        isBin1 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&disk),
+                                                        blob_size(&disk));
+        isBin2 = fIncludeBinary ? 0 : looks_like_binary(blob_str(&b),
+                                                        blob_size(&b));
+        diff_file_mem(&disk, &b, isBin1, isBin2, zNew, zDiffCmd,
+                      zBinGlob, fIncludeBinary, diffFlags);
         blob_reset(&a);
         blob_reset(&b);
       }
       blob_reset(&disk);
     }
@@ -413,10 +435,11 @@
   int nCmd;
   int stashid;
 
   undo_capture_command_line();
   db_must_be_within_tree();
+  db_open_config(0);
   db_begin_transaction();
   zDb = db_name("localdb");
   db_multi_exec(zStashInit, zDb, zDb);
   if( g.argc<=2 ){
     zCmd = "save";
@@ -550,21 +573,33 @@
                   stashid);
     undo_finish();
   }else
   if( memcmp(zCmd, "diff", nCmd)==0 ){
     const char *zDiffCmd = diff_command_external(0);
+    const char *zBinGlob = 0;
+    int fIncludeBinary = 0;
     u64 diffFlags = diff_options();
     if( g.argc>4 ) usage("diff STASHID");
+    if( zDiffCmd ){
+      zBinGlob = diff_get_binary_glob();
+      fIncludeBinary = diff_include_binary_files();
+    }
     stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
-    stash_diff(stashid, zDiffCmd, diffFlags);
+    stash_diff(stashid, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   }else
   if( memcmp(zCmd, "gdiff", nCmd)==0 ){
     const char *zDiffCmd = diff_command_external(1);
+    const char *zBinGlob = 0;
+    int fIncludeBinary = 0;
     u64 diffFlags = diff_options();
     if( g.argc>4 ) usage("gdiff STASHID");
+    if( zDiffCmd ){
+      zBinGlob = diff_get_binary_glob();
+      fIncludeBinary = diff_include_binary_files();
+    }
     stashid = stash_get_id(g.argc==4 ? g.argv[3] : 0);
-    stash_diff(stashid, zDiffCmd, diffFlags);
+    stash_diff(stashid, zDiffCmd, zBinGlob, fIncludeBinary, diffFlags);
   }else
   if( memcmp(zCmd, "help", nCmd)==0 ){
     g.argv[1] = "help";
     g.argv[2] = "stash";
     g.argc = 3;

Index: src/update.c
==================================================================
--- src/update.c
+++ src/update.c
@@ -595,12 +595,13 @@
 */
 int historical_version_of_file(
   const char *revision,    /* The checkin containing the file */
   const char *file,        /* Full treename of the file */
   Blob *content,           /* Put the content here */
-  int *pIsLink,             /* Set to true if file is link. */
+  int *pIsLink,            /* Set to true if file is link. */
   int *pIsExe,             /* Set to true if file is executable */
+  int *pIsBin,             /* Set to true if file is binary */
   int errCode              /* Error code if file not found.  Panic if 0. */
 ){
   Manifest *pManifest;
   ManifestFile *pFile;
   int rid=0;
@@ -617,15 +618,20 @@
   pManifest = manifest_get(rid, CFTYPE_MANIFEST);
   
   if( pManifest ){
     pFile = manifest_file_find(pManifest, file);
     if( pFile ){
+      int rc;
       rid = uuid_to_rid(pFile->zUuid, 0);
       if( pIsExe ) *pIsExe = ( manifest_file_mperm(pFile)==PERM_EXE );
       if( pIsLink ) *pIsLink = ( manifest_file_mperm(pFile)==PERM_LNK );
       manifest_destroy(pManifest);
-      return content_get(rid, content);
+      rc = content_get(rid, content);
+      if( rc && pIsBin ){
+        *pIsBin = looks_like_binary(blob_str(content), blob_size(content));
+      }
+      return rc;
     }
     manifest_destroy(pManifest);
     if( errCode<=0 ){
       fossil_fatal("file %s does not exist in checkin: %s", file, revision);
     }
@@ -712,11 +718,11 @@
     int isLink = 0;
     char *zFull;
     zFile = db_column_text(&q, 0);
     zFull = mprintf("%/%/", g.zLocalRoot, zFile);
     errCode = historical_version_of_file(zRevision, zFile, &record,
-                                         &isLink, &isExe,2);
+                                         &isLink, &isExe, 0, 2);
     if( errCode==2 ){
       if( db_int(0, "SELECT rid FROM vfile WHERE pathname=%Q", zFile)==0 ){
         fossil_print("UNMANAGE: %s\n", zFile);
       }else{
         undo_save(zFile);