[PATCH 2/2] Allow passing pipes for input pipes to diff --no-index

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



A very handy way to pass data to applications is to use the <() process
substitution syntax in bash variants. It allow comparing files streamed
from a remote server or doing on-the-fly stream processing to alter the
diff. These are usually implemented as a symlink that points to a bogus
name (ex "pipe:[209326419]") but opens as a pipe.

Git normally tracks symlinks targets. This patch makes it detect such
pipes in --no-index mode and read the file normally like it would do for
stdin ("-"), so they can be compared directly.

Signed-off-by: Thomas Guyot-Sionnest <tguyot@xxxxxxxxx>
---
 diff-no-index.c          |  56 ++++++++++--
 t/t4053-diff-no-index.sh | 189 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 238 insertions(+), 7 deletions(-)

diff --git a/diff-no-index.c b/diff-no-index.c
index 7814eabfe0..779c686d23 100644
--- a/diff-no-index.c
+++ b/diff-no-index.c
@@ -41,6 +41,33 @@ static int read_directory_contents(const char *path, struct string_list *list)
  */
 static const char file_from_standard_input[] = "-";
 
+/* Check that file is - (STDIN) or unnamed pipe - explicitly
+ * avoid on-disk named pipes which could block
+ */
+static int ispipe(const char *name)
+{
+	struct stat st;
+
+	if (name == file_from_standard_input)
+		return 1;  /* STDIN */
+
+	if (!lstat(name, &st)) {
+		if (S_ISLNK(st.st_mode)) {
+			/* symlink - read it and check it doesn't exists
+			 * as a file yet link to a pipe */
+			struct strbuf sb = STRBUF_INIT;
+			strbuf_realpath(&sb, name, 0);
+			/* We're abusing strbuf_realpath here, it may append
+			 * pipe:[NNNNNNNNN] to an abs path */
+			if (!stat(sb.buf, &st))
+				return 0; /* link target exists , not pipe */
+			if (!stat(name, &st))
+				return S_ISFIFO(st.st_mode);
+		}
+	}
+	return 0;
+}
+
 static int get_mode(const char *path, int *mode)
 {
 	struct stat st;
@@ -51,7 +78,7 @@ static int get_mode(const char *path, int *mode)
 	else if (!strcasecmp(path, "nul"))
 		*mode = 0;
 #endif
-	else if (path == file_from_standard_input)
+	else if (ispipe(path))
 		*mode = create_ce_mode(0666);
 	else if (lstat(path, &st))
 		return error("Could not access '%s'", path);
@@ -60,13 +87,13 @@ static int get_mode(const char *path, int *mode)
 	return 0;
 }
 
-static int populate_from_stdin(struct diff_filespec *s)
+static int populate_from_fd(struct diff_filespec *s, int fd)
 {
 	struct strbuf buf = STRBUF_INIT;
 	size_t size = 0;
 
-	if (strbuf_read(&buf, 0, 0) < 0)
-		return error_errno("error while reading from stdin");
+	if (strbuf_read(&buf, fd, 0) < 0)
+		return error_errno(_("error while reading from fd %i"), fd);
 
 	s->should_munmap = 0;
 	s->data = strbuf_detach(&buf, &size);
@@ -76,6 +103,20 @@ static int populate_from_stdin(struct diff_filespec *s)
 	return 0;
 }
 
+static int populate_from_pipe(struct diff_filespec *s, const char *name)
+{
+	int ret;
+	FILE *f;
+
+	f = fopen(name, "r");
+	if (!f)
+		return error_errno(_("cannot open %s"), name);
+
+	ret = populate_from_fd(s, fileno(f));
+	fclose(f);
+	return ret;
+}
+
 static struct diff_filespec *noindex_filespec(const char *name, int mode)
 {
 	struct diff_filespec *s;
@@ -85,7 +126,9 @@ static struct diff_filespec *noindex_filespec(const char *name, int mode)
 	s = alloc_filespec(name);
 	fill_filespec(s, &null_oid, 0, mode);
 	if (name == file_from_standard_input)
-		populate_from_stdin(s);
+		populate_from_fd(s, 0);
+	else if (ispipe(name))
+		populate_from_pipe(s, name);
 	return s;
 }
 
@@ -218,8 +261,7 @@ static void fixup_paths(const char **path, struct strbuf *replacement)
 {
 	unsigned int isdir0, isdir1;
 
-	if (path[0] == file_from_standard_input ||
-	    path[1] == file_from_standard_input)
+	if (ispipe(path[0]) || ispipe(path[1]))
 		return;
 	isdir0 = is_directory(path[0]);
 	isdir1 = is_directory(path[1]);
diff --git a/t/t4053-diff-no-index.sh b/t/t4053-diff-no-index.sh
index 0168946b63..e49f773515 100755
--- a/t/t4053-diff-no-index.sh
+++ b/t/t4053-diff-no-index.sh
@@ -144,4 +144,193 @@ test_expect_success 'diff --no-index allows external diff' '
 	test_cmp expect actual
 '
 
+test_expect_success 'diff --no-index can diff piped subshells' '
+	echo 1 >non/git/c &&
+	test_expect_code 0 git diff --no-index non/git/b <(cat non/git/c) &&
+	test_expect_code 0 git diff --no-index <(cat non/git/b) non/git/c &&
+	test_expect_code 0 git diff --no-index <(cat non/git/b) <(cat non/git/c) &&
+	test_expect_code 0 cat non/git/b | git diff --no-index - non/git/c &&
+	test_expect_code 0 cat non/git/c | git diff --no-index non/git/b - &&
+	test_expect_code 0 cat non/git/b | git diff --no-index - <(cat non/git/c) &&
+	test_expect_code 0 cat non/git/c | git diff --no-index <(cat non/git/b) -
+'
+
+test_expect_success 'diff --no-index finds diff in piped subshells' '
+	(
+		set -- <(cat /dev/null) <(cat /dev/null)
+		cat <<-EOF >expect
+			diff --git a$1 b$2
+			--- a$1
+			+++ b$2
+			@@ -1 +1 @@
+			-1
+			+2
+		EOF
+	) &&
+	test_expect_code 1 \
+		git diff --no-index <(cat non/git/b) <(sed s/1/2/ non/git/c) >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'diff --no-index with stat and numstat' '
+	(
+		set -- <(cat /dev/null) <(cat /dev/null)
+		min=$((${#1} < ${#2} ? ${#1} : ${#2}))
+		for ((i=0; i<min; i++)); do [ "${1:i:1}" = "${2:i:1}" ] || break; done
+		base=${1:0:i-1}
+		cat <<-EOF >expect1
+			 $base{${1#$base} => ${2#$base}} | 2 +-
+			 1 file changed, 1 insertion(+), 1 deletion(-)
+		EOF
+		cat <<-EOF >expect2
+			1	1	$base{${1#$base} => ${2#$base}}
+		EOF
+	) &&
+	test_expect_code 1 \
+		git diff --no-index --stat <(cat non/git/a) <(sed s/1/2/ non/git/b) >actual &&
+	test_cmp expect1 actual &&
+	test_expect_code 1 \
+		git diff --no-index --numstat <(cat non/git/a) <(sed s/1/2/ non/git/b) >actual &&
+	test_cmp expect2 actual
+'
+
+test_expect_success PIPE 'diff --no-index on filesystem pipes' '
+	(
+		cd non/git &&
+		mkdir f g &&
+		mkfifo f/1 g/1 &&
+		test_expect_code 128 git diff --no-index f g &&
+		test_expect_code 128 git diff --no-index ../../a f &&
+		test_expect_code 128 git diff --no-index g ../../a &&
+		test_expect_code 128 git diff --no-index f/1 g/1 &&
+		test_expect_code 128 git diff --no-index f/1 ../../a/1 &&
+		test_expect_code 128 git diff --no-index ../../a/1 g/1
+	)
+'
+
+test_expect_success PIPE 'diff --no-index reads symlinks to named pipes as symlinks' '
+	(
+		cd non/git &&
+		mkdir h i &&
+		ln -s ../f/1 h/1 &&
+		ln -s ../g/1 i/1 &&
+		test_expect_code 1 git diff --no-index h i >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/h/1 b/i/1
+			index d0b5850..d8b9c34 120000
+			--- a/h/1
+			+++ b/i/1
+			@@ -1 +1 @@
+			-../f/1
+			\ No newline at end of file
+			+../g/1
+			\ No newline at end of file
+		EOF
+		test_cmp expect actual &&
+		test_expect_code 1 git diff --no-index ../../a h >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/../../a/1 b/../../a/1
+			deleted file mode 100644
+			index d00491f..0000000
+			--- a/../../a/1
+			+++ /dev/null
+			@@ -1 +0,0 @@
+			-1
+			diff --git a/h/1 b/h/1
+			new file mode 120000
+			index 0000000..d0b5850
+			--- /dev/null
+			+++ b/h/1
+			@@ -0,0 +1 @@
+			+../f/1
+			\ No newline at end of file
+			diff --git a/../../a/2 b/../../a/2
+			deleted file mode 100644
+			index 0cfbf08..0000000
+			--- a/../../a/2
+			+++ /dev/null
+			@@ -1 +0,0 @@
+			-2
+		EOF
+		test_cmp expect actual &&
+		test_expect_code 1 git diff --no-index i ../../a >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/i/1 b/i/1
+			deleted file mode 120000
+			index d8b9c34..0000000
+			--- a/i/1
+			+++ /dev/null
+			@@ -1 +0,0 @@
+			-../g/1
+			\ No newline at end of file
+			diff --git a/../../a/1 b/../../a/1
+			new file mode 100644
+			index 0000000..d00491f
+			--- /dev/null
+			+++ b/../../a/1
+			@@ -0,0 +1 @@
+			+1
+			diff --git a/../../a/2 b/../../a/2
+			new file mode 100644
+			index 0000000..0cfbf08
+			--- /dev/null
+			+++ b/../../a/2
+			@@ -0,0 +1 @@
+			+2
+		EOF
+		test_cmp expect actual &&
+		test_expect_code 1 git diff --no-index h/1 i/1 >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/h/1 b/i/1
+			index d0b5850..d8b9c34 120000
+			--- a/h/1
+			+++ b/i/1
+			@@ -1 +1 @@
+			-../f/1
+			\ No newline at end of file
+			+../g/1
+			\ No newline at end of file
+		EOF
+		test_cmp expect actual &&
+		test_expect_code 1 git diff --no-index h/1 ../../a/1 >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/h/1 b/h/1
+			deleted file mode 120000
+			index d0b5850..0000000
+			--- a/h/1
+			+++ /dev/null
+			@@ -1 +0,0 @@
+			-../f/1
+			\ No newline at end of file
+			diff --git a/../../a/1 b/../../a/1
+			new file mode 100644
+			index 0000000..d00491f
+			--- /dev/null
+			+++ b/../../a/1
+			@@ -0,0 +1 @@
+			+1
+		EOF
+		test_cmp expect actual &&
+		test_expect_code 1 git diff --no-index ../../a/1 i/1 >actual &&
+		cat <<-EOF >expect &&
+			diff --git a/../../a/1 b/../../a/1
+			deleted file mode 100644
+			index d00491f..0000000
+			--- a/../../a/1
+			+++ /dev/null
+			@@ -1 +0,0 @@
+			-1
+			diff --git a/i/1 b/i/1
+			new file mode 120000
+			index 0000000..d8b9c34
+			--- /dev/null
+			+++ b/i/1
+			@@ -0,0 +1 @@
+			+../g/1
+			\ No newline at end of file
+		EOF
+		test_cmp expect actual
+	)
+'
+
 test_done
-- 
2.20.1




[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux