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 <gitster@pobox.com>
Junio C Hamano committed
Jul 10, 2009 at 18:38 UTC
c4593faf2df8f9c5e8ac409e40be991294a73e54
2 files changed
+176
-1
builtin-apply.c
+81
-1
@@ -457,6 +457,76 @@ static int guess_p_value(const char *nameline)
457
return val;
458
}
459
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
+
530
/*
531
* Get the name etc info from the ---/+++ lines of a traditional patch header
532
*
@@ -493,7 +563,17 @@ static void parse_traditional_patch(const char *first, const char *second, struc
563
} else {
564
name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
565
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
+ }
577
}
578
if (!name)
579
die("unable to find filename in patch at line %d", linenr);
t/t4132-apply-removal.sh
new
+95
@@ -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