Allow git-apply to recount the lines in a hunk (AKA recountdiff)
Sometimes, the easiest way to fix up a patch is to edit it directly, even adding or deleting lines. Now, many people are not as divine as certain benevolent dictators as to update the hunk headers correctly at the first try. So teach the tool to do it for us. [jc: with tests] Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Johannes Schindelin committed
Jun 27, 2008 at 18:43 UTC
c14b9d1e330a7f68ffd0ad7e22d6148c6097c122
3 files changed
+101
-43
Documentation/git-apply.txt
+6
-1
@@ -12,7 +12,7 @@ SYNOPSIS
12
'git-apply' [--stat] [--numstat] [--summary] [--check] [--index]
13
[--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
14
[--allow-binary-replacement | --binary] [--reject] [-z]
15
- [-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
15
+ [-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
16
[--whitespace=<nowarn|warn|fix|error|error-all>]
17
[--exclude=PATH] [--verbose] [<patch>...]
18
@@ -177,6 +177,11 @@ behavior:
177
current patch being applied will be printed. This option will cause
178
additional information to be reported.
179
180
+--recount::
181
+ Do not trust the line counts in the hunk headers, but infer them
182
+ by inspecting the patch (e.g. after editing the patch without
183
+ adjusting the hunk headers appropriately).
184
+
185
Configuration
186
-------------
187
builtin-apply.c
+68
-7
@@ -153,6 +153,7 @@ struct patch {
153
unsigned int is_binary:1;
154
unsigned int is_copy:1;
155
unsigned int is_rename:1;
156
+ unsigned int recount:1;
157
struct fragment *fragments;
158
char *result;
159
size_t resultsize;
@@ -882,6 +883,56 @@ static int parse_range(const char *line, int len, int offset, const char *expect
883
return offset + ex;
884
}
885
886
+static void recount_diff(char *line, int size, struct fragment *fragment)
887
+{
888
+ int oldlines = 0, newlines = 0, ret = 0;
889
+
890
+ if (size < 1) {
891
+ warning("recount: ignore empty hunk");
892
+ return;
893
+ }
894
+
895
+ for (;;) {
896
+ int len = linelen(line, size);
897
+ size -= len;
898
+ line += len;
899
+
900
+ if (size < 1)
901
+ break;
902
+
903
+ switch (*line) {
904
+ case ' ': case '\n':
905
+ newlines++;
906
+ /* fall through */
907
+ case '-':
908
+ oldlines++;
909
+ continue;
910
+ case '+':
911
+ newlines++;
912
+ continue;
913
+ case '\\':
914
+ break;
915
+ case '@':
916
+ ret = size < 3 || prefixcmp(line, "@@ ");
917
+ break;
918
+ case 'd':
919
+ ret = size < 5 || prefixcmp(line, "diff ");
920
+ break;
921
+ default:
922
+ ret = -1;
923
+ break;
924
+ }
925
+ if (ret) {
926
+ warning("recount: unexpected line: %.*s",
927
+ (int)linelen(line, size), line);
928
+ return;
929
+ }
930
+ break;
931
+ }
932
+ fragment->oldlines = oldlines;
933
+ fragment->newlines = newlines;
934
+}
935
+
936
/*
937
* Parse a unified diff fragment header of the
938
* form "@@ -a,b +c,d @@"
@@ -1013,6 +1064,8 @@ static int parse_fragment(char *line, unsigned long size,
1064
offset = parse_fragment_header(line, len, fragment);
1065
if (offset < 0)
1066
return -1;
1067
+ if (offset > 0 && patch->recount)
1068
+ recount_diff(line + offset, size - offset, fragment);
1069
oldlines = fragment->oldlines;
1070
newlines = fragment->newlines;
1071
leading = 0;
@@ -2912,7 +2965,10 @@ static void prefix_patches(struct patch *p)
2965
}
2966
}
2967
2915
-static int apply_patch(int fd, const char *filename, int inaccurate_eof)
2968
+#define INACCURATE_EOF (1<<0)
2969
+#define RECOUNT (1<<1)
2970
+
2971
+static int apply_patch(int fd, const char *filename, int options)
2972
{
2973
size_t offset;
2974
struct strbuf buf;
@@ -2928,7 +2984,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
2984
int nr;
2985
2986
patch = xcalloc(1, sizeof(*patch));
2931
- patch->inaccurate_eof = inaccurate_eof;
2987
+ patch->inaccurate_eof = !!(options & INACCURATE_EOF);
2988
+ patch->recount = !!(options & RECOUNT);
2989
nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
2990
if (nr < 0)
2991
break;
@@ -2997,7 +3054,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3054
{
3055
int i;
3056
int read_stdin = 1;
3000
- int inaccurate_eof = 0;
3057
+ int options = 0;
3058
int errs = 0;
3059
int is_not_gitdir;
3060
@@ -3015,7 +3072,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3072
int fd;
3073
3074
if (!strcmp(arg, "-")) {
3018
- errs |= apply_patch(0, "<stdin>", inaccurate_eof);
3075
+ errs |= apply_patch(0, "<stdin>", options);
3076
read_stdin = 0;
3077
continue;
3078
}
@@ -3115,7 +3172,11 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3172
continue;
3173
}
3174
if (!strcmp(arg, "--inaccurate-eof")) {
3118
- inaccurate_eof = 1;
3175
+ options |= INACCURATE_EOF;
3176
+ continue;
3177
+ }
3178
+ if (!strcmp(arg, "--recount")) {
3179
+ options |= RECOUNT;
3180
continue;
3181
}
3182
if (0 < prefix_length)
@@ -3126,12 +3187,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3187
die("can't open patch '%s': %s", arg, strerror(errno));
3188
read_stdin = 0;
3189
set_default_whitespace_mode(whitespace_option);
3129
- errs |= apply_patch(fd, arg, inaccurate_eof);
3190
+ errs |= apply_patch(fd, arg, options);
3191
close(fd);
3192
}
3193
set_default_whitespace_mode(whitespace_option);
3194
if (read_stdin)
3134
- errs |= apply_patch(0, "<stdin>", inaccurate_eof);
3195
+ errs |= apply_patch(0, "<stdin>", options);
3196
if (whitespace_error) {
3197
if (squelch_whitespace_errors &&
3198
squelch_whitespace_errors < whitespace_error) {
t/t4100-apply-stat.sh
+27
-35
@@ -3,44 +3,36 @@
3
# Copyright (c) 2005 Junio C Hamano
4
#
5
6
-test_description='git apply --stat --summary test.
6
+test_description='git apply --stat --summary test, with --recount
7
8
'
9
. ./test-lib.sh
10
11
-test_expect_success \
12
- 'rename' \
13
- 'git apply --stat --summary <../t4100/t-apply-1.patch >current &&
14
- test_cmp ../t4100/t-apply-1.expect current'
15
-
16
-test_expect_success \
17
- 'copy' \
18
- 'git apply --stat --summary <../t4100/t-apply-2.patch >current &&
19
- test_cmp ../t4100/t-apply-2.expect current'
20
-
21
-test_expect_success \
22
- 'rewrite' \
23
- 'git apply --stat --summary <../t4100/t-apply-3.patch >current &&
24
- test_cmp ../t4100/t-apply-3.expect current'
25
-
26
-test_expect_success \
27
- 'mode' \
28
- 'git apply --stat --summary <../t4100/t-apply-4.patch >current &&
29
- test_cmp ../t4100/t-apply-4.expect current'
30
-
31
-test_expect_success \
32
- 'non git' \
33
- 'git apply --stat --summary <../t4100/t-apply-5.patch >current &&
34
- test_cmp ../t4100/t-apply-5.expect current'
35
-
36
-test_expect_success \
37
- 'non git' \
38
- 'git apply --stat --summary <../t4100/t-apply-6.patch >current &&
39
- test_cmp ../t4100/t-apply-6.expect current'
40
-
41
-test_expect_success \
42
- 'non git' \
43
- 'git apply --stat --summary <../t4100/t-apply-7.patch >current &&
44
- test_cmp ../t4100/t-apply-7.expect current'
11
+UNC='s/^\(@@ -[1-9][0-9]*\),[0-9]* \(+[1-9][0-9]*\),[0-9]* @@/\1,999 \2,999 @@/'
12
+
13
+num=0
14
+while read title
15
+do
16
+ num=$(( $num + 1 ))
17
+ test_expect_success "$title" '
18
+ git apply --stat --summary \
19
+ <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" >current &&
20
+ test_cmp ../t4100/t-apply-$num.expect current
21
+ '
22
+
23
+ test_expect_success "$title with recount" '
24
+ sed -e "$UNC" <"$TEST_DIRECTORY/t4100/t-apply-$num.patch" |
25
+ git apply --recount --stat --summary >current &&
26
+ test_cmp ../t4100/t-apply-$num.expect current
27
+ '
28
+done <<\EOF
29
+rename
30
+copy
31
+rewrite
32
+mode
33
+non git (1)
34
+non git (2)
35
+non git (3)
36
+EOF
37
38
test_done