From: Junio C Hamano <gitster@pobox.com>
To: Phillip Wood <phillip.wood123@gmail.com>
Cc: git@vger.kernel.org, Elijah Newren <newren@gmail.com>
Subject: Re: [PATCH 2/2] merge: remember conflict labels
Date: Wed, 30 Sep 2026 09:42:48 -0700 [thread overview]
Message-ID: <xmqq1paad71z.fsf@gitster.g> (raw)
In-Reply-To: <fdaf3da993366878b51bd0b2a950888710cafb8a.1790761727.git.phillip.wood@dunelm.org.uk> (Phillip Wood's message of "Wed, 30 Sep 2026 10:48:49 +0100")
Phillip Wood <phillip.wood123@gmail.com> writes:
> From: Phillip Wood <phillip.wood@dunelm.org.uk>
>
> 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
> write ".git/MERGE_LABELS" when it updates the worktree and
> there are conflicts. The labels are then read from that file by "git
> checkout -m <path>" when recreating the conflicts.
>
> As "git checkout -m <branch>" calls remove_branch_state() which
> ordinarily removes the labels file, we need to pass a flag down
> to optionally prevent that so that the labels are available for any
> subsequent "git checkout -m <path>". Note that merge_switch_to_result()
> we assign "result->priv" to "opt->priv" and later clear "opt->priv" in
> order to get a pointer to the private struct as result->priv is void*.
>
> Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk>
> ---
> branch.c | 11 ++++++--
> branch.h | 1 +
> builtin/checkout.c | 24 +++++++++++++++---
> builtin/commit.c | 1 +
> merge-ort.c | 19 ++++++++++++++
> merge.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++
> merge.h | 4 +++
> path.c | 1 +
> path.h | 1 +
> repository.c | 1 +
> repository.h | 1 +
> sequencer.c | 1 +
> t/t7201-co.sh | 21 ++++++++++++++++
> 13 files changed, 143 insertions(+), 6 deletions(-)
Where do we talk about MERGE_HEAD and CHERRY_PICK_HEAD in the
current documentation set? Do we want to mention MERGE_LABELS
alongside them?
> +
> +int write_merge_labels(struct repository *r, const char *base,
> + const char *ours, const char *theirs)
> +{
> + 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);
> + if (fclose(f))
> + return error_errno("could not write '%s'",
> + git_path_merge_labels(r));
> +
> + return 0;
> +}
> +
We write three items, one per line, delimited by LF. As this goes
through stdio, wouldn't Windows write CRLF-delimited lines? I guess
if we read this back through stdio, that will cancel out and we get
the LF-delimited lines back?
Wait. Do we want to read this file via stdio, one line at a time,
using three calls to fgets()? No, we do not give a strict upper
limit to the length of these labels. So if we read with
strbuf_read_line() or something, we would be safe, I guess, but alas
there is no such helper function X-<.
> +static int parse_merge_label_line(const char **p, char **line)
> +{
> + const char *eol = strchr(*p, '\n');
> +
> + if (!eol)
> + return -1;
> +
> + *line = xmemdupz(*p, eol - *p);
> + *p = eol + 1;
> +
> + return 0;
> +}
OK, this reads one line at a time from the file contents already
fully read by strbuf_read_file(), as seen below.
Which means that the CRLF fprintf() may have written in
write_merge_labels() will come back to this function, and our 'ours'
may become 'ours\015' after stripping only the LF at the end?
> +int read_merge_labels(struct repository *r,
> + char **pbase, char** pours, char** ptheirs)
> +{
> + struct strbuf buf = STRBUF_INIT;
> + const char *p;
> + char *base = NULL, *ours = NULL, *theirs = NULL;
> + int ret = -1;
> +
> + if (strbuf_read_file(&buf, git_path_merge_labels(r), 0) < 0)
> + return -1;
Can strbuf_read_file() fill '.buf' halfway and return a failure, or
does it ensure that it frees '.buf' before returning failure? Just
double-checking.
... goes and checks ...
strbuf_read_file() calls strbuf_read(), which calls read_in_full() to
fill a sufficiently large buffer, and a failure from there results in
strbuf_release() or strbuf_setlen() resetting back to the '.len'
before strbuf_read() was called (i.e., 0 in this case), so we do not
leak anything on the error path and this code is safe, I think.
> +
> + p = buf.buf;
> + if (parse_merge_label_line(&p, &base))
> + goto out;
> + if (parse_merge_label_line(&p, &ours))
> + goto out;
> + if (parse_merge_label_line(&p, &theirs))
> + goto out;
OK, we read three things.
> + ret = 0;
> + *pbase = base;
> + *pours = ours;
> + *ptheirs = theirs;
> +out:
> + if (ret) {
> + free(base);
> + free(ours);
> + free(theirs);
> + }
> + strbuf_release(&buf);
OK, so the contract is that we will not touch p{base,ours,theirs}
if we return failure, and we will not leak anything when doing so.
Which is very sensible.
> + return ret;
> +}
Looking good so far, modulo a small worry about writing via stdio
and reading back while bypassing stdio. But perhaps CRLF is so
annoying that the compat/mingw layer takes care of all of the above
worries by passing the 'binary' bit down to the msvcrt/ucrt layer,
in which case we should not have to worry about it. I dunno.
Thanks for working on these patches.
next prev parent reply other threads:[~2026-09-30 16:42 UTC|newest]
Thread overview: 29+ 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 [this message]
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 ` [PATCH v3 " Phillip Wood
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-12 14:10 ` Phillip Wood
2026-10-12 15:09 ` 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=xmqq1paad71z.fsf@gitster.g \
--to=gitster@pobox.com \
--cc=git@vger.kernel.org \
--cc=newren@gmail.com \
--cc=phillip.wood123@gmail.com \
/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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.