Merge branch 'bc/reread-attributes-during-rebase'
authorJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2019 19:26:40 +0000 (12:26 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 9 Sep 2019 19:26:40 +0000 (12:26 -0700)
The "git am" based backend of "git rebase" ignored the result of
updating ".gitattributes" done in one step when replaying
subsequent steps.

* bc/reread-attributes-during-rebase:
am: reload .gitattributes after patching it
path: add a function to check for path suffix

1  2 
apply.c
t/t3400-rebase.sh
diff --combined apply.c
index cde95369bb3f3a9c763108fd91e1e48e0e455e29,2e11438667eabfa15aa1e7be8f359e2c6896237b..57a61f2881bed383862044413210b42abca18434
+++ b/apply.c
  #include "rerere.h"
  #include "apply.h"
  
 +struct gitdiff_data {
 +      struct strbuf *root;
 +      int linenr;
 +      int p_value;
 +};
 +
  static void git_apply_config(void)
  {
        git_config_get_string_const("apply.whitespace", &apply_default_whitespace);
@@@ -207,6 -201,40 +207,6 @@@ struct fragment 
  #define BINARY_DELTA_DEFLATED 1
  #define BINARY_LITERAL_DEFLATED 2
  
 -/*
 - * This represents a "patch" to a file, both metainfo changes
 - * such as creation/deletion, filemode and content changes represented
 - * as a series of fragments.
 - */
 -struct patch {
 -      char *new_name, *old_name, *def_name;
 -      unsigned int old_mode, new_mode;
 -      int is_new, is_delete;  /* -1 = unknown, 0 = false, 1 = true */
 -      int rejected;
 -      unsigned ws_rule;
 -      int lines_added, lines_deleted;
 -      int score;
 -      int extension_linenr; /* first line specifying delete/new/rename/copy */
 -      unsigned int is_toplevel_relative:1;
 -      unsigned int inaccurate_eof:1;
 -      unsigned int is_binary:1;
 -      unsigned int is_copy:1;
 -      unsigned int is_rename:1;
 -      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;
 -      char old_oid_prefix[GIT_MAX_HEXSZ + 1];
 -      char new_oid_prefix[GIT_MAX_HEXSZ + 1];
 -      struct patch *next;
 -
 -      /* three-way fallback result */
 -      struct object_id threeway_stage[3];
 -};
 -
  static void free_fragment_list(struct fragment *list)
  {
        while (list) {
@@@ -441,7 -469,7 +441,7 @@@ static char *squash_slash(char *name
        return name;
  }
  
 -static char *find_name_gnu(struct apply_state *state,
 +static char *find_name_gnu(struct strbuf *root,
                           const char *line,
                           int p_value)
  {
  
        /*
         * Proposed "new-style" GNU patch/diff format; see
 -       * http://marc.info/?l=git&m=112927316408690&w=2
 +       * https://public-inbox.org/git/7vll0wvb2a.fsf@assigned-by-dhcp.cox.net/
         */
        if (unquote_c_style(&name, line, NULL)) {
                strbuf_release(&name);
        }
  
        strbuf_remove(&name, 0, cp - name.buf);
 -      if (state->root.len)
 -              strbuf_insert(&name, 0, state->root.buf, state->root.len);
 +      if (root->len)
 +              strbuf_insert(&name, 0, root->buf, root->len);
        return squash_slash(strbuf_detach(&name, NULL));
  }
  
@@@ -631,7 -659,7 +631,7 @@@ static size_t diff_timestamp_len(const 
        return line + len - end;
  }
  
 -static char *find_name_common(struct apply_state *state,
 +static char *find_name_common(struct strbuf *root,
                              const char *line,
                              const char *def,
                              int p_value,
                        return squash_slash(xstrdup(def));
        }
  
 -      if (state->root.len) {
 -              char *ret = xstrfmt("%s%.*s", state->root.buf, len, start);
 +      if (root->len) {
 +              char *ret = xstrfmt("%s%.*s", root->buf, len, start);
                return squash_slash(ret);
        }
  
        return squash_slash(xmemdupz(start, len));
  }
  
 -static char *find_name(struct apply_state *state,
 +static char *find_name(struct strbuf *root,
                       const char *line,
                       char *def,
                       int p_value,
                       int terminate)
  {
        if (*line == '"') {
 -              char *name = find_name_gnu(state, line, p_value);
 +              char *name = find_name_gnu(root, line, p_value);
                if (name)
                        return name;
        }
  
 -      return find_name_common(state, line, def, p_value, NULL, terminate);
 +      return find_name_common(root, line, def, p_value, NULL, terminate);
  }
  
 -static char *find_name_traditional(struct apply_state *state,
 +static char *find_name_traditional(struct strbuf *root,
                                   const char *line,
                                   char *def,
                                   int p_value)
        size_t date_len;
  
        if (*line == '"') {
 -              char *name = find_name_gnu(state, line, p_value);
 +              char *name = find_name_gnu(root, line, p_value);
                if (name)
                        return name;
        }
        len = strchrnul(line, '\n') - line;
        date_len = diff_timestamp_len(line, len);
        if (!date_len)
 -              return find_name_common(state, line, def, p_value, NULL, TERM_TAB);
 +              return find_name_common(root, line, def, p_value, NULL, TERM_TAB);
        len -= date_len;
  
 -      return find_name_common(state, line, def, p_value, line + len, 0);
 +      return find_name_common(root, line, def, p_value, line + len, 0);
  }
  
  /*
@@@ -731,7 -759,7 +731,7 @@@ static int guess_p_value(struct apply_s
  
        if (is_dev_null(nameline))
                return -1;
 -      name = find_name_traditional(state, nameline, NULL, 0);
 +      name = find_name_traditional(&state->root, nameline, NULL, 0);
        if (!name)
                return -1;
        cp = strchr(name, '/');
@@@ -855,17 -883,17 +855,17 @@@ static int parse_traditional_patch(stru
        if (is_dev_null(first)) {
                patch->is_new = 1;
                patch->is_delete = 0;
 -              name = find_name_traditional(state, second, NULL, state->p_value);
 +              name = find_name_traditional(&state->root, second, NULL, state->p_value);
                patch->new_name = name;
        } else if (is_dev_null(second)) {
                patch->is_new = 0;
                patch->is_delete = 1;
 -              name = find_name_traditional(state, first, NULL, state->p_value);
 +              name = find_name_traditional(&state->root, first, NULL, state->p_value);
                patch->old_name = name;
        } else {
                char *first_name;
 -              first_name = find_name_traditional(state, first, NULL, state->p_value);
 -              name = find_name_traditional(state, second, first_name, state->p_value);
 +              first_name = find_name_traditional(&state->root, first, NULL, state->p_value);
 +              name = find_name_traditional(&state->root, second, first_name, state->p_value);
                free(first_name);
                if (has_epoch_timestamp(first)) {
                        patch->is_new = 1;
        return 0;
  }
  
 -static int gitdiff_hdrend(struct apply_state *state,
 +static int gitdiff_hdrend(struct gitdiff_data *state,
                          const char *line,
                          struct patch *patch)
  {
  #define DIFF_OLD_NAME 0
  #define DIFF_NEW_NAME 1
  
 -static int gitdiff_verify_name(struct apply_state *state,
 +static int gitdiff_verify_name(struct gitdiff_data *state,
                               const char *line,
                               int isnull,
                               char **name,
                               int side)
  {
        if (!*name && !isnull) {
 -              *name = find_name(state, line, NULL, state->p_value, TERM_TAB);
 +              *name = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
                return 0;
        }
  
                if (isnull)
                        return error(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
                                     *name, state->linenr);
 -              another = find_name(state, line, NULL, state->p_value, TERM_TAB);
 +              another = find_name(state->root, line, NULL, state->p_value, TERM_TAB);
                if (!another || strcmp(another, *name)) {
                        free(another);
                        return error((side == DIFF_NEW_NAME) ?
        return 0;
  }
  
 -static int gitdiff_oldname(struct apply_state *state,
 +static int gitdiff_oldname(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
                                   DIFF_OLD_NAME);
  }
  
 -static int gitdiff_newname(struct apply_state *state,
 +static int gitdiff_newname(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
@@@ -964,21 -992,21 +964,21 @@@ static int parse_mode_line(const char *
        return 0;
  }
  
 -static int gitdiff_oldmode(struct apply_state *state,
 +static int gitdiff_oldmode(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
        return parse_mode_line(line, state->linenr, &patch->old_mode);
  }
  
 -static int gitdiff_newmode(struct apply_state *state,
 +static int gitdiff_newmode(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
        return parse_mode_line(line, state->linenr, &patch->new_mode);
  }
  
 -static int gitdiff_delete(struct apply_state *state,
 +static int gitdiff_delete(struct gitdiff_data *state,
                          const char *line,
                          struct patch *patch)
  {
        return gitdiff_oldmode(state, line, patch);
  }
  
 -static int gitdiff_newfile(struct apply_state *state,
 +static int gitdiff_newfile(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
        return gitdiff_newmode(state, line, patch);
  }
  
 -static int gitdiff_copysrc(struct apply_state *state,
 +static int gitdiff_copysrc(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
        patch->is_copy = 1;
        free(patch->old_name);
 -      patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
 +      patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
  }
  
 -static int gitdiff_copydst(struct apply_state *state,
 +static int gitdiff_copydst(struct gitdiff_data *state,
                           const char *line,
                           struct patch *patch)
  {
        patch->is_copy = 1;
        free(patch->new_name);
 -      patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
 +      patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
  }
  
 -static int gitdiff_renamesrc(struct apply_state *state,
 +static int gitdiff_renamesrc(struct gitdiff_data *state,
                             const char *line,
                             struct patch *patch)
  {
        patch->is_rename = 1;
        free(patch->old_name);
 -      patch->old_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
 +      patch->old_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
  }
  
 -static int gitdiff_renamedst(struct apply_state *state,
 +static int gitdiff_renamedst(struct gitdiff_data *state,
                             const char *line,
                             struct patch *patch)
  {
        patch->is_rename = 1;
        free(patch->new_name);
 -      patch->new_name = find_name(state, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
 +      patch->new_name = find_name(state->root, line, NULL, state->p_value ? state->p_value - 1 : 0, 0);
        return 0;
  }
  
 -static int gitdiff_similarity(struct apply_state *state,
 +static int gitdiff_similarity(struct gitdiff_data *state,
                              const char *line,
                              struct patch *patch)
  {
        return 0;
  }
  
 -static int gitdiff_dissimilarity(struct apply_state *state,
 +static int gitdiff_dissimilarity(struct gitdiff_data *state,
                                 const char *line,
                                 struct patch *patch)
  {
        return 0;
  }
  
 -static int gitdiff_index(struct apply_state *state,
 +static int gitdiff_index(struct gitdiff_data *state,
                         const char *line,
                         struct patch *patch)
  {
   * This is normal for a diff that doesn't change anything: we'll fall through
   * into the next diff. Tell the parser to break out.
   */
 -static int gitdiff_unrecognized(struct apply_state *state,
 +static int gitdiff_unrecognized(struct gitdiff_data *state,
                                const char *line,
                                struct patch *patch)
  {
   * Skip p_value leading components from "line"; as we do not accept
   * absolute paths, return NULL in that case.
   */
 -static const char *skip_tree_prefix(struct apply_state *state,
 +static const char *skip_tree_prefix(int p_value,
                                    const char *line,
                                    int llen)
  {
        int nslash;
        int i;
  
 -      if (!state->p_value)
 +      if (!p_value)
                return (llen && line[0] == '/') ? NULL : line;
  
 -      nslash = state->p_value;
 +      nslash = p_value;
        for (i = 0; i < llen; i++) {
                int ch = line[i];
                if (ch == '/' && --nslash <= 0)
   * creation or deletion of an empty file.  In any of these cases,
   * both sides are the same name under a/ and b/ respectively.
   */
 -static char *git_header_name(struct apply_state *state,
 +static char *git_header_name(int p_value,
                             const char *line,
                             int llen)
  {
                        goto free_and_fail1;
  
                /* strip the a/b prefix including trailing slash */
 -              cp = skip_tree_prefix(state, first.buf, first.len);
 +              cp = skip_tree_prefix(p_value, first.buf, first.len);
                if (!cp)
                        goto free_and_fail1;
                strbuf_remove(&first, 0, cp - first.buf);
                if (*second == '"') {
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail1;
 -                      cp = skip_tree_prefix(state, sp.buf, sp.len);
 +                      cp = skip_tree_prefix(p_value, sp.buf, sp.len);
                        if (!cp)
                                goto free_and_fail1;
                        /* They must match, otherwise ignore */
                }
  
                /* unquoted second */
 -              cp = skip_tree_prefix(state, second, line + llen - second);
 +              cp = skip_tree_prefix(p_value, second, line + llen - second);
                if (!cp)
                        goto free_and_fail1;
                if (line + llen - cp != first.len ||
        }
  
        /* unquoted first name */
 -      name = skip_tree_prefix(state, line, llen);
 +      name = skip_tree_prefix(p_value, line, llen);
        if (!name)
                return NULL;
  
                        if (unquote_c_style(&sp, second, NULL))
                                goto free_and_fail2;
  
 -                      np = skip_tree_prefix(state, sp.buf, sp.len);
 +                      np = skip_tree_prefix(p_value, sp.buf, sp.len);
                        if (!np)
                                goto free_and_fail2;
  
                         */
                        if (!name[len + 1])
                                return NULL; /* no postimage name */
 -                      second = skip_tree_prefix(state, name + len + 1,
 +                      second = skip_tree_prefix(p_value, name + len + 1,
                                                  line_len - (len + 1));
                        if (!second)
                                return NULL;
        }
  }
  
 -static int check_header_line(struct apply_state *state, struct patch *patch)
 +static int check_header_line(int linenr, struct patch *patch)
  {
        int extensions = (patch->is_delete == 1) + (patch->is_new == 1) +
                         (patch->is_rename == 1) + (patch->is_copy == 1);
        if (extensions > 1)
                return error(_("inconsistent header lines %d and %d"),
 -                           patch->extension_linenr, state->linenr);
 +                           patch->extension_linenr, linenr);
        if (extensions && !patch->extension_linenr)
 -              patch->extension_linenr = state->linenr;
 +              patch->extension_linenr = linenr;
        return 0;
  }
  
 -/* Verify that we recognize the lines following a git header */
 -static int parse_git_header(struct apply_state *state,
 -                          const char *line,
 -                          int len,
 -                          unsigned int size,
 -                          struct patch *patch)
 +int parse_git_diff_header(struct strbuf *root,
 +                        int *linenr,
 +                        int p_value,
 +                        const char *line,
 +                        int len,
 +                        unsigned int size,
 +                        struct patch *patch)
  {
        unsigned long offset;
 +      struct gitdiff_data parse_hdr_state;
  
        /* A git diff has explicit new/delete information, so we don't guess */
        patch->is_new = 0;
         * or removing or adding empty files), so we get
         * the default name from the header.
         */
 -      patch->def_name = git_header_name(state, line, len);
 -      if (patch->def_name && state->root.len) {
 -              char *s = xstrfmt("%s%s", state->root.buf, patch->def_name);
 +      patch->def_name = git_header_name(p_value, line, len);
 +      if (patch->def_name && root->len) {
 +              char *s = xstrfmt("%s%s", root->buf, patch->def_name);
                free(patch->def_name);
                patch->def_name = s;
        }
  
        line += len;
        size -= len;
 -      state->linenr++;
 -      for (offset = len ; size > 0 ; offset += len, size -= len, line += len, state->linenr++) {
 +      (*linenr)++;
 +      parse_hdr_state.root = root;
 +      parse_hdr_state.linenr = *linenr;
 +      parse_hdr_state.p_value = p_value;
 +
 +      for (offset = len ; size > 0 ; offset += len, size -= len, line += len, (*linenr)++) {
                static const struct opentry {
                        const char *str;
 -                      int (*fn)(struct apply_state *, const char *, struct patch *);
 +                      int (*fn)(struct gitdiff_data *, const char *, struct patch *);
                } optable[] = {
                        { "@@ -", gitdiff_hdrend },
                        { "--- ", gitdiff_oldname },
                        int res;
                        if (len < oplen || memcmp(p->str, line, oplen))
                                continue;
 -                      res = p->fn(state, line + oplen, patch);
 +                      res = p->fn(&parse_hdr_state, line + oplen, patch);
                        if (res < 0)
                                return -1;
 -                      if (check_header_line(state, patch))
 +                      if (check_header_line(*linenr, patch))
                                return -1;
                        if (res > 0)
                                return offset;
@@@ -1539,9 -1561,7 +1539,9 @@@ static int find_header(struct apply_sta
                 * or mode change, so we handle that specially
                 */
                if (!memcmp("diff --git ", line, 11)) {
 -                      int git_hdr_len = parse_git_header(state, line, len, size, patch);
 +                      int git_hdr_len = parse_git_diff_header(&state->root, &state->linenr,
 +                                                              state->p_value, line, len,
 +                                                              size, patch);
                        if (git_hdr_len < 0)
                                return -128;
                        if (git_hdr_len <= len)
@@@ -4643,6 -4663,7 +4643,7 @@@ static int apply_patch(struct apply_sta
        struct patch *list = NULL, **listp = &list;
        int skipped_patch = 0;
        int res = 0;
+       int flush_attributes = 0;
  
        state->patch_input_file = filename;
        if (read_patch_file(&buf, fd) < 0)
                        patch_stats(state, patch);
                        *listp = patch;
                        listp = &patch->next;
+                       if ((patch->new_name &&
+                            ends_with_path_components(patch->new_name,
+                                                      GITATTRIBUTES_FILE)) ||
+                           (patch->old_name &&
+                            ends_with_path_components(patch->old_name,
+                                                      GITATTRIBUTES_FILE)))
+                               flush_attributes = 1;
                }
                else {
                        if (state->apply_verbosity > verbosity_normal)
        if (state->summary && state->apply_verbosity > verbosity_silent)
                summary_patch_list(list);
  
+       if (flush_attributes)
+               reset_parsed_attributes();
  end:
        free_patch_list(list);
        strbuf_release(&buf);
diff --combined t/t3400-rebase.sh
index 80b23fd3269c7828660469207728f67087c82467,4f22e6d3d82cb739c6db7190ead5f94e6ad03c0a..23469cc78937eeec8aabc26c158680c2ca60dd7d
@@@ -285,7 -285,7 +285,7 @@@ EO
        test_cmp From_.msg out
  '
  
 -test_expect_success 'rebase--am.sh and --show-current-patch' '
 +test_expect_success 'rebase --am and --show-current-patch' '
        test_create_repo conflict-apply &&
        (
                cd conflict-apply &&
        )
  '
  
+ test_expect_success 'rebase --am and .gitattributes' '
+       test_create_repo attributes &&
+       (
+               cd attributes &&
+               test_commit init &&
+               git config filter.test.clean "sed -e '\''s/smudged/clean/g'\''" &&
+               git config filter.test.smudge "sed -e '\''s/clean/smudged/g'\''" &&
+               test_commit second &&
+               git checkout -b test HEAD^ &&
+               echo "*.txt filter=test" >.gitattributes &&
+               git add .gitattributes &&
+               test_commit third &&
+               echo "This text is smudged." >a.txt &&
+               git add a.txt &&
+               test_commit fourth &&
+               git checkout -b removal HEAD^ &&
+               git rm .gitattributes &&
+               git add -u &&
+               test_commit fifth &&
+               git cherry-pick test &&
+               git checkout test &&
+               git rebase master &&
+               grep "smudged" a.txt &&
+               git checkout removal &&
+               git reset --hard &&
+               git rebase master &&
+               grep "clean" a.txt
+       )
+ '
  test_expect_success 'rebase--merge.sh and --show-current-patch' '
        test_create_repo conflict-merge &&
        (