Commits

Anonymous committed 6b37b3d Merge

Merge branch 'jc/apply-epoch-patch' into maint

* jc/apply-epoch-patch:
apply: notice creation/removal patches produced by GNU diff

Comments (0)

Files changed (2)

 }
 
 /*
+ * 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.
+	 * For the same reason, the date must be either 1969-12-31 or
+	 * 1970-01-01, and the seconds part must be "00".
+	 */
+	const char stamp_regexp[] =
+		"^(1969-12-31|1970-01-01)"
+		" "
+		"[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
+		" "
+		"([-+][0-2][0-9][0-5][0-9])\n";
+	const char *timestamp = NULL, *cp;
+	static regex_t *stamp;
+	regmatch_t m[10];
+	int zoneoffset;
+	int hourminute;
+	int status;
+
+	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, ARRAY_SIZE(m), m, 0);
+	if (status) {
+		if (status != REG_NOMATCH)
+			warning("regexec returned %d for input: %s",
+				status, timestamp);
+		return 0;
+	}
+
+	zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
+	zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+	if (timestamp[m[3].rm_so] == '-')
+		zoneoffset = -zoneoffset;
+
+	/*
+	 * YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
+	 * (west of GMT) or 1970-01-01 (east of GMT)
+	 */
+	if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
+	    (0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
+		return 0;
+
+	hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
+		      strtol(timestamp + 14, NULL, 10) -
+		      zoneoffset);
+
+	return ((zoneoffset < 0 && hourminute == 1440) ||
+		(0 <= zoneoffset && !hourminute));
+}
+
+/*
  * 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
 	} 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);

t/t4132-apply-removal.sh

+#!/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" &&
+	 timeGMT="1982-09-16 15:00:00.000000000 +0000" &&
+	timeEast="1982-09-17 00:00:00.000000000 +0900" &&
+
+	epocWest="1969-12-31 16:00:00.000000000 -0800" &&
+	 epocGMT="1970-01-01 00:00:00.000000000 +0000" &&
+	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/$epocGMT/" -e "s/TS1/$timeGMT/" <c >createGMT.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/$timeGMT/" -e "s/TS1/$timeGMT/" <c >addGMT.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/$timeGMT/" -e "s/TS1/$timeGMT/" <d >emptyGMT.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 &&
+	sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.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