From c4593faf2df8f9c5e8ac409e40be991294a73e54 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 10 Jul 2009 18:38:08 -0700 Subject: apply: notice creation/removal patches produced by GNU diff 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 diff --git a/builtin-apply.c b/builtin-apply.c index dc0ff5e..39dc96a 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -458,6 +458,76 @@ 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. + * 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 @@ -493,7 +563,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..bb1ffe3 --- /dev/null +++ b/t/t4132-apply-removal.sh @@ -0,0 +1,95 @@ +#!/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/" createWest.patch && + sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" createEast.patch && + sed -e "s/TS0/$epocGMT/" -e "s/TS1/$timeGMT/" createGMT.patch && + + sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" addWest.patch && + sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" addEast.patch && + sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" addGMT.patch && + + sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" emptyWest.patch && + sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" emptyEast.patch && + sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" emptyGMT.patch && + + sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" removeWest.patch && + sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" removeEast.patch && + sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" 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 -- cgit v0.10.2-6-g49f6