{"thread":{"id":"37745","subject":"[PATCH] diff: Handle process substitution","startedAt":"2014-10-16T15:18:44Z","lastAt":"2014-10-16T15:18:44Z","messageCount":1,"participants":["Gabriel de Perthuis"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"250753","messageId":"543FE1D4.6020707@gmail.com","threadId":"37745","inReplyTo":null,"subject":"[PATCH] diff: Handle process substitution","fromName":"Gabriel de Perthuis","fromEmail":"g2p.code@gmail.com","sentAt":"2014-10-16T15:18:44Z","receivedAt":"2014-10-16T15:18:44Z","isPatch":true,"sender":{"key":"g2p.code@gmail.com","avatar":null},"body":"For example:\n\n    git diff --color-words <(echo a b c) <(echo a d c)\n\nChanges to struct diff_filespec:\n- is_stdin renamed to skip_hashing, which is what it did\n- follow_symlinks added, causing diff_populate_filespec to look at\n  file contents instead of the readlink value\n\nPaths that are handled specially (using\nskip_hashing and follow_symlinks):\n\n/dev/stdin added as an alternative to -\n/dev/fd/*\n/proc/self/fd/*\n\nThe first two are standard ways to refer to file descriptors,\nand there is precedence for handling them specially (bash\nredirections for example).  The last one is there to support\nzsh process substitution, which on Linux uses an OS-specific path.\n---\n Documentation/git-diff.txt |  7 +++++++\n diff-no-index.c            |  8 +++++++-\n diff.c                     | 26 +++++++++++++++++++++-----\n diffcore.h                 |  8 +++++---\n 4 files changed, 40 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-diff.txt b/Documentation/git-diff.txt\nindex bbab35f..f4ca476 100644\n--- a/Documentation/git-diff.txt\n+++ b/Documentation/git-diff.txt\n@@ -99,10 +99,17 @@ include::diff-options.txt[]\n  <path>...::\n \tThe <paths> parameters, when given, are used to limit\n \tthe diff to the named paths (you can give directory\n \tnames and get diff for all files under them).\n+\t+\n+\tWith --no-index, or with paths outside the worktree,\n+\tsome paths are handled specially: - and /dev/stdin\n+\trefer to standard input, and /dev/fd/* can be used\n+\tto refer to existing file descriptors, as in:\n+\n+\t$ git diff --color-words <(echo a b c) <(echo a d c)\n   include::diff-format.txt[]\n  EXAMPLES\ndiff --git a/diff-no-index.c b/diff-no-index.c\nindex 265709b..586036b 100644\n--- a/diff-no-index.c\n+++ b/diff-no-index.c\n@@ -70,11 +70,11 @@ static int populate_from_stdin(struct diff_filespec *s)\n  \ts->should_munmap = 0;\n \ts->data = strbuf_detach(&buf, &size);\n \ts->size = size;\n \ts->should_free = 1;\n-\ts->is_stdin = 1;\n+\ts->skip_hashing = 1;\n \treturn 0;\n }\n  static struct diff_filespec *noindex_filespec(const char *name, int mode)\n {\n@@ -84,10 +84,16 @@ static struct diff_filespec *noindex_filespec(const\nchar *name, int mode)\n \t\tname = \"/dev/null\";\n \ts = alloc_filespec(name);\n \tfill_filespec(s, null_sha1, 0, mode);\n \tif (name == file_from_standard_input)\n \t\tpopulate_from_stdin(s);\n+\telse if (!strcmp(name, \"/dev/stdin\") ||\n+\t\t\t !strncmp(name, \"/dev/fd/\", 8) ||\n+\t\t\t !strncmp(name, \"/proc/self/fd/\", 14)) {\n+\t\ts->skip_hashing = 1;\n+\t\ts->follow_symlinks = 1;\n+\t}\n \treturn s;\n }\n  static int queue_diff(struct diff_options *o,\n \t\t      const char *name1, const char *name2)\ndiff --git a/diff.c b/diff.c\nindex d7a5c81..c1150d7 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -2711,22 +2711,26 @@ int diff_populate_filespec(struct diff_filespec\n*s, unsigned int flags)\n \t    reuse_worktree_file(s->path, s->sha1, 0)) {\n \t\tstruct strbuf buf = STRBUF_INIT;\n \t\tstruct stat st;\n \t\tint fd;\n -\t\tif (lstat(s->path, &st) < 0) {\n+\t\tif (s->follow_symlinks)\n+\t\t\terr = stat(s->path, &st);\n+\t\telse\n+\t\t\terr = lstat(s->path, &st);\n+\t\tif (err < 0) {\n \t\t\tif (errno == ENOENT) {\n \t\t\terr_empty:\n \t\t\t\terr = -1;\n \t\t\tempty:\n \t\t\t\ts->data = (char *)\"\";\n \t\t\t\ts->size = 0;\n \t\t\t\treturn err;\n \t\t\t}\n \t\t}\n \t\ts->size = xsize_t(st.st_size);\n-\t\tif (!s->size)\n+\t\tif (S_ISREG(st.st_mode) && !s->size)\n \t\t\tgoto empty;\n \t\tif (S_ISLNK(st.st_mode)) {\n \t\t\tstruct strbuf sb = STRBUF_INIT;\n  \t\t\tif (strbuf_readlink(&sb, s->path, s->size))\n@@ -2744,13 +2748,25 @@ int diff_populate_filespec(struct diff_filespec\n*s, unsigned int flags)\n \t\t\treturn 0;\n \t\t}\n \t\tfd = open(s->path, O_RDONLY);\n \t\tif (fd < 0)\n \t\t\tgoto err_empty;\n-\t\ts->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\t\tif (S_ISREG(st.st_mode)) {\n+\t\t\ts->data = xmmap(NULL, s->size, PROT_READ, MAP_PRIVATE, fd, 0);\n+\t\t\ts->should_munmap = 1;\n+\t\t} else {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tif (strbuf_read(&sb, fd, s->size) < 0) {\n+\t\t\t\terr = error(\"error while reading from %s: %s\",\n+\t\t\t\t     s->path, strerror(errno));\n+\t\t\t\tgoto err_empty;\n+\t\t\t}\n+\t\t\ts->data = strbuf_detach(&sb, &s->size);\n+\t\t\ts->should_munmap = 0;\n+\t\t\ts->should_free = 1;\n+\t\t}\n \t\tclose(fd);\n-\t\ts->should_munmap = 1;\n  \t\t/*\n \t\t * Convert from working tree format to canonical git format\n \t\t */\n \t\tif (convert_to_git(s->path, s->data, s->size, &buf, crlf_warn)) {\n@@ -3082,11 +3098,11 @@ static void run_diff_cmd(const char *pgm,\n static void diff_fill_sha1_info(struct diff_filespec *one)\n {\n \tif (DIFF_FILE_VALID(one)) {\n \t\tif (!one->sha1_valid) {\n \t\t\tstruct stat st;\n-\t\t\tif (one->is_stdin) {\n+\t\t\tif (one->skip_hashing) {\n \t\t\t\thashcpy(one->sha1, null_sha1);\n \t\t\t\treturn;\n \t\t\t}\n \t\t\tif (lstat(one->path, &st) < 0)\n \t\t\t\tdie_errno(\"stat '%s'\", one->path);\ndiff --git a/diffcore.h b/diffcore.h\nindex 33ea2de..e19b379 100644\n--- a/diffcore.h\n+++ b/diffcore.h\n@@ -32,20 +32,22 @@ struct diff_filespec {\n \tunsigned long size;\n \tint count;               /* Reference count */\n \tint rename_used;         /* Count of rename users */\n \tunsigned short mode;\t /* file mode */\n \tunsigned sha1_valid : 1; /* if true, use sha1 and trust mode;\n-\t\t\t\t  * if false, use the name and read from\n-\t\t\t\t  * the filesystem.\n+\t\t\t\t  * if false, and skip_hashing is false, fill sha1\n+\t\t\t\t  * by using path and reading from the filesystem.\n+\t\t\t\t  * If skip_hashing is true, use null_sha1.\n \t\t\t\t  */\n #define DIFF_FILE_VALID(spec) (((spec)->mode) != 0)\n \tunsigned should_free : 1; /* data should be free()'ed */\n \tunsigned should_munmap : 1; /* data should be munmap()'ed */\n \tunsigned dirty_submodule : 2;  /* For submodules: its work tree is\ndirty */\n #define DIRTY_SUBMODULE_UNTRACKED 1\n #define DIRTY_SUBMODULE_MODIFIED  2\n-\tunsigned is_stdin : 1;\n+\tunsigned skip_hashing : 1;\n+\tunsigned follow_symlinks : 1;\n \tunsigned has_more_entries : 1; /* only appear in combined diff */\n \t/* data should be considered \"binary\"; -1 means \"don't know yet\" */\n \tsigned int is_binary : 2;\n \tstruct userdiff_driver *driver;\n };\n-- \n2.1.2.378.g50ea8b6\n"}]}