Skip to content

Commit 86c91f9

Browse files
Oblomovgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 07a4a3b commit 86c91f9

File tree

11 files changed

+389
-10
lines changed

11 files changed

+389
-10
lines changed

Documentation/config.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,14 @@ it will be treated as a shell command. For example, defining
461461
executed from the top-level directory of a repository, which may
462462
not necessarily be the current directory.
463463

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+
464472
apply.whitespace::
465473
Tells 'git-apply' how to handle whitespaces, in the same way
466474
as the '--whitespace' option. See linkgit:git-apply[1].

Documentation/git-am.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ SYNOPSIS
1111
[verse]
1212
'git am' [--signoff] [--keep] [--utf8 | --no-utf8]
1313
[--3way] [--interactive] [--committer-date-is-author-date]
14-
[--ignore-date]
14+
[--ignore-date] [--ignore-space-change | --ignore-whitespace]
1515
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
1616
[--reject] [-q | --quiet]
1717
[<mbox> | <Maildir>...]
@@ -65,6 +65,9 @@ default. You can use `--no-utf8` to override this.
6565
it is supposed to apply to and we have those blobs
6666
available locally.
6767

68+
--ignore-date::
69+
--ignore-space-change::
70+
--ignore-whitespace::
6871
--whitespace=<option>::
6972
-C<n>::
7073
-p<n>::

Documentation/git-apply.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ SYNOPSIS
1313
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
1414
[--allow-binary-replacement | --binary] [--reject] [-z]
1515
[-pNUM] [-CNUM] [--inaccurate-eof] [--recount] [--cached]
16+
[--ignore-space-change | --ignore-whitespace ]
1617
[--whitespace=<nowarn|warn|fix|error|error-all>]
1718
[--exclude=PATH] [--include=PATH] [--directory=<root>]
1819
[--verbose] [<patch>...]
@@ -149,6 +150,14 @@ patch to each path is used. A patch to a path that does not match any
149150
include/exclude pattern is used by default if there is no include pattern
150151
on the command line, and ignored if there is any include pattern.
151152

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+
152161
--whitespace=<action>::
153162
When applying a patch, detect a new or modified line that has
154163
whitespace errors. What are considered whitespace errors is
@@ -205,6 +214,10 @@ running `git apply --directory=modules/git-gui`.
205214
Configuration
206215
-------------
207216

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.
208221
apply.whitespace::
209222
When no `--whitespace` flag is given from the command
210223
line, this configuration item is used as the default.

Documentation/git-rebase.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,9 @@ OPTIONS
268268
exit with the message "Current branch is up to date" in such a
269269
situation.
270270

271+
--ignore-whitespace::
271272
--whitespace=<option>::
272-
This flag is passed to the 'git-apply' program
273+
These flag are passed to the 'git-apply' program
273274
(see linkgit:git-apply[1]) that applies the patch.
274275
Incompatible with the --interactive option.
275276

builtin-apply.c

Lines changed: 166 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,13 @@ static enum ws_error_action {
6161
static int whitespace_error;
6262
static int squelch_whitespace_errors = 5;
6363
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+
6471
static const char *patch_input_file;
6572
static const char *root;
6673
static int root_len;
@@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option)
97104
die("unrecognized whitespace option '%s'", option);
98105
}
99106

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+
100122
static void set_default_whitespace_mode(const char *whitespace_option)
101123
{
102124
if (!whitespace_option && !apply_default_whitespace)
@@ -214,6 +236,62 @@ static uint32_t hash_line(const char *cp, size_t len)
214236
return h;
215237
}
216238

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+
217295
static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
218296
{
219297
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)
15921670
}
15931671
}
15941672

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+
15951680
static void update_pre_post_images(struct image *preimage,
15961681
struct image *postimage,
15971682
char *buf,
1598-
size_t len)
1683+
size_t len, size_t postlen)
15991684
{
16001685
int i, ctx;
16011686
char *new, *old, *fixed;
@@ -1614,11 +1699,19 @@ static void update_pre_post_images(struct image *preimage,
16141699
*preimage = fixed_preimage;
16151700

16161701
/*
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.
16201709
*/
1621-
new = old = postimage->buf;
1710+
old = postimage->buf;
1711+
if (postlen)
1712+
new = postimage->buf = xmalloc(postlen);
1713+
else
1714+
new = old;
16221715
fixed = preimage->buf;
16231716
for (i = ctx = 0; i < postimage->nr; i++) {
16241717
size_t len = postimage->line[i].len;
@@ -1693,12 +1786,58 @@ static int match_fragment(struct image *img,
16931786
!memcmp(img->buf + try, preimage->buf, preimage->len))
16941787
return 1;
16951788

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+
16961833
if (ws_error_action != correct_ws_error)
16971834
return 0;
16981835

16991836
/*
17001837
* 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.
17021841
*/
17031842
fixed_buf = xmalloc(preimage->len + 1);
17041843
buf = fixed_buf;
@@ -1750,7 +1889,7 @@ static int match_fragment(struct image *img,
17501889
* hunk match. Update the context lines in the postimage.
17511890
*/
17521891
update_pre_post_images(preimage, postimage,
1753-
fixed_buf, buf - fixed_buf);
1892+
fixed_buf, buf - fixed_buf, 0);
17541893
return 1;
17551894

17561895
unmatch_exit:
@@ -3192,6 +3331,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
31923331
{
31933332
if (!strcmp(var, "apply.whitespace"))
31943333
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);
31953336
return git_default_config(var, value, cb);
31963337
}
31973338

@@ -3228,6 +3369,16 @@ static int option_parse_z(const struct option *opt,
32283369
return 0;
32293370
}
32303371

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+
32313382
static int option_parse_whitespace(const struct option *opt,
32323383
const char *arg, int unset)
32333384
{
@@ -3304,6 +3455,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33043455
{ OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
33053456
"detect new or modified lines that have whitespace errors",
33063457
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 },
33073464
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
33083465
"apply the patch in reverse"),
33093466
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
@@ -3328,6 +3485,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33283485
git_config(git_apply_config, NULL);
33293486
if (apply_default_whitespace)
33303487
parse_whitespace_option(apply_default_whitespace);
3488+
if (apply_default_ignorewhitespace)
3489+
parse_ignorewhitespace_option(apply_default_ignorewhitespace);
33313490

33323491
argc = parse_options(argc, argv, prefix, builtin_apply_options,
33333492
apply_usage, 0);

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ extern int log_all_ref_updates;
509509
extern int warn_ambiguous_refs;
510510
extern int shared_repository;
511511
extern const char *apply_default_whitespace;
512+
extern const char *apply_default_ignorewhitespace;
512513
extern int zlib_compression_level;
513514
extern int core_compression_level;
514515
extern int core_compression_seen;

contrib/completion/git-completion.bash

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,7 @@ _git_am ()
674674
--*)
675675
__gitcomp "
676676
--3way --committer-date-is-author-date --ignore-date
677+
--ignore-whitespace --ignore-space-change
677678
--interactive --keep --no-utf8 --signoff --utf8
678679
--whitespace=
679680
"
@@ -695,6 +696,7 @@ _git_apply ()
695696
--stat --numstat --summary --check --index
696697
--cached --index-info --reverse --reject --unidiff-zero
697698
--apply --no-add --exclude=
699+
--ignore-whitespace --ignore-space-change
698700
--whitespace= --inaccurate-eof --verbose
699701
"
700702
return
@@ -1536,6 +1538,7 @@ _git_config ()
15361538
__gitcomp "
15371539
add.ignore-errors
15381540
alias.
1541+
apply.ignorewhitespace
15391542
apply.whitespace
15401543
branch.autosetupmerge
15411544
branch.autosetuprebase

environment.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const char *git_commit_encoding;
2626
const char *git_log_output_encoding;
2727
int shared_repository = PERM_UMASK;
2828
const char *apply_default_whitespace;
29+
const char *apply_default_ignorewhitespace;
2930
int zlib_compression_level = Z_BEST_SPEED;
3031
int core_compression_level;
3132
int core_compression_seen;

git-am.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ s,signoff add a Signed-off-by line to the commit message
1616
u,utf8 recode into utf8 (default)
1717
k,keep pass -k flag to git-mailinfo
1818
whitespace= pass it through git-apply
19+
ignore-space-change pass it through git-apply
20+
ignore-whitespace pass it through git-apply
1921
directory= pass it through git-apply
2022
C= pass it through git-apply
2123
p= pass it through git-apply
@@ -303,7 +305,7 @@ do
303305
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
304306
--patch-format)
305307
shift ; patch_format="$1" ;;
306-
--reject)
308+
--reject|--ignore-whitespace|--ignore-space-change)
307309
git_apply_opt="$git_apply_opt $1" ;;
308310
--committer-date-is-author-date)
309311
committer_date_is_author_date=t ;;

0 commit comments

Comments
 (0)