Skip to content

Commit 11f0fb7

Browse files
committed
merge: remember conflict labels
When recreating merge conflicts with "git checkout -m <path>" the original conflict labels are lost. For commands like "git merge" and "git cherry-pick" we could use the presence of the related root ref (MERGE_HEAD and CHERRY_PICK_HEAD respectively) to recreate the labels. However if the conflicts are from "git stash pop" or "git checkout -m <branch>" then there is no ref to deduce the labels from. To ensure the labels are always available the merge machinery is updated to record the labels .git/MERGE_LABELS when it updates the worktree and there are conflicts. The labels are then read from the file by "git checkout -m <path>" when recreating the conflicts. There is a subtlety with "git checkout -m <branch>" in that it resets the index to remove the conflict entries but they are saved in merge undo so we want to preserve the MERGE_LABELS file when switching branches if there were conflicts. Signed-off-by: Phillip Wood <[email protected]>
1 parent 3562df0 commit 11f0fb7

File tree

14 files changed

+157
-18
lines changed

14 files changed

+157
-18
lines changed

branch.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -826,9 +826,11 @@ void create_branches_recursively(struct repository *r, const char *name,
826826
free(branch_point);
827827
}
828828

829-
void remove_merge_branch_state(struct repository *r)
829+
void remove_merge_branch_state(struct repository *r, unsigned flags)
830830
{
831831
unlink(git_path_merge_head(r));
832+
if (!(flags & REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS))
833+
unlink(git_path_merge_labels(r));
832834
unlink(git_path_merge_rr(r));
833835
unlink(git_path_merge_msg(r));
834836
unlink(git_path_merge_mode(r));
@@ -837,11 +839,11 @@ void remove_merge_branch_state(struct repository *r)
837839
save_autostash_ref(r, "MERGE_AUTOSTASH");
838840
}
839841

840-
void remove_branch_state(struct repository *r, int verbose)
842+
void remove_branch_state(struct repository *r, unsigned flags)
841843
{
842-
sequencer_post_commit_cleanup(r, verbose);
844+
sequencer_post_commit_cleanup(r, flags & REMOVE_BRANCH_STATE_VERBOSE);
843845
unlink(git_path_squash_msg(r));
844-
remove_merge_branch_state(r);
846+
remove_merge_branch_state(r, flags);
845847
}
846848

847849
void die_if_checked_out(const char *branch, int ignore_current_worktree)

branch.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,17 +123,19 @@ int validate_branchname(const char *name, struct strbuf *ref);
123123
*/
124124
int validate_new_branchname(const char *name, struct strbuf *ref, int force);
125125

126+
#define REMOVE_BRANCH_STATE_VERBOSE (1u << 0)
127+
#define REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS (1u << 1)
126128
/*
127129
* Remove information about the merge state on the current
128130
* branch. (E.g., MERGE_HEAD)
129131
*/
130-
void remove_merge_branch_state(struct repository *r);
132+
void remove_merge_branch_state(struct repository *r, unsigned flags);
131133

132134
/*
133135
* Remove information about the state of working on the current
134136
* branch. (E.g., MERGE_HEAD)
135137
*/
136-
void remove_branch_state(struct repository *r, int verbose);
138+
void remove_branch_state(struct repository *r, unsigned flags);
137139

138140
/*
139141
* Configure local branch "local" as downstream to branch "remote"

builtin/checkout.c

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "hex.h"
1616
#include "hook.h"
1717
#include "merge-ll.h"
18+
#include "merge.h"
1819
#include "lockfile.h"
1920
#include "mem-pool.h"
2021
#include "merge-ort-wrappers.h"
@@ -263,6 +264,7 @@ static int checkout_merged(int pos, const struct checkout *state,
263264
struct cache_entry *ce = the_repository->index->cache[pos];
264265
const char *path = ce->name;
265266
mmfile_t ancestor, ours, theirs;
267+
char *base_label, *ours_label, *theirs_label;
266268
enum ll_merge_result merge_status;
267269
int status;
268270
struct object_id oid;
@@ -293,10 +295,19 @@ static int checkout_merged(int pos, const struct checkout *state,
293295

294296
git_config_get_bool("merge.renormalize", &renormalize);
295297
ll_opts.renormalize = renormalize;
298+
if (read_merge_labels(the_repository, &base_label, &ours_label,
299+
&theirs_label)) {
300+
base_label = xstrdup("base");
301+
ours_label = xstrdup("ours");
302+
theirs_label = xstrdup("theirs");
303+
}
296304
ll_opts.conflict_style = conflict_style;
297-
merge_status = ll_merge(&result_buf, path, &ancestor, "base",
298-
&ours, "ours", &theirs, "theirs",
305+
merge_status = ll_merge(&result_buf, path, &ancestor, base_label,
306+
&ours, ours_label, &theirs, theirs_label,
299307
state->istate, &ll_opts);
308+
free(base_label);
309+
free(ours_label);
310+
free(theirs_label);
300311
free(ancestor.ptr);
301312
free(ours.ptr);
302313
free(theirs.ptr);
@@ -692,6 +703,7 @@ static void describe_detached_head(const char *msg, struct commit *commit)
692703
}
693704

694705
#define ERROR_FLAG_WRITEOUT (1u << 0)
706+
#define ERROR_FLAG_CONFLICTS (1u << 1)
695707

696708
static int reset_tree(struct tree *tree, const struct checkout_opts *o,
697709
int worktree, unsigned int *error_flags,
@@ -916,6 +928,8 @@ static int merge_working_tree(const struct checkout_opts *opts,
916928
old_tree);
917929
if (ret < 0)
918930
exit(128);
931+
if (!ret)
932+
*error_flags |= ERROR_FLAG_CONFLICTS;
919933
ret = reset_tree(new_tree,
920934
opts, 0,
921935
error_flags, new_branch_info);
@@ -951,10 +965,13 @@ static void report_tracking(struct branch_info *new_branch_info)
951965

952966
static void update_refs_for_switch(const struct checkout_opts *opts,
953967
struct branch_info *old_branch_info,
954-
struct branch_info *new_branch_info)
968+
struct branch_info *new_branch_info,
969+
int merge_conflicts)
955970
{
956971
struct strbuf msg = STRBUF_INIT;
957972
const char *old_desc, *reflog_msg;
973+
unsigned flags = 0;
974+
958975
if (opts->new_branch) {
959976
if (opts->new_orphan_branch) {
960977
enum log_refs_config log_all_ref_updates =
@@ -1046,7 +1063,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
10461063
old_branch_info->path);
10471064
}
10481065
}
1049-
remove_branch_state(the_repository, !opts->quiet);
1066+
if (!opts->quiet)
1067+
flags |= REMOVE_BRANCH_STATE_VERBOSE;
1068+
if (merge_conflicts)
1069+
flags |= REMOVE_BRANCH_STATE_PRESERVE_CONFLICT_LABELS;
1070+
remove_branch_state(the_repository, flags);
10501071
strbuf_release(&msg);
10511072
if (!opts->quiet &&
10521073
!opts->force_detach &&
@@ -1218,12 +1239,13 @@ static int switch_branches(const struct checkout_opts *opts,
12181239
if (!opts->quiet && !old_branch_info.path && old_branch_info.commit && new_branch_info->commit != old_branch_info.commit)
12191240
orphaned_commit_warning(old_branch_info.commit, new_branch_info->commit);
12201241

1221-
update_refs_for_switch(opts, &old_branch_info, new_branch_info);
1242+
update_refs_for_switch(opts, &old_branch_info, new_branch_info,
1243+
!!(error_flags & ERROR_FLAG_CONFLICTS));
12221244

12231245
ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
12241246
branch_info_release(&old_branch_info);
12251247

1226-
return ret || error_flags;
1248+
return ret || !!(error_flags & ~ERROR_FLAG_CONFLICTS);
12271249
}
12281250

12291251
static int git_checkout_config(const char *var, const char *value,

builtin/commit.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1920,6 +1920,7 @@ int cmd_commit(int argc,
19201920

19211921
sequencer_post_commit_cleanup(the_repository, 0);
19221922
unlink(git_path_merge_head(the_repository));
1923+
unlink(git_path_merge_labels(the_repository));
19231924
unlink(git_path_merge_msg(the_repository));
19241925
unlink(git_path_merge_mode(the_repository));
19251926
unlink(git_path_squash_msg(the_repository));

builtin/merge.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -416,7 +416,7 @@ static void finish_up_to_date(void)
416416
else
417417
puts(_("Already up to date."));
418418
}
419-
remove_merge_branch_state(the_repository);
419+
remove_merge_branch_state(the_repository, 0);
420420
}
421421

422422
static void squash_message(struct commit *commit, struct commit_list *remoteheads)
@@ -946,8 +946,7 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
946946
&result_commit, NULL, sign_commit))
947947
die(_("failed to write commit object"));
948948
finish(head, remoteheads, &result_commit, "In-index merge");
949-
950-
remove_merge_branch_state(the_repository);
949+
remove_merge_branch_state(the_repository, 0);
951950
free_commit_list(parents);
952951
return 0;
953952
}
@@ -976,7 +975,7 @@ static int finish_automerge(struct commit *head,
976975
finish(head, remoteheads, &result_commit, buf.buf);
977976

978977
strbuf_release(&buf);
979-
remove_merge_branch_state(the_repository);
978+
remove_merge_branch_state(the_repository, 0);
980979
free_commit_list(parents);
981980
return 0;
982981
}
@@ -1386,7 +1385,7 @@ int cmd_merge(int argc,
13861385
builtin_merge_usage,
13871386
builtin_merge_options);
13881387

1389-
remove_merge_branch_state(the_repository);
1388+
remove_merge_branch_state(the_repository, 0);
13901389
goto done;
13911390
}
13921391

@@ -1621,7 +1620,7 @@ int cmd_merge(int argc,
16211620
}
16221621

16231622
finish(head_commit, remoteheads, &commit->object.oid, msg);
1624-
remove_merge_branch_state(the_repository);
1623+
remove_merge_branch_state(the_repository, 0);
16251624
goto done;
16261625
} else if (!remoteheads->next && common->next)
16271626
;

merge-ort.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "hex.h"
3636
#include "entry.h"
3737
#include "merge-ll.h"
38+
#include "merge.h"
3839
#include "match-trees.h"
3940
#include "mem-pool.h"
4041
#include "object-file.h"
@@ -405,6 +406,9 @@ struct merge_options_internal {
405406

406407
/* field that holds submodule conflict information */
407408
struct string_list conflicted_submodules;
409+
410+
/* Copies of the labels used for conflict markers */
411+
char *labels[3];
408412
};
409413

410414
struct conflicted_submodule_item {
@@ -4870,6 +4874,16 @@ void merge_switch_to_result(struct merge_options *opt,
48704874
return;
48714875
}
48724876
trace2_region_leave("merge", "write_auto_merge", opt->repo);
4877+
4878+
trace2_region_enter("merge", "write_merge_labels",
4879+
opt->repo);
4880+
opt->priv = result->priv;
4881+
write_merge_labels(opt->repo, opt->priv->labels[0],
4882+
opt->priv->labels[1],
4883+
opt->priv->labels[2]);
4884+
opt->priv = NULL;
4885+
trace2_region_leave("merge", "write_merge_labels",
4886+
opt->repo);
48734887
}
48744888
if (display_update_msgs)
48754889
merge_display_update_messages(opt, /* detailed */ 0, result);
@@ -5134,6 +5148,14 @@ static void move_opt_priv_to_result_priv(struct merge_options *opt,
51345148
* to move it.
51355149
*/
51365150
assert(opt->priv && !result->priv);
5151+
if (!result->clean) {
5152+
opt->priv->labels[0] = mem_pool_strdup(&opt->priv->pool,
5153+
opt->ancestor);
5154+
opt->priv->labels[1] = mem_pool_strdup(&opt->priv->pool,
5155+
opt->branch1);
5156+
opt->priv->labels[2] = mem_pool_strdup(&opt->priv->pool,
5157+
opt->branch2);
5158+
}
51375159
result->priv = opt->priv;
51385160
result->_properly_initialized = RESULT_INITIALIZED;
51395161
opt->priv = NULL;

merge.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@
88
#include "merge.h"
99
#include "commit.h"
1010
#include "repository.h"
11+
#include "path.h"
1112
#include "run-command.h"
1213
#include "resolve-undo.h"
1314
#include "tree.h"
1415
#include "tree-walk.h"
1516
#include "unpack-trees.h"
17+
#include "wrapper.h"
18+
#include <stdio.h>
1619

1720
static const char *merge_argument(struct commit *commit)
1821
{
@@ -111,3 +114,61 @@ int checkout_fast_forward(struct repository *r,
111114
return error(_("unable to write new index file"));
112115
return 0;
113116
}
117+
118+
int write_merge_labels(struct repository *r, const char *base,
119+
const char *ours, const char *theirs)
120+
{
121+
FILE *f = fopen_or_warn(git_path_merge_labels(r), "w");
122+
if (!f)
123+
return -1;
124+
fprintf(f, "%s\n%s\n%s\n", base, ours, theirs);
125+
if (fclose(f))
126+
return error_errno("could not write '%s'",
127+
git_path_merge_labels(r));
128+
129+
return 0;
130+
}
131+
132+
static int parse_merge_label_line(const char **p, char **line)
133+
{
134+
const char *eol = strchr(*p, '\n');
135+
if (!eol)
136+
return -1;
137+
138+
*line = xmemdupz(*p, eol - *p);
139+
*p = eol + 1;
140+
141+
return 0;
142+
}
143+
144+
int read_merge_labels(struct repository *r,
145+
char **pbase, char** pours, char** ptheirs)
146+
{
147+
struct strbuf buf = STRBUF_INIT;
148+
const char *p;
149+
char *base = NULL, *ours = NULL, *theirs = NULL;
150+
int ret = -1;
151+
152+
if (strbuf_read_file(&buf, git_path_merge_labels(r), 0) < 0)
153+
return -1;
154+
p = buf.buf;
155+
if (parse_merge_label_line(&p, &base))
156+
goto out;
157+
if (parse_merge_label_line(&p, &ours))
158+
goto out;
159+
if (parse_merge_label_line(&p, &theirs))
160+
goto out;
161+
ret = 0;
162+
*pbase = base;
163+
*pours = ours;
164+
*ptheirs = theirs;
165+
out:
166+
if (ret) {
167+
free(base);
168+
free(ours);
169+
free(theirs);
170+
}
171+
strbuf_release(&buf);
172+
173+
return ret;
174+
}

merge.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,9 @@ int checkout_fast_forward(struct repository *r,
1313
const struct object_id *from,
1414
const struct object_id *to,
1515
int overwrite_ignore);
16+
int write_merge_labels(struct repository *r,
17+
const char *base, const char *ours, const char *theirs);
18+
int read_merge_labels(struct repository *r,
19+
char **base, char **ours, char **theirs);
1620

1721
#endif /* MERGE_H */

path.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1681,3 +1681,4 @@ REPO_GIT_PATH_FUNC(merge_mode, "MERGE_MODE")
16811681
REPO_GIT_PATH_FUNC(merge_head, "MERGE_HEAD")
16821682
REPO_GIT_PATH_FUNC(fetch_head, "FETCH_HEAD")
16831683
REPO_GIT_PATH_FUNC(shallow, "shallow")
1684+
REPO_GIT_PATH_FUNC(merge_labels, "MERGE_LABELS")

path.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ const char *git_path_merge_mode(struct repository *r);
138138
const char *git_path_merge_head(struct repository *r);
139139
const char *git_path_fetch_head(struct repository *r);
140140
const char *git_path_shallow(struct repository *r);
141+
const char *git_path_merge_labels(struct repository *r);
141142

142143
int ends_with_path_components(const char *path, const char *components);
143144

0 commit comments

Comments
 (0)