This series makes rebase --am honor the .gitattributes file for subsequent patches when a patch changes it. Note that there are two places we load attributes in ll-merge.c, but this code only handles the one that git am uses. The two cannot be unified because the one in ll_merge_marker_size intentionally doesn't load the merge attribute, since it wants to always use the recursive strategy. Loading it anyway causes t4017 to fail. Changes from v4: * Wrap lines in apply.c. * Handle merge and conflict-marker-size attributes. * Add tests for am and am -3 in addition to rebase. Changes from v3: * Check for both addition and removal of .gitattributes files. * Switch from "test_config" to "git config". Changes from v2: * Rename has_path_suffix to ends_with_path_components. Changes from v1: * Add has_path_suffix in a separate commit. brian m. carlson (2): path: add a function to check for path suffix am: reload .gitattributes after patching it apply.c | 11 ++++++++++ convert.c | 11 +++++++++- convert.h | 6 ++++++ ll-merge.c | 19 +++++++++++++---- ll-merge.h | 1 + path.c | 39 +++++++++++++++++++++++++++-------- path.h | 3 +++ t/t3400-rebase.sh | 36 ++++++++++++++++++++++++++++++++ t/t4150-am.sh | 52 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 164 insertions(+), 14 deletions(-) Range-diff against v4: 1: fa825e4b40 ! 1: 2077a0829e apply: reload .gitattributes after patching it @@ Metadata Author: brian m. carlson <sandals@xxxxxxxxxxxxxxxxxxxx> ## Commit message ## - apply: reload .gitattributes after patching it + am: reload .gitattributes after patching it When applying multiple patches with git am, or when rebasing using the am backend, it's possible that one of our patches has updated a @@ Commit message To ensure we write the correct data into the working tree, expire the cache after each patch that touches a path ending in ".gitattributes". + Since we load these attributes in multiple separate files, we must + expire them accordingly. + + Verify that both the am and rebase code paths work correctly, including + the conflict marker size with am -3. Signed-off-by: brian m. carlson <sandals@xxxxxxxxxxxxxxxxxxxx> @@ apply.c: static int apply_patch(struct apply_state *state, *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))) ++ 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 { @@ apply.c: static int apply_patch(struct apply_state *state, strbuf_release(&buf); ## convert.c ## +@@ + #include "pkt-line.h" + #include "sub-process.h" + #include "utf8.h" ++#include "ll-merge.h" + + /* + * convert.c - convert a file when checking it out and checking it in. @@ convert.c: struct conv_attrs { const char *working_tree_encoding; /* Supported encoding or default encoding if NULL */ }; @@ convert.c: static void convert_attrs(const struct index_state *istate, +{ + attr_check_free(check); + check = NULL; ++ reset_merge_attributes(); +} + int would_convert_to_git_filter_fd(const struct index_state *istate, const char *path) @@ convert.h: void convert_to_git_filter_fd(const struct index_state *istate, * * Streaming conversion support + ## ll-merge.c ## +@@ ll-merge.c: struct ll_merge_driver { + char *cmdline; + }; + ++static struct attr_check *merge_attributes; ++static struct attr_check *load_merge_attributes(void) ++{ ++ if (!merge_attributes) ++ merge_attributes = attr_check_initl("merge", "conflict-marker-size", NULL); ++ return merge_attributes; ++} ++ ++void reset_merge_attributes(void) ++{ ++ attr_check_free(merge_attributes); ++ merge_attributes = NULL; ++} ++ + /* + * Built-in low-levels + */ +@@ ll-merge.c: int ll_merge(mmbuffer_t *result_buf, + struct index_state *istate, + const struct ll_merge_options *opts) + { +- static struct attr_check *check; ++ struct attr_check *check = load_merge_attributes(); + static const struct ll_merge_options default_opts; + const char *ll_driver_name = NULL; + int marker_size = DEFAULT_CONFLICT_MARKER_SIZE; +@@ ll-merge.c: int ll_merge(mmbuffer_t *result_buf, + normalize_file(theirs, path, istate); + } + +- if (!check) +- check = attr_check_initl("merge", "conflict-marker-size", NULL); +- + git_check_attr(istate, path, check); + ll_driver_name = check->items[0].value; + if (check->items[1].value) { + + ## ll-merge.h ## +@@ ll-merge.h: int ll_merge(mmbuffer_t *result_buf, + const struct ll_merge_options *opts); + + int ll_merge_marker_size(struct index_state *istate, const char *path); ++void reset_merge_attributes(void); + + #endif + ## t/t3400-rebase.sh ## @@ t/t3400-rebase.sh: test_expect_success 'rebase --am and --show-current-patch' ' ) @@ t/t3400-rebase.sh: test_expect_success 'rebase --am and --show-current-patch' ' test_expect_success 'rebase--merge.sh and --show-current-patch' ' test_create_repo conflict-merge && ( + + ## t/t4150-am.sh ## +@@ t/t4150-am.sh: test_expect_success 'am --quit keeps HEAD where it is' ' + test_cmp expected actual + ' + ++test_expect_success 'am and .gitattibutes' ' ++ 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 conflict-marker-size=10" >.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 -b conflict third && ++ echo "This text is different." >a.txt && ++ git add a.txt && ++ test_commit sixth && ++ ++ git checkout test && ++ git format-patch --stdout master..HEAD >patches && ++ git reset --hard master && ++ git am patches && ++ grep "smudged" a.txt && ++ ++ git checkout removal && ++ git reset --hard && ++ git format-patch --stdout master..HEAD >patches && ++ git reset --hard master && ++ git am patches && ++ grep "clean" a.txt && ++ ++ git checkout conflict && ++ git reset --hard && ++ git format-patch --stdout master..HEAD >patches && ++ git reset --hard fourth && ++ test_must_fail git am -3 patches && ++ grep "<<<<<<<<<<" a.txt ++ ) ++' ++ + test_done