Skip to content

Commit 27158e4

Browse files
committed
Merge branch 'js/apply-recount'
* js/apply-recount: Allow git-apply to recount the lines in a hunk (AKA recountdiff)
2 parents d4b76e1 + c14b9d1 commit 27158e4

File tree

3 files changed

+101
-43
lines changed

3 files changed

+101
-43
lines changed

Documentation/git-apply.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ SYNOPSIS
1212
'git-apply' [--stat] [--numstat] [--summary] [--check] [--index]
1313
[--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
1414
[--allow-binary-replacement | --binary] [--reject] [-z]
15-
[-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
15+
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
1616
[--whitespace=<nowarn|warn|fix|error|error-all>]
1717
[--exclude=PATH] [--verbose] [<patch>...]
1818

@@ -177,6 +177,11 @@ behavior:
177177
current patch being applied will be printed. This option will cause
178178
additional information to be reported.
179179

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+
180185
Configuration
181186
-------------
182187

builtin-apply.c

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ struct patch {
154154
unsigned int is_binary:1;
155155
unsigned int is_copy:1;
156156
unsigned int is_rename:1;
157+
unsigned int recount:1;
157158
struct fragment *fragments;
158159
char *result;
159160
size_t resultsize;
@@ -890,6 +891,56 @@ static int parse_range(const char *line, int len, int offset, const char *expect
890891
return offset + ex;
891892
}
892893

894+
static void recount_diff(char *line, int size, struct fragment *fragment)
895+
{
896+
int oldlines = 0, newlines = 0, ret = 0;
897+
898+
if (size < 1) {
899+
warning("recount: ignore empty hunk");
900+
return;
901+
}
902+
903+
for (;;) {
904+
int len = linelen(line, size);
905+
size -= len;
906+
line += len;
907+
908+
if (size < 1)
909+
break;
910+
911+
switch (*line) {
912+
case ' ': case '\n':
913+
newlines++;
914+
/* fall through */
915+
case '-':
916+
oldlines++;
917+
continue;
918+
case '+':
919+
newlines++;
920+
continue;
921+
case '\\':
922+
break;
923+
case '@':
924+
ret = size < 3 || prefixcmp(line, "@@ ");
925+
break;
926+
case 'd':
927+
ret = size < 5 || prefixcmp(line, "diff ");
928+
break;
929+
default:
930+
ret = -1;
931+
break;
932+
}
933+
if (ret) {
934+
warning("recount: unexpected line: %.*s",
935+
(int)linelen(line, size), line);
936+
return;
937+
}
938+
break;
939+
}
940+
fragment->oldlines = oldlines;
941+
fragment->newlines = newlines;
942+
}
943+
893944
/*
894945
* Parse a unified diff fragment header of the
895946
* form "@@ -a,b +c,d @@"
@@ -1020,6 +1071,8 @@ static int parse_fragment(char *line, unsigned long size,
10201071
offset = parse_fragment_header(line, len, fragment);
10211072
if (offset < 0)
10221073
return -1;
1074+
if (offset > 0 && patch->recount)
1075+
recount_diff(line + offset, size - offset, fragment);
10231076
oldlines = fragment->oldlines;
10241077
newlines = fragment->newlines;
10251078
leading = 0;
@@ -2971,7 +3024,10 @@ static void prefix_patches(struct patch *p)
29713024
}
29723025
}
29733026

2974-
static int apply_patch(int fd, const char *filename, int inaccurate_eof)
3027+
#define INACCURATE_EOF (1<<0)
3028+
#define RECOUNT (1<<1)
3029+
3030+
static int apply_patch(int fd, const char *filename, int options)
29753031
{
29763032
size_t offset;
29773033
struct strbuf buf;
@@ -2989,7 +3045,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
29893045
int nr;
29903046

29913047
patch = xcalloc(1, sizeof(*patch));
2992-
patch->inaccurate_eof = inaccurate_eof;
3048+
patch->inaccurate_eof = !!(options & INACCURATE_EOF);
3049+
patch->recount = !!(options & RECOUNT);
29933050
nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
29943051
if (nr < 0)
29953052
break;
@@ -3058,7 +3115,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
30583115
{
30593116
int i;
30603117
int read_stdin = 1;
3061-
int inaccurate_eof = 0;
3118+
int options = 0;
30623119
int errs = 0;
30633120
int is_not_gitdir;
30643121

@@ -3076,7 +3133,7 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
30763133
int fd;
30773134

30783135
if (!strcmp(arg, "-")) {
3079-
errs |= apply_patch(0, "<stdin>", inaccurate_eof);
3136+
errs |= apply_patch(0, "<stdin>", options);
30803137
read_stdin = 0;
30813138
continue;
30823139
}
@@ -3176,7 +3233,11 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
31763233
continue;
31773234
}
31783235
if (!strcmp(arg, "--inaccurate-eof")) {
3179-
inaccurate_eof = 1;
3236+
options |= INACCURATE_EOF;
3237+
continue;
3238+
}
3239+
if (!strcmp(arg, "--recount")) {
3240+
options |= RECOUNT;
31803241
continue;
31813242
}
31823243
if (0 < prefix_length)
@@ -3187,12 +3248,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
31873248
die("can't open patch '%s': %s", arg, strerror(errno));
31883249
read_stdin = 0;
31893250
set_default_whitespace_mode(whitespace_option);
3190-
errs |= apply_patch(fd, arg, inaccurate_eof);
3251+
errs |= apply_patch(fd, arg, options);
31913252
close(fd);
31923253
}
31933254
set_default_whitespace_mode(whitespace_option);
31943255
if (read_stdin)
3195-
errs |= apply_patch(0, "<stdin>", inaccurate_eof);
3256+
errs |= apply_patch(0, "<stdin>", options);
31963257
if (whitespace_error) {
31973258
if (squelch_whitespace_errors &&
31983259
squelch_whitespace_errors < whitespace_error) {

t/t4100-apply-stat.sh

Lines changed: 27 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,44 +3,36 @@
33
# Copyright (c) 2005 Junio C Hamano
44
#
55

6-
test_description='git apply --stat --summary test.
6+
test_description='git apply --stat --summary test, with --recount
77
88
'
99
. ./test-lib.sh
1010

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
4537

4638
test_done

0 commit comments

Comments
 (0)