Skip to content

Commit 5e092b5

Browse files
committed
Merge branch 'gb/apply-ignore-whitespace'
* gb/apply-ignore-whitespace: git apply: option to ignore whitespace differences
2 parents bcd45e2 + 86c91f9 commit 5e092b5

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);
@@ -1672,10 +1750,17 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
16721750
}
16731751
}
16741752

1753+
/*
1754+
* Update the preimage, and the common lines in postimage,
1755+
* from buffer buf of length len. If postlen is 0 the postimage
1756+
* is updated in place, otherwise it's updated on a new buffer
1757+
* of length postlen
1758+
*/
1759+
16751760
static void update_pre_post_images(struct image *preimage,
16761761
struct image *postimage,
16771762
char *buf,
1678-
size_t len)
1763+
size_t len, size_t postlen)
16791764
{
16801765
int i, ctx;
16811766
char *new, *old, *fixed;
@@ -1694,11 +1779,19 @@ static void update_pre_post_images(struct image *preimage,
16941779
*preimage = fixed_preimage;
16951780

16961781
/*
1697-
* Adjust the common context lines in postimage, in place.
1698-
* This is possible because whitespace fixing does not make
1699-
* the string grow.
1782+
* Adjust the common context lines in postimage. This can be
1783+
* done in-place when we are just doing whitespace fixing,
1784+
* which does not make the string grow, but needs a new buffer
1785+
* when ignoring whitespace causes the update, since in this case
1786+
* we could have e.g. tabs converted to multiple spaces.
1787+
* We trust the caller to tell us if the update can be done
1788+
* in place (postlen==0) or not.
17001789
*/
1701-
new = old = postimage->buf;
1790+
old = postimage->buf;
1791+
if (postlen)
1792+
new = postimage->buf = xmalloc(postlen);
1793+
else
1794+
new = old;
17021795
fixed = preimage->buf;
17031796
for (i = ctx = 0; i < postimage->nr; i++) {
17041797
size_t len = postimage->line[i].len;
@@ -1773,12 +1866,58 @@ static int match_fragment(struct image *img,
17731866
!memcmp(img->buf + try, preimage->buf, preimage->len))
17741867
return 1;
17751868

1869+
/*
1870+
* No exact match. If we are ignoring whitespace, run a line-by-line
1871+
* fuzzy matching. We collect all the line length information because
1872+
* we need it to adjust whitespace if we match.
1873+
*/
1874+
if (ws_ignore_action == ignore_ws_change) {
1875+
size_t imgoff = 0;
1876+
size_t preoff = 0;
1877+
size_t postlen = postimage->len;
1878+
size_t imglen[preimage->nr];
1879+
for (i = 0; i < preimage->nr; i++) {
1880+
size_t prelen = preimage->line[i].len;
1881+
1882+
imglen[i] = img->line[try_lno+i].len;
1883+
if (!fuzzy_matchlines(
1884+
img->buf + try + imgoff, imglen[i],
1885+
preimage->buf + preoff, prelen))
1886+
return 0;
1887+
if (preimage->line[i].flag & LINE_COMMON)
1888+
postlen += imglen[i] - prelen;
1889+
imgoff += imglen[i];
1890+
preoff += prelen;
1891+
}
1892+
1893+
/*
1894+
* Ok, the preimage matches with whitespace fuzz. Update it and
1895+
* the common postimage lines to use the same whitespace as the
1896+
* target. imgoff now holds the true length of the target that
1897+
* matches the preimage, and we need to update the line lengths
1898+
* of the preimage to match the target ones.
1899+
*/
1900+
fixed_buf = xmalloc(imgoff);
1901+
memcpy(fixed_buf, img->buf + try, imgoff);
1902+
for (i = 0; i < preimage->nr; i++)
1903+
preimage->line[i].len = imglen[i];
1904+
1905+
/*
1906+
* Update the preimage buffer and the postimage context lines.
1907+
*/
1908+
update_pre_post_images(preimage, postimage,
1909+
fixed_buf, imgoff, postlen);
1910+
return 1;
1911+
}
1912+
17761913
if (ws_error_action != correct_ws_error)
17771914
return 0;
17781915

17791916
/*
17801917
* The hunk does not apply byte-by-byte, but the hash says
1781-
* it might with whitespace fuzz.
1918+
* it might with whitespace fuzz. We haven't been asked to
1919+
* ignore whitespace, we were asked to correct whitespace
1920+
* errors, so let's try matching after whitespace correction.
17821921
*/
17831922
fixed_buf = xmalloc(preimage->len + 1);
17841923
buf = fixed_buf;
@@ -1830,7 +1969,7 @@ static int match_fragment(struct image *img,
18301969
* hunk match. Update the context lines in the postimage.
18311970
*/
18321971
update_pre_post_images(preimage, postimage,
1833-
fixed_buf, buf - fixed_buf);
1972+
fixed_buf, buf - fixed_buf, 0);
18341973
return 1;
18351974

18361975
unmatch_exit:
@@ -3272,6 +3411,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
32723411
{
32733412
if (!strcmp(var, "apply.whitespace"))
32743413
return git_config_string(&apply_default_whitespace, var, value);
3414+
else if (!strcmp(var, "apply.ignorewhitespace"))
3415+
return git_config_string(&apply_default_ignorewhitespace, var, value);
32753416
return git_default_config(var, value, cb);
32763417
}
32773418

@@ -3308,6 +3449,16 @@ static int option_parse_z(const struct option *opt,
33083449
return 0;
33093450
}
33103451

3452+
static int option_parse_space_change(const struct option *opt,
3453+
const char *arg, int unset)
3454+
{
3455+
if (unset)
3456+
ws_ignore_action = ignore_ws_none;
3457+
else
3458+
ws_ignore_action = ignore_ws_change;
3459+
return 0;
3460+
}
3461+
33113462
static int option_parse_whitespace(const struct option *opt,
33123463
const char *arg, int unset)
33133464
{
@@ -3384,6 +3535,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
33843535
{ OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
33853536
"detect new or modified lines that have whitespace errors",
33863537
0, option_parse_whitespace },
3538+
{ OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
3539+
"ignore changes in whitespace when finding context",
3540+
PARSE_OPT_NOARG, option_parse_space_change },
3541+
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
3542+
"ignore changes in whitespace when finding context",
3543+
PARSE_OPT_NOARG, option_parse_space_change },
33873544
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
33883545
"apply the patch in reverse"),
33893546
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
@@ -3408,6 +3565,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
34083565
git_config(git_apply_config, NULL);
34093566
if (apply_default_whitespace)
34103567
parse_whitespace_option(apply_default_whitespace);
3568+
if (apply_default_ignorewhitespace)
3569+
parse_ignorewhitespace_option(apply_default_ignorewhitespace);
34113570

34123571
argc = parse_options(argc, argv, prefix, builtin_apply_options,
34133572
apply_usage, 0);

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,7 @@ extern int log_all_ref_updates;
512512
extern int warn_ambiguous_refs;
513513
extern int shared_repository;
514514
extern const char *apply_default_whitespace;
515+
extern const char *apply_default_ignorewhitespace;
515516
extern int zlib_compression_level;
516517
extern int core_compression_level;
517518
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
@@ -1537,6 +1539,7 @@ _git_config ()
15371539
__gitcomp "
15381540
add.ignore-errors
15391541
alias.
1542+
apply.ignorewhitespace
15401543
apply.whitespace
15411544
branch.autosetupmerge
15421545
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
@@ -327,7 +329,7 @@ do
327329
git_apply_opt="$git_apply_opt $(sq "$1$2")"; shift ;;
328330
--patch-format)
329331
shift ; patch_format="$1" ;;
330-
--reject)
332+
--reject|--ignore-whitespace|--ignore-space-change)
331333
git_apply_opt="$git_apply_opt $1" ;;
332334
--committer-date-is-author-date)
333335
committer_date_is_author_date=t ;;

0 commit comments

Comments
 (0)