From: Phillip Wood <phillip.wood123@gmail.com>
To: git@vger.kernel.org
Cc: Elijah Newren <newren@gmail.com>, Johannes Sixt <j6t@kdbg.org>,
Phillip Wood <phillip.wood123@gmail.com>
Subject: [PATCH v3 0/2] checkout -m: recreate conflict labels
Date: Fri, 9 Oct 2026 10:13:23 +0100 [thread overview]
Message-ID: <cover.1791537203.git.phillip.wood@dunelm.org.uk> (raw)
In-Reply-To: <cover.1790761727.git.phillip.wood@dunelm.org.uk>
When "git checkout -m <path>" recreates a merge conflict, it uses
the labels "base", "ours", "theirs", rather than the labels used by
the original merge. This short series teaches the ort machinery to
write the labels to ".git/MERGE_LABELS" when it switches to a merge
result containing conflicts, so that "git checkout -m" can then read
that file and use the same labels.
Thanks to Junio and Johannes for their comments.
Changes since V2:
- only write ".git/MERGE_LABELS" when there are conflicts and added
a check to an existing "checkout -m <branch>" test
- change write_merge_labels() to take an array of labels
- use a local variable to store the internal merge state when writing
labels
- use strbuf_detach() rather than xmemdupz() when reading labels
- add a comment to say we ignore trailing cruft when reading the
labels file
Changes since V1:
- use strbuf_getline() rather than strbuf_read_file() to read labels
so that the newline handling of the reading and writing sides match.
NB ".git/MERGE_LABELS" is still undocumented - I'm hoping to find time
to add some documentation for all the MERGE_* files in a future series.
Johannes suggested using an index extension to store the labels,
but as we already have MERGE_MODE, MERGE_RR and MERGE_MSG I think it
is easier just to add another file.
base-commit: 3bc0341126508f78f5869cbfc0005e987efdf0c7
Published-As: https://github.com/phillipwood/git/releases/tag/pw%2Fconflict-labels%2Fv3
View-Changes-At: https://github.com/phillipwood/git/compare/3bc034112...182edb2e8
Fetch-It-Via: git fetch https://github.com/phillipwood/git pw/conflict-labels/v3
Phillip Wood (2):
remove_branch_state: convert boolean argument to flags
merge: remember conflict labels
branch.c | 17 ++++++++----
branch.h | 4 ++-
builtin/checkout.c | 30 +++++++++++++++++----
builtin/commit.c | 1 +
merge-ort.c | 20 ++++++++++++++
merge.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++
merge.h | 3 +++
path.c | 1 +
path.h | 1 +
repository.c | 1 +
repository.h | 1 +
sequencer.c | 1 +
t/t7201-co.sh | 22 ++++++++++++++++
13 files changed, 157 insertions(+), 11 deletions(-)
Range-diff against v2:
1: 86ef0f848a = 1: 86ef0f848a remove_branch_state: convert boolean argument to flags
2: 18bdf7df49 ! 2: 182edb2e88 merge: remember conflict labels
@@ merge-ort.c: struct merge_options_internal {
struct string_list conflicted_submodules;
+
+ /* Copies of the labels used for conflict markers */
-+ char *labels[3];
++ const char *labels[3];
};
struct conflicted_submodule_item {
@@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
}
trace2_region_leave("merge", "write_auto_merge", opt->repo);
+
-+ trace2_region_enter("merge", "write_merge_labels", opt->repo);
-+ opt->priv = result->priv;
-+ write_merge_labels(opt->repo, opt->priv->labels[0], opt->priv->labels[1],
-+ opt->priv->labels[2]);
-+ opt->priv = NULL;
-+ trace2_region_leave("merge", "write_merge_labels", 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);
@@ merge.c: int checkout_fast_forward(struct repository *r,
return 0;
}
+
-+int write_merge_labels(struct repository *r, const char *base,
-+ const char *ours, const char *theirs)
++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", base, ours, theirs);
++ 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(struct strbuf *buf, FILE *fp)
-+{
-+ if (strbuf_getline(buf, fp) == EOF)
-+ return NULL;
-+
-+ return xmemdupz(buf->buf, buf->len);
-+}
-+
-+int read_merge_labels(struct repository *r,
-+ char **pbase, char** pours, char** ptheirs)
++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(&buf, fp);
++ base = parse_merge_label_line(fp);
+ if (!base)
+ goto out;
+
-+ ours = parse_merge_label_line(&buf, fp);
++ ours = parse_merge_label_line(fp);
+ if (!ours)
+ goto out;
+
-+ theirs = parse_merge_label_line(&buf, fp);
++ theirs = parse_merge_label_line(fp);
+ if (!theirs)
+ goto out;
++ /* We ignore any trailing lines */
+
+ ret = 0;
+ *pbase = base;
@@ merge.c: int checkout_fast_forward(struct repository *r,
+ free(theirs);
+ }
+ fclose(fp);
-+ strbuf_release(&buf);
+
+ return ret;
+}
@@ merge.h: 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 *base, const char *ours, const char *theirs);
++int write_merge_labels(struct repository *r, const char *labels[3]);
+int read_merge_labels(struct repository *r,
+ char **base, char **ours, char **theirs);
@@ sequencer.c: static int pick_commits(struct repository *r,
struct todo_item *item = todo_list->items + todo_list->current;
## t/t7201-co.sh ##
+@@ t/t7201-co.sh: 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" &&
+
@@ t/t7201-co.sh: test_expect_success 'format of merge conflict from checkout -m' '
d
>>>>>>> local
--
2.56.0.134.g299a3c16181
next prev parent reply other threads:[~2026-10-09 9:13 UTC|newest]
Thread overview: 27+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-30 9:48 [PATCH 0/2] checkout -m: recreate conflict labels Phillip Wood
2026-09-30 9:48 ` [PATCH 1/2] remove_branch_state: convert boolean argument to flags Phillip Wood
2026-09-30 9:48 ` [PATCH 2/2] merge: remember conflict labels Phillip Wood
2026-09-30 16:42 ` Junio C Hamano
2026-10-01 8:54 ` Phillip Wood
2026-10-01 17:14 ` Junio C Hamano
2026-09-30 20:24 ` [PATCH 0/2] checkout -m: recreate " Johannes Sixt
2026-09-30 20:40 ` Junio C Hamano
2026-09-30 21:20 ` Johannes Sixt
2026-10-01 8:45 ` Phillip Wood
2026-10-05 13:24 ` [PATCH v2 " Phillip Wood
2026-10-05 13:24 ` [PATCH v2 1/2] remove_branch_state: convert boolean argument to flags Phillip Wood
2026-10-05 13:24 ` [PATCH v2 2/2] merge: remember conflict labels Phillip Wood
2026-10-05 16:19 ` Junio C Hamano
2026-10-06 15:21 ` Phillip Wood
2026-10-05 16:31 ` Junio C Hamano
2026-10-06 15:05 ` Phillip Wood
2026-10-06 15:46 ` Junio C Hamano
2026-10-07 13:38 ` Phillip Wood
2026-10-05 14:48 ` [PATCH v2 0/2] checkout -m: recreate " Johannes Sixt
2026-10-05 15:09 ` Phillip Wood
2026-10-05 15:53 ` Junio C Hamano
2026-10-09 9:13 ` Phillip Wood [this message]
2026-10-09 9:13 ` [PATCH v3 1/2] remove_branch_state: convert boolean argument to flags Phillip Wood
2026-10-09 9:13 ` [PATCH v3 2/2] merge: remember conflict labels Phillip Wood
2026-10-10 0:24 ` Junio C Hamano
2026-10-09 20:31 ` [PATCH v3 0/2] checkout -m: recreate " Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=cover.1791537203.git.phillip.wood@dunelm.org.uk \
--to=phillip.wood123@gmail.com \
--cc=git@vger.kernel.org \
--cc=j6t@kdbg.org \
--cc=newren@gmail.com \
--cc=phillip.wood@dunelm.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox