Git development
 help / color / mirror / Atom feed
From: Junio C Hamano <gitster@pobox.com>
To: "Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
Cc: git@vger.kernel.org,  Patrick Steinhardt <ps@pks.im>,
	 Johannes Schindelin <johannes.schindelin@gmx.de>
Subject: Re* [PATCH] history: close COMMIT_EDITMSG before launching the editor
Date: Thu, 25 Jun 2026 13:06:52 -0700	[thread overview]
Message-ID: <xmqqh5mqfkpv.fsf@gitster.g> (raw)
In-Reply-To: <pull.2158.git.1782412427801.gitgitgadget@gmail.com> (Johannes Schindelin via GitGitGadget's message of "Thu, 25 Jun 2026 18:33:46 +0000")

"Johannes Schindelin via GitGitGadget" <gitgitgadget@gmail.com>
writes:

> Close the handle once the status has been written, before handing the
> file off to the editor.
>
> Assisted-by: Opus 4.8

Do we even need this?

> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
> ---
>     history: close COMMIT_EDITMSG before launching the editor
>     
>     I noticed this problem while trying to whip MinGit-BusyBox into a better
>     shape during the -rc phase. Technically, this is not a fix for a
>     regression during the v2.55.0 period, but I figured it'd be better to
>     send it now anyway than to forget about sending it after v2.55.0 is
>     released.
>
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2158%2Fdscho%2Ffix-fd-leak-in-history-reword-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2158/dscho/fix-fd-leak-in-history-reword-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/2158
>
>  builtin/history.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
>
> diff --git a/builtin/history.c b/builtin/history.c
> index 9526938085..4a5d9192f3 100644
> --- a/builtin/history.c
> +++ b/builtin/history.c
> @@ -74,6 +74,14 @@ static int fill_commit_message(struct repository *repo,
>  	wt_status_collect_free_buffers(&s);
>  	string_list_clear_func(&s.change, change_data_free);
>  
> +	/*
> +	 * Close the handle before launching the editor: on Windows an open
> +	 * handle would prevent the editor from replacing the file (e.g.
> +	 * BusyBox' `ash` cannot overwrite a file that another process keeps
> +	 * open), and leaving it open leaks the descriptor everywhere else.
> +	 */
> +	fclose(s.fp);
> +
>  	strbuf_reset(out);
>  	if (launch_editor(path, out, NULL)) {
>  		fprintf(stderr, _("Aborting commit as launching the editor failed.\n"));

The function is extremely sloppy beyond words X-<.  Thanks for
taking the first step to clean it up.

 * It calls git_path_commit_editmsg() to obtain a constant pathname
   into "const char *path", but then the part that leads to this
   file stream leak does not even use that "path" constant.  It
   makes two independent calls to git_path_commit_editmsg()!

 * The function first calls write_file_buf() to the file, which is a
   convenience function when you have something you need to write
   upfront and just want to write it and be done with it.

 * And then the unclosed file stream you just fixed.

What is surprising is that all of this was created in a single
commit.  I suspected that this part that does fopen() to leak the
file stream was a later addition than the initial write_file_buf(),
which should have been critiqued with "once you want to do your
custom writing that is more than "I have this block of memory, write
it into file", you should rewrite write_file_buf() call and roll it
into your own custom writing", but that is not the case.

So taking the opportunity to clean things up, how about doing it
this way intead?

----- >8 ---------- >8 ---------- >8 -----

Subject: [PATCH] history: streamline message preparation and plug file stream leak

An early part of fill_commit_mmessage() function uses write_file_buf()
to write out what was prepared in a strbuf, which is primarily meant
for use by callers that have their own message prepared fully and
called as the last thing to flush it to the destination file.

However, the function then opens a file stream in append mode to
further write into it.  It may have been understandable if this was
a later addition, but it seems it came from a single commit,
d205234c (builtin/history: implement "reword" subcommand,
2026-01-13), which is somewhat puzzling, but anyway...

Just open the file stream upfront for writing, write the message
the function has in the strbuf, and then keep writing whatever it
wants to write to the same open file stream.

And do not forget to close the stream.  We are about to pass the
resulting file to an external editor, and on some systems, notably
Windows, you are not supposed to keep a file open while expecting
another program to access it.

Diagnosed-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/history.c | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/builtin/history.c b/builtin/history.c
index 8dcb9a6046..a882ad82e5 100644
--- a/builtin/history.c
+++ b/builtin/history.c
@@ -41,11 +41,6 @@ static int fill_commit_message(struct repository *repo,
 		  " empty message aborts the commit.\n");
 	struct wt_status s;
 
-	strbuf_addstr(out, default_message);
-	strbuf_addch(out, '\n');
-	strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str);
-	write_file_buf(path, out->buf, out->len);
-
 	wt_status_prepare(repo, &s);
 	FREE_AND_NULL(s.branch);
 	s.ahead_behind_flags = AHEAD_BEHIND_QUICK;
@@ -57,14 +52,20 @@ static int fill_commit_message(struct repository *repo,
 	s.whence = FROM_COMMIT;
 	s.committable = 1;
 
-	s.fp = fopen(git_path_commit_editmsg(), "a");
+	s.fp = fopen(path, "w");
 	if (!s.fp)
-		return error_errno(_("could not open '%s'"), git_path_commit_editmsg());
+		return error_errno(_("could not open '%s'"), path);
+
+	strbuf_addstr(out, default_message);
+	strbuf_addch(out, '\n');
+	strbuf_commented_addf(out, comment_line_str, hint, action, comment_line_str);
+	fwrite(out.buf, 1, out.len, s.fp);
 
 	wt_status_collect_changes_trees(&s, old_tree, new_tree);
 	wt_status_print(&s);
 	wt_status_collect_free_buffers(&s);
 	string_list_clear_func(&s.change, change_data_free);
+	fclose(s.fp);
 
 	strbuf_reset(out);
 	if (launch_editor(path, out, NULL)) {
-- 
2.55.0-rc2-165-g3249676ba5


  reply	other threads:[~2026-06-25 20:06 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-06-25 18:33 [PATCH] history: close COMMIT_EDITMSG before launching the editor Johannes Schindelin via GitGitGadget
2026-06-25 20:06 ` Junio C Hamano [this message]
2026-06-25 20:12   ` Re* " 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=xmqqh5mqfkpv.fsf@gitster.g \
    --to=gitster@pobox.com \
    --cc=git@vger.kernel.org \
    --cc=gitgitgadget@gmail.com \
    --cc=johannes.schindelin@gmx.de \
    --cc=ps@pks.im \
    /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