Merge branch 'tb/apply-with-crlf'
authorJunio C Hamano <gitster@pobox.com>
Sun, 27 Aug 2017 05:55:05 +0000 (22:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sun, 27 Aug 2017 05:55:05 +0000 (22:55 -0700)
"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

apply.c
convert.c
convert.h
t/t4124-apply-ws-rule.sh

diff --git a/apply.c b/apply.c
index 956f56a..8666621 100644 (file)
--- a/apply.c
+++ b/apply.c
@@ -219,6 +219,7 @@ struct patch {
        unsigned int recount:1;
        unsigned int conflicted_threeway:1;
        unsigned int direct_to_threeway:1;
+       unsigned int crlf_in_old:1;
        struct fragment *fragments;
        char *result;
        size_t resultsize;
@@ -1661,6 +1662,19 @@ static void check_whitespace(struct apply_state *state,
        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
  * fragment separately, since the only way to know the difference
@@ -1711,11 +1725,14 @@ static int parse_fragment(struct apply_state *state,
                        if (!deleted && !added)
                                leading++;
                        trailing++;
+                       check_old_for_crlf(patch, line, len);
                        if (!state->apply_in_reverse &&
                            state->ws_error_action == correct_ws_error)
                                check_whitespace(state, line, len, patch->ws_rule);
                        break;
                case '-':
+                       if (!state->apply_in_reverse)
+                               check_old_for_crlf(patch, line, len);
                        if (state->apply_in_reverse &&
                            state->ws_error_action != nowarn_ws_error)
                                check_whitespace(state, line, len, patch->ws_rule);
@@ -1724,6 +1741,8 @@ static int parse_fragment(struct apply_state *state,
                        trailing = 0;
                        break;
                case '+':
+                       if (state->apply_in_reverse)
+                               check_old_for_crlf(patch, line, len);
                        if (!state->apply_in_reverse &&
                            state->ws_error_action != nowarn_ws_error)
                                check_whitespace(state, line, len, patch->ws_rule);
@@ -2266,8 +2285,11 @@ static void show_stats(struct apply_state *state, struct patch *patch)
                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) {
        case S_IFLNK:
                if (strbuf_readlink(buf, path, st->st_size) < 0)
@@ -2276,7 +2298,15 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
        case S_IFREG:
                if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
                        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;
        default:
                return -1;
@@ -3379,6 +3409,7 @@ static int load_patch_target(struct apply_state *state,
                             struct strbuf *buf,
                             const struct cache_entry *ce,
                             struct stat *st,
+                            struct patch *patch,
                             const char *name,
                             unsigned expected_mode)
 {
@@ -3394,7 +3425,7 @@ static int load_patch_target(struct apply_state *state,
                } else if (has_symlink_leading_path(name, strlen(name))) {
                        return error(_("reading from '%s' beyond a symbolic link"), name);
                } else {
-                       if (read_old_data(st, name, buf))
+                       if (read_old_data(st, patch, name, buf))
                                return error(_("failed to read %s"), name);
                }
        }
@@ -3427,7 +3458,7 @@ static int load_preimage(struct apply_state *state,
                /* We have a patched copy in memory; use that. */
                strbuf_add(&buf, previous->result, previous->resultsize);
        } else {
-               status = load_patch_target(state, &buf, ce, st,
+               status = load_patch_target(state, &buf, ce, st, patch,
                                           patch->old_name, patch->old_mode);
                if (status < 0)
                        return status;
@@ -3515,7 +3546,7 @@ static int load_current(struct apply_state *state,
        if (verify_index_match(ce, &st))
                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)
                return status;
        else if (status)
index 1012462..c5f0b21 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -1132,10 +1132,12 @@ int convert_to_git(const struct index_state *istate,
                src = dst->buf;
                len = dst->len;
        }
-       ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe);
-       if (ret && dst) {
-               src = dst->buf;
-               len = dst->len;
+       if (checksafe != SAFE_CRLF_KEEP_CRLF) {
+               ret |= crlf_to_git(istate, path, src, len, dst, ca.crlf_action, checksafe);
+               if (ret && dst) {
+                       src = dst->buf;
+                       len = dst->len;
+               }
        }
        return ret | ident_to_git(path, src, len, dst, ca.ident);
 }
index 6b06144..4f2da22 100644 (file)
--- a/convert.h
+++ b/convert.h
@@ -12,7 +12,8 @@ enum safe_crlf {
        SAFE_CRLF_FALSE = 0,
        SAFE_CRLF_FAIL = 1,
        SAFE_CRLF_WARN = 2,
-       SAFE_CRLF_RENORMALIZE = 3
+       SAFE_CRLF_RENORMALIZE = 3,
+       SAFE_CRLF_KEEP_CRLF = 4
 };
 
 extern enum safe_crlf safe_crlf;
index d350065..4fc27c5 100755 (executable)
@@ -467,21 +467,42 @@ test_expect_success 'same, but with CR-LF line endings && cr-at-eol set' '
        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 &&
        printf "a\r\n" >one &&
+       cp one save-one &&
+       git add one &&
        printf "b\r\n" >>one &&
-       printf "c\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 'CR-LF line endings && change line && text=auto' '
+       printf "a\r\n" >one &&
        cp one save-one &&
-       printf "                 \r\n" >>one &&
        git add one &&
+       printf "b\r\n" >one &&
        cp one expect &&
-       printf "d\r\n" >>one &&
        git diff -- one >patch &&
        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 '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
 '