git apply: option to ignore whitespace differences
Introduce --ignore-whitespace option and corresponding config bool to ignore whitespace differences while applying patches, akin to the 'patch' program. 'git am', 'git rebase' and the bash git completion are made aware of this option. Signed-off-by: Giuseppe Bilotta <giuseppe.bilotta@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Giuseppe Bilotta committed
Aug 4, 2009 at 13:16 UTC
86c91f91794cd6af8e19fbe68ab283d567d2b66f
11 files changed
+389
-10
Documentation/config.txt
+8
@@ -461,6 +461,14 @@ it will be treated as a shell command. For example, defining
461
executed from the top-level directory of a repository, which may
462
not necessarily be the current directory.
463
464
+apply.ignorewhitespace::
465
+ When set to 'change', tells 'git-apply' to ignore changes in
466
+ whitespace, in the same way as the '--ignore-space-change'
467
+ option.
468
+ When set to one of: no, none, never, false tells 'git-apply' to
469
+ respect all whitespace differences.
470
+ See linkgit:git-apply[1].
471
+
472
apply.whitespace::
473
Tells 'git-apply' how to handle whitespaces, in the same way
474
as the '--whitespace' option. See linkgit:git-apply[1].
Documentation/git-am.txt
+4
-1
@@ -11,7 +11,7 @@ SYNOPSIS
11
[verse]
12
'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
13
[--3way] [--interactive] [--committer-date-is-author-date]
14
- [--ignore-date]
14
+ [--ignore-date] [--ignore-space-change | --ignore-whitespace]
15
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
16
[--reject] [-q | --quiet]
17
[<mbox> | <Maildir>...]
@@ -65,6 +65,9 @@ default. You can use `--no-utf8` to override this.
65
it is supposed to apply to and we have those blobs
66
available locally.
67
68
+--ignore-date::
69
+--ignore-space-change::
70
+--ignore-whitespace::
71
--whitespace=<option>::
72
-C<n>::
73
-p<n>::
Documentation/git-apply.txt
+13
@@ -13,6 +13,7 @@ SYNOPSIS
13
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
14
[--allow-binary-replacement | --binary] [--reject] [-z]
15
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
16
+ [--ignore-space-change | --ignore-whitespace ]
17
[--whitespace=<nowarn|warn|fix|error|error-all>]
18
[--exclude=PATH] [--include=PATH] [--directory=<root>]
19
[--verbose] [<patch>...]
@@ -149,6 +150,14 @@ patch to each path is used. A patch to a path that does not match any
150
include/exclude pattern is used by default if there is no include pattern
151
on the command line, and ignored if there is any include pattern.
152
153
+--ignore-space-change::
154
+--ignore-whitespace::
155
+ When applying a patch, ignore changes in whitespace in context
156
+ lines if necessary.
157
+ Context lines will preserve their whitespace, and they will not
158
+ undergo whitespace fixing regardless of the value of the
159
+ `--whitespace` option. New lines will still be fixed, though.
160
+
161
--whitespace=<action>::
162
When applying a patch, detect a new or modified line that has
163
whitespace errors. What are considered whitespace errors is
@@ -205,6 +214,10 @@ running `git apply --directory=modules/git-gui`.
214
Configuration
215
-------------
216
217
+apply.ignorewhitespace::
218
+ Set to 'change' if you want changes in whitespace to be ignored by default.
219
+ Set to one of: no, none, never, false if you want changes in
220
+ whitespace to be significant.
221
apply.whitespace::
222
When no `--whitespace` flag is given from the command
223
line, this configuration item is used as the default.
Documentation/git-rebase.txt
+2
-1
@@ -268,8 +268,9 @@ OPTIONS
268
exit with the message "Current branch is up to date" in such a
269
situation.
270
271
+--ignore-whitespace::
272
--whitespace=<option>::
272
- This flag is passed to the 'git-apply' program
273
+ These flag are passed to the 'git-apply' program
274
(see linkgit:git-apply[1]) that applies the patch.
275
Incompatible with the --interactive option.
276
builtin-apply.c
+166
-7
@@ -61,6 +61,13 @@ static enum ws_error_action {
61
static int whitespace_error;
62
static int squelch_whitespace_errors = 5;
63
static int applied_after_fixing_ws;
64
+
65
+static enum ws_ignore {
66
+ ignore_ws_none,
67
+ ignore_ws_change,
68
+} ws_ignore_action = ignore_ws_none;
69
+
70
+
71
static const char *patch_input_file;
72
static const char *root;
73
static int root_len;
@@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option)
104
die("unrecognized whitespace option '%s'", option);
105
}
106
107
+static void parse_ignorewhitespace_option(const char *option)
108
+{
109
+ if (!option || !strcmp(option, "no") ||
110
+ !strcmp(option, "false") || !strcmp(option, "never") ||
111
+ !strcmp(option, "none")) {
112
+ ws_ignore_action = ignore_ws_none;
113
+ return;
114
+ }
115
+ if (!strcmp(option, "change")) {
116
+ ws_ignore_action = ignore_ws_change;
117
+ return;
118
+ }
119
+ die("unrecognized whitespace ignore option '%s'", option);
120
+}
121
+
122
static void set_default_whitespace_mode(const char *whitespace_option)
123
{
124
if (!whitespace_option && !apply_default_whitespace)
@@ -214,6 +236,62 @@ static uint32_t hash_line(const char *cp, size_t len)
236
return h;
237
}
238
239
+/*
240
+ * Compare lines s1 of length n1 and s2 of length n2, ignoring
241
+ * whitespace difference. Returns 1 if they match, 0 otherwise
242
+ */
243
+static int fuzzy_matchlines(const char *s1, size_t n1,
244
+ const char *s2, size_t n2)
245
+{
246
+ const char *last1 = s1 + n1 - 1;
247
+ const char *last2 = s2 + n2 - 1;
248
+ int result = 0;
249
+
250
+ if (n1 < 0 || n2 < 0)
251
+ return 0;
252
+
253
+ /* ignore line endings */
254
+ while ((*last1 == '\r') || (*last1 == '\n'))
255
+ last1--;
256
+ while ((*last2 == '\r') || (*last2 == '\n'))
257
+ last2--;
258
+
259
+ /* skip leading whitespace */
260
+ while (isspace(*s1) && (s1 <= last1))
261
+ s1++;
262
+ while (isspace(*s2) && (s2 <= last2))
263
+ s2++;
264
+ /* early return if both lines are empty */
265
+ if ((s1 > last1) && (s2 > last2))
266
+ return 1;
267
+ while (!result) {
268
+ result = *s1++ - *s2++;
269
+ /*
270
+ * Skip whitespace inside. We check for whitespace on
271
+ * both buffers because we don't want "a b" to match
272
+ * "ab"
273
+ */
274
+ if (isspace(*s1) && isspace(*s2)) {
275
+ while (isspace(*s1) && s1 <= last1)
276
+ s1++;
277
+ while (isspace(*s2) && s2 <= last2)
278
+ s2++;
279
+ }
280
+ /*
281
+ * If we reached the end on one side only,
282
+ * lines don't match
283
+ */
284
+ if (
285
+ ((s2 > last2) && (s1 <= last1)) ||
286
+ ((s1 > last1) && (s2 <= last2)))
287
+ return 0;
288
+ if ((s1 > last1) && (s2 > last2))
289
+ break;
290
+ }
291
+
292
+ return !result;
293
+}
294
+
295
static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
296
{
297
ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
@@ -1592,10 +1670,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
1670
}
1671
}
1672
1673
+/*
1674
+ * Update the preimage, and the common lines in postimage,
1675
+ * from buffer buf of length len. If postlen is 0 the postimage
1676
+ * is updated in place, otherwise it's updated on a new buffer
1677
+ * of length postlen
1678
+ */
1679
+
1680
static void update_pre_post_images(struct image *preimage,
1681
struct image *postimage,
1682
char *buf,
1598
- size_t len)
1683
+ size_t len, size_t postlen)
1684
{
1685
int i, ctx;
1686
char *new, *old, *fixed;
@@ -1614,11 +1699,19 @@ static void update_pre_post_images(struct image *preimage,
1699
*preimage = fixed_preimage;
1700
1701
/*
1617
- * Adjust the common context lines in postimage, in place.
1618
- * This is possible because whitespace fixing does not make
1619
- * the string grow.
1702
+ * Adjust the common context lines in postimage. This can be
1703
+ * done in-place when we are just doing whitespace fixing,
1704
+ * which does not make the string grow, but needs a new buffer
1705
+ * when ignoring whitespace causes the update, since in this case
1706
+ * we could have e.g. tabs converted to multiple spaces.
1707
+ * We trust the caller to tell us if the update can be done
1708
+ * in place (postlen==0) or not.
1709
*/
1621
- new = old = postimage->buf;
1710
+ old = postimage->buf;
1711
+ if (postlen)
1712
+ new = postimage->buf = xmalloc(postlen);
1713
+ else
1714
+ new = old;
1715
fixed = preimage->buf;
1716
for (i = ctx = 0; i < postimage->nr; i++) {
1717
size_t len = postimage->line[i].len;
@@ -1693,12 +1786,58 @@ static int match_fragment(struct image *img,
1786
!memcmp(img->buf + try, preimage->buf, preimage->len))
1787
return 1;
1788
1789
+ /*
1790
+ * No exact match. If we are ignoring whitespace, run a line-by-line
1791
+ * fuzzy matching. We collect all the line length information because
1792
+ * we need it to adjust whitespace if we match.
1793
+ */
1794
+ if (ws_ignore_action == ignore_ws_change) {
1795
+ size_t imgoff = 0;
1796
+ size_t preoff = 0;
1797
+ size_t postlen = postimage->len;
1798
+ size_t imglen[preimage->nr];
1799
+ for (i = 0; i < preimage->nr; i++) {
1800
+ size_t prelen = preimage->line[i].len;
1801
+
1802
+ imglen[i] = img->line[try_lno+i].len;
1803
+ if (!fuzzy_matchlines(
1804
+ img->buf + try + imgoff, imglen[i],
1805
+ preimage->buf + preoff, prelen))
1806
+ return 0;
1807
+ if (preimage->line[i].flag & LINE_COMMON)
1808
+ postlen += imglen[i] - prelen;
1809
+ imgoff += imglen[i];
1810
+ preoff += prelen;
1811
+ }
1812
+
1813
+ /*
1814
+ * Ok, the preimage matches with whitespace fuzz. Update it and
1815
+ * the common postimage lines to use the same whitespace as the
1816
+ * target. imgoff now holds the true length of the target that
1817
+ * matches the preimage, and we need to update the line lengths
1818
+ * of the preimage to match the target ones.
1819
+ */
1820
+ fixed_buf = xmalloc(imgoff);
1821
+ memcpy(fixed_buf, img->buf + try, imgoff);
1822
+ for (i = 0; i < preimage->nr; i++)
1823
+ preimage->line[i].len = imglen[i];
1824
+
1825
+ /*
1826
+ * Update the preimage buffer and the postimage context lines.
1827
+ */
1828
+ update_pre_post_images(preimage, postimage,
1829
+ fixed_buf, imgoff, postlen);
1830
+ return 1;
1831
+ }
1832
+
1833
if (ws_error_action != correct_ws_error)
1834
return 0;
1835
1836
/*
1837
* The hunk does not apply byte-by-byte, but the hash says
1701
- * it might with whitespace fuzz.
1838
+ * it might with whitespace fuzz. We haven't been asked to
1839
+ * ignore whitespace, we were asked to correct whitespace
1840
+ * errors, so let's try matching after whitespace correction.
1841
*/
1842
fixed_buf = xmalloc(preimage->len + 1);
1843
buf = fixed_buf;
@@ -1750,7 +1889,7 @@ static int match_fragment(struct image *img,
1889
* hunk match. Update the context lines in the postimage.
1890
*/
1891
update_pre_post_images(preimage, postimage,
1753
- fixed_buf, buf - fixed_buf);
1892
+ fixed_buf, buf - fixed_buf, 0);
1893
return 1;
1894
1895
unmatch_exit:
@@ -3192,6 +3331,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
3331
{
3332
if (!strcmp(var, "apply.whitespace"))
3333
return git_config_string(&apply_default_whitespace, var, value);
3334
+ else if (!strcmp(var, "apply.ignorewhitespace"))
3335
+ return git_config_string(&apply_default_ignorewhitespace, var, value);
3336
return git_default_config(var, value, cb);
3337
}
3338
@@ -3228,6 +3369,16 @@ static int option_parse_z(const struct option *opt,
3369
return 0;
3370
}
3371
3372
+static int option_parse_space_change(const struct option *opt,
3373
+ const char *arg, int unset)
3374
+{
3375
+ if (unset)
3376
+ ws_ignore_action = ignore_ws_none;
3377
+ else
3378
+ ws_ignore_action = ignore_ws_change;
3379
+ return 0;
3380
+}
3381
+
3382
static int option_parse_whitespace(const struct option *opt,
3383
const char *arg, int unset)
3384
{
@@ -3304,6 +3455,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3455
{ OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
3456
"detect new or modified lines that have whitespace errors",
3457
0, option_parse_whitespace },
3458
+ { OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
3459
+ "ignore changes in whitespace when finding context",
3460
+ PARSE_OPT_NOARG, option_parse_space_change },
3461
+ { OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
3462
+ "ignore changes in whitespace when finding context",
3463
+ PARSE_OPT_NOARG, option_parse_space_change },
3464
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
3465
"apply the patch in reverse"),
3466
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
@@ -3328,6 +3485,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
3485
git_config(git_apply_config, NULL);
3486
if (apply_default_whitespace)
3487
parse_whitespace_option(apply_default_whitespace);
3488
+ if (apply_default_ignorewhitespace)
3489
+ parse_ignorewhitespace_option(apply_default_ignorewhitespace);
3490
3491
argc = parse_options(argc, argv, prefix, builtin_apply_options,
3492
apply_usage, 0);
cache.h
+1
@@ -509,6 +509,7 @@ extern int log_all_ref_updates;
509
extern int warn_ambiguous_refs;
510
extern int shared_repository;
511
extern const char *apply_default_whitespace;
512
+extern const char *apply_default_ignorewhitespace;
513
extern int zlib_compression_level;
514
extern int core_compression_level;
515
extern int core_compression_seen;
contrib/completion/git-completion.bash
+3
@@ -674,6 +674,7 @@ _git_am ()
674
--*)
675
__gitcomp "
676
--3way --committer-date-is-author-date --ignore-date
677
+ --ignore-whitespace --ignore-space-change
678
--interactive --keep --no-utf8 --signoff --utf8
679
--whitespace=
680
"
@@ -695,6 +696,7 @@ _git_apply ()
696
--stat --numstat --summary --check --index
697
--cached --index-info --reverse --reject --unidiff-zero
698
--apply --no-add --exclude=
699
+ --ignore-whitespace --ignore-space-change
700
--whitespace= --inaccurate-eof --verbose
701
"
702
return
@@ -1536,6 +1538,7 @@ _git_config ()
1538
__gitcomp "
1539
add.ignore-errors
1540
alias.
1541
+ apply.ignorewhitespace
1542
apply.whitespace
1543
branch.autosetupmerge
1544
branch.autosetuprebase
environment.c
+1
@@ -26,6 +26,7 @@ const char *git_commit_encoding;
26
const char *git_log_output_encoding;
27
int shared_repository = PERM_UMASK;
28
const char *apply_default_whitespace;
29
+const char *apply_default_ignorewhitespace;
30
int zlib_compression_level = Z_BEST_SPEED;
31
int core_compression_level;
32
int core_compression_seen;
git-am.sh
+3
-1
@@ -16,6 +16,8 @@ s,signoff add a Signed-off-by line to the commit message
16
u,utf8 recode into utf8 (default)
17
k,keep pass -k flag to git-mailinfo
18
whitespace= pass it through git-apply
19
+ignore-space-change pass it through git-apply
20
+ignore-whitespace pass it through git-apply
21
directory= pass it through git-apply
22
C= pass it through git-apply
23
p= pass it through git-apply
@@ -303,7 +305,7 @@ do
305
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
306
--patch-format)
307
shift ; patch_format="$1" ;;
306
- --reject)
308
+ --reject|--ignore-whitespace|--ignore-space-change)
309
git_apply_opt="$git_apply_opt $1" ;;
310
--committer-date-is-author-date)
311
committer_date_is_author_date=t ;;
git-rebase.sh
+3
@@ -333,6 +333,9 @@ do
333
;;
334
esac
335
;;
336
+ --ignore-whitespace)
337
+ git_am_opt="$git_am_opt $1"
338
+ ;;
339
--committer-date-is-author-date|--ignore-date)
340
git_am_opt="$git_am_opt $1"
341
force_rebase=t
t/t4107-apply-ignore-whitespace.sh
new
+185
@@ -0,0 +1,185 @@
1
+#!/bin/sh
2
+#
3
+# Copyright (c) 2009 Giuseppe Bilotta
4
+#
5
+
6
+test_description='git-apply --ignore-whitespace.
7
+
8
+'
9
+. ./test-lib.sh
10
+
11
+# This primes main.c file that indents without using HT at all.
12
+# Various patches with HT and other spaces are attempted in the test.
13
+
14
+cat > patch1.patch <<\EOF
15
+diff --git a/main.c b/main.c
16
+new file mode 100644
17
+--- /dev/null
18
++++ b/main.c
19
+@@ -0,0 +1,22 @@
20
++#include <stdio.h>
21
++
22
++void print_int(int num);
23
++int func(int num);
24
++
25
++int main() {
26
++ int i;
27
++
28
++ for (i = 0; i < 10; i++) {
29
++ print_int(func(i)); /* stuff */
30
++ }
31
++
32
++ return 0;
33
++}
34
++
35
++int func(int num) {
36
++ return num * num;
37
++}
38
++
39
++void print_int(int num) {
40
++ printf("%d", num);
41
++}
42
+EOF
43
+
44
+# Since whitespace is very significant and we want to prevent whitespace
45
+# mangling when creating this test from a patch, we protect 'fixable'
46
+# whitespace by replacing spaces with Z and replacing them at patch
47
+# creation time, hence the sed trick.
48
+
49
+# This patch will fail unless whitespace differences are being ignored
50
+
51
+sed -e 's/Z/ /g' > patch2.patch <<\EOF
52
+diff --git a/main.c b/main.c
53
+--- a/main.c
54
++++ b/main.c
55
+@@ -10,6 +10,8 @@
56
+Z print_int(func(i)); /* stuff */
57
+Z }
58
+Z
59
++ printf("\n");
60
++
61
+Z return 0;
62
+Z}
63
+Z
64
+EOF
65
+
66
+# This patch will fail even if whitespace differences are being ignored,
67
+# because of the missing string at EOL. TODO: this testcase should be
68
+# improved by creating a line that has the same hash with and without
69
+# the final string.
70
+
71
+sed -e 's/Z/ /g' > patch3.patch <<\EOF
72
+diff --git a/main.c b/main.c
73
+--- a/main.c
74
++++ b/main.c
75
+@@ -10,3 +10,4 @@
76
+Z for (i = 0; i < 10; i++) {
77
+Z print_int(func(i));Z
78
++ /* stuff */
79
+Z }
80
+EOF
81
+
82
+# This patch will fail even if whitespace differences are being ignored,
83
+# because of the missing EOL at EOF.
84
+
85
+sed -e 's/Z/ /g' > patch4.patch <<\EOF
86
+diff --git a/main.c b/main.c
87
+--- a/main.c
88
++++ b/main.c
89
+@@ -21,1 +21,1 @@
90
+- };Z
91
+\ No newline at end of file
92
++ };
93
+EOF
94
+
95
+# This patch will fail unless whitespace differences are being ignored.
96
+
97
+sed -e 's/Z/ /g' > patch5.patch <<\EOF
98
+diff --git a/main.c b/main.c
99
+--- a/main.c
100
++++ b/main.c
101
+@@ -2,2 +2,3 @@
102
+Z void print_int(int num);
103
++ /* a comment */
104
+Z int func(int num);
105
+EOF
106
+
107
+# And this is how the final output should be. Patches introduce
108
+# HTs but the original SP indents are mostly kept.
109
+
110
+sed -e 's/T/ /g' > main.c.final <<\EOF
111
+#include <stdio.h>
112
+
113
+void print_int(int num);
114
+T/* a comment */
115
+int func(int num);
116
+
117
+int main() {
118
+ int i;
119
+
120
+ for (i = 0; i < 10; i++) {
121
+ print_int(func(i)); /* stuff */
122
+ }
123
+
124
+Tprintf("\n");
125
+
126
+ return 0;
127
+}
128
+
129
+int func(int num) {
130
+ return num * num;
131
+}
132
+
133
+void print_int(int num) {
134
+ printf("%d", num);
135
+}
136
+EOF
137
+
138
+test_expect_success 'file creation' '
139
+ git-apply patch1.patch
140
+'
141
+
142
+test_expect_success 'patch2 fails (retab)' '
143
+ test_must_fail git-apply patch2.patch
144
+'
145
+
146
+test_expect_success 'patch2 applies with --ignore-whitespace' '
147
+ git-apply --ignore-whitespace patch2.patch
148
+'
149
+
150
+test_expect_success 'patch2 reverse applies with --ignore-space-change' '
151
+ git-apply -R --ignore-space-change patch2.patch
152
+'
153
+
154
+git config apply.ignorewhitespace change
155
+
156
+test_expect_success 'patch2 applies (apply.ignorewhitespace = change)' '
157
+ git-apply patch2.patch
158
+'
159
+
160
+test_expect_success 'patch3 fails (missing string at EOL)' '
161
+ test_must_fail git-apply patch3.patch
162
+'
163
+
164
+test_expect_success 'patch4 fails (missing EOL at EOF)' '
165
+ test_must_fail git-apply patch4.patch
166
+'
167
+
168
+test_expect_success 'patch5 applies (leading whitespace)' '
169
+ git-apply patch5.patch
170
+'
171
+
172
+test_expect_success 'patches do not mangle whitespace' '
173
+ test_cmp main.c main.c.final
174
+'
175
+
176
+test_expect_success 're-create file (with --ignore-whitespace)' '
177
+ rm -f main.c &&
178
+ git-apply patch1.patch
179
+'
180
+
181
+test_expect_success 'patch5 fails (--no-ignore-whitespace)' '
182
+ test_must_fail git-apply --no-ignore-whitespace patch5.patch
183
+'
184
+
185
+test_done