Thread (2 messages) 2 messages, 2 authors, 2016-06-15
DORMANTno replies

[PATCH 2/2] apply: notice creation/removal patches produced by GNU diff

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:47:02
Subsystem: the rest · Maintainer: Linus Torvalds

Possibly related (same subject, not in this thread)

Unified context patch generated by GNU diff has UNIX epoch timestamp
on the side that does not exist when the patch is about a creation or
a deletion event.  Notice this convention when reading a non-git diff.

Signed-off-by: Junio C Hamano <redacted>
---
 builtin-apply.c          |   63 ++++++++++++++++++++++++++++++++-
 t/t4132-apply-removal.sh |   88 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 150 insertions(+), 1 deletions(-)
 create mode 100755 t/t4132-apply-removal.sh
diff --git a/builtin-apply.c b/builtin-apply.c
index dc0ff5e..06e80e4 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -458,6 +458,57 @@ static int guess_p_value(const char *nameline)
 }
 
 /*
+ * Does the ---/+++ line has the POSIX timestamp after the last HT?
+ * GNU diff puts epoch there to signal a creation/deletion event.  Is
+ * this such a timestamp?
+ */
+static int has_epoch_timestamp(const char *nameline)
+{
+	/*
+	 * We are only interested in epoch timestamp; any non-zero
+	 * fraction cannot be one, hence "(\.0+)?" in the regexp below.
+	 */
+	const char stamp_regexp[] =
+		"^[0-9][0-9][0-9][0-9]-[01][0-9]-[0-3][0-9]"
+		" "
+		"[0-2][0-9]:[0-5][0-9]:[0-6][0-9](\\.0+)?"
+		" "
+		"[-+][0-2][0-9][0-5][0-9]\n";
+	const char *timestamp = NULL, *cp;
+	static regex_t *stamp;
+	int status;
+	char parsed[100];
+
+	for (cp = nameline; *cp != '\n'; cp++) {
+		if (*cp == '\t')
+			timestamp = cp + 1;
+	}
+	if (!timestamp)
+		return 0;
+	if (!stamp) {
+		stamp = xmalloc(sizeof(*stamp));
+		if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
+			warning("Cannot prepare timestamp regexp %s",
+				stamp_regexp);
+			return 0;
+		}
+	}
+
+	status = regexec(stamp, timestamp, 0, NULL, 0);
+	if (status) {
+		if (status != REG_NOMATCH)
+			warning("regexec returned %d for input: %s",
+				status, timestamp);
+		return 0;
+	}
+
+	parse_date(timestamp, parsed, sizeof(parsed));
+	if (parsed[0] == '0' && parsed[1] == ' ')
+		return 1;
+	return 0;
+}
+
+/*
  * Get the name etc info from the ---/+++ lines of a traditional patch header
  *
  * FIXME! The end-of-filename heuristics are kind of screwy. For existing
@@ -493,7 +544,17 @@ static void parse_traditional_patch(const char *first, const char *second, struc
 	} else {
 		name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
 		name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
-		patch->old_name = patch->new_name = name;
+		if (has_epoch_timestamp(first)) {
+			patch->is_new = 1;
+			patch->is_delete = 0;
+			patch->new_name = name;
+		} else if (has_epoch_timestamp(second)) {
+			patch->is_new = 0;
+			patch->is_delete = 1;
+			patch->old_name = name;
+		} else {
+			patch->old_name = patch->new_name = name;
+		}
 	}
 	if (!name)
 		die("unable to find filename in patch at line %d", linenr);
diff --git a/t/t4132-apply-removal.sh b/t/t4132-apply-removal.sh
new file mode 100755
index 0000000..eb971f7
--- /dev/null
+++ b/t/t4132-apply-removal.sh
@@ -0,0 +1,88 @@
+#!/bin/sh
+#
+# Copyright (c) 2009 Junio C Hamano
+
+test_description='git-apply notices removal patches generated by GNU diff'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	cat <<-EOF >c &&
+	diff -ruN a/file b/file
+	--- a/file	TS0
+	+++ b/file	TS1
+	@@ -0,0 +1 @@
+	+something
+	EOF
+
+	cat <<-EOF >d &&
+	diff -ruN a/file b/file
+	--- a/file	TS0
+	+++ b/file	TS1
+	@@ -1 +0,0 @@
+	-something
+	EOF
+
+	timeWest="1982-09-16 07:00:00.000000000 -0800" &&
+	timeEast="1982-09-17 00:00:00.000000000 +0900" &&
+	epocWest="1969-12-31 16:00:00.000000000 -0800" &&
+	epocEast="1970-01-01 09:00:00.000000000 +0900" &&
+
+	sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch &&
+	sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch &&
+
+	sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <c >addWest.patch &&
+	sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <c >addEast.patch &&
+
+	sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <d >emptyWest.patch &&
+	sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <d >emptyEast.patch &&
+
+	sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch &&
+	sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch &&
+
+	echo something >something &&
+	>empty
+'
+
+for patch in *.patch
+do
+	test_expect_success "test $patch" '
+		rm -f file .git/index &&
+		case "$patch" in
+		create*)
+			# must be able to create
+			git apply --index $patch &&
+			test_cmp file something &&
+			# must notice the file is already there
+			>file &&
+			git add file &&
+			test_must_fail git apply $patch
+			;;
+		add*)
+			# must be able to create or patch
+			git apply $patch &&
+			test_cmp file something &&
+			>file &&
+			git apply $patch &&
+			test_cmp file something
+			;;
+		empty*)
+			# must leave an empty file
+			cat something >file &&
+			git add file &&
+			git apply --index $patch &&
+			test -f file &&
+			test_cmp empty file
+			;;
+		remove*)
+			# must remove the file
+			cat something >file &&
+			git add file &&
+			git apply --index $patch &&
+			! test -f file
+			;;
+		esac
+	'
+done
+
+test_done
-- 
1.6.3.3.412.gf581d
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help