Merge branch 'gb/apply-ignore-whitespace'
* gb/apply-ignore-whitespace: git apply: option to ignore whitespace differences
This commit is contained in:
173
builtin-apply.c
173
builtin-apply.c
@ -61,6 +61,13 @@ static enum ws_error_action {
|
||||
static int whitespace_error;
|
||||
static int squelch_whitespace_errors = 5;
|
||||
static int applied_after_fixing_ws;
|
||||
|
||||
static enum ws_ignore {
|
||||
ignore_ws_none,
|
||||
ignore_ws_change,
|
||||
} ws_ignore_action = ignore_ws_none;
|
||||
|
||||
|
||||
static const char *patch_input_file;
|
||||
static const char *root;
|
||||
static int root_len;
|
||||
@ -97,6 +104,21 @@ static void parse_whitespace_option(const char *option)
|
||||
die("unrecognized whitespace option '%s'", option);
|
||||
}
|
||||
|
||||
static void parse_ignorewhitespace_option(const char *option)
|
||||
{
|
||||
if (!option || !strcmp(option, "no") ||
|
||||
!strcmp(option, "false") || !strcmp(option, "never") ||
|
||||
!strcmp(option, "none")) {
|
||||
ws_ignore_action = ignore_ws_none;
|
||||
return;
|
||||
}
|
||||
if (!strcmp(option, "change")) {
|
||||
ws_ignore_action = ignore_ws_change;
|
||||
return;
|
||||
}
|
||||
die("unrecognized whitespace ignore option '%s'", option);
|
||||
}
|
||||
|
||||
static void set_default_whitespace_mode(const char *whitespace_option)
|
||||
{
|
||||
if (!whitespace_option && !apply_default_whitespace)
|
||||
@ -214,6 +236,62 @@ static uint32_t hash_line(const char *cp, size_t len)
|
||||
return h;
|
||||
}
|
||||
|
||||
/*
|
||||
* Compare lines s1 of length n1 and s2 of length n2, ignoring
|
||||
* whitespace difference. Returns 1 if they match, 0 otherwise
|
||||
*/
|
||||
static int fuzzy_matchlines(const char *s1, size_t n1,
|
||||
const char *s2, size_t n2)
|
||||
{
|
||||
const char *last1 = s1 + n1 - 1;
|
||||
const char *last2 = s2 + n2 - 1;
|
||||
int result = 0;
|
||||
|
||||
if (n1 < 0 || n2 < 0)
|
||||
return 0;
|
||||
|
||||
/* ignore line endings */
|
||||
while ((*last1 == '\r') || (*last1 == '\n'))
|
||||
last1--;
|
||||
while ((*last2 == '\r') || (*last2 == '\n'))
|
||||
last2--;
|
||||
|
||||
/* skip leading whitespace */
|
||||
while (isspace(*s1) && (s1 <= last1))
|
||||
s1++;
|
||||
while (isspace(*s2) && (s2 <= last2))
|
||||
s2++;
|
||||
/* early return if both lines are empty */
|
||||
if ((s1 > last1) && (s2 > last2))
|
||||
return 1;
|
||||
while (!result) {
|
||||
result = *s1++ - *s2++;
|
||||
/*
|
||||
* Skip whitespace inside. We check for whitespace on
|
||||
* both buffers because we don't want "a b" to match
|
||||
* "ab"
|
||||
*/
|
||||
if (isspace(*s1) && isspace(*s2)) {
|
||||
while (isspace(*s1) && s1 <= last1)
|
||||
s1++;
|
||||
while (isspace(*s2) && s2 <= last2)
|
||||
s2++;
|
||||
}
|
||||
/*
|
||||
* If we reached the end on one side only,
|
||||
* lines don't match
|
||||
*/
|
||||
if (
|
||||
((s2 > last2) && (s1 <= last1)) ||
|
||||
((s1 > last1) && (s2 <= last2)))
|
||||
return 0;
|
||||
if ((s1 > last1) && (s2 > last2))
|
||||
break;
|
||||
}
|
||||
|
||||
return !result;
|
||||
}
|
||||
|
||||
static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the preimage, and the common lines in postimage,
|
||||
* from buffer buf of length len. If postlen is 0 the postimage
|
||||
* is updated in place, otherwise it's updated on a new buffer
|
||||
* of length postlen
|
||||
*/
|
||||
|
||||
static void update_pre_post_images(struct image *preimage,
|
||||
struct image *postimage,
|
||||
char *buf,
|
||||
size_t len)
|
||||
size_t len, size_t postlen)
|
||||
{
|
||||
int i, ctx;
|
||||
char *new, *old, *fixed;
|
||||
@ -1694,11 +1779,19 @@ static void update_pre_post_images(struct image *preimage,
|
||||
*preimage = fixed_preimage;
|
||||
|
||||
/*
|
||||
* Adjust the common context lines in postimage, in place.
|
||||
* This is possible because whitespace fixing does not make
|
||||
* the string grow.
|
||||
* Adjust the common context lines in postimage. This can be
|
||||
* done in-place when we are just doing whitespace fixing,
|
||||
* which does not make the string grow, but needs a new buffer
|
||||
* when ignoring whitespace causes the update, since in this case
|
||||
* we could have e.g. tabs converted to multiple spaces.
|
||||
* We trust the caller to tell us if the update can be done
|
||||
* in place (postlen==0) or not.
|
||||
*/
|
||||
new = old = postimage->buf;
|
||||
old = postimage->buf;
|
||||
if (postlen)
|
||||
new = postimage->buf = xmalloc(postlen);
|
||||
else
|
||||
new = old;
|
||||
fixed = preimage->buf;
|
||||
for (i = ctx = 0; i < postimage->nr; i++) {
|
||||
size_t len = postimage->line[i].len;
|
||||
@ -1773,12 +1866,58 @@ static int match_fragment(struct image *img,
|
||||
!memcmp(img->buf + try, preimage->buf, preimage->len))
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* No exact match. If we are ignoring whitespace, run a line-by-line
|
||||
* fuzzy matching. We collect all the line length information because
|
||||
* we need it to adjust whitespace if we match.
|
||||
*/
|
||||
if (ws_ignore_action == ignore_ws_change) {
|
||||
size_t imgoff = 0;
|
||||
size_t preoff = 0;
|
||||
size_t postlen = postimage->len;
|
||||
size_t imglen[preimage->nr];
|
||||
for (i = 0; i < preimage->nr; i++) {
|
||||
size_t prelen = preimage->line[i].len;
|
||||
|
||||
imglen[i] = img->line[try_lno+i].len;
|
||||
if (!fuzzy_matchlines(
|
||||
img->buf + try + imgoff, imglen[i],
|
||||
preimage->buf + preoff, prelen))
|
||||
return 0;
|
||||
if (preimage->line[i].flag & LINE_COMMON)
|
||||
postlen += imglen[i] - prelen;
|
||||
imgoff += imglen[i];
|
||||
preoff += prelen;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ok, the preimage matches with whitespace fuzz. Update it and
|
||||
* the common postimage lines to use the same whitespace as the
|
||||
* target. imgoff now holds the true length of the target that
|
||||
* matches the preimage, and we need to update the line lengths
|
||||
* of the preimage to match the target ones.
|
||||
*/
|
||||
fixed_buf = xmalloc(imgoff);
|
||||
memcpy(fixed_buf, img->buf + try, imgoff);
|
||||
for (i = 0; i < preimage->nr; i++)
|
||||
preimage->line[i].len = imglen[i];
|
||||
|
||||
/*
|
||||
* Update the preimage buffer and the postimage context lines.
|
||||
*/
|
||||
update_pre_post_images(preimage, postimage,
|
||||
fixed_buf, imgoff, postlen);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (ws_error_action != correct_ws_error)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* The hunk does not apply byte-by-byte, but the hash says
|
||||
* it might with whitespace fuzz.
|
||||
* it might with whitespace fuzz. We haven't been asked to
|
||||
* ignore whitespace, we were asked to correct whitespace
|
||||
* errors, so let's try matching after whitespace correction.
|
||||
*/
|
||||
fixed_buf = xmalloc(preimage->len + 1);
|
||||
buf = fixed_buf;
|
||||
@ -1830,7 +1969,7 @@ static int match_fragment(struct image *img,
|
||||
* hunk match. Update the context lines in the postimage.
|
||||
*/
|
||||
update_pre_post_images(preimage, postimage,
|
||||
fixed_buf, buf - fixed_buf);
|
||||
fixed_buf, buf - fixed_buf, 0);
|
||||
return 1;
|
||||
|
||||
unmatch_exit:
|
||||
@ -3272,6 +3411,8 @@ static int git_apply_config(const char *var, const char *value, void *cb)
|
||||
{
|
||||
if (!strcmp(var, "apply.whitespace"))
|
||||
return git_config_string(&apply_default_whitespace, var, value);
|
||||
else if (!strcmp(var, "apply.ignorewhitespace"))
|
||||
return git_config_string(&apply_default_ignorewhitespace, var, value);
|
||||
return git_default_config(var, value, cb);
|
||||
}
|
||||
|
||||
@ -3308,6 +3449,16 @@ static int option_parse_z(const struct option *opt,
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int option_parse_space_change(const struct option *opt,
|
||||
const char *arg, int unset)
|
||||
{
|
||||
if (unset)
|
||||
ws_ignore_action = ignore_ws_none;
|
||||
else
|
||||
ws_ignore_action = ignore_ws_change;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int option_parse_whitespace(const struct option *opt,
|
||||
const char *arg, int unset)
|
||||
{
|
||||
@ -3384,6 +3535,12 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
|
||||
{ OPTION_CALLBACK, 0, "whitespace", &whitespace_option, "action",
|
||||
"detect new or modified lines that have whitespace errors",
|
||||
0, option_parse_whitespace },
|
||||
{ OPTION_CALLBACK, 0, "ignore-space-change", NULL, NULL,
|
||||
"ignore changes in whitespace when finding context",
|
||||
PARSE_OPT_NOARG, option_parse_space_change },
|
||||
{ OPTION_CALLBACK, 0, "ignore-whitespace", NULL, NULL,
|
||||
"ignore changes in whitespace when finding context",
|
||||
PARSE_OPT_NOARG, option_parse_space_change },
|
||||
OPT_BOOLEAN('R', "reverse", &apply_in_reverse,
|
||||
"apply the patch in reverse"),
|
||||
OPT_BOOLEAN(0, "unidiff-zero", &unidiff_zero,
|
||||
@ -3408,6 +3565,8 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
|
||||
git_config(git_apply_config, NULL);
|
||||
if (apply_default_whitespace)
|
||||
parse_whitespace_option(apply_default_whitespace);
|
||||
if (apply_default_ignorewhitespace)
|
||||
parse_ignorewhitespace_option(apply_default_ignorewhitespace);
|
||||
|
||||
argc = parse_options(argc, argv, prefix, builtin_apply_options,
|
||||
apply_usage, 0);
|
||||
|
Reference in New Issue
Block a user