49

Why does git diff not work with process substitution?

$ echo hallo > hallo
$ echo holla > holla

$ git diff hallo holla  # works

$ git diff hallo <(cat holla)  # works not

diff --git a/hallo b/hallo
deleted file mode 100644
index 4cf5aa5..0000000
--- a/hallo
+++ /dev/null
@@ -1 +0,0 @@
-hallo
diff --git a/dev/fd/63 b/dev/fd/63
new file mode 120000
index 0000000..864a6ca`

Same with git diff --no-index.

It works with plain diff. cat is only a trivial example, can be replaced by a non-trivial sed expression.

Workaround:

$ cat holla | git diff hallo -  # works

But it will not work if both arguments should be affected by process substitution, like described in many examples for diff and process substitution.

Whymarrh
  • 13,139
  • 14
  • 57
  • 108
user6928
  • 591
  • 3
  • 3
  • `$cat holla | git diff hallo` this is basically 2 commands on 1 line, hence why it works – Tim Mar 28 '14 at 07:31

3 Answers3

43

git diff does not work with process substitution because a patch that added handling of process substitution was ignored.

marcin
  • 3,351
  • 1
  • 29
  • 33
  • 2
    That patch was submitted (and ignored) in 2014 - and we _still_ suffer from the same lack of process substitution in 2022! – Popup Oct 27 '22 at 11:12
3

No more "unsupported file type"!

Now, git diff --no-index holla <(cat hallo) should work, as discussed here.

With Git 2.42 (Q3 2023), "git diff --no-index"(man) learned to read from named pipes as if they were regular files, to allow "git diff <(process) <(substitution)" some shells support.

See commit 1e3f265, commit df52146, commit 4e61e0f, commit 4981984 (05 Jul 2023) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster -- in commit 9187b27, 17 Jul 2023)

diff --no-index: support reading from named pipes

Helped-by: Junio C Hamano
Signed-off-by: Phillip Wood

In some shells, such as bash and zsh, it's possible to use a command substitution to provide the output of a command as a file argument to another process, like so:

diff -u <(printf "a\nb\n") <(printf "a\nc\n")

However, this syntax does not produce useful results with "git diff --no-index"(man)".
On macOS, the arguments to the command are named pipes under /dev/fd, and 'git diff' doesnt know how to handle a named pipe.
On Linux, the arguments are symlinks to pipes, so git diff helpfully diffs these symlinks, comparing their targets like "pipe:[1234]" and "pipe:[5678]".

To address this "diff --no-index" is changed so that if a path given on the commandline is a named pipe or a symbolic link that resolves to a named pipe then we read the data to diff from that pipe.
This is implemented by generalizing the code that already exists to handle reading from stdin when the user passes the path "-".

If the user tries to compare a named pipe to a directory then we die as we do when trying to compare stdin to a directory.

As process substitution is not support by POSIX this change is tested by using a pipe and a symbolic link to a pipe.

VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

git diff cannot be used as a substitute for diff. It does not diff between two arbitrary files on disk EXCEPT when you're outside of a Git repository or you pass in the --no-index flag.

$ git diff --no-index holla hallo
diff --git a/holla b/hallo
index 5cf9d44..ba1a6c1 100644
--- a/holla
+++ b/hallo
@@ -1 +1,2 @@
-holla
+hallo
+new line

$ cat hallo | git diff --no-index holla -
diff --git a/holla b/-
index 5cf9d44..0000000 100644
--- a/holla
+++ b/-
@@ -1 +1,2 @@
-holla
+hallo
+new line

This all works because git diff is just reading from STDIN, and that's so standard they'd have to work hard to make it not work.

As for process substitution...

$ git diff --no-index holla <(cat hallo)
error: /dev/fd/63: unsupported file type
fatal: cannot hash /dev/fd/63

What I can interpret from that is Git is trying to run its hash algorithm on the named pipe, without which Git cannot operate, and refusing because it's not a file, it's a pipe. There's no reason to have a pipe in a Git repository, so there's no reason for git diff to support them.

It's a bad idea to use git diff as replacement for diff. Git is a content tracker which works on files, not a general purpose diff tool. This "I'm going to silently sort of work like diff outside a Git repository" is one of those overly flexible design choices that just confuses users.

Schwern
  • 153,029
  • 25
  • 195
  • 336
  • 21
    "It's a bad idea to use git diff as replacement for diff." I have had diff aliased to "git diff --no-index" for many years, and it is much nicer 99% of the time. There are only occasional edge cases like this process substitution issue. – ctrueden Oct 17 '18 at 18:47