Skip to content

Commit c4593fa

Browse files
committed
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 <[email protected]>
1 parent 4ecbc17 commit c4593fa

File tree

2 files changed

+176
-1
lines changed

2 files changed

+176
-1
lines changed

builtin-apply.c

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,76 @@ static int guess_p_value(const char *nameline)
457457
return val;
458458
}
459459

460+
/*
461+
* Does the ---/+++ line has the POSIX timestamp after the last HT?
462+
* GNU diff puts epoch there to signal a creation/deletion event. Is
463+
* this such a timestamp?
464+
*/
465+
static int has_epoch_timestamp(const char *nameline)
466+
{
467+
/*
468+
* We are only interested in epoch timestamp; any non-zero
469+
* fraction cannot be one, hence "(\.0+)?" in the regexp below.
470+
* For the same reason, the date must be either 1969-12-31 or
471+
* 1970-01-01, and the seconds part must be "00".
472+
*/
473+
const char stamp_regexp[] =
474+
"^(1969-12-31|1970-01-01)"
475+
" "
476+
"[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
477+
" "
478+
"([-+][0-2][0-9][0-5][0-9])\n";
479+
const char *timestamp = NULL, *cp;
480+
static regex_t *stamp;
481+
regmatch_t m[10];
482+
int zoneoffset;
483+
int hourminute;
484+
int status;
485+
486+
for (cp = nameline; *cp != '\n'; cp++) {
487+
if (*cp == '\t')
488+
timestamp = cp + 1;
489+
}
490+
if (!timestamp)
491+
return 0;
492+
if (!stamp) {
493+
stamp = xmalloc(sizeof(*stamp));
494+
if (regcomp(stamp, stamp_regexp, REG_EXTENDED)) {
495+
warning("Cannot prepare timestamp regexp %s",
496+
stamp_regexp);
497+
return 0;
498+
}
499+
}
500+
501+
status = regexec(stamp, timestamp, ARRAY_SIZE(m), m, 0);
502+
if (status) {
503+
if (status != REG_NOMATCH)
504+
warning("regexec returned %d for input: %s",
505+
status, timestamp);
506+
return 0;
507+
}
508+
509+
zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
510+
zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
511+
if (timestamp[m[3].rm_so] == '-')
512+
zoneoffset = -zoneoffset;
513+
514+
/*
515+
* YYYY-MM-DD hh:mm:ss must be from either 1969-12-31
516+
* (west of GMT) or 1970-01-01 (east of GMT)
517+
*/
518+
if ((zoneoffset < 0 && memcmp(timestamp, "1969-12-31", 10)) ||
519+
(0 <= zoneoffset && memcmp(timestamp, "1970-01-01", 10)))
520+
return 0;
521+
522+
hourminute = (strtol(timestamp + 11, NULL, 10) * 60 +
523+
strtol(timestamp + 14, NULL, 10) -
524+
zoneoffset);
525+
526+
return ((zoneoffset < 0 && hourminute == 1440) ||
527+
(0 <= zoneoffset && !hourminute));
528+
}
529+
460530
/*
461531
* Get the name etc info from the ---/+++ lines of a traditional patch header
462532
*
@@ -493,7 +563,17 @@ static void parse_traditional_patch(const char *first, const char *second, struc
493563
} else {
494564
name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
495565
name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
496-
patch->old_name = patch->new_name = name;
566+
if (has_epoch_timestamp(first)) {
567+
patch->is_new = 1;
568+
patch->is_delete = 0;
569+
patch->new_name = name;
570+
} else if (has_epoch_timestamp(second)) {
571+
patch->is_new = 0;
572+
patch->is_delete = 1;
573+
patch->old_name = name;
574+
} else {
575+
patch->old_name = patch->new_name = name;
576+
}
497577
}
498578
if (!name)
499579
die("unable to find filename in patch at line %d", linenr);

t/t4132-apply-removal.sh

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2009 Junio C Hamano
4+
5+
test_description='git-apply notices removal patches generated by GNU diff'
6+
7+
. ./test-lib.sh
8+
9+
test_expect_success setup '
10+
cat <<-EOF >c &&
11+
diff -ruN a/file b/file
12+
--- a/file TS0
13+
+++ b/file TS1
14+
@@ -0,0 +1 @@
15+
+something
16+
EOF
17+
18+
cat <<-EOF >d &&
19+
diff -ruN a/file b/file
20+
--- a/file TS0
21+
+++ b/file TS1
22+
@@ -1 +0,0 @@
23+
-something
24+
EOF
25+
26+
timeWest="1982-09-16 07:00:00.000000000 -0800" &&
27+
timeGMT="1982-09-16 15:00:00.000000000 +0000" &&
28+
timeEast="1982-09-17 00:00:00.000000000 +0900" &&
29+
30+
epocWest="1969-12-31 16:00:00.000000000 -0800" &&
31+
epocGMT="1970-01-01 00:00:00.000000000 +0000" &&
32+
epocEast="1970-01-01 09:00:00.000000000 +0900" &&
33+
34+
sed -e "s/TS0/$epocWest/" -e "s/TS1/$timeWest/" <c >createWest.patch &&
35+
sed -e "s/TS0/$epocEast/" -e "s/TS1/$timeEast/" <c >createEast.patch &&
36+
sed -e "s/TS0/$epocGMT/" -e "s/TS1/$timeGMT/" <c >createGMT.patch &&
37+
38+
sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <c >addWest.patch &&
39+
sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <c >addEast.patch &&
40+
sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" <c >addGMT.patch &&
41+
42+
sed -e "s/TS0/$timeWest/" -e "s/TS1/$timeWest/" <d >emptyWest.patch &&
43+
sed -e "s/TS0/$timeEast/" -e "s/TS1/$timeEast/" <d >emptyEast.patch &&
44+
sed -e "s/TS0/$timeGMT/" -e "s/TS1/$timeGMT/" <d >emptyGMT.patch &&
45+
46+
sed -e "s/TS0/$timeWest/" -e "s/TS1/$epocWest/" <d >removeWest.patch &&
47+
sed -e "s/TS0/$timeEast/" -e "s/TS1/$epocEast/" <d >removeEast.patch &&
48+
sed -e "s/TS0/$timeGMT/" -e "s/TS1/$epocGMT/" <d >removeGMT.patch &&
49+
50+
echo something >something &&
51+
>empty
52+
'
53+
54+
for patch in *.patch
55+
do
56+
test_expect_success "test $patch" '
57+
rm -f file .git/index &&
58+
case "$patch" in
59+
create*)
60+
# must be able to create
61+
git apply --index $patch &&
62+
test_cmp file something &&
63+
# must notice the file is already there
64+
>file &&
65+
git add file &&
66+
test_must_fail git apply $patch
67+
;;
68+
add*)
69+
# must be able to create or patch
70+
git apply $patch &&
71+
test_cmp file something &&
72+
>file &&
73+
git apply $patch &&
74+
test_cmp file something
75+
;;
76+
empty*)
77+
# must leave an empty file
78+
cat something >file &&
79+
git add file &&
80+
git apply --index $patch &&
81+
test -f file &&
82+
test_cmp empty file
83+
;;
84+
remove*)
85+
# must remove the file
86+
cat something >file &&
87+
git add file &&
88+
git apply --index $patch &&
89+
! test -f file
90+
;;
91+
esac
92+
'
93+
done
94+
95+
test_done

0 commit comments

Comments
 (0)