Merge branch 'tb/apply-with-crlf' into maint

"git apply" that is used as a better "patch -p1" failed to apply a
taken from a file with CRLF line endings to a file with CRLF line
endings.  The root cause was because it misused convert_to_git()
that tried to do "safe-crlf" processing by looking at the index
entry at the same path, which is a nonsense---in that mode, "apply"
is not working on the data in (or derived from) the index at all.
This has been fixed.

* tb/apply-with-crlf:
  apply: file commited with CRLF should roundtrip diff and apply
  convert: add SAFE_CRLF_KEEP_CRLF
This commit is contained in:
Junio C Hamano 2017-09-10 17:02:55 +09:00
Родитель 27015b4f95 c24f3abace
Коммит 648a50a08a
4 изменённых файлов: 72 добавлений и 17 удалений

41
apply.c
Просмотреть файл

@ -220,6 +220,7 @@ struct patch {
unsigned int recount:1; unsigned int recount:1;
unsigned int conflicted_threeway:1; unsigned int conflicted_threeway:1;
unsigned int direct_to_threeway:1; unsigned int direct_to_threeway:1;
unsigned int crlf_in_old:1;
struct fragment *fragments; struct fragment *fragments;
char *result; char *result;
size_t resultsize; size_t resultsize;
@ -1662,6 +1663,19 @@ static void check_whitespace(struct apply_state *state,
record_ws_error(state, result, line + 1, len - 2, state->linenr); record_ws_error(state, result, line + 1, len - 2, state->linenr);
} }
/*
* Check if the patch has context lines with CRLF or
* the patch wants to remove lines with CRLF.
*/
static void check_old_for_crlf(struct patch *patch, const char *line, int len)
{
if (len >= 2 && line[len-1] == '\n' && line[len-2] == '\r') {
patch->ws_rule |= WS_CR_AT_EOL;
patch->crlf_in_old = 1;
}
}
/* /*
* Parse a unified diff. Note that this really needs to parse each * Parse a unified diff. Note that this really needs to parse each
* fragment separately, since the only way to know the difference * fragment separately, since the only way to know the difference
@ -1712,11 +1726,14 @@ static int parse_fragment(struct apply_state *state,
if (!deleted && !added) if (!deleted && !added)
leading++; leading++;
trailing++; trailing++;
check_old_for_crlf(patch, line, len);
if (!state->apply_in_reverse && if (!state->apply_in_reverse &&
state->ws_error_action == correct_ws_error) state->ws_error_action == correct_ws_error)
check_whitespace(state, line, len, patch->ws_rule); check_whitespace(state, line, len, patch->ws_rule);
break; break;
case '-': case '-':
if (!state->apply_in_reverse)
check_old_for_crlf(patch, line, len);
if (state->apply_in_reverse && if (state->apply_in_reverse &&
state->ws_error_action != nowarn_ws_error) state->ws_error_action != nowarn_ws_error)
check_whitespace(state, line, len, patch->ws_rule); check_whitespace(state, line, len, patch->ws_rule);
@ -1725,6 +1742,8 @@ static int parse_fragment(struct apply_state *state,
trailing = 0; trailing = 0;
break; break;
case '+': case '+':
if (state->apply_in_reverse)
check_old_for_crlf(patch, line, len);
if (!state->apply_in_reverse && if (!state->apply_in_reverse &&
state->ws_error_action != nowarn_ws_error) state->ws_error_action != nowarn_ws_error)
check_whitespace(state, line, len, patch->ws_rule); check_whitespace(state, line, len, patch->ws_rule);
@ -2268,8 +2287,11 @@ static void show_stats(struct apply_state *state, struct patch *patch)
add, pluses, del, minuses); add, pluses, del, minuses);
} }
static int read_old_data(struct stat *st, const char *path, struct strbuf *buf) static int read_old_data(struct stat *st, struct patch *patch,
const char *path, struct strbuf *buf)
{ {
enum safe_crlf safe_crlf = patch->crlf_in_old ?
SAFE_CRLF_KEEP_CRLF : SAFE_CRLF_RENORMALIZE;
switch (st->st_mode & S_IFMT) { switch (st->st_mode & S_IFMT) {
case S_IFLNK: case S_IFLNK:
if (strbuf_readlink(buf, path, st->st_size) < 0) if (strbuf_readlink(buf, path, st->st_size) < 0)
@ -2278,7 +2300,15 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
case S_IFREG: case S_IFREG:
if (strbuf_read_file(buf, path, st->st_size) != st->st_size) if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
return error(_("unable to open or read %s"), path); return error(_("unable to open or read %s"), path);
convert_to_git(&the_index, path, buf->buf, buf->len, buf, 0); /*
* "git apply" without "--index/--cached" should never look
* at the index; the target file may not have been added to
* the index yet, and we may not even be in any Git repository.
* Pass NULL to convert_to_git() to stress this; the function
* should never look at the index when explicit crlf option
* is given.
*/
convert_to_git(NULL, path, buf->buf, buf->len, buf, safe_crlf);
return 0; return 0;
default: default:
return -1; return -1;
@ -3381,6 +3411,7 @@ static int load_patch_target(struct apply_state *state,
struct strbuf *buf, struct strbuf *buf,
const struct cache_entry *ce, const struct cache_entry *ce,
struct stat *st, struct stat *st,
struct patch *patch,
const char *name, const char *name,
unsigned expected_mode) unsigned expected_mode)
{ {
@ -3396,7 +3427,7 @@ static int load_patch_target(struct apply_state *state,
} else if (has_symlink_leading_path(name, strlen(name))) { } else if (has_symlink_leading_path(name, strlen(name))) {
return error(_("reading from '%s' beyond a symbolic link"), name); return error(_("reading from '%s' beyond a symbolic link"), name);
} else { } else {
if (read_old_data(st, name, buf)) if (read_old_data(st, patch, name, buf))
return error(_("failed to read %s"), name); return error(_("failed to read %s"), name);
} }
} }
@ -3429,7 +3460,7 @@ static int load_preimage(struct apply_state *state,
/* We have a patched copy in memory; use that. */ /* We have a patched copy in memory; use that. */
strbuf_add(&buf, previous->result, previous->resultsize); strbuf_add(&buf, previous->result, previous->resultsize);
} else { } else {
status = load_patch_target(state, &buf, ce, st, status = load_patch_target(state, &buf, ce, st, patch,
patch->old_name, patch->old_mode); patch->old_name, patch->old_mode);
if (status < 0) if (status < 0)
return status; return status;
@ -3517,7 +3548,7 @@ static int load_current(struct apply_state *state,
if (verify_index_match(ce, &st)) if (verify_index_match(ce, &st))
return error(_("%s: does not match index"), name); return error(_("%s: does not match index"), name);
status = load_patch_target(state, &buf, ce, &st, name, mode); status = load_patch_target(state, &buf, ce, &st, patch, name, mode);
if (status < 0) if (status < 0)
return status; return status;
else if (status) else if (status)

Просмотреть файл

@ -1133,10 +1133,12 @@ int convert_to_git(const struct index_state *istate,
src = dst->buf; src = dst->buf;
len = dst->len; len = dst->len;
} }
ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe); if (checksafe != SAFE_CRLF_KEEP_CRLF) {
if (ret && dst) { ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe);
src = dst->buf; if (ret && dst) {
len = dst->len; src = dst->buf;
len = dst->len;
}
} }
return ret | ident_to_git(path, src, len, dst, ca.ident); return ret | ident_to_git(path, src, len, dst, ca.ident);
} }

Просмотреть файл

@ -12,7 +12,8 @@ enum safe_crlf {
SAFE_CRLF_FALSE = 0, SAFE_CRLF_FALSE = 0,
SAFE_CRLF_FAIL = 1, SAFE_CRLF_FAIL = 1,
SAFE_CRLF_WARN = 2, SAFE_CRLF_WARN = 2,
SAFE_CRLF_RENORMALIZE = 3 SAFE_CRLF_RENORMALIZE = 3,
SAFE_CRLF_KEEP_CRLF = 4
}; };
extern enum safe_crlf safe_crlf; extern enum safe_crlf safe_crlf;

Просмотреть файл

@ -467,21 +467,42 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
test_cmp one expect test_cmp one expect
' '
test_expect_success 'same, but with CR-LF line endings && cr-at-eol unset' ' test_expect_success 'CR-LF line endings && add line && text=auto' '
git config --unset core.whitespace && git config --unset core.whitespace &&
printf "a\r\n" >one && printf "a\r\n" >one &&
printf "b\r\n" >>one &&
printf "c\r\n" >>one &&
cp one save-one && cp one save-one &&
printf " \r\n" >>one &&
git add one && git add one &&
printf "b\r\n" >>one &&
cp one expect && cp one expect &&
printf "d\r\n" >>one &&
git diff -- one >patch && git diff -- one >patch &&
mv save-one one && mv save-one one &&
echo d >>expect && echo "one text=auto" >.gitattributes &&
git apply patch &&
test_cmp one expect
'
git apply --ignore-space-change --whitespace=fix patch && test_expect_success 'CR-LF line endings && change line && text=auto' '
printf "a\r\n" >one &&
cp one save-one &&
git add one &&
printf "b\r\n" >one &&
cp one expect &&
git diff -- one >patch &&
mv save-one one &&
echo "one text=auto" >.gitattributes &&
git apply patch &&
test_cmp one expect
'
test_expect_success 'LF in repo, CRLF in worktree && change line && text=auto' '
printf "a\n" >one &&
git add one &&
printf "b\r\n" >one &&
git diff -- one >patch &&
printf "a\r\n" >one &&
echo "one text=auto" >.gitattributes &&
git -c core.eol=CRLF apply patch &&
printf "b\r\n" >expect &&
test_cmp one expect test_cmp one expect
' '