@matheusbsbs / matheus-bagliano / commits / 86c91f9179
Fork 0

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