diff --git a/branch.c b/branch.c index 22f4f46b96..5bb1c28915 100644 --- a/branch.c +++ b/branch.c @@ -860,9 +860,11 @@ out: free(branch_point); } -void remove_merge_branch_state(struct repository *r) +static void do_remove_merge_branch_state(struct repository *r, unsigned flags) { unlink(git_path_merge_head(r)); + if (!(flags & REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS)) + unlink(git_path_merge_labels(r)); unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); @@ -871,11 +873,16 @@ void remove_merge_branch_state(struct repository *r) save_autostash_ref(r, "MERGE_AUTOSTASH"); } -void remove_branch_state(struct repository *r, int verbose) +void remove_merge_branch_state(struct repository *r) { - sequencer_post_commit_cleanup(r, verbose); + do_remove_merge_branch_state(r, 0); +} + +void remove_branch_state(struct repository *r, unsigned flags) +{ + sequencer_post_commit_cleanup(r, flags & REMOVE_BRANCH_STATE_VERBOSE); unlink(git_path_squash_msg(r)); - remove_merge_branch_state(r); + do_remove_merge_branch_state(r, flags); } void die_if_checked_out(const char *branch, int ignore_current_worktree) diff --git a/branch.h b/branch.h index e9b1f7b37d..95b2431f24 100644 --- a/branch.h +++ b/branch.h @@ -127,6 +127,8 @@ int validate_branchname(const char *name, struct strbuf *ref); */ int validate_new_branchname(const char *name, struct strbuf *ref, int force); +#define REMOVE_BRANCH_STATE_VERBOSE (1u << 0) +#define REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS (1u << 1) /* * Remove information about the merge state on the current * branch. (E.g., MERGE_HEAD) @@ -137,7 +139,7 @@ void remove_merge_branch_state(struct repository *r); * Remove information about the state of working on the current * branch. (E.g., MERGE_HEAD) */ -void remove_branch_state(struct repository *r, int verbose); +void remove_branch_state(struct repository *r, unsigned flags); /* * Configure local branch "local" as downstream to branch "remote" diff --git a/builtin/checkout.c b/builtin/checkout.c index 2d575a4f57..91e0332ec2 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -15,6 +15,7 @@ #include "hex.h" #include "hook.h" #include "merge-ll.h" +#include "merge.h" #include "lockfile.h" #include "mem-pool.h" #include "object-file.h" @@ -310,9 +311,36 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, return error(_("path '%s' does not have their version"), ce->name); } +struct checkout_merged_data { + int conflict_style; + int renormalize; + char *base_label, *ours_label, *theirs_label; +}; + +static void checkout_merged_release(struct checkout_merged_data *data) +{ + free(data->base_label); + free(data->ours_label); + free(data->theirs_label); +} + +static void checkout_merged_prepare(struct checkout_merged_data *data) +{ + int renormalize = 0; + + repo_config_get_bool(the_repository, "merge.renormalize", &renormalize); + data->renormalize = renormalize; + if (read_merge_labels(the_repository, &data->base_label, &data->ours_label, + &data->theirs_label)) { + data->base_label = xstrdup("base"); + data->ours_label = xstrdup("ours"); + data->theirs_label = xstrdup("theirs"); + } +} + static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts, struct mem_pool *ce_mem_pool, - int conflict_style) + struct checkout_merged_data *data) { struct cache_entry *ce = the_repository->index->cache[pos]; const char *path = ce->name; @@ -324,7 +352,6 @@ static int checkout_merged(int pos, const struct checkout *state, struct object_id threeway[3]; unsigned mode = 0; struct ll_merge_options ll_opts = LL_MERGE_OPTIONS_INIT; - int renormalize = 0; memset(threeway, 0, sizeof(threeway)); while (pos < the_repository->index->cache_nr) { @@ -345,11 +372,10 @@ static int checkout_merged(int pos, const struct checkout *state, read_mmblob(&ours, the_repository->objects, &threeway[1]); read_mmblob(&theirs, the_repository->objects, &threeway[2]); - repo_config_get_bool(the_repository, "merge.renormalize", &renormalize); - ll_opts.renormalize = renormalize; - ll_opts.conflict_style = conflict_style; - merge_status = ll_merge(&result_buf, path, &ancestor, "base", - &ours, "ours", &theirs, "theirs", + ll_opts.renormalize = data->renormalize; + ll_opts.conflict_style = data->conflict_style; + merge_status = ll_merge(&result_buf, path, &ancestor, data->base_label, + &ours, data->ours_label, &theirs, data->theirs_label, state->istate, &ll_opts); free(ancestor.ptr); free(ours.ptr); @@ -446,6 +472,7 @@ static int checkout_worktree(const struct checkout_opts *opts, int pos; int pc_workers, pc_threshold; struct mem_pool ce_mem_pool; + struct checkout_merged_data checkout_merged_data = {0}; state.force = 1; state.refresh_cache = 1; @@ -462,6 +489,10 @@ static int checkout_worktree(const struct checkout_opts *opts, if (pc_workers > 1) init_parallel_checkout(); + if (opts->merge) { + checkout_merged_prepare(&checkout_merged_data); + checkout_merged_data.conflict_style = opts->conflict_style; + } for (pos = 0; pos < the_repository->index->cache_nr; pos++) { struct cache_entry *ce = the_repository->index->cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -479,10 +510,14 @@ static int checkout_worktree(const struct checkout_opts *opts, errs |= checkout_merged(pos, &state, &nr_unmerged, &ce_mem_pool, - opts->conflict_style); + &checkout_merged_data); pos = skip_same_name(ce, pos) - 1; } } + + if (opts->merge) + checkout_merged_release(&checkout_merged_data); + if (pc_workers > 1) errs |= run_parallel_checkout(&state, pc_workers, pc_threshold, NULL, NULL); @@ -946,10 +981,13 @@ static void report_tracking(struct branch_info *new_branch_info) static void update_refs_for_switch(const struct checkout_opts *opts, struct branch_info *old_branch_info, - struct branch_info *new_branch_info) + struct branch_info *new_branch_info, + bool merge_conflicts) { struct strbuf msg = STRBUF_INIT; const char *old_desc, *reflog_msg; + unsigned flags = 0; + if (opts->new_branch) { if (opts->new_orphan_branch) { enum log_refs_config log_all_ref_updates = LOG_REFS_UNSET; @@ -1044,7 +1082,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts, old_branch_info->path); } } - remove_branch_state(the_repository, !opts->quiet); + if (!opts->quiet) + flags |= REMOVE_BRANCH_STATE_VERBOSE; + if (merge_conflicts) + flags |= REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS; + remove_branch_state(the_repository, flags); strbuf_release(&msg); if (!opts->quiet && !opts->force_detach && @@ -1258,7 +1300,9 @@ static int switch_branches(const struct checkout_opts *opts, if (autostash_res == STASH_APPLY_CONFLICT && !opts->quiet) fputc('\n', stderr); - update_refs_for_switch(opts, &old_branch_info, new_branch_info); + + update_refs_for_switch(opts, &old_branch_info, new_branch_info, + autostash_res == STASH_APPLY_CONFLICT); if (created_autostash) { discard_index(the_repository->index); diff --git a/builtin/commit.c b/builtin/commit.c index 0e750f6157..8ffd42681b 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1950,6 +1950,7 @@ int cmd_commit(int argc, sequencer_post_commit_cleanup(the_repository, 0); unlink(git_path_merge_head(the_repository)); + unlink(git_path_merge_labels(the_repository)); unlink(git_path_merge_msg(the_repository)); unlink(git_path_merge_mode(the_repository)); unlink(git_path_squash_msg(the_repository)); diff --git a/merge-ort.c b/merge-ort.c index 1d3d193d35..4d8cf79cb0 100644 --- a/merge-ort.c +++ b/merge-ort.c @@ -35,6 +35,7 @@ #include "hex.h" #include "entry.h" #include "merge-ll.h" +#include "merge.h" #include "match-trees.h" #include "mem-pool.h" #include "object-file.h" @@ -418,6 +419,9 @@ struct merge_options_internal { /* field that holds submodule conflict information */ struct string_list conflicted_submodules; + + /* Copies of the labels used for conflict markers */ + const char *labels[3]; }; struct conflicted_submodule_item { @@ -4969,6 +4973,14 @@ void merge_switch_to_result(struct merge_options *opt, return; } trace2_region_leave("merge", "write_auto_merge", opt->repo); + + if (!result->clean) { + struct merge_options_internal *priv = result->priv; + + trace2_region_enter("merge", "write_merge_labels", opt->repo); + write_merge_labels(opt->repo, priv->labels); + trace2_region_leave("merge", "write_merge_labels", opt->repo); + } } if (display_update_msgs) merge_display_update_messages(opt, /* detailed */ 0, result); @@ -5234,6 +5246,14 @@ static void move_opt_priv_to_result_priv(struct merge_options *opt, * to move it. */ assert(opt->priv && !result->priv); + if (!result->clean) { + opt->priv->labels[0] = + mem_pool_strdup(&opt->priv->pool, opt->ancestor); + opt->priv->labels[1] = + mem_pool_strdup(&opt->priv->pool, opt->branch1); + opt->priv->labels[2] = + mem_pool_strdup(&opt->priv->pool, opt->branch2); + } result->priv = opt->priv; result->_properly_initialized = RESULT_INITIALIZED; opt->priv = NULL; diff --git a/merge.c b/merge.c index 0f5e823e63..6a557d11ac 100644 --- a/merge.c +++ b/merge.c @@ -8,6 +8,7 @@ #include "merge.h" #include "commit.h" #include "repository.h" +#include "path.h" #include "run-command.h" #include "resolve-undo.h" #include "tree.h" @@ -111,3 +112,68 @@ int checkout_fast_forward(struct repository *r, return error(_("unable to write new index file")); return 0; } + +int write_merge_labels(struct repository *r, const char *labels[3]) +{ + FILE *f = fopen_or_warn(git_path_merge_labels(r), "w"); + + if (!f) + return -1; + + fprintf(f, "%s\n%s\n%s\n", labels[0], labels[1], labels[2]); + if (fclose(f)) + return error_errno("could not write '%s'", + git_path_merge_labels(r)); + + return 0; +} + +static char *parse_merge_label_line(FILE *fp) +{ + struct strbuf buf = STRBUF_INIT; + + if (strbuf_getline(&buf, fp) == EOF) { + strbuf_release(&buf); + return NULL; + } + + return strbuf_detach(&buf, NULL); +} + +int read_merge_labels(struct repository *r, + char **pbase, char **pours, char **ptheirs) +{ + char *base = NULL, *ours = NULL, *theirs = NULL; + int ret = -1; + FILE *fp = fopen(git_path_merge_labels(r), "r"); + + if (!fp) + return -1; + + base = parse_merge_label_line(fp); + if (!base) + goto out; + + ours = parse_merge_label_line(fp); + if (!ours) + goto out; + + theirs = parse_merge_label_line(fp); + if (!theirs) + goto out; + /* We ignore any trailing lines */ + + ret = 0; + *pbase = base; + *pours = ours; + *ptheirs = theirs; +out: + if (ret) { + free(base); + free(ours); + free(theirs); + } + fclose(fp); + + return ret; +} diff --git a/merge.h b/merge.h index 21ac7ef2f1..3d936d6988 100644 --- a/merge.h +++ b/merge.h @@ -13,5 +13,8 @@ int checkout_fast_forward(struct repository *r, const struct object_id *from, const struct object_id *to, int overwrite_ignore); +int write_merge_labels(struct repository *r, const char *labels[3]); +int read_merge_labels(struct repository *r, + char **base, char **ours, char **theirs); #endif /* MERGE_H */ diff --git a/path.c b/path.c index 4608a56927..f039044233 100644 --- a/path.c +++ b/path.c @@ -1655,3 +1655,4 @@ REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE") REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD") REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD") REPO_GIT_PATH_FUNC(shallow, "shallow") +REPO_GIT_PATH_FUNC(merge_labels, "MERGE_LABELS") diff --git a/path.h b/path.h index 7e7408dd05..8cd12ccfde 100644 --- a/path.h +++ b/path.h @@ -142,6 +142,7 @@ const char *git_path_merge_mode(struct repository *r); const char *git_path_merge_head(struct repository *r); const char *git_path_fetch_head(struct repository *r); const char *git_path_shallow(struct repository *r); +const char *git_path_merge_labels(struct repository *r); int ends_with_path_components(const char *path, const char *components); diff --git a/repository.c b/repository.c index fa4214ff41..e584d6c405 100644 --- a/repository.c +++ b/repository.c @@ -367,6 +367,7 @@ static void repo_clear_path_cache(struct repo_path_cache *cache) FREE_AND_NULL(cache->merge_head); FREE_AND_NULL(cache->fetch_head); FREE_AND_NULL(cache->shallow); + FREE_AND_NULL(cache->merge_labels); } void repo_clear(struct repository *repo) diff --git a/repository.h b/repository.h index 11f5c2ed10..91b1f57db7 100644 --- a/repository.h +++ b/repository.h @@ -36,6 +36,7 @@ struct repo_path_cache { char *merge_head; char *fetch_head; char *shallow; + char *merge_labels; }; struct repository { diff --git a/sequencer.c b/sequencer.c index efbfd1315b..2ac78e15b8 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5177,6 +5177,7 @@ static int pick_commits(struct repository *r, unlink(rebase_path_stopped_sha()); unlink(rebase_path_amend()); unlink(rebase_path_patch()); + unlink(git_path_merge_labels(r)); while (todo_list->current < todo_list->nr) { struct todo_item *item = todo_list->items + todo_list->current; diff --git a/t/t7201-co.sh b/t/t7201-co.sh index 9ea9462914..3e9e04738a 100755 --- a/t/t7201-co.sh +++ b/t/t7201-co.sh @@ -99,6 +99,7 @@ test_expect_success 'checkout -m with dirty tree' ' fill 0 1 2 3 4 5 6 7 8 >one && git checkout -m side >messages && + test_path_is_missing .git/MERGE_LABELS && test "$(git symbolic-ref HEAD)" = "refs/heads/side" && @@ -183,6 +184,27 @@ test_expect_success 'format of merge conflict from checkout -m' ' d >>>>>>> local EOF + test_cmp expect two && + + test_path_is_file .git/MERGE_LABELS && + + git checkout --conflict=diff3 two && + cat >expect <<-\EOF && + <<<<<<< simple + a + c + e + ||||||| main + a + b + c + d + e + ======= + b + d + >>>>>>> local + EOF test_cmp expect two '