Skip to content

Commit 4be6096

Browse files
author
Junio C Hamano
committed
apply --unidiff-zero: loosen sanity checks for --unidiff=0 patches
In "git-apply", we have a few sanity checks and heuristics that expects that the patch fed to us is a unified diff with at least one line of context. * When there is no leading context line in a hunk, the hunk must apply at the beginning of the preimage. Similarly, no trailing context means that the hunk is anchored at the end. * We learn a patch deletes the file from a hunk that has no resulting line (i.e. all lines are prefixed with '-') if it has not otherwise been known if the patch deletes the file. Similarly, no old line means the file is being created. And we declare an error condition when the file created by a creation patch already exists, and/or when a deletion patch still leaves content in the file. These sanity checks are good safety measures, but breaks down when people feed a diff generated with --unified=0. This was recently noticed first by Matthew Wilcox and Gerrit Pape. This adds a new flag, --unified-zero, to allow bypassing these checks. If you are in control of the patch generation process, you should not use --unified=0 patch and fix it up with this flag; rather you should try work with a patch with context. But if all you have to work with is a patch without context, this flag may come handy as the last resort. Signed-off-by: Junio C Hamano <junkio@cox.net>
1 parent 8aac4b4 commit 4be6096

File tree

3 files changed

+197
-34
lines changed

3 files changed

+197
-34
lines changed

builtin-apply.c

Lines changed: 79 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ static const char *prefix;
2727
static int prefix_length = -1;
2828
static int newfd = -1;
2929

30+
static int unidiff_zero;
3031
static int p_value = 1;
3132
static int check_index;
3233
static int write_index;
@@ -854,11 +855,10 @@ static int find_header(char *line, unsigned long size, int *hdrsize, struct patc
854855
}
855856

856857
/*
857-
* Parse a unified diff. Note that this really needs
858-
* to parse each fragment separately, since the only
859-
* way to know the difference between a "---" that is
860-
* part of a patch, and a "---" that starts the next
861-
* patch is to look at the line counts..
858+
* Parse a unified diff. Note that this really needs to parse each
859+
* fragment separately, since the only way to know the difference
860+
* between a "---" that is part of a patch, and a "---" that starts
861+
* the next patch is to look at the line counts..
862862
*/
863863
static int parse_fragment(char *line, unsigned long size, struct patch *patch, struct fragment *fragment)
864864
{
@@ -875,31 +875,14 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
875875
leading = 0;
876876
trailing = 0;
877877

878-
if (patch->is_new < 0) {
879-
patch->is_new = !oldlines;
880-
if (!oldlines)
881-
patch->old_name = NULL;
882-
}
883-
if (patch->is_delete < 0) {
884-
patch->is_delete = !newlines;
885-
if (!newlines)
886-
patch->new_name = NULL;
887-
}
888-
889-
if (patch->is_new && oldlines)
890-
return error("new file depends on old contents");
891-
if (patch->is_delete != !newlines) {
892-
if (newlines)
893-
return error("deleted file still has contents");
894-
fprintf(stderr, "** warning: file %s becomes empty but is not deleted\n", patch->new_name);
895-
}
896-
897878
/* Parse the thing.. */
898879
line += len;
899880
size -= len;
900881
linenr++;
901882
added = deleted = 0;
902-
for (offset = len; size > 0; offset += len, size -= len, line += len, linenr++) {
883+
for (offset = len;
884+
0 < size;
885+
offset += len, size -= len, line += len, linenr++) {
903886
if (!oldlines && !newlines)
904887
break;
905888
len = linelen(line, size);
@@ -972,12 +955,18 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
972955

973956
patch->lines_added += added;
974957
patch->lines_deleted += deleted;
958+
959+
if (0 < patch->is_new && oldlines)
960+
return error("new file depends on old contents");
961+
if (0 < patch->is_delete && newlines)
962+
return error("deleted file still has contents");
975963
return offset;
976964
}
977965

978966
static int parse_single_patch(char *line, unsigned long size, struct patch *patch)
979967
{
980968
unsigned long offset = 0;
969+
unsigned long oldlines = 0, newlines = 0, context = 0;
981970
struct fragment **fragp = &patch->fragments;
982971

983972
while (size > 4 && !memcmp(line, "@@ -", 4)) {
@@ -988,9 +977,11 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
988977
len = parse_fragment(line, size, patch, fragment);
989978
if (len <= 0)
990979
die("corrupt patch at line %d", linenr);
991-
992980
fragment->patch = line;
993981
fragment->size = len;
982+
oldlines += fragment->oldlines;
983+
newlines += fragment->newlines;
984+
context += fragment->leading + fragment->trailing;
994985

995986
*fragp = fragment;
996987
fragp = &fragment->next;
@@ -999,6 +990,46 @@ static int parse_single_patch(char *line, unsigned long size, struct patch *patc
999990
line += len;
1000991
size -= len;
1001992
}
993+
994+
/*
995+
* If something was removed (i.e. we have old-lines) it cannot
996+
* be creation, and if something was added it cannot be
997+
* deletion. However, the reverse is not true; --unified=0
998+
* patches that only add are not necessarily creation even
999+
* though they do not have any old lines, and ones that only
1000+
* delete are not necessarily deletion.
1001+
*
1002+
* Unfortunately, a real creation/deletion patch do _not_ have
1003+
* any context line by definition, so we cannot safely tell it
1004+
* apart with --unified=0 insanity. At least if the patch has
1005+
* more than one hunk it is not creation or deletion.
1006+
*/
1007+
if (patch->is_new < 0 &&
1008+
(oldlines || (patch->fragments && patch->fragments->next)))
1009+
patch->is_new = 0;
1010+
if (patch->is_delete < 0 &&
1011+
(newlines || (patch->fragments && patch->fragments->next)))
1012+
patch->is_delete = 0;
1013+
if (!unidiff_zero || context) {
1014+
/* If the user says the patch is not generated with
1015+
* --unified=0, or if we have seen context lines,
1016+
* then not having oldlines means the patch is creation,
1017+
* and not having newlines means the patch is deletion.
1018+
*/
1019+
if (patch->is_new < 0 && !oldlines)
1020+
patch->is_new = 1;
1021+
if (patch->is_delete < 0 && !newlines)
1022+
patch->is_delete = 1;
1023+
}
1024+
1025+
if (0 < patch->is_new && oldlines)
1026+
die("new file %s depends on old contents", patch->new_name);
1027+
if (0 < patch->is_delete && newlines)
1028+
die("deleted file %s still has contents", patch->old_name);
1029+
if (!patch->is_delete && !newlines && context)
1030+
fprintf(stderr, "** warning: file %s becomes empty but "
1031+
"is not deleted\n", patch->new_name);
1032+
10021033
return offset;
10031034
}
10041035

@@ -1556,9 +1587,19 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, i
15561587
/*
15571588
* If we don't have any leading/trailing data in the patch,
15581589
* we want it to match at the beginning/end of the file.
1590+
*
1591+
* But that would break if the patch is generated with
1592+
* --unified=0; sane people wouldn't do that to cause us
1593+
* trouble, but we try to please not so sane ones as well.
15591594
*/
1560-
match_beginning = !leading && (frag->oldpos == 1);
1561-
match_end = !trailing;
1595+
if (unidiff_zero) {
1596+
match_beginning = (!leading && !frag->oldpos);
1597+
match_end = 0;
1598+
}
1599+
else {
1600+
match_beginning = !leading && (frag->oldpos == 1);
1601+
match_end = !trailing;
1602+
}
15621603

15631604
lines = 0;
15641605
pos = frag->newpos;
@@ -1804,7 +1845,7 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
18041845
patch->result = desc.buffer;
18051846
patch->resultsize = desc.size;
18061847

1807-
if (patch->is_delete && patch->resultsize)
1848+
if (0 < patch->is_delete && patch->resultsize)
18081849
return error("removal patch leaves file contents");
18091850

18101851
return 0;
@@ -1876,7 +1917,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
18761917
old_name, st_mode, patch->old_mode);
18771918
}
18781919

1879-
if (new_name && prev_patch && prev_patch->is_delete &&
1920+
if (new_name && prev_patch && 0 < prev_patch->is_delete &&
18801921
!strcmp(prev_patch->old_name, new_name))
18811922
/* A type-change diff is always split into a patch to
18821923
* delete old, immediately followed by a patch to
@@ -1889,7 +1930,8 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
18891930
else
18901931
ok_if_exists = 0;
18911932

1892-
if (new_name && (patch->is_new | patch->is_rename | patch->is_copy)) {
1933+
if (new_name &&
1934+
((0 < patch->is_new) | (0 < patch->is_rename) | patch->is_copy)) {
18931935
if (check_index &&
18941936
cache_name_pos(new_name, strlen(new_name)) >= 0 &&
18951937
!ok_if_exists)
@@ -1906,7 +1948,7 @@ static int check_patch(struct patch *patch, struct patch *prev_patch)
19061948
return error("%s: %s", new_name, strerror(errno));
19071949
}
19081950
if (!patch->new_mode) {
1909-
if (patch->is_new)
1951+
if (0 < patch->is_new)
19101952
patch->new_mode = S_IFREG | 0644;
19111953
else
19121954
patch->new_mode = patch->old_mode;
@@ -1957,7 +1999,7 @@ static void show_index_list(struct patch *list)
19571999
const char *name;
19582000

19592001
name = patch->old_name ? patch->old_name : patch->new_name;
1960-
if (patch->is_new)
2002+
if (0 < patch->is_new)
19612003
sha1_ptr = null_sha1;
19622004
else if (get_sha1(patch->old_sha1_prefix, sha1))
19632005
die("sha1 information is lacking or useless (%s).",
@@ -2543,6 +2585,10 @@ int cmd_apply(int argc, const char **argv, const char *prefix)
25432585
apply_in_reverse = 1;
25442586
continue;
25452587
}
2588+
if (!strcmp(arg, "--unidiff-zero")) {
2589+
unidiff_zero = 1;
2590+
continue;
2591+
}
25462592
if (!strcmp(arg, "--reject")) {
25472593
apply = apply_with_reject = apply_verbosely = 1;
25482594
continue;

t/t3403-rebase-skip.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,9 @@ test_expect_success setup '
3737
git branch skip-merge skip-reference
3838
'
3939

40-
test_expect_failure 'rebase with git am -3 (default)' 'git rebase master'
40+
test_expect_failure 'rebase with git am -3 (default)' '
41+
git rebase master
42+
'
4143

4244
test_expect_success 'rebase --skip with am -3' '
4345
git reset --hard HEAD &&

t/t4104-apply-boundary.sh

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/bin/sh
2+
#
3+
# Copyright (c) 2005 Junio C Hamano
4+
#
5+
6+
test_description='git-apply boundary tests
7+
8+
'
9+
. ./test-lib.sh
10+
11+
L="c d e f g h i j k l m n o p q r s t u v w x"
12+
13+
test_expect_success setup '
14+
for i in b '"$L"' y
15+
do
16+
echo $i
17+
done >victim &&
18+
cat victim >original &&
19+
git update-index --add victim &&
20+
21+
: add to the head
22+
for i in a b '"$L"' y
23+
do
24+
echo $i
25+
done >victim &&
26+
cat victim >add-a-expect &&
27+
git diff victim >add-a-patch.with &&
28+
git diff --unified=0 >add-a-patch.without &&
29+
30+
: modify at the head
31+
for i in a '"$L"' y
32+
do
33+
echo $i
34+
done >victim &&
35+
cat victim >mod-a-expect &&
36+
git diff victim >mod-a-patch.with &&
37+
git diff --unified=0 >mod-a-patch.without &&
38+
39+
: remove from the head
40+
for i in '"$L"' y
41+
do
42+
echo $i
43+
done >victim &&
44+
cat victim >del-a-expect &&
45+
git diff victim >del-a-patch.with
46+
git diff --unified=0 >del-a-patch.without &&
47+
48+
: add to the tail
49+
for i in b '"$L"' y z
50+
do
51+
echo $i
52+
done >victim &&
53+
cat victim >add-z-expect &&
54+
git diff victim >add-z-patch.with &&
55+
git diff --unified=0 >add-z-patch.without &&
56+
57+
: modify at the tail
58+
for i in a '"$L"' y
59+
do
60+
echo $i
61+
done >victim &&
62+
cat victim >mod-z-expect &&
63+
git diff victim >mod-z-patch.with &&
64+
git diff --unified=0 >mod-z-patch.without &&
65+
66+
: remove from the tail
67+
for i in b '"$L"'
68+
do
69+
echo $i
70+
done >victim &&
71+
cat victim >del-z-expect &&
72+
git diff victim >del-z-patch.with
73+
git diff --unified=0 >del-z-patch.without &&
74+
75+
: done
76+
'
77+
78+
for with in with without
79+
do
80+
case "$with" in
81+
with) u= ;;
82+
without) u='--unidiff-zero ' ;;
83+
esac
84+
for kind in add-a add-z mod-a mod-z del-a del-z
85+
do
86+
test_expect_success "apply $kind-patch $with context" '
87+
cat original >victim &&
88+
git update-index victim &&
89+
git apply --index '"$u$kind-patch.$with"' || {
90+
cat '"$kind-patch.$with"'
91+
(exit 1)
92+
} &&
93+
diff -u '"$kind"'-expect victim
94+
'
95+
done
96+
done
97+
98+
for kind in add-a add-z mod-a mod-z del-a del-z
99+
do
100+
rm -f $kind-ng.without
101+
sed -e "s/^diff --git /diff /" \
102+
-e '/^index /d' \
103+
<$kind-patch.without >$kind-ng.without
104+
test_expect_success "apply non-git $kind-patch without context" '
105+
cat original >victim &&
106+
git update-index victim &&
107+
git apply --unidiff-zero --index '"$kind-ng.without"' || {
108+
cat '"$kind-ng.without"'
109+
(exit 1)
110+
} &&
111+
diff -u '"$kind"'-expect victim
112+
'
113+
done
114+
115+
test_done

0 commit comments

Comments
 (0)