Git development
 help / color / mirror / Atom feed
* Re: [PATCH 2/2] builtin/init-db: deprecate alias for git-init(1)
From: Junio C Hamano @ 2026-06-02 13:50 UTC (permalink / raw)
  To: Phillip Wood; +Cc: Patrick Steinhardt, phillip.wood, Kristoffer Haugsbakk, git
In-Reply-To: <336a4202-a55f-4223-b654-985d47233653@gmail.com>

Phillip Wood <phillip.wood123@gmail.com> writes:

>> I was wondering whether we want to call `you_still_use_that()` here. I
>> found it to be a bit heavy-handed as it's so trivial to replace with
>> git-init(1), but on the other hand it's a trivial thing to do.
>
> I agree you_still_use_that() is too heavy handed, I was thinking of 
> something like
>
> 	warning(_("this command is deprecated, please use \"git init\""
> 		  "instead");
>
> but that would mean we need to add a separate cmd_init_db() function 
> that prints the warning and then calls cmd_init().

If we do plan to remove it in the future, then something like that
may be needed.

But it is not like having "init-db" hidden but accessible in the
command table is hurting anything.  Other than that those who want
to create their own

    [alias "init-db"] command = foo

that is, and I'd see it a bit crazy.

The "init-db" form is hidden from "git help" listing, and we know
whenever we suggest to run "git init" we do not say "git init-db",
so if we do not have to remove it in the future, I do not think we
even need such a warning().


^ permalink raw reply

* Re: [PATCH 2/2] SubmittingPatches: describe cover letter
From: Junio C Hamano @ 2026-06-02 13:43 UTC (permalink / raw)
  To: Derrick Stolee; +Cc: git
In-Reply-To: <fd588cff-be2b-4422-9c01-cef06b2ea5fd@gmail.com>

Derrick Stolee <stolee@gmail.com> writes:

>> +. Make sure your target audience can understand what the patches are
>> +  about and why they are needed without prior context.
>
> The thing that I like to say about the cover letter is that this is
> your opportunity to communicate why the value of your change is worth
> the risk of regressions and the cost of maintenance. Perhaps:
>
> . Every code change comes with risk of regression and maintenance cost.
>   The cover letter should clearly communicate why the value of your
>   proposed change is worth applying. You can also describe how the risk
>   is reduced by the design choices you made while writing the patches.
>
> Or something similar may be helpful? I may just be over explaining.

Yeah, it may be a bit on the heavy side, but complements what I
wanted to achieve with this update very well.  I wanted to encourage
writing for wider audience, without leaving those "not in the know"
behind.  What you wrote above is more about what to write, which is
very much appreciated.  I think it fits well as the 0th item before
the three-bullet list.

>> +. For a second or subsequent iteration of the same topic, make sure
>> +  people who missed the earlier discussion can still understand what
>> +  the patches are about, so they can judge if the topic is worth their
>> +  time to read and comment on.
>> +
>> +. To help those who are familiar with earlier iterations, give a
>> +  summary of changes since the previous rounds.
>
> I find these updates to be particularly helpful, even for GitGitGadget
> PRs that include a range-diff automatically. It's good to double-check
> the human description of the update against the computed diff.

Oh, absolutely.

A GitGitGadget generated cover letter that lack any human input but
just range-diff dump is often very hard to read, and the receiving
end is better off pretending there was no useful information in the
cover letter.  "git diff @{-1}..." after applying the patches to the
same base is sadly a lot easier to read than "git range-diff @{-1}..."
for many series.

^ permalink raw reply

* Re: [PATCH v11 0/6] branch: prune-merged
From: Harald Nordgren @ 2026-06-02 13:41 UTC (permalink / raw)
  To: phillip.wood
  Cc: Harald Nordgren via GitGitGadget, git, Kristoffer Haugsbakk,
	Johannes Sixt
In-Reply-To: <9b44d867-219a-4ca3-b8ae-67fdac1c72f6@gmail.com>

> Hi Harald
>
> Just a quick note to say I've not forgotten about this, hopefully I
> should have time to review it later in the week now I'm back on the list.
>
> Thanks
>
> Phillip

Great to hear! Thanks!


Harald

^ permalink raw reply

* [PATCH v5 2/2] config: improve diagnostic for "set" with missing value
From: Harald Nordgren via GitGitGadget @ 2026-06-02 13:39 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2302.v5.git.git.1780407557.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

"git config set pull.rebase=false" currently fails with "wrong
number of arguments", and the implicit form "git config
pull.rebase=false" fails with "invalid key". Neither points at
the real problem: the value is missing.

Report that directly, and when the argument has the shape
"<valid-key>=<value>", also suggest the split form:

    $ git config set pull.rebase=false
    error: missing value to set to the variable 'pull.rebase=false'
    hint: did you mean "git config set pull.rebase false"?

When the prefix before "=" is not a valid key, drop the hint:

    $ git config set foo=bar
    error: missing value to set to a variable with an invalid name 'foo=bar'

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/config.c  | 32 ++++++++++++++++++++++++++-
 t/t1300-config.sh | 55 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 86 insertions(+), 1 deletion(-)

diff --git a/builtin/config.c b/builtin/config.c
index b3188cd8d4..a2d46d0ce1 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -1,6 +1,7 @@
 #define USE_THE_REPOSITORY_VARIABLE
 #include "builtin.h"
 #include "abspath.h"
+#include "advice.h"
 #include "config.h"
 #include "color.h"
 #include "date.h"
@@ -210,6 +211,26 @@ static void check_argc(int argc, int min, int max)
 	exit(129);
 }
 
+static NORETURN void die_missing_set_value(const char *arg)
+{
+	const char *last_dot = strrchr(arg, '.');
+	const char *eq = last_dot ? strchr(last_dot + 1, '=') : NULL;
+	char *prefix = eq ? xstrndup(arg, eq - arg) : NULL;
+
+	if (prefix && !git_config_parse_key(prefix, NULL, NULL, 1)) {
+		error(_("missing value to set to the variable '%s'"), arg);
+		advise(_("did you mean \"git config set %s %s\"?"),
+		       prefix, eq + 1);
+	} else if (!git_config_parse_key(arg, NULL, NULL, 1)) {
+		error(_("missing value to set to the variable '%s'"), arg);
+	} else {
+		error(_("missing value to set to a variable with an invalid name '%s'"),
+		      arg);
+	}
+	free(prefix);
+	exit(129);
+}
+
 static void show_config_origin(const struct config_display_options *opts,
 			       const struct key_value_info *kvi,
 			       struct strbuf *buf)
@@ -1133,6 +1154,8 @@ static int cmd_config_set(int argc, const char **argv, const char *prefix,
 
 	argc = parse_options(argc, argv, prefix, opts, builtin_config_set_usage,
 			     PARSE_OPT_STOP_AT_NON_OPTION);
+	if (argc == 1)
+		die_missing_set_value(argv[0]);
 	check_argc(argc, 2, 2);
 
 	if ((flags & CONFIG_FLAGS_FIXED_VALUE) && !value_pattern)
@@ -1371,6 +1394,7 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
 	};
 	char *value = NULL, *comment = NULL;
 	int ret = 0;
+	int actions_implicit;
 	struct key_value_info default_kvi = KVI_INIT;
 
 	argc = parse_options(argc, argv, prefix, opts,
@@ -1385,7 +1409,8 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
 		exit(129);
 	}
 
-	if (actions == 0)
+	actions_implicit = (actions == 0);
+	if (actions_implicit)
 		switch (argc) {
 		case 1: actions = ACTION_GET; break;
 		case 2: actions = ACTION_SET; break;
@@ -1394,6 +1419,11 @@ static int cmd_config_actions(int argc, const char **argv, const char *prefix)
 			error(_("no action specified"));
 			exit(129);
 		}
+	if (actions_implicit && argc == 1) {
+		const char *last_dot = strrchr(argv[0], '.');
+		if (last_dot && strchr(last_dot + 1, '='))
+			die_missing_set_value(argv[0]);
+	}
 	if (display_opts.omit_values &&
 	    !(actions == ACTION_LIST || actions == ACTION_GET_REGEXP)) {
 		error(_("--name-only is only applicable to --list or --get-regexp"));
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 11fc976f3a..ed122d1100 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -469,6 +469,61 @@ test_expect_success 'invalid key' '
 	test_must_fail git config inval.2key blabla
 '
 
+test_expect_success 'set with 1 arg of "key=value": valid key suggests split form' '
+	test_must_fail git config set pull.rebase=false 2>err &&
+	test_grep "missing value to set to the variable .pull\\.rebase=false." err &&
+	test_grep "did you mean .git config set pull\\.rebase false." err
+'
+
+test_expect_success 'set with 1 arg of "key=value": implicit form suggests split form' '
+	test_must_fail git config pull.rebase=false 2>err &&
+	test_grep "missing value to set to the variable .pull\\.rebase=false." err &&
+	test_grep "did you mean .git config set pull\\.rebase false." err
+'
+
+test_expect_success 'set with 1 arg of "key=value": invalid key does not suggest split form' '
+	test_must_fail git config set foo=bar 2>err &&
+	test_grep "missing value to set to a variable with an invalid name .foo=bar." err &&
+	test_grep ! "did you mean" err
+'
+
+test_expect_success 'set with 1 arg: variable name starting with digit is invalid' '
+	test_must_fail git config set foo.1bar=baz 2>err &&
+	test_grep "missing value to set to a variable with an invalid name .foo\\.1bar=baz." err &&
+	test_grep ! "did you mean" err
+'
+
+test_expect_success 'set with 1 arg: digit-led section name is valid' '
+	test_must_fail git config set 1foo.bar=baz 2>err &&
+	test_grep "missing value to set to the variable .1foo\\.bar=baz." err &&
+	test_grep "did you mean .git config set 1foo\\.bar baz." err
+'
+
+test_expect_success 'set with 1 arg: subsection plus invalid variable name' '
+	test_must_fail git config set foo.some.b_r=baz 2>err &&
+	test_grep "missing value to set to a variable with an invalid name .foo\\.some\\.b_r=baz." err &&
+	test_grep ! "did you mean" err
+'
+
+test_expect_success 'set with 1 arg of valid key reports missing value' '
+	test_must_fail git config set pull.rebase 2>err &&
+	test_grep "missing value to set to the variable .pull\\.rebase." err &&
+	test_grep ! "did you mean" err
+'
+
+test_expect_success 'set with 2 args including "=" in invalid key does not suggest' '
+	test_must_fail git config set pull.rebase=false true 2>err &&
+	test_grep ! "did you mean" err
+'
+
+test_expect_success '"=" inside subsection is valid' '
+	test_when_finished "rm -f subsection.cfg" &&
+	git config set -f subsection.cfg foo.bar=baz.boo qux &&
+	echo qux >expect &&
+	git config get -f subsection.cfg foo.bar=baz.boo >actual &&
+	test_cmp expect actual
+'
+
 test_expect_success 'correct key' '
 	git config 123456.a123 987
 '
-- 
gitgitgadget

^ permalink raw reply related

* [PATCH v5 1/2] config: let git_config_parse_key() validate quietly
From: Harald Nordgren via GitGitGadget @ 2026-06-02 13:39 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2302.v5.git.git.1780407557.gitgitgadget@gmail.com>

From: Harald Nordgren <haraldnordgren@gmail.com>

Add a "quiet" parameter that suppresses the error() calls, and let
store_key be NULL to skip the canonical-copy allocation.  Existing
callers pass 0 for quiet.

Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
 builtin/config.c   |  2 +-
 config.c           | 34 ++++++++++++++++++++++------------
 config.h           |  2 +-
 submodule-config.c |  2 +-
 4 files changed, 25 insertions(+), 15 deletions(-)

diff --git a/builtin/config.c b/builtin/config.c
index cf4ba0f7cc..b3188cd8d4 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -555,7 +555,7 @@ static int get_value(const struct config_location_options *opts,
 			goto free_strings;
 		}
 	} else {
-		if (git_config_parse_key(key_, &key, NULL)) {
+		if (git_config_parse_key(key_, &key, NULL, 0)) {
 			ret = CONFIG_INVALID_KEY;
 			goto free_strings;
 		}
diff --git a/config.c b/config.c
index a1b92fe083..81b31c5155 100644
--- a/config.c
+++ b/config.c
@@ -536,11 +536,14 @@ static inline int iskeychar(int c)
  * -2 if there is no section name in the key.
  *
  * store_key - pointer to char* which will hold a copy of the key with
- *             lowercase section and variable name
+ *             lowercase section and variable name, can be NULL to skip
+ *             allocation when only validation is needed
  * baselen - pointer to size_t which will hold the length of the
  *           section + subsection part, can be NULL
+ * quiet - when non-zero, suppress error() reports on rejection
  */
-int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
+int git_config_parse_key(const char *key, char **store_key, size_t *baselen_,
+			 int quiet)
 {
 	size_t i, baselen;
 	int dot;
@@ -552,12 +555,14 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
 	 */
 
 	if (last_dot == NULL || last_dot == key) {
-		error(_("key does not contain a section: %s"), key);
+		if (!quiet)
+			error(_("key does not contain a section: %s"), key);
 		return -CONFIG_NO_SECTION_OR_NAME;
 	}
 
 	if (!last_dot[1]) {
-		error(_("key does not contain variable name: %s"), key);
+		if (!quiet)
+			error(_("key does not contain variable name: %s"), key);
 		return -CONFIG_NO_SECTION_OR_NAME;
 	}
 
@@ -568,7 +573,8 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
 	/*
 	 * Validate the key and while at it, lower case it for matching.
 	 */
-	*store_key = xmallocz(strlen(key));
+	if (store_key)
+		*store_key = xmallocz(strlen(key));
 
 	dot = 0;
 	for (i = 0; key[i]; i++) {
@@ -579,21 +585,25 @@ int git_config_parse_key(const char *key, char **store_key, size_t *baselen_)
 		if (!dot || i > baselen) {
 			if (!iskeychar(c) ||
 			    (i == baselen + 1 && !isalpha(c))) {
-				error(_("invalid key: %s"), key);
+				if (!quiet)
+					error(_("invalid key: %s"), key);
 				goto out_free_ret_1;
 			}
 			c = tolower(c);
 		} else if (c == '\n') {
-			error(_("invalid key (newline): %s"), key);
+			if (!quiet)
+				error(_("invalid key (newline): %s"), key);
 			goto out_free_ret_1;
 		}
-		(*store_key)[i] = c;
+		if (store_key)
+			(*store_key)[i] = c;
 	}
 
 	return 0;
 
 out_free_ret_1:
-	FREE_AND_NULL(*store_key);
+	if (store_key)
+		FREE_AND_NULL(*store_key);
 	return -CONFIG_INVALID_KEY;
 }
 
@@ -609,7 +619,7 @@ static int config_parse_pair(const char *key, const char *value,
 
 	if (!strlen(key))
 		return error(_("empty config key"));
-	if (git_config_parse_key(key, &canonical_name, NULL))
+	if (git_config_parse_key(key, &canonical_name, NULL, 0))
 		return -1;
 
 	ret = (fn(canonical_name, value, &ctx, data) < 0) ? -1 : 0;
@@ -1708,7 +1718,7 @@ static int configset_find_element(struct config_set *set, const char *key,
 	 * `key` may come from the user, so normalize it before using it
 	 * for querying entries from the hashmap.
 	 */
-	ret = git_config_parse_key(key, &normalized_key, NULL);
+	ret = git_config_parse_key(key, &normalized_key, NULL, 0);
 	if (ret)
 		return ret;
 
@@ -3001,7 +3011,7 @@ int repo_config_set_multivar_in_file_gently(struct repository *r,
 	validate_comment_string(comment);
 
 	/* parse-key returns negative; flip the sign to feed exit(3) */
-	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen);
+	ret = 0 - git_config_parse_key(key, &store.key, &store.baselen, 0);
 	if (ret)
 		goto out_free;
 
diff --git a/config.h b/config.h
index bf47fb3afc..2c66d334c1 100644
--- a/config.h
+++ b/config.h
@@ -341,7 +341,7 @@ int repo_config_set_worktree_gently(struct repository *, const char *, const cha
  */
 void repo_config_set(struct repository *, const char *, const char *);
 
-int git_config_parse_key(const char *, char **, size_t *);
+int git_config_parse_key(const char *, char **, size_t *, int quiet);
 
 /*
  * The following macros specify flag bits that alter the behavior
diff --git a/submodule-config.c b/submodule-config.c
index a81897b4e0..a319956f7a 100644
--- a/submodule-config.c
+++ b/submodule-config.c
@@ -970,7 +970,7 @@ int print_config_from_gitmodules(struct repository *repo, const char *key)
 	int ret;
 	char *store_key;
 
-	ret = git_config_parse_key(key, &store_key, NULL);
+	ret = git_config_parse_key(key, &store_key, NULL, 0);
 	if (ret < 0)
 		return CONFIG_INVALID_KEY;
 
-- 
gitgitgadget


^ permalink raw reply related

* [PATCH v5 0/2] config: suggest the correct form when key contains "="
From: Harald Nordgren via GitGitGadget @ 2026-06-02 13:39 UTC (permalink / raw)
  To: git; +Cc: Kristoffer Haugsbakk, Harald Nordgren
In-Reply-To: <pull.2302.v4.git.git.1779823288005.gitgitgadget@gmail.com>

 * New commit config: let git_config_parse_key() validate quietly adds a
   quiet parameter (and an optional store_key) so callers can validate
   without writing to stderr.
 * Validation in die_missing_set_value() now routes through
   git_config_parse_key(key, NULL, NULL, 1) instead of the previous local
   helper.
 * Added tests for 1foo.bar=baz and foo.some.b_r=baz.

Harald Nordgren (2):
  config: let git_config_parse_key() validate quietly
  config: improve diagnostic for "set" with missing value

 builtin/config.c   | 34 ++++++++++++++++++++++++++--
 config.c           | 34 ++++++++++++++++++----------
 config.h           |  2 +-
 submodule-config.c |  2 +-
 t/t1300-config.sh  | 55 ++++++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 111 insertions(+), 16 deletions(-)


base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2302%2FHaraldNordgren%2Fconfig-hint-equals-key-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2302/HaraldNordgren/config-hint-equals-key-v5
Pull-Request: https://github.com/git/git/pull/2302

Range-diff vs v4:

 -:  ---------- > 1:  d938ebf95a config: let git_config_parse_key() validate quietly
 1:  780b99409c ! 2:  e5a2070ee1 config: improve diagnostic for "set" with missing value
     @@ builtin/config.c: static void check_argc(int argc, int min, int max)
       	exit(129);
       }
       
     -+static int is_valid_key(const char *key)
     -+{
     -+	const char *last_dot = strrchr(key, '.');
     -+
     -+	return last_dot && isalpha(last_dot[1]);
     -+}
     -+
      +static NORETURN void die_missing_set_value(const char *arg)
      +{
      +	const char *last_dot = strrchr(arg, '.');
      +	const char *eq = last_dot ? strchr(last_dot + 1, '=') : NULL;
      +	char *prefix = eq ? xstrndup(arg, eq - arg) : NULL;
      +
     -+	if (prefix && is_valid_key(prefix)) {
     ++	if (prefix && !git_config_parse_key(prefix, NULL, NULL, 1)) {
      +		error(_("missing value to set to the variable '%s'"), arg);
      +		advise(_("did you mean \"git config set %s %s\"?"),
      +		       prefix, eq + 1);
     -+	} else if (is_valid_key(arg)) {
     ++	} else if (!git_config_parse_key(arg, NULL, NULL, 1)) {
      +		error(_("missing value to set to the variable '%s'"), arg);
      +	} else {
      +		error(_("missing value to set to a variable with an invalid name '%s'"),
     @@ t/t1300-config.sh: test_expect_success 'invalid key' '
      +	test_grep ! "did you mean" err
      +'
      +
     ++test_expect_success 'set with 1 arg: digit-led section name is valid' '
     ++	test_must_fail git config set 1foo.bar=baz 2>err &&
     ++	test_grep "missing value to set to the variable .1foo\\.bar=baz." err &&
     ++	test_grep "did you mean .git config set 1foo\\.bar baz." err
     ++'
     ++
     ++test_expect_success 'set with 1 arg: subsection plus invalid variable name' '
     ++	test_must_fail git config set foo.some.b_r=baz 2>err &&
     ++	test_grep "missing value to set to a variable with an invalid name .foo\\.some\\.b_r=baz." err &&
     ++	test_grep ! "did you mean" err
     ++'
     ++
      +test_expect_success 'set with 1 arg of valid key reports missing value' '
      +	test_must_fail git config set pull.rebase 2>err &&
      +	test_grep "missing value to set to the variable .pull\\.rebase." err &&

-- 
gitgitgadget

^ permalink raw reply

* Re: [PATCH 2/2] SubmittingPatches: describe cover letter
From: Junio C Hamano @ 2026-06-02 13:36 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <ah7HZuy_WRCD9ZZ-@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Tue, Jun 02, 2026 at 06:08:08PM +0900, Junio C Hamano wrote:
>> We talk about how a commit log message should look like, but do not
>> give advice on writing the cover letter to sell a series to widest
>
> s/to widest/to the widest/?

Thanks.

>> +[[cover-letter]]
>> +=== Cover Letter
>> +
>> +The purpose of your cover letter is to sell your changes, explain what
>> +they are about, and get your target audience interested enough to read
>> +the patches.
>> +
>> +. Make sure your target audience can understand what the patches are
>> +  about and why they are needed without prior context.
>> +
>> +. For a second or subsequent iteration of the same topic, make sure
>> +  people who missed the earlier discussion can still understand what
>> +  the patches are about, so they can judge if the topic is worth their
>> +  time to read and comment on.
>> +
>> +. To help those who are familiar with earlier iterations, give a
>> +  summary of changes since the previous rounds.
>
> We might also recommend to include a range-diff in subsequent
> iterations. That being said though, I just sent a small series to the
> mailing list that recommends using b4, and there it get this for free.
> So no idea whether it's still worth it to then cover this here
> explicitly.

I think these are orthogonal.  What b4 helps you with is the shape
of the letter, how it looks like.  This update is about the contents
in the letter, what you convey to your readers.

Of course, "format-patch --cover-letter" also lets you do range-diff
or interdiff, so they come for free.  But the above description is
not tied to any particular tool to prepare your cover letter.


^ permalink raw reply

* Re: [PATCH 1/2] b4: introduce configuration for the Git project
From: Junio C Hamano @ 2026-06-02 13:32 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <20260602-pks-b4-v1-1-a7ae5a49e9cf@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> We're about to extend our documentation to recommend b4 for sending
> patch series ot the mailing list. Prepare for this by introducing a b4
> configuration so that the tool knows to honor our preferences. For now,
> this configuration does two things:
>
>   - It configures "send-same-thread = shallow", which tells b4 to always
>     send subsequent versions of the same patch series as a reply to the
>     cover letter of the first version.
>
>   - It configures "prep-cover-template", which tells b4 to use a custom
>     template for the cover letter. The most important change compared to
>     the default template is that our custom template also includes a
>     range-diff.
>
> There's potentially more things that we may want to configure going
> forward, like for example auto-configuration of folks to Cc on certain
> patches. But these two tweaks feel like a good place to start.
>
> Signed-off-by: Patrick Steinhardt <ps@pks.im>
> ---
>  .b4-config         |  3 +++
>  .b4-cover-template | 11 +++++++++++
>  2 files changed, 14 insertions(+)

Shipping a sample like ".b4-config.sample" that users who opt-in can
copy-and-edit into the final name ".b4-config" is OK, but I'd rather
not to ship the configuration files that the users would want to edit
(hence making the tree dirty).

^ permalink raw reply

* Re: [PATCH 3/4] t/lib-git-p4: silence output when killing p4d and its watchdog
From: Junio C Hamano @ 2026-06-02 13:16 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git
In-Reply-To: <ah6uZ6tdIh38X2uZ@pks.im>

Patrick Steinhardt <ps@pks.im> writes:

> On Tue, Jun 02, 2026 at 06:32:55PM +0900, Junio C Hamano wrote:
>> Patrick Steinhardt <ps@pks.im> writes:
>> 
>> >  stop_p4d_and_watchdog () {
>> >  	kill -9 $p4d_pid $watchdog_pid
>> > +	wait $p4d $watchdog_pid 2>/dev/null
>> >  }
>> 
>> Shoudln't we be waiting on $p4d_pid (not $p4d)...
>> 
>> > @@ -175,7 +176,7 @@ retry_until_success () {
>> >  
>> >  stop_and_cleanup_p4d () {
>> >  	kill -9 $p4d_pid $watchdog_pid
>> > -	wait $p4d_pid
>> > +	wait $p4d_pid $watchdog_pid 2>/dev/null
>> >  	rm -rf "$db" "$cli" "$pidfile"
>> >  }
>> 
>> ... like we do here?
>
> Oh, good catch. The statement basically doesn't do anything, which isn't
> much of a problem because we really only care about silencing the error
> message when the watchdog is being terminated. Will fix.

Thanks.  Another thing I noticed is that they look suspiciously
similar.

^ permalink raw reply

* Re: [PATCH 2/2] builtin/init-db: deprecate alias for git-init(1)
From: Phillip Wood @ 2026-06-02 13:12 UTC (permalink / raw)
  To: Patrick Steinhardt, Junio C Hamano
  Cc: Kristoffer Haugsbakk, Phillip Wood, git
In-Reply-To: <ah7N5bKAiAORtNkp@pks.im>

On 02/06/2026 13:34, Patrick Steinhardt wrote:
> 
> That's entirely fair. My take on this is a bit different, as I think
> it's beneficial to accept a short-term adjustment for core contributors
> in favor of making stuff easier to discover/maintain going forward.
> > A new contributor would probably be quick to learn that every
> `cmd_foo()` entry point is named exactly the same as the subcommand
> name, but they will then eventually trip over the few exceptions like
> `cmd_init_db()` where that assumption doesn't hold.

Yes, those exceptions to the rule are annoying. Though they mostly exist 
for a good reason (code sharing between builtin commands), it would be 
nice to minimize them where we can.

Thanks

Phillip


^ permalink raw reply

* Re: [PATCH 2/2] builtin/init-db: deprecate alias for git-init(1)
From: Phillip Wood @ 2026-06-02 13:09 UTC (permalink / raw)
  To: Patrick Steinhardt, phillip.wood; +Cc: Kristoffer Haugsbakk, git
In-Reply-To: <ah2VL-ftCQelNoOc@pks.im>

Hi Patrick

On 01/06/2026 15:20, Patrick Steinhardt wrote:
> On Mon, Jun 01, 2026 at 02:48:05PM +0100, Phillip Wood wrote:
>>
>>
>> On 01/06/2026 13:10, Patrick Steinhardt wrote:
>>> On Mon, Jun 01, 2026 at 11:31:46AM +0200, Kristoffer Haugsbakk wrote:
>>>> On Mon, Jun 1, 2026, at 09:56, Patrick Steinhardt wrote:
>>>>> diff --git a/git.c b/git.c
>>>>> index a72394b599..6bf6a60360 100644
>>>>> --- a/git.c
>>>>> +++ b/git.c
>>>>> @@ -591,7 +591,9 @@ static struct cmd_struct commands[] = {
>>>>>    	{ "hook", cmd_hook, RUN_SETUP_GENTLY },
>>>>>    	{ "index-pack", cmd_index_pack, RUN_SETUP_GENTLY | NO_PARSEOPT },
>>>>>    	{ "init", cmd_init },
>>>>> +#ifndef WITH_BREAKING_CHANGES
>>>>>    	{ "init-db", cmd_init },
>>>>
>>>> This can be marked as deprecated.
>>>>
>>>> 	{ "init-db", cmd_init, DEPRECATED },
>>>
>>> Ah, indeed! Added locally now, thanks.
>>
>> Deprecating this command seems very sensible to me. As well as marking it
>> deprecated, do we want to print a warning when it is run? I imagine anyone
>> who has this command in their muscle memory is unlikely to be reading the
>> man page on a regular basis so wont see the warning there.
> 
> I was wondering whether we want to call `you_still_use_that()` here. I
> found it to be a bit heavy-handed as it's so trivial to replace with
> git-init(1), but on the other hand it's a trivial thing to do.

I agree you_still_use_that() is too heavy handed, I was thinking of 
something like

	warning(_("this command is deprecated, please use \"git init\""
		  "instead");

but that would mean we need to add a separate cmd_init_db() function 
that prints the warning and then calls cmd_init().

Thanks

Phillip


^ permalink raw reply

* Re: [PATCH v11 0/6] branch: prune-merged
From: Phillip Wood @ 2026-06-02 13:05 UTC (permalink / raw)
  To: Harald Nordgren via GitGitGadget, git
  Cc: Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <pull.2285.v11.git.git.1779449498.gitgitgadget@gmail.com>

Hi Harald

Just a quick note to say I've not forgotten about this, hopefully I 
should have time to review it later in the week now I'm back on the list.

Thanks

Phillip

On 22/05/2026 12:31, Harald Nordgren via GitGitGadget wrote:
> After releasing v10, I hard-reset back to v9 and reworked the series from
> there.
> 
>   * The flags now take a branch, not a remote. --forked and --prune-merged
>     accept a literal upstream short name like origin/main or a wildmatch
>     pattern like origin/*. The old --all-remotes flag is gone, since origin/*
>     covers that case.
>   * The prune guard now compares @{push} against @{upstream}. A branch is
>     spared when these are equal. That is the trunk like case, such as local
>     main tracking and pushing to origin/main, where "fully merged to
>     upstream" cannot be told apart from "just pulled". Only branches that
>     push somewhere other than their upstream, typically fork based topics,
>     are candidates. The earlier <remote>/HEAD by name guard that the reviewer
>     rejected is gone.
>   * New --dry-run for --prune-merged.
> 
> Harald Nordgren (6):
>    branch: add --forked <branch>
>    branch: let delete_branches warn instead of error on bulk refusal
>    branch: prepare delete_branches for a bulk caller
>    branch: add --prune-merged <branch>
>    branch: add branch.<name>.pruneMerged opt-out
>    branch: add --dry-run for --prune-merged
> 
>   Documentation/config/branch.adoc |   7 +
>   Documentation/git-branch.adoc    |  42 ++++
>   builtin/branch.c                 | 303 +++++++++++++++++++++++++--
>   t/t3200-branch.sh                | 347 +++++++++++++++++++++++++++++++
>   4 files changed, 682 insertions(+), 17 deletions(-)
> 
> 
> base-commit: aec3f587505a472db67e9462d0702e7d463a449d
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2285%2FHaraldNordgren%2Ffetch-prune-local-branches-v11
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2285/HaraldNordgren/fetch-prune-local-branches-v11
> Pull-Request: https://github.com/git/git/pull/2285
> 
> Range-diff vs v10:
> 
>   1:  f2df159830 ! 1:  b9fddd124a branch: add --forked <branch>
>       @@ Metadata
>         ## Commit message ##
>            branch: add --forked <branch>
>        
>       -            git branch --forked <branch>...
>       +    List local branches whose configured upstream
>       +    (branch.<name>.merge resolved against branch.<name>.remote)
>       +    matches any of the given <branch> arguments.
>        
>       -    lists local branches whose configured upstream matches any
>       -    of the given <branch> arguments.
>       +    Each <branch> is interpreted against the local repository, not
>       +    against any specific remote:
>        
>       -    Each <branch> is resolved to the same kind of ref that
>       -    branch.<name>.remote and branch.<name>.merge together point at:
>       -    a remote-tracking branch (e.g. origin/master), or, for branches
>       -    tracking a local upstream, a local branch (e.g. master).
>       -    Shell-style globs are also accepted (e.g. 'origin/*'). Multiple
>       -    arguments are unioned.
>       +      * a literal upstream short name, e.g. "origin/main" or "master"
>       +        for a branch whose upstream is local;
>       +      * a wildmatch pattern, e.g. "origin/*";
>       +      * a bare configured-remote name, e.g. "origin", which resolves
>       +        to whatever refs/remotes/origin/HEAD points at, matching how
>       +        "git checkout -b topic origin" picks a starting point.
>        
>       -    This is the building block for --prune-merged.
>       +    The literal-vs-wildcard distinction is settled at parse time so
>       +    the per-branch matching loop calls wildmatch() only for genuine
>       +    wildcards. Multiple <branch> arguments are unioned. Output is
>       +    sorted by branch name.
>       +
>       +    This is the building block for --prune-merged, which deletes the
>       +    listed branches once they have landed on their upstream.
>        
>            Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
>        
>       @@ Documentation/git-branch.adoc: This option is only applicable in non-verbose mod
>         	nothing is printed.
>         
>        +`--forked`::
>       -+	List local branches whose configured upstream matches any
>       -+	of the given _<branch>_ arguments. Each argument is either
>       -+	a ref (e.g. `origin/master`, `master`) or a shell-style
>       -+	glob (e.g. `'origin/*'`). Multiple arguments are unioned.
>       ++	List local branches whose configured upstream
>       ++	(`branch.<name>.merge` resolved against `branch.<name>.remote`)
>       ++	matches any of the given _<branch>_ arguments.
>       +++
>       ++Each _<branch>_ is interpreted against the local repository: a literal
>       ++upstream like `origin/main` or a local branch like `master`, or a
>       ++wildmatch pattern like `'origin/*'`.  A bare configured-remote name
>       ++(e.g. `origin`) resolves to the target of `refs/remotes/<remote>/HEAD`,
>       ++to match the way `git checkout -b topic origin` picks a starting
>       ++point.  Multiple _<branch>_ arguments are unioned.
>        +
>         `-v`::
>         `-vv`::
>       @@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
>         	NULL
>         };
>         
>       -@@ builtin/branch.c: static int branch_merged(int kind, const char *name,
>       -
>       - static int check_branch_commit(const char *branchname, const char *refname,
>       - 			       const struct object_id *oid, struct commit *head_rev,
>       --			       int kinds, int force)
>       -+			       int kinds, int force, int warn_only,
>       -+			       int *n_not_merged)
>       - {
>       - 	struct commit *rev = lookup_commit_reference(the_repository, oid);
>       - 	if (!force && !rev) {
>       -@@ builtin/branch.c: static int check_branch_commit(const char *branchname, const char *refname,
>       - 		return -1;
>       - 	}
>       - 	if (!force && !branch_merged(kinds, branchname, rev, head_rev)) {
>       --		error(_("the branch '%s' is not fully merged"), branchname);
>       --		advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
>       --				  _("If you are sure you want to delete it, "
>       --				  "run 'git branch -D %s'"), branchname);
>       -+		if (warn_only) {
>       -+			warning(_("the branch '%s' is not fully merged"),
>       -+				branchname);
>       -+		} else {
>       -+			error(_("the branch '%s' is not fully merged"),
>       -+			      branchname);
>       -+			advise_if_enabled(ADVICE_FORCE_DELETE_BRANCH,
>       -+					  _("If you are sure you want to delete it, "
>       -+					  "run 'git branch -D %s'"), branchname);
>       -+		}
>       -+		if (n_not_merged)
>       -+			(*n_not_merged)++;
>       - 		return -1;
>       - 	}
>       - 	return 0;
>       -@@ builtin/branch.c: static void delete_branch_config(const char *branchname)
>       - }
>       -
>       - static int delete_branches(int argc, const char **argv, int force, int kinds,
>       --			   int quiet)
>       -+			   int quiet, int warn_only, int *n_not_merged)
>       - {
>       - 	struct commit *head_rev = NULL;
>       - 	struct object_id oid;
>       -@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
>       -
>       - 		if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
>       - 		    check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
>       --					force)) {
>       --			ret = 1;
>       -+					force, warn_only, n_not_merged)) {
>       -+			if (!warn_only)
>       -+				ret = 1;
>       - 			goto next;
>       - 		}
>       -
>        @@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const char *newname, int
>         	free_worktrees(worktrees);
>         }
>         
>       ++struct upstream_pattern {
>       ++	char *name;
>       ++	int is_wildcard;
>       ++};
>       ++
>       ++static void upstream_pattern_list_clear(struct upstream_pattern *items,
>       ++					size_t nr)
>       ++{
>       ++	size_t i;
>       ++	for (i = 0; i < nr; i++)
>       ++		free(items[i].name);
>       ++	free(items);
>       ++}
>       ++
>       ++static const char *short_upstream_name(const char *full_ref)
>       ++{
>       ++	const char *short_name = full_ref;
>       ++	(void)(skip_prefix(short_name, "refs/heads/", &short_name) ||
>       ++	       skip_prefix(short_name, "refs/remotes/", &short_name));
>       ++	return short_name;
>       ++}
>       ++
>       ++static int parse_one_forked_arg(const char *arg, struct upstream_pattern *out)
>       ++{
>       ++	struct ref_store *refs = get_main_ref_store(the_repository);
>       ++	struct remote *remote;
>       ++	struct object_id oid;
>       ++	char *full_ref = NULL;
>       ++	struct strbuf head_ref = STRBUF_INIT;
>       ++	const char *resolved;
>       ++
>       ++	if (has_glob_specials(arg)) {
>       ++		out->name = xstrdup(arg);
>       ++		out->is_wildcard = 1;
>       ++		return 0;
>       ++	}
>       ++
>       ++	remote = remote_get(arg);
>       ++	if (remote && remote_is_configured(remote, 0)) {
>       ++		strbuf_addf(&head_ref, "refs/remotes/%s/HEAD", remote->name);
>       ++		resolved = refs_resolve_ref_unsafe(refs, head_ref.buf,
>       ++						   RESOLVE_REF_NO_RECURSE,
>       ++						   NULL, NULL);
>       ++		if (resolved && starts_with(resolved, "refs/remotes/")) {
>       ++			out->name = xstrdup(short_upstream_name(resolved));
>       ++			out->is_wildcard = 0;
>       ++			strbuf_release(&head_ref);
>       ++			return 0;
>       ++		}
>       ++		strbuf_release(&head_ref);
>       ++	}
>       ++
>       ++	if (repo_dwim_ref(the_repository, arg, strlen(arg), &oid,
>       ++			  &full_ref, 0) == 1 &&
>       ++	    (starts_with(full_ref, "refs/heads/") ||
>       ++	     starts_with(full_ref, "refs/remotes/"))) {
>       ++		out->name = xstrdup(short_upstream_name(full_ref));
>       ++		out->is_wildcard = 0;
>       ++		free(full_ref);
>       ++		return 0;
>       ++	}
>       ++	free(full_ref);
>       ++	return -1;
>       ++}
>       ++
>        +static void parse_forked_args(int argc, const char **argv,
>       -+			      struct string_list *upstream_patterns)
>       ++			      struct upstream_pattern **patterns_out,
>       ++			      size_t *nr_out)
>        +{
>       ++	struct upstream_pattern *patterns;
>        +	int i;
>        +
>       ++	ALLOC_ARRAY(patterns, argc);
>        +	for (i = 0; i < argc; i++) {
>       -+		const char *arg = argv[i];
>       -+		struct object_id oid;
>       -+		char *full_ref = NULL;
>       -+		const char *short_ref;
>       -+
>       -+		if (has_glob_specials(arg)) {
>       -+			string_list_insert(upstream_patterns, arg);
>       -+			continue;
>       ++		if (parse_one_forked_arg(argv[i], &patterns[i]) < 0) {
>       ++			upstream_pattern_list_clear(patterns, i);
>       ++			die(_("'%s' is not a valid branch or pattern"),
>       ++			    argv[i]);
>        +		}
>       ++	}
>       ++	*patterns_out = patterns;
>       ++	*nr_out = argc;
>       ++}
>        +
>       -+		if (repo_dwim_ref(the_repository, arg, strlen(arg), &oid,
>       -+				  &full_ref, 0) == 1 &&
>       -+		    (skip_prefix(full_ref, "refs/heads/", &short_ref) ||
>       -+		     skip_prefix(full_ref, "refs/remotes/", &short_ref))) {
>       -+			string_list_insert(upstream_patterns, short_ref);
>       -+			free(full_ref);
>       -+			continue;
>       -+		}
>       -+		free(full_ref);
>       ++static int upstream_matches(const char *short_upstream,
>       ++			    const struct upstream_pattern *patterns,
>       ++			    size_t nr)
>       ++{
>       ++	size_t i;
>        +
>       -+		die(_("'%s' is not a valid branch or pattern"), arg);
>       ++	for (i = 0; i < nr; i++) {
>       ++		const struct upstream_pattern *p = &patterns[i];
>       ++		if (p->is_wildcard) {
>       ++			if (!wildmatch(p->name, short_upstream, WM_PATHNAME))
>       ++				return 1;
>       ++		} else if (!strcmp(p->name, short_upstream)) {
>       ++			return 1;
>       ++		}
>        +	}
>       ++	return 0;
>        +}
>        +
>        +struct forked_cb {
>       -+	const struct string_list *upstream_patterns;
>       ++	const struct upstream_pattern *patterns;
>       ++	size_t nr_patterns;
>        +	struct string_list *out;
>        +};
>        +
>       @@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const c
>        +{
>        +	struct forked_cb *cb = cb_data;
>        +	struct branch *branch;
>       -+	const char *upstream, *short_upstream;
>       -+	const struct string_list_item *item;
>       ++	const char *upstream;
>        +
>        +	if (ref->flags & REF_ISSYMREF)
>        +		return 0;
>       @@ builtin/branch.c: static void copy_or_rename_branch(const char *oldname, const c
>        +	upstream = branch_get_upstream(branch, NULL);
>        +	if (!upstream)
>        +		return 0;
>       -+	short_upstream = upstream;
>       -+	(void)(skip_prefix(short_upstream, "refs/heads/", &short_upstream) ||
>       -+	       skip_prefix(short_upstream, "refs/remotes/", &short_upstream));
>       -+
>       -+	for_each_string_list_item(item, cb->upstream_patterns)
>       -+		if (!wildmatch(item->string, short_upstream, WM_PATHNAME)) {
>       -+			string_list_append(cb->out, ref->name)->util =
>       -+				xstrdup(upstream);
>       -+			return 0;
>       -+		}
>       ++	if (upstream_matches(short_upstream_name(upstream),
>       ++			     cb->patterns, cb->nr_patterns))
>       ++		string_list_append(cb->out, ref->name);
>        +	return 0;
>        +}
>        +
>       -+static void collect_forked_set(int argc, const char **argv,
>       -+			       struct string_list *out)
>       -+{
>       -+	struct string_list upstream_patterns = STRING_LIST_INIT_DUP;
>       -+	struct forked_cb cb = {
>       -+		.upstream_patterns = &upstream_patterns,
>       -+		.out = out,
>       -+	};
>       -+
>       -+	parse_forked_args(argc, argv, &upstream_patterns);
>       -+
>       -+	refs_for_each_branch_ref(get_main_ref_store(the_repository),
>       -+				 collect_forked_branch, &cb);
>       -+
>       -+	string_list_clear(&upstream_patterns, 0);
>       -+}
>       -+
>        +static int list_forked_branches(int argc, const char **argv)
>        +{
>       ++	struct upstream_pattern *patterns = NULL;
>       ++	size_t nr_patterns = 0;
>        +	struct string_list out = STRING_LIST_INIT_DUP;
>        +	struct string_list_item *item;
>       ++	struct forked_cb cb;
>        +
>        +	if (!argc)
>        +		die(_("--forked requires at least one <branch>"));
>        +
>       -+	collect_forked_set(argc, argv, &out);
>       ++	parse_forked_args(argc, argv, &patterns, &nr_patterns);
>       ++	cb.patterns = patterns;
>       ++	cb.nr_patterns = nr_patterns;
>       ++	cb.out = &out;
>       ++
>       ++	refs_for_each_branch_ref(get_main_ref_store(the_repository),
>       ++				 collect_forked_branch, &cb);
>       ++
>       ++	string_list_sort(&out);
>        +	for_each_string_list_item(item, &out)
>        +		puts(item->string);
>        +
>       -+	string_list_clear(&out, 1);
>       ++	upstream_pattern_list_clear(patterns, nr_patterns);
>       ++	string_list_clear(&out, 0);
>        +	return 0;
>        +}
>        +
>       @@ builtin/branch.c: int cmd_branch(int argc,
>         		usage_with_options(builtin_branch_usage, options);
>         
>        @@ builtin/branch.c: int cmd_branch(int argc,
>       - 	if (delete) {
>       - 		if (!argc)
>         			die(_("branch name required"));
>       --		ret = delete_branches(argc, argv, delete > 1, filter.kind, quiet);
>       -+		ret = delete_branches(argc, argv, delete > 1, filter.kind,
>       -+				      quiet, 0, NULL);
>       -+		goto out;
>       + 		ret = delete_branches(argc, argv, delete > 1, filter.kind, quiet);
>       + 		goto out;
>        +	} else if (forked) {
>        +		ret = list_forked_branches(argc, argv);
>       - 		goto out;
>       ++		goto out;
>         	} else if (show_current) {
>         		print_current_branch_name();
>       + 		ret = 0;
>        
>         ## t/t3200-branch.sh ##
>        @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
>       @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
>        +	git clone forked-upstream forked &&
>        +	git -C forked remote add other ../forked-other &&
>        +	git -C forked fetch other &&
>       ++	git -C forked branch local-base &&
>        +	git -C forked branch --track local-one origin/one &&
>        +	git -C forked branch --track local-two origin/two &&
>        +	git -C forked branch --track local-foreign other/foreign &&
>        +	git -C forked branch detached &&
>       -+	git -C forked branch --track topic-on-main main
>       ++	git -C forked branch --track local-trunk local-base
>        +'
>        +
>       -+test_expect_success '--forked <remote-tracking-branch> lists matching branches' '
>       ++test_expect_success '--forked <upstream-tracking-branch> lists matching branches' '
>        +	git -C forked branch --forked origin/one >actual &&
>        +	echo local-one >expect &&
>        +	test_cmp expect actual
>        +'
>        +
>       -+test_expect_success '--forked <local-branch> lists branches tracking that local branch' '
>       -+	git -C forked branch --forked main >actual &&
>       -+	echo topic-on-main >expect &&
>       -+	test_cmp expect actual
>       -+'
>       -+
>       -+test_expect_success '--forked <glob> matches every upstream under the pattern' '
>       ++test_expect_success '--forked <glob> matches by wildmatch' '
>        +	git -C forked branch --forked "origin/*" >actual &&
>        +	cat >expect <<-\EOF &&
>        +	local-one
>       @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
>        +	test_cmp expect actual
>        +'
>        +
>       ++test_expect_success '--forked <local-branch> matches branches with local upstream' '
>       ++	git -C forked branch --forked local-base >actual &&
>       ++	echo local-trunk >expect &&
>       ++	test_cmp expect actual
>       ++'
>       ++
>       ++test_expect_success '--forked <remote> resolves via refs/remotes/<remote>/HEAD' '
>       ++	test_when_finished "git -C forked symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main" &&
>       ++	git -C forked symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/one &&
>       ++	git -C forked branch --forked origin >actual &&
>       ++	echo local-one >expect &&
>       ++	test_cmp expect actual
>       ++'
>       ++
>        +test_expect_success '--forked unions multiple <branch> arguments' '
>        +	git -C forked branch --forked origin/one other/foreign >actual &&
>        +	cat >expect <<-\EOF &&
>       @@ t/t3200-branch.sh: test_expect_success 'errors if given a bad branch name' '
>        +'
>        +
>        +test_expect_success '--forked combines literal and glob arguments' '
>       -+	git -C forked branch --forked main "other/*" >actual &&
>       ++	git -C forked branch --forked local-base "other/*" >actual &&
>        +	cat >expect <<-\EOF &&
>        +	local-foreign
>       -+	topic-on-main
>       ++	local-trunk
>        +	EOF
>        +	test_cmp expect actual
>        +'
>   -:  ---------- > 2:  b666d09bf5 branch: let delete_branches warn instead of error on bulk refusal
>   -:  ---------- > 3:  6e6580270e branch: prepare delete_branches for a bulk caller
>   2:  718e28c7e0 ! 4:  e7e03c1338 branch: add --prune-merged <branch>
>       @@ Commit message
>        
>                    git branch --prune-merged <branch>...
>        
>       -    deletes the local branches that --forked <branch> would list,
>       -    but only those whose tip is reachable from their configured
>       -    upstream: the work has already landed on the upstream the
>       -    branch tracks, so the local copy is no longer needed.
>       +    deletes the local branches that "--forked <branch>" would list,
>       +    restricted to those whose tip is reachable from their configured
>       +    upstream: the work has already landed on the upstream they track,
>       +    so the local copy is no longer needed.
>        
>       -    The following branches are always preserved:
>       +    Reachability is read from the local refs only -- nothing is
>       +    fetched. Users who want fresh upstream refs run "git fetch" first;
>       +    the deletion path stays a separate, idempotent step that also
>       +    works offline.
>        
>       -    * the currently checked-out branch in any worktree;
>       -    * any local branch whose name matches the default branch of
>       -      any configured remote (the target of
>       -      refs/remotes/<remote>/HEAD) -- typically 'main' or
>       -      'master';
>       -    * any branch whose upstream no longer resolves locally.
>       +    Three classes of branches are spared:
>        
>       -    Reachability is read from whatever branch.<name>.merge
>       -    resolves to locally, which is usually a remote-tracking ref
>       -    but may also be a local branch. When the upstream is a
>       -    remote-tracking ref, the natural workflow is
>       +      * any branch checked out in any worktree;
>       +      * any branch whose upstream no longer resolves locally (its
>       +        disappearance is not, on its own, evidence of integration);
>       +      * any branch whose push destination equals its upstream
>       +        (<branch>@{push} == <branch>@{upstream}). Such a branch
>       +        cannot be distinguished from a freshly pulled trunk that
>       +        just looks "fully merged" -- e.g. local "main" tracking and
>       +        pushing to "origin/main" right after a pull. Only branches
>       +        that push somewhere other than their upstream (typically
>       +        topics in a fork-based workflow) are treated as candidates.
>        
>       -            git fetch <remote>
>       -            git branch --prune-merged <upstream-pattern>
>       -
>       -    so the upstream reflects the current state before pruning.
>       +    Deletion goes through the existing delete_branches() in warn-only
>       +    mode and with the HEAD-fallback disabled: a branch that is not
>       +    yet fully merged to its upstream is reported as a one-line warning
>       +    and skipped, so a single un-mergeable topic does not abort the
>       +    whole sweep, and there is no fallback to "merged into the
>       +    currently checked out branch" -- we only act on upstream-merged
>       +    status.
>        
>            Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
>        
>       @@ Documentation/git-branch.adoc: git branch (-c|-C) [<old-branch>] <new-branch>
>         
>         DESCRIPTION
>         -----------
>       -@@ Documentation/git-branch.adoc: This option is only applicable in non-verbose mode.
>       - 	a ref (e.g. `origin/master`, `master`) or a shell-style
>       - 	glob (e.g. `'origin/*'`). Multiple arguments are unioned.
>       +@@ Documentation/git-branch.adoc: wildmatch pattern like `'origin/*'`.  A bare configured-remote name
>       + to match the way `git checkout -b topic origin` picks a starting
>       + point.  Multiple _<branch>_ arguments are unioned.
>         
>        +`--prune-merged`::
>       -+	Delete the local branches that `--forked` would list for
>       -+	the same _<branch>_ arguments, but only those whose tip is
>       -+	reachable from their configured upstream.
>       ++	Delete the local branches that `--forked` would list for the
>       ++	same _<branch>_ arguments, but only those whose tip is
>       ++	reachable from their configured upstream.  In other words,
>       ++	the work on the branch has already landed on the upstream it
>       ++	tracks, so the local copy is no longer needed.
>        ++
>       -+For arguments that refer to remote-tracking branches, run
>       -+`git fetch` first so reachability is checked against the
>       -+current upstream state; refs are read locally.
>       ++Reachability is checked against whatever the upstream refs say
>       ++locally; nothing is fetched.  Run `git fetch` first if you want
>       ++the upstream refs refreshed.
>        ++
>       -+The following branches are always preserved:
>       ++A branch is left alone if any of the following holds:
>       ++its upstream no longer resolves locally; it is checked out in any
>       ++worktree; or its push destination (`<branch>@{push}`) equals its
>       ++upstream (`<branch>@{upstream}`), so it cannot be distinguished
>       ++from a freshly pulled trunk that just looks "fully merged".
>        ++
>       -+--
>       -+* the currently checked-out branch in any worktree;
>       -+* any local branch whose name matches the default branch of
>       -+  any configured remote (the target of
>       -+  `refs/remotes/<remote>/HEAD`) -- typically `main` or
>       -+  `master`;
>       -+* any branch whose upstream no longer resolves locally.
>       -+--
>       ++Branches refused by the "fully merged" safety check are listed as
>       ++warnings and skipped; pass them to `git branch -D` explicitly if
>       ++you want them gone.
>        +
>         `-v`::
>         `-vv`::
>         `--verbose`::
>        
>         ## builtin/branch.c ##
>       -@@
>       - #include "branch.h"
>       - #include "path.h"
>       - #include "string-list.h"
>       -+#include "strvec.h"
>       - #include "column.h"
>       - #include "utf8.h"
>       - #include "ref-filter.h"
>       -@@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
>       - 	N_("git branch [<options>] [-r | -a] [--points-at]"),
>       - 	N_("git branch [<options>] [-r | -a] [--format]"),
>       - 	N_("git branch [<options>] --forked <branch>..."),
>       -+	N_("git branch [<options>] --prune-merged <branch>..."),
>       - 	NULL
>       - };
>       -
>       -@@ builtin/branch.c: static int branch_merged(int kind, const char *name,
>       - 	 * any of the following code, but during the transition period,
>       - 	 * a gentle reminder is in order.
>       - 	 */
>       --	if (head_rev != reference_rev) {
>       --		int expect = head_rev ? repo_in_merge_bases(the_repository, rev, head_rev) : 0;
>       -+	if (head_rev && head_rev != reference_rev) {
>       -+		int expect = repo_in_merge_bases(the_repository, rev, head_rev);
>       - 		if (expect < 0)
>       - 			exit(128);
>       - 		if (expect == merged)
>        @@ builtin/branch.c: static int collect_forked_branch(const struct reference *ref, void *cb_data)
>         	return 0;
>         }
>         
>       -+static int collect_default_branch_name(struct remote *remote, void *cb_data)
>       -+{
>       -+	struct string_list *protected = cb_data;
>       -+	struct ref_store *refs = get_main_ref_store(the_repository);
>       -+	struct strbuf head = STRBUF_INIT;
>       -+	const char *target;
>       -+
>       -+	strbuf_addf(&head, "refs/remotes/%s/HEAD", remote->name);
>       -+	target = refs_resolve_ref_unsafe(refs, head.buf,
>       -+					 RESOLVE_REF_NO_RECURSE, NULL, NULL);
>       -+	if (target) {
>       -+		const char *leaf = strrchr(target, '/');
>       -+		if (leaf)
>       -+			string_list_insert(protected, leaf + 1);
>       -+	}
>       -+	strbuf_release(&head);
>       -+	return 0;
>       +-static int list_forked_branches(int argc, const char **argv)
>       ++static void collect_forked_set(int argc, const char **argv,
>       ++			       struct string_list *out)
>       + {
>       + 	struct upstream_pattern *patterns = NULL;
>       + 	size_t nr_patterns = 0;
>       +-	struct string_list out = STRING_LIST_INIT_DUP;
>       +-	struct string_list_item *item;
>       + 	struct forked_cb cb;
>       +
>       +-	if (!argc)
>       +-		die(_("--forked requires at least one <branch>"));
>       +-
>       + 	parse_forked_args(argc, argv, &patterns, &nr_patterns);
>       + 	cb.patterns = patterns;
>       + 	cb.nr_patterns = nr_patterns;
>       +-	cb.out = &out;
>       ++	cb.out = out;
>       +
>       + 	refs_for_each_branch_ref(get_main_ref_store(the_repository),
>       + 				 collect_forked_branch, &cb);
>       +
>       +-	string_list_sort(&out);
>       ++	string_list_sort(out);
>       ++
>       ++	upstream_pattern_list_clear(patterns, nr_patterns);
>        +}
>        +
>       - static void collect_forked_set(int argc, const char **argv,
>       - 			       struct string_list *out)
>       - {
>       -@@ builtin/branch.c: static int list_forked_branches(int argc, const char **argv)
>       ++static int list_forked_branches(int argc, const char **argv)
>       ++{
>       ++	struct string_list out = STRING_LIST_INIT_DUP;
>       ++	struct string_list_item *item;
>       ++
>       ++	if (!argc)
>       ++		die(_("--forked requires at least one <branch>"));
>       ++
>       ++	collect_forked_set(argc, argv, &out);
>       + 	for_each_string_list_item(item, &out)
>       + 		puts(item->string);
>       +
>       +-	upstream_pattern_list_clear(patterns, nr_patterns);
>       + 	string_list_clear(&out, 0);
>         	return 0;
>         }
>         
>       @@ builtin/branch.c: static int list_forked_branches(int argc, const char **argv)
>        +{
>        +	struct ref_store *refs = get_main_ref_store(the_repository);
>        +	struct string_list candidates = STRING_LIST_INIT_DUP;
>       -+	struct string_list protected_default_names = STRING_LIST_INIT_DUP;
>        +	struct strvec deletable = STRVEC_INIT;
>       -+	struct strbuf buf = STRBUF_INIT;
>        +	struct string_list_item *item;
>       -+	int n_not_merged = 0;
>        +	int ret = 0;
>        +
>        +	if (!argc)
>        +		die(_("--prune-merged requires at least one <branch>"));
>        +
>        +	collect_forked_set(argc, argv, &candidates);
>       -+	for_each_remote(collect_default_branch_name, &protected_default_names);
>        +
>        +	for_each_string_list_item(item, &candidates) {
>        +		const char *short_name = item->string;
>       -+		const char *upstream = item->util;
>       -+
>       -+		strbuf_reset(&buf);
>       -+		strbuf_addf(&buf, "refs/heads/%s", short_name);
>       -+		if (branch_checked_out(buf.buf))
>       ++		struct branch *branch = branch_get(short_name);
>       ++		const char *upstream, *push;
>       ++		struct strbuf full = STRBUF_INIT;
>       ++		int skip;
>       ++
>       ++		strbuf_addf(&full, "refs/heads/%s", short_name);
>       ++		skip = !!branch_checked_out(full.buf);
>       ++		strbuf_release(&full);
>       ++		if (skip)
>        +			continue;
>        +
>       -+		if (string_list_has_string(&protected_default_names,
>       -+					   short_name))
>       ++		upstream = branch ? branch_get_upstream(branch, NULL) : NULL;
>       ++		if (!upstream || !refs_ref_exists(refs, upstream))
>        +			continue;
>       -+
>       -+		if (!refs_ref_exists(refs, upstream))
>       ++		push = branch ? branch_get_push(branch, NULL) : NULL;
>       ++		if (!push || !strcmp(push, upstream))
>        +			continue;
>        +
>        +		strvec_push(&deletable, short_name);
>        +	}
>       -+	strbuf_release(&buf);
>        +
>        +	if (deletable.nr)
>        +		ret = delete_branches(deletable.nr, deletable.v,
>       -+				      0, FILTER_REFS_BRANCHES, quiet,
>       -+				      1, &n_not_merged);
>       -+
>       -+	if (n_not_merged && !quiet)
>       -+		fprintf(stderr,
>       -+			Q_("Skipped %d branch that is not fully merged; "
>       -+			   "delete it with 'git branch -D' if you are sure.\n",
>       -+			   "Skipped %d branches that are not fully merged; "
>       -+			   "delete them with 'git branch -D' if you are sure.\n",
>       -+			   n_not_merged),
>       -+			n_not_merged);
>       ++				      0, /* force */
>       ++				      FILTER_REFS_BRANCHES,
>       ++				      quiet,
>       ++				      1, /* warn_only */
>       ++				      1, /* no_head_fallback */
>       ++				      0  /* dry_run */);
>        +
>        +	strvec_clear(&deletable);
>       -+	string_list_clear(&candidates, 1);
>       -+	string_list_clear(&protected_default_names, 0);
>       ++	string_list_clear(&candidates, 0);
>        +	return ret;
>        +}
>        +
>       @@ builtin/branch.c: int cmd_branch(int argc,
>         		OPT_BOOL(0, "forked", &forked,
>         			N_("list local branches whose upstream matches the given <branch>...")),
>        +		OPT_BOOL(0, "prune-merged", &prune_merged,
>       -+			N_("delete local branches whose upstream matches the given <branch>... and that are merged into it")),
>       ++			N_("delete local branches whose upstream matches the given <branch>... and is merged")),
>         		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
>         		OPT_MERGED(&filter, N_("print only branches that are merged")),
>         		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +	git -C pm-upstream branch one HEAD~ &&
>        +	git -C pm-upstream branch two HEAD &&
>        +	git -C pm-upstream branch wip main &&
>       -+	git -C pm-upstream checkout main
>       ++	git -C pm-upstream checkout main &&
>       ++	test_create_repo pm-fork
>        +'
>        +
>        +test_expect_success '--prune-merged deletes branches integrated into upstream' '
>        +	test_when_finished "rm -rf pm-merged" &&
>        +	git clone pm-upstream pm-merged &&
>       ++	git -C pm-merged remote add fork ../pm-fork &&
>       ++	test_config -C pm-merged remote.pushDefault fork &&
>       ++	test_config -C pm-merged push.default current &&
>        +	git -C pm-merged branch one one-commit &&
>        +	git -C pm-merged branch --set-upstream-to=origin/next one &&
>        +	git -C pm-merged branch two two-commit &&
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +	test_must_fail git -C pm-merged rev-parse --verify refs/heads/two
>        +'
>        +
>       -+test_expect_success '--prune-merged with a literal upstream argument' '
>       ++test_expect_success '--prune-merged accepts a literal upstream' '
>        +	test_when_finished "rm -rf pm-literal" &&
>        +	git clone pm-upstream pm-literal &&
>       ++	git -C pm-literal remote add fork ../pm-fork &&
>       ++	test_config -C pm-literal remote.pushDefault fork &&
>       ++	test_config -C pm-literal push.default current &&
>        +	git -C pm-literal branch one one-commit &&
>        +	git -C pm-literal branch --set-upstream-to=origin/next one &&
>       -+	git -C pm-literal branch keepme one-commit &&
>       -+	git -C pm-literal branch --set-upstream-to=origin/main keepme &&
>        +
>        +	git -C pm-literal branch --prune-merged origin/next &&
>        +
>       -+	test_must_fail git -C pm-literal rev-parse --verify refs/heads/one &&
>       -+	git -C pm-literal rev-parse --verify refs/heads/keepme
>       ++	test_must_fail git -C pm-literal rev-parse --verify refs/heads/one
>        +'
>        +
>        +test_expect_success '--prune-merged unions multiple <branch> arguments' '
>        +	test_when_finished "rm -rf pm-union" &&
>        +	git clone pm-upstream pm-union &&
>       ++	git -C pm-union remote add fork ../pm-fork &&
>       ++	test_config -C pm-union remote.pushDefault fork &&
>       ++	test_config -C pm-union push.default current &&
>        +	git -C pm-union branch one one-commit &&
>        +	git -C pm-union branch --set-upstream-to=origin/next one &&
>        +	git -C pm-union branch two base &&
>        +	git -C pm-union branch --set-upstream-to=origin/main two &&
>       ++	git -C pm-union checkout --detach &&
>        +
>        +	git -C pm-union branch --prune-merged origin/next origin/main &&
>        +
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +	test_must_fail git -C pm-union rev-parse --verify refs/heads/two
>        +'
>        +
>       -+test_expect_success '--prune-merged with a local-branch argument' '
>       -+	test_create_repo pm-local &&
>       ++test_expect_success '--prune-merged accepts a local upstream' '
>        +	test_when_finished "rm -rf pm-local" &&
>       -+	test_commit -C pm-local base &&
>       -+	git -C pm-local branch topic base &&
>       -+	git -C pm-local config branch.topic.remote . &&
>       -+	git -C pm-local config branch.topic.merge refs/heads/main &&
>       -+	git -C pm-local checkout --detach &&
>       -+
>       -+	git -C pm-local branch --prune-merged main &&
>       -+
>       -+	test_must_fail git -C pm-local rev-parse --verify refs/heads/topic &&
>       -+	git -C pm-local rev-parse --verify refs/heads/main
>       ++	git clone pm-upstream pm-local &&
>       ++	git -C pm-local remote add fork ../pm-fork &&
>       ++	test_config -C pm-local remote.pushDefault fork &&
>       ++	test_config -C pm-local push.default current &&
>       ++	git -C pm-local checkout -b trunk &&
>       ++	git -C pm-local branch one one-commit &&
>       ++	git -C pm-local branch --set-upstream-to=trunk one &&
>       ++	git -C pm-local merge --ff-only one-commit &&
>       ++
>       ++	git -C pm-local branch --prune-merged trunk &&
>       ++
>       ++	test_must_fail git -C pm-local rev-parse --verify refs/heads/one
>        +'
>        +
>       -+test_expect_success '--prune-merged spares branches with un-integrated commits' '
>       ++test_expect_success '--prune-merged warns instead of erroring on un-integrated commits' '
>        +	test_when_finished "rm -rf pm-unmerged" &&
>        +	git clone pm-upstream pm-unmerged &&
>       ++	git -C pm-unmerged remote add fork ../pm-fork &&
>       ++	test_config -C pm-unmerged remote.pushDefault fork &&
>       ++	test_config -C pm-unmerged push.default current &&
>        +	git -C pm-unmerged checkout -b wip origin/wip &&
>        +	git -C pm-unmerged branch --set-upstream-to=origin/next wip &&
>        +	test_commit -C pm-unmerged local-only &&
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +
>        +	git -C pm-unmerged branch --prune-merged "origin/*" 2>err &&
>        +	test_grep "not fully merged" err &&
>       -+	test_grep "Skipped 1 branch" err &&
>       -+	test_grep "git branch -D" err &&
>        +	test_grep ! "If you are sure you want to delete it" err &&
>        +	git -C pm-unmerged rev-parse --verify refs/heads/wip
>        +'
>        +
>       ++test_expect_success '--prune-merged is silent about not-merged-to-HEAD' '
>       ++	test_when_finished "rm -rf pm-nohead" &&
>       ++	git clone pm-upstream pm-nohead &&
>       ++	git -C pm-nohead remote add fork ../pm-fork &&
>       ++	test_config -C pm-nohead remote.pushDefault fork &&
>       ++	test_config -C pm-nohead push.default current &&
>       ++	git -C pm-nohead branch topic one-commit &&
>       ++	git -C pm-nohead branch --set-upstream-to=origin/next topic &&
>       ++
>       ++	git -C pm-nohead branch --prune-merged "origin/*" 2>err &&
>       ++
>       ++	test_grep ! "not yet merged to HEAD" err &&
>       ++	test_must_fail git -C pm-nohead rev-parse --verify refs/heads/topic
>       ++'
>       ++
>        +test_expect_success '--prune-merged skips branches whose upstream is gone' '
>        +	test_when_finished "rm -rf pm-upstream-gone" &&
>        +	git clone pm-upstream pm-upstream-gone &&
>       ++	git -C pm-upstream-gone remote add fork ../pm-fork &&
>       ++	test_config -C pm-upstream-gone remote.pushDefault fork &&
>       ++	test_config -C pm-upstream-gone push.default current &&
>        +	git -C pm-upstream-gone branch one one-commit &&
>        +	git -C pm-upstream-gone branch --set-upstream-to=origin/next one &&
>        +
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +test_expect_success '--prune-merged never deletes the checked-out branch' '
>        +	test_when_finished "rm -rf pm-head" &&
>        +	git clone pm-upstream pm-head &&
>       ++	git -C pm-head remote add fork ../pm-fork &&
>       ++	test_config -C pm-head remote.pushDefault fork &&
>       ++	test_config -C pm-head push.default current &&
>        +	git -C pm-head checkout -b one one-commit &&
>        +	git -C pm-head branch --set-upstream-to=origin/next one &&
>        +
>       @@ t/t3200-branch.sh: test_expect_success '--forked requires at least one <branch>'
>        +	git -C pm-head rev-parse --verify refs/heads/one
>        +'
>        +
>       -+test_expect_success '--prune-merged spares the local default branch' '
>       -+	test_when_finished "rm -rf pm-default" &&
>       -+	git clone pm-upstream pm-default &&
>       -+	git -C pm-default checkout --detach &&
>       -+	git -C pm-default branch --prune-merged "origin/*" &&
>       -+	git -C pm-default rev-parse --verify refs/heads/main
>       ++test_expect_success '--prune-merged spares branches that push back to their upstream' '
>       ++	test_when_finished "rm -rf pm-push-eq" &&
>       ++	git clone pm-upstream pm-push-eq &&
>       ++	git -C pm-push-eq checkout --detach &&
>       ++
>       ++	git -C pm-push-eq branch --prune-merged "origin/*" &&
>       ++
>       ++	git -C pm-push-eq rev-parse --verify refs/heads/main
>        +'
>        +
>       -+test_expect_success '--prune-merged protects the default branch by name only' '
>       -+	test_when_finished "rm -rf pm-default-alias" &&
>       -+	git clone pm-upstream pm-default-alias &&
>       -+	git -C pm-default-alias branch --track trunk origin/main &&
>       -+	git -C pm-default-alias checkout --detach &&
>       -+	git -C pm-default-alias branch --prune-merged "origin/*" &&
>       -+	git -C pm-default-alias rev-parse --verify refs/heads/main &&
>       -+	test_must_fail git -C pm-default-alias rev-parse --verify refs/heads/trunk
>       ++test_expect_success '--prune-merged spares a per-branch pushRemote==upstream remote' '
>       ++	test_when_finished "rm -rf pm-push-branch" &&
>       ++	git clone pm-upstream pm-push-branch &&
>       ++	git -C pm-push-branch remote add fork ../pm-fork &&
>       ++	test_config -C pm-push-branch remote.pushDefault fork &&
>       ++	test_config -C pm-push-branch push.default current &&
>       ++	test_config -C pm-push-branch branch.main.pushRemote origin &&
>       ++	git -C pm-push-branch checkout --detach &&
>       ++
>       ++	git -C pm-push-branch branch --prune-merged "origin/*" &&
>       ++
>       ++	git -C pm-push-branch rev-parse --verify refs/heads/main
>        +'
>        +
>       -+test_expect_success '--prune-merged with literal arg also protects default-name' '
>       -+	test_when_finished "rm -rf pm-literal-default" &&
>       -+	git clone pm-upstream pm-literal-default &&
>       -+	git -C pm-literal-default checkout --detach &&
>       -+	git -C pm-literal-default branch --prune-merged origin/main &&
>       -+	git -C pm-literal-default rev-parse --verify refs/heads/main
>       ++test_expect_success '--prune-merged prunes when @{push} differs from @{upstream}' '
>       ++	test_when_finished "rm -rf pm-push-diff" &&
>       ++	git clone pm-upstream pm-push-diff &&
>       ++	git -C pm-push-diff remote add fork ../pm-fork &&
>       ++	test_config -C pm-push-diff remote.pushDefault fork &&
>       ++	test_config -C pm-push-diff push.default current &&
>       ++	git -C pm-push-diff branch topic one-commit &&
>       ++	git -C pm-push-diff branch --set-upstream-to=origin/next topic &&
>       ++	git -C pm-push-diff checkout --detach &&
>       ++
>       ++	git -C pm-push-diff branch --prune-merged "origin/*" &&
>       ++
>       ++	test_must_fail git -C pm-push-diff rev-parse --verify refs/heads/topic
>        +'
>        +
>        +test_expect_success '--prune-merged requires at least one <branch>' '
>       -+	test_must_fail git -C pm-upstream branch --prune-merged 2>err &&
>       ++	test_must_fail git -C forked branch --prune-merged 2>err &&
>        +	test_grep "at least one <branch>" err
>        +'
>        +
>   3:  6e38d7af3a ! 5:  75b6d2366a branch: add branch.<name>.pruneMerged opt-out
>       @@ Metadata
>         ## Commit message ##
>            branch: add branch.<name>.pruneMerged opt-out
>        
>       -    Setting branch.<name>.pruneMerged=false exempts that branch
>       -    from --prune-merged. Useful for topic branches you intend to
>       -    develop further after an initial round has been merged
>       +    Setting branch.<name>.pruneMerged=false exempts that branch from
>       +    "git branch --prune-merged". Useful for a topic branch you want
>       +    to develop further after an initial round has been merged
>            upstream.
>        
>       -    Explicit deletion via 'git branch -d' is unaffected.
>       +    Unless --quiet is given, the skip is reported per branch so the
>       +    user knows why their topic was preserved.
>       +
>       +    Explicit deletion via "git branch -d" continues to consult the
>       +    normal merge check and is not affected by this setting.
>        
>            Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
>        
>       @@ Documentation/config/branch.adoc: for details).
>        +
>        +`branch.<name>.pruneMerged`::
>        +	If set to `false`, branch _<name>_ is exempt from
>       -+	`git branch --prune-merged`. Defaults to true. Explicit
>       -+	deletion via `git branch -d` is unaffected.
>       ++	`git branch --prune-merged`.  Useful for a topic branch you
>       ++	intend to develop further after an initial round has been
>       ++	merged upstream.  Defaults to true.  Explicit deletion via
>       ++	`git branch -d` is unaffected.
>        
>         ## Documentation/git-branch.adoc ##
>       -@@ Documentation/git-branch.adoc: The following branches are always preserved:
>       -   any configured remote (the target of
>       -   `refs/remotes/<remote>/HEAD`) -- typically `main` or
>       -   `master`;
>       -+* any branch with `branch.<name>.pruneMerged` set to `false`;
>       - * any branch whose upstream no longer resolves locally.
>       - --
>       -
>       +@@ Documentation/git-branch.adoc: the upstream refs refreshed.
>       + +
>       + A branch is left alone if any of the following holds:
>       + its upstream no longer resolves locally; it is checked out in any
>       +-worktree; or its push destination (`<branch>@{push}`) equals its
>       ++worktree; its push destination (`<branch>@{push}`) equals its
>       + upstream (`<branch>@{upstream}`), so it cannot be distinguished
>       +-from a freshly pulled trunk that just looks "fully merged".
>       ++from a freshly pulled trunk that just looks "fully merged"; or
>       ++`branch.<name>.pruneMerged` is set to `false`.
>       + +
>       + Branches refused by the "fully merged" safety check are listed as
>       + warnings and skipped; pass them to `git branch -D` explicitly if
>        
>         ## builtin/branch.c ##
>        @@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
>       - 	for_each_string_list_item(item, &candidates) {
>       - 		const char *short_name = item->string;
>       - 		const char *upstream = item->util;
>       -+		int prune_allowed = 1;
>       + 		struct branch *branch = branch_get(short_name);
>       + 		const char *upstream, *push;
>       + 		struct strbuf full = STRBUF_INIT;
>       ++		struct strbuf key = STRBUF_INIT;
>       + 		int skip;
>       ++		int opt_out;
>         
>       - 		strbuf_reset(&buf);
>       - 		strbuf_addf(&buf, "refs/heads/%s", short_name);
>       + 		strbuf_addf(&full, "refs/heads/%s", short_name);
>       + 		skip = !!branch_checked_out(full.buf);
>        @@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
>       - 		if (!refs_ref_exists(refs, upstream))
>       + 		if (!push || !strcmp(push, upstream))
>         			continue;
>         
>       -+		strbuf_reset(&buf);
>       -+		strbuf_addf(&buf, "branch.%s.prunemerged", short_name);
>       -+		if (!repo_config_get_bool(the_repository, buf.buf,
>       -+					  &prune_allowed) &&
>       -+		    !prune_allowed) {
>       ++		strbuf_addf(&key, "branch.%s.prunemerged", short_name);
>       ++		if (!repo_config_get_bool(the_repository, key.buf, &opt_out) &&
>       ++		    !opt_out) {
>        +			if (!quiet)
>       -+				fprintf(stderr, _("Skipping '%s' "
>       -+						  "(branch.%s.pruneMerged is false)\n"),
>       ++				fprintf(stderr,
>       ++					_("Skipping '%s' (branch.%s.pruneMerged is false)\n"),
>        +					short_name, short_name);
>       ++			strbuf_release(&key);
>        +			continue;
>        +		}
>       ++		strbuf_release(&key);
>        +
>         		strvec_push(&deletable, short_name);
>         	}
>       - 	strbuf_release(&buf);
>       +
>        
>         ## t/t3200-branch.sh ##
>        @@ t/t3200-branch.sh: test_expect_success '--prune-merged requires at least one <branch>' '
>       @@ t/t3200-branch.sh: test_expect_success '--prune-merged requires at least one <br
>        +test_expect_success '--prune-merged honours branch.<name>.pruneMerged=false' '
>        +	test_when_finished "rm -rf pm-optout" &&
>        +	git clone pm-upstream pm-optout &&
>       ++	git -C pm-optout remote add fork ../pm-fork &&
>       ++	test_config -C pm-optout remote.pushDefault fork &&
>       ++	test_config -C pm-optout push.default current &&
>        +	git -C pm-optout branch one one-commit &&
>        +	git -C pm-optout branch --set-upstream-to=origin/next one &&
>        +	git -C pm-optout branch two two-commit &&
>        +	git -C pm-optout branch --set-upstream-to=origin/next two &&
>       -+	git -C pm-optout config branch.one.pruneMerged false &&
>       ++	test_config -C pm-optout branch.one.pruneMerged false &&
>        +
>        +	git -C pm-optout branch --prune-merged "origin/*" 2>err &&
>        +
>       @@ t/t3200-branch.sh: test_expect_success '--prune-merged requires at least one <br
>        +	git clone pm-upstream pm-optout-d &&
>        +	git -C pm-optout-d branch one one-commit &&
>        +	git -C pm-optout-d branch --set-upstream-to=origin/next one &&
>       -+	git -C pm-optout-d config branch.one.pruneMerged false &&
>       ++	test_config -C pm-optout-d branch.one.pruneMerged false &&
>        +
>        +	git -C pm-optout-d branch -d one &&
>        +	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
>   4:  c68d162e22 ! 6:  a1a42a6b19 branch: add --dry-run for --prune-merged
>       @@ Metadata
>         ## Commit message ##
>            branch: add --dry-run for --prune-merged
>        
>       -    With --dry-run, --prune-merged prints the branches it would
>       -    delete and exits without touching any ref. Useful for
>       -    sanity-checking a glob like 'origin/*' before letting it run.
>       +    With --dry-run, --prune-merged prints the local branches it would
>       +    delete -- one "Would delete branch <name>" line per candidate --
>       +    and exits without touching any ref.
>       +
>       +    This is the natural sanity check before letting a broad pattern
>       +    like 'origin/*' run for real: the @{push}-vs-@{upstream} and
>       +    unmerged filtering still applies, so the dry-run output is
>       +    exactly the set that the live run would delete.
>       +
>       +    --dry-run is only meaningful in combination with --prune-merged
>       +    and is rejected otherwise.
>        
>            Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
>        
>       @@ Documentation/git-branch.adoc: git branch (-c|-C) [<old-branch>] <new-branch>
>         
>         DESCRIPTION
>         -----------
>       -@@ Documentation/git-branch.adoc: The following branches are always preserved:
>       - * any branch whose upstream no longer resolves locally.
>       - --
>       +@@ Documentation/git-branch.adoc: Branches refused by the "fully merged" safety check are listed as
>       + warnings and skipped; pass them to `git branch -D` explicitly if
>       + you want them gone.
>         
>        +`--dry-run`::
>       -+	With `--prune-merged`, print the branches that would be
>       -+	deleted instead of deleting them.
>       ++	With `--prune-merged`, print which branches would be
>       ++	deleted and exit without touching any ref.  Useful for
>       ++	sanity-checking a wide pattern like `'origin/*'` before
>       ++	committing to the deletion.
>        +
>         `-v`::
>         `-vv`::
>         `--verbose`::
>        
>         ## builtin/branch.c ##
>       -@@ builtin/branch.c: static const char * const builtin_branch_usage[] = {
>       - 	N_("git branch [<options>] [-r | -a] [--points-at]"),
>       - 	N_("git branch [<options>] [-r | -a] [--format]"),
>       - 	N_("git branch [<options>] --forked <branch>..."),
>       --	N_("git branch [<options>] --prune-merged <branch>..."),
>       -+	N_("git branch [<options>] --prune-merged [--dry-run] <branch>..."),
>       - 	NULL
>       - };
>       -
>       -@@ builtin/branch.c: static void delete_branch_config(const char *branchname)
>       - }
>       -
>       - static int delete_branches(int argc, const char **argv, int force, int kinds,
>       --			   int quiet, int warn_only, int *n_not_merged)
>       -+			   int quiet, int warn_only, int dry_run,
>       -+			   int *n_not_merged)
>       - {
>       - 	struct commit *head_rev = NULL;
>       - 	struct object_id oid;
>       -@@ builtin/branch.c: static int delete_branches(int argc, const char **argv, int force, int kinds,
>       - 			goto next;
>       - 		}
>       -
>       -+		if (dry_run) {
>       -+			printf(_("Would delete branch '%s'\n"),
>       -+			       name + branch_name_pos);
>       -+			goto next;
>       -+		}
>       -+
>       - 		item = string_list_append(&refs_to_delete, name);
>       - 		item->util = xstrdup((flags & REF_ISBROKEN) ? "broken"
>       - 				    : (flags & REF_ISSYMREF) ? target
>        @@ builtin/branch.c: static int list_forked_branches(int argc, const char **argv)
>         	return 0;
>         }
>         
>        -static int prune_merged_branches(int argc, const char **argv, int quiet)
>       -+static int prune_merged_branches(int argc, const char **argv,
>       -+				 int dry_run, int quiet)
>       ++static int prune_merged_branches(int argc, const char **argv, int quiet,
>       ++				 int dry_run)
>         {
>         	struct ref_store *refs = get_main_ref_store(the_repository);
>         	struct string_list candidates = STRING_LIST_INIT_DUP;
>        @@ builtin/branch.c: static int prune_merged_branches(int argc, const char **argv, int quiet)
>       - 	if (deletable.nr)
>       - 		ret = delete_branches(deletable.nr, deletable.v,
>       - 				      0, FILTER_REFS_BRANCHES, quiet,
>       --				      1, &n_not_merged);
>       -+				      1, dry_run, &n_not_merged);
>       + 				      quiet,
>       + 				      1, /* warn_only */
>       + 				      1, /* no_head_fallback */
>       +-				      0  /* dry_run */);
>       ++				      dry_run);
>         
>       - 	if (n_not_merged && !quiet)
>       - 		fprintf(stderr,
>       + 	strvec_clear(&deletable);
>       + 	string_list_clear(&candidates, 0);
>        @@ builtin/branch.c: int cmd_branch(int argc,
>         	    unset_upstream = 0, show_current = 0, edit_description = 0;
>         	int forked = 0;
>       @@ builtin/branch.c: int cmd_branch(int argc,
>        @@ builtin/branch.c: int cmd_branch(int argc,
>         			N_("list local branches whose upstream matches the given <branch>...")),
>         		OPT_BOOL(0, "prune-merged", &prune_merged,
>       - 			N_("delete local branches whose upstream matches the given <branch>... and that are merged into it")),
>       + 			N_("delete local branches whose upstream matches the given <branch>... and is merged")),
>        +		OPT_BOOL(0, "dry-run", &dry_run,
>       -+			N_("with --prune-merged, only print what would be deleted")),
>       ++			N_("with --prune-merged, only print which branches would be deleted")),
>         		OPT__FORCE(&force, N_("force creation, move/rename, deletion"), PARSE_OPT_NOCOMPLETE),
>         		OPT_MERGED(&filter, N_("print only branches that are merged")),
>         		OPT_NO_MERGED(&filter, N_("print only branches that are not merged")),
>        @@ builtin/branch.c: int cmd_branch(int argc,
>       - 	argc = parse_options(argc, argv, prefix, options, builtin_branch_usage,
>       - 			     0);
>       + 	if (noncreate_actions > 1)
>       + 		usage_with_options(builtin_branch_usage, options);
>         
>        +	if (dry_run && !prune_merged)
>        +		die(_("--dry-run requires --prune-merged"));
>        +
>       - 	if (!delete && !rename && !copy && !edit_description && !new_upstream &&
>       - 	    !show_current && !unset_upstream && !forked && !prune_merged &&
>       - 	    argc == 0)
>       + 	if (recurse_submodules_explicit) {
>       + 		if (!submodule_propagate_branches)
>       + 			die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled"));
>        @@ builtin/branch.c: int cmd_branch(int argc,
>       - 		if (!argc)
>       - 			die(_("branch name required"));
>       - 		ret = delete_branches(argc, argv, delete > 1, filter.kind,
>       --				      quiet, 0, NULL);
>       -+				      quiet, 0, 0, NULL);
>       - 		goto out;
>       - 	} else if (forked) {
>         		ret = list_forked_branches(argc, argv);
>         		goto out;
>         	} else if (prune_merged) {
>        -		ret = prune_merged_branches(argc, argv, quiet);
>       -+		ret = prune_merged_branches(argc, argv, dry_run, quiet);
>       ++		ret = prune_merged_branches(argc, argv, quiet, dry_run);
>         		goto out;
>         	} else if (show_current) {
>         		print_current_branch_name();
>       @@ t/t3200-branch.sh: test_expect_success 'branch -d still deletes a pruneMerged=fa
>         	test_must_fail git -C pm-optout-d rev-parse --verify refs/heads/one
>         '
>         
>       -+test_expect_success '--prune-merged --dry-run prints but does not delete' '
>       -+	test_when_finished "rm -rf pm-dryrun" &&
>       -+	git clone pm-upstream pm-dryrun &&
>       -+	git -C pm-dryrun branch one one-commit &&
>       -+	git -C pm-dryrun branch --set-upstream-to=origin/next one &&
>       ++test_expect_success '--prune-merged --dry-run lists but does not delete' '
>       ++	test_when_finished "rm -rf pm-dry" &&
>       ++	git clone pm-upstream pm-dry &&
>       ++	git -C pm-dry remote add fork ../pm-fork &&
>       ++	test_config -C pm-dry remote.pushDefault fork &&
>       ++	test_config -C pm-dry push.default current &&
>       ++	git -C pm-dry branch one one-commit &&
>       ++	git -C pm-dry branch --set-upstream-to=origin/next one &&
>       ++	git -C pm-dry branch two two-commit &&
>       ++	git -C pm-dry branch --set-upstream-to=origin/next two &&
>       ++
>       ++	git -C pm-dry branch --prune-merged --dry-run "origin/*" >actual &&
>       ++	test_grep "Would delete branch one " actual &&
>       ++	test_grep "Would delete branch two " actual &&
>        +
>       -+	git -C pm-dryrun branch --prune-merged --dry-run "origin/*" >out &&
>       -+	test_grep "Would delete branch .one." out &&
>       -+	git -C pm-dryrun rev-parse --verify refs/heads/one
>       ++	git -C pm-dry rev-parse --verify refs/heads/one &&
>       ++	git -C pm-dry rev-parse --verify refs/heads/two
>        +'
>        +
>       -+test_expect_success '--prune-merged --dry-run skips un-integrated branches' '
>       -+	test_when_finished "rm -rf pm-dryrun-unmerged" &&
>       -+	git clone pm-upstream pm-dryrun-unmerged &&
>       -+	git -C pm-dryrun-unmerged checkout -b wip origin/next &&
>       -+	git -C pm-dryrun-unmerged branch --set-upstream-to=origin/next wip &&
>       -+	test_commit -C pm-dryrun-unmerged local-only &&
>       -+	git -C pm-dryrun-unmerged checkout - &&
>       -+	git -C pm-dryrun-unmerged branch merged one-commit &&
>       -+	git -C pm-dryrun-unmerged branch --set-upstream-to=origin/next merged &&
>       ++test_expect_success '--prune-merged --dry-run only lists branches the live run would delete' '
>       ++	test_when_finished "rm -rf pm-dry-mixed" &&
>       ++	git clone pm-upstream pm-dry-mixed &&
>       ++	git -C pm-dry-mixed remote add fork ../pm-fork &&
>       ++	test_config -C pm-dry-mixed remote.pushDefault fork &&
>       ++	test_config -C pm-dry-mixed push.default current &&
>       ++	git -C pm-dry-mixed checkout -b wip origin/next &&
>       ++	git -C pm-dry-mixed branch --set-upstream-to=origin/next wip &&
>       ++	test_commit -C pm-dry-mixed local-only &&
>       ++	git -C pm-dry-mixed checkout - &&
>       ++	git -C pm-dry-mixed branch merged one-commit &&
>       ++	git -C pm-dry-mixed branch --set-upstream-to=origin/next merged &&
>        +
>       -+	git -C pm-dryrun-unmerged branch --prune-merged --dry-run "origin/*" \
>       -+		>out 2>err &&
>       -+	test_grep "Would delete branch .merged." out &&
>       -+	test_grep ! "Would delete branch .wip." out &&
>       -+	test_grep "not fully merged" err &&
>       -+	git -C pm-dryrun-unmerged rev-parse --verify refs/heads/wip &&
>       -+	git -C pm-dryrun-unmerged rev-parse --verify refs/heads/merged
>       ++	git -C pm-dry-mixed branch --prune-merged --dry-run "origin/*" >out &&
>       ++	test_grep "Would delete branch merged" out &&
>       ++	test_grep ! "Would delete branch wip" out &&
>       ++	git -C pm-dry-mixed rev-parse --verify refs/heads/wip &&
>       ++	git -C pm-dry-mixed rev-parse --verify refs/heads/merged
>        +'
>        +
>       -+test_expect_success '--dry-run requires --prune-merged' '
>       -+	test_must_fail git -C pm-upstream branch --dry-run 2>err &&
>       ++test_expect_success '--dry-run without --prune-merged is rejected' '
>       ++	test_must_fail git -C forked branch --dry-run 2>err &&
>        +	test_grep "requires --prune-merged" err
>        +'
>        +
> 


^ permalink raw reply

* Re: [GSoC][PATCH 0/4] teach git repo info to handle path keys
From: Phillip Wood @ 2026-06-02 13:03 UTC (permalink / raw)
  To: K Jayatheerth, git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416
In-Reply-To: <20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com>

On 01/06/2026 16:19, K Jayatheerth wrote:
> 
> So in patches 3 and 4, we add both `path.<field>.absolute` and
> `path.<field>.relative` for `gitdir` and `commondir`. Initially,
> it was proposed by Ayush to use `path.absolute.<field>`, but
> this would break the lexicographical order of the internal field
> array. I tweaked it to place the variant at the end as a suffix instead.

I don't understand the comment about breaking the lexicographical order, 
surely it only breaks if the new items are added out of order? Why can't 
we have

	path.absolute.commondir
	path.absolute.gitdir
	path.relative.commondir
	path.relative.gitdir

?

Thanks

Phillip

> There are still a few open questions that should be addressed
> by the community. I am tagging members who were involved in the
> previous discussions:
> 
> Justin Tobler, Lucas Seiki Oshiro, Junio, Phillip Wood,
> brian m. carlson, and Ayush Jha.
> 
> Apologies if I missed anyone; I included everyone who reviewed
> or participated in the discussions of Eslam's and Lucas's
> patches.
> 
> Questions:
> 
> 1. Should there still be a --path-format flag?
> 2. Should we consider a default option?
>     Currently we have path.gitdir.absolute; should we consider
>     an option where a plain path.gitdir returns some default?
>     If yes:
>       2.1 Should we keep the default the same as rev-parse? Or
>           should either relative or absolute be the default?
>       2.2 When printing using --all, should the default be
>           printed, or should we print both absolute and
>           relative?
> 3. Is printing both absolute and relative in a single call
>     using --all acceptable?
>     If no:
>       3.1 What's a better approach?
> 
> I have discussed these changes with both Justin and Lucas
> internally. This series is presented to gather opinions from the
> wider community before moving forward.
> 
> K Jayatheerth (4):
>    path: add strbuf_add_path for formatting paths
>    rev-parse: use strbuf_add_path for path formatting
>    repo: add path.gitdir with absolute and relative suffix formatting
>    repo: add path.commondir with absolute and relative suffix formatting
> 
>   Documentation/git-repo.adoc |  15 ++++++
>   builtin/repo.c              |  50 ++++++++++++++++++
>   builtin/rev-parse.c         | 100 ++++++++----------------------------
>   path.c                      |  58 +++++++++++++++++++++
>   path.h                      |  16 ++++++
>   t/t1900-repo-info.sh        |  32 ++++++++++++
>   6 files changed, 192 insertions(+), 79 deletions(-)
> 


^ permalink raw reply

* Re: [GSoC][PATCH 1/4] path: add strbuf_add_path for formatting paths
From: Phillip Wood @ 2026-06-02 13:00 UTC (permalink / raw)
  To: K Jayatheerth, git
  Cc: jltobler, lucasseikioshiro, gitster, phillip.wood, sandals,
	kumarayushjha123, a3205153416
In-Reply-To: <20260601151950.30686-2-jayatheerthkulkarni2005@gmail.com>

On 01/06/2026 16:19, K Jayatheerth wrote:
> 
> diff --git a/path.h b/path.h
> index 0434ba5e07..b9b626ce4a 100644
> --- a/path.h
> +++ b/path.h
> @@ -262,6 +262,22 @@ enum scld_error safe_create_leading_directories_no_share(char *path);
>   int safe_create_file_with_leading_directories(struct repository *repo,
>   					      const char *path);
>   
> +enum path_format_type {
> +	PATH_FORMAT_DEFAULT,
> +	PATH_FORMAT_RELATIVE,
> +	PATH_FORMAT_CANONICAL
> +};
> +
> +enum path_default_type {
> +	PATH_DEFAULT_RELATIVE,
> +	PATH_DEFAULT_RELATIVE_IF_SHARED,
> +	PATH_DEFAULT_CANONICAL,
> +	PATH_DEFAULT_UNMODIFIED
> +};
> +
> +void strbuf_add_path(struct strbuf *buf, const char *path, const char *prefix,
> +		     enum path_format_type format, enum path_default_type def);

This API is very specific to rev-parse and to me at least it is hard to 
understand. I think it would be clearer if we had a single enum 
describing the desired format and let the rev-parse code worry about 
passing the appropriate value based on the options the user passed.

enum path_format {
	PATH_FORMAT_ABSOLUTE,
	PATH_FORMAT_CANONICAL,
	PATH_FORMAT_RELATIVE,
	PATH_FORMAT_RELATIVE_IF_SHARED	PATH_FORMAT_UNMODIFIED,
};

void format_path(struct strbuf *buf, const char *path,
		 const char *prefix, enum path_format format);

We tend to avoid adding "strbuf_" to the beginning of functions these 
days when they're adding things to a strbuf. This function also needs 
some documentation explaining what the arguments are.

Thanks

Phillip


^ permalink raw reply

* Re: [PATCH 2/2] builtin/init-db: deprecate alias for git-init(1)
From: Patrick Steinhardt @ 2026-06-02 12:34 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Kristoffer Haugsbakk, Phillip Wood, git
In-Reply-To: <xmqqv7c1xs76.fsf@gitster.g>

On Tue, Jun 02, 2026 at 05:27:41PM +0900, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > I wouldn't mind that outcome much, either. What triggered this series is
> > that I'm always annoyed that it's "builtin/init-db.c" instead of
> > "builtin/init.c", and the same for `cmd_init_db()`. But I intentionally
> > constructed the series in a way that the first commit can be picked
> > as-is, so that we can adjust our code to the modern world while not
> > doing the deprecation dance.
> >
> > So I'd be equally happy if we just drop the second commit in this
> > series.
> 
> I'd actually find myself annoyed by such a rename when looking for
> builtin/init-db.c only to find it gone---much like how a previous
> rename made ll-merge difficult to locate.
> 
> My point is that while static names may annoy some, renaming them
> does not resolve the annoyance; it merely shifts it to someone else.
> 
> So, if the primary motivation is just the first patch, I would be
> less inclined to support this series.

That's entirely fair. My take on this is a bit different, as I think
it's beneficial to accept a short-term adjustment for core contributors
in favor of making stuff easier to discover/maintain going forward.

A new contributor would probably be quick to learn that every
`cmd_foo()` entry point is named exactly the same as the subcommand
name, but they will then eventually trip over the few exceptions like
`cmd_init_db()` where that assumption doesn't hold.

But I can see that this is not always clear-cut.

Patrick

^ permalink raw reply

* Re: [PATCH 2/2] builtin/init-db: deprecate alias for git-init(1)
From: Patrick Steinhardt @ 2026-06-02 12:34 UTC (permalink / raw)
  To: Kristoffer Haugsbakk; +Cc: Junio C Hamano, Phillip Wood, git
In-Reply-To: <455fc75a-444f-4760-a22f-54a2ec29618b@app.fastmail.com>

On Tue, Jun 02, 2026 at 09:54:02AM +0200, Kristoffer Haugsbakk wrote:
> On Tue, Jun 2, 2026, at 08:45, Patrick Steinhardt wrote:
> > On Tue, Jun 02, 2026 at 07:22:50AM +0900, Junio C Hamano wrote:
> >> "Kristoffer Haugsbakk" <kristofferhaugsbakk@fastmail.com> writes:
> >>>[snip]
> >> Or just leave it without deprecation.  It does not cost much to keep
> >> "init-db", and because we expanded what "git database" means in
> >> later versions of Git since its invention, the name still makes
> >> sense.  Thank Linus for not naming it "init-odb"---that might have
> >> been a valid excuse to rename it because it does not cover the ref
> >> database and config database and others.
> >
> > I wouldn't mind that outcome much, either. What triggered this series is
> > that I'm always annoyed that it's "builtin/init-db.c" instead of
> > "builtin/init.c", and the same for `cmd_init_db()`. But I intentionally
> > constructed the series in a way that the first commit can be picked
> > as-is, so that we can adjust our code to the modern world while not
> > doing the deprecation dance.
> >
> > So I'd be equally happy if we just drop the second commit in this
> > series.
> 
> Could it be worthwhile to mark it as soft deprecated? In the sense that
> it is a legacy alias that is not planned for removal?

The question is how such a soft deprecation would look like. Would it be
a warning, only, but other than that it behaves just as before? Should
we mark it as `DEPRECATED` in "git.c"? Both of those?

Patrick

^ permalink raw reply

* Re: [PATCH 2/2] SubmittingPatches: describe cover letter
From: Derrick Stolee @ 2026-06-02 12:29 UTC (permalink / raw)
  To: Junio C Hamano, git
In-Reply-To: <20260602090808.87837-3-gitster@pobox.com>

On 6/2/2026 5:08 AM, Junio C Hamano wrote:
> We talk about how a commit log message should look like, but do not
> give advice on writing the cover letter to sell a series to widest
> possible audience.

This is a good thing to boost in the documentation.

> +[[cover-letter]]
> +=== Cover Letter
> +
> +The purpose of your cover letter is to sell your changes, explain what
> +they are about, and get your target audience interested enough to read
> +the patches.
> +
> +. Make sure your target audience can understand what the patches are
> +  about and why they are needed without prior context.

The thing that I like to say about the cover letter is that this is
your opportunity to communicate why the value of your change is worth
the risk of regressions and the cost of maintenance. Perhaps:

. Every code change comes with risk of regression and maintenance cost.
  The cover letter should clearly communicate why the value of your
  proposed change is worth applying. You can also describe how the risk
  is reduced by the design choices you made while writing the patches.

Or something similar may be helpful? I may just be over explaining.

> +. For a second or subsequent iteration of the same topic, make sure
> +  people who missed the earlier discussion can still understand what
> +  the patches are about, so they can judge if the topic is worth their
> +  time to read and comment on.
> +
> +. To help those who are familiar with earlier iterations, give a
> +  summary of changes since the previous rounds.
I find these updates to be particularly helpful, even for GitGitGadget
PRs that include a range-diff automatically. It's good to double-check
the human description of the update against the computed diff.

Thanks,
-Stolee

^ permalink raw reply

* Re: [PATCH 2/2] SubmittingPatches: describe cover letter
From: Patrick Steinhardt @ 2026-06-02 12:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git
In-Reply-To: <20260602090808.87837-3-gitster@pobox.com>

On Tue, Jun 02, 2026 at 06:08:08PM +0900, Junio C Hamano wrote:
> We talk about how a commit log message should look like, but do not
> give advice on writing the cover letter to sell a series to widest

s/to widest/to the widest/?

> possible audience.
> 
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  Documentation/SubmittingPatches | 19 +++++++++++++++++++
>  1 file changed, 19 insertions(+)
> 
> diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
> index dec8aea4cb..8ff1792b9b 100644
> --- a/Documentation/SubmittingPatches
> +++ b/Documentation/SubmittingPatches
> @@ -472,6 +472,25 @@ highlighted above.
>  Only capitalize the very first letter of the trailer, i.e. favor
>  "Signed-off-by" over "Signed-Off-By" and "Acked-by:" over "Acked-By".
>  
> +[[cover-letter]]
> +=== Cover Letter
> +
> +The purpose of your cover letter is to sell your changes, explain what
> +they are about, and get your target audience interested enough to read
> +the patches.
> +
> +. Make sure your target audience can understand what the patches are
> +  about and why they are needed without prior context.
> +
> +. For a second or subsequent iteration of the same topic, make sure
> +  people who missed the earlier discussion can still understand what
> +  the patches are about, so they can judge if the topic is worth their
> +  time to read and comment on.
> +
> +. To help those who are familiar with earlier iterations, give a
> +  summary of changes since the previous rounds.

We might also recommend to include a range-diff in subsequent
iterations. That being said though, I just sent a small series to the
mailing list that recommends using b4, and there it get this for free.
So no idea whether it's still worth it to then cover this here
explicitly.

Patrick

^ permalink raw reply

* [PATCH 2/2] Documentation/MyFirstContribution: recommend the use of b4
From: Patrick Steinhardt @ 2026-06-02 11:59 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano
In-Reply-To: <20260602-pks-b4-v1-0-a7ae5a49e9cf@pks.im>

The b4 tool originates from the Linux kernel community and is intended
to help mailing-list based workflows. It automates a lot of the annoying
bookkeeping tasks that contributors typically need to do: tracking the
list of recipients, Message-IDs, range-diffs and the like. In addition
to that, b4 also has many other subcommands that help the maintainer and
reviewers.

The Git project uses the same infrastructure as the kernel, so this tool
is also a very good fit for us. Adapt "MyFirstContribution" to
explicitly recommend its use.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/MyFirstContribution.adoc | 81 ++++++++++++++++++++++++++++++++--
 Documentation/SubmittingPatches        |  6 ++-
 2 files changed, 82 insertions(+), 5 deletions(-)

diff --git a/Documentation/MyFirstContribution.adoc b/Documentation/MyFirstContribution.adoc
index b9fdefce02..2e50111d89 100644
--- a/Documentation/MyFirstContribution.adoc
+++ b/Documentation/MyFirstContribution.adoc
@@ -833,7 +833,7 @@ This patchset is part of the MyFirstContribution tutorial and should not
 be merged.
 ----
 
-At this point the tutorial diverges, in order to demonstrate two
+At this point the tutorial diverges, in order to demonstrate three
 different methods of formatting your patchset and getting it reviewed.
 
 The first method to be covered is GitGitGadget, which is useful for those
@@ -845,9 +845,14 @@ more fine-grained control over the emails to be sent. This method requires some
 setup which can change depending on your system and will not be covered in this
 tutorial.
 
+The third method to be covered is `b4`, which builds on top of `git
+format-patch` and `git send-email`. This method is the recommended way to
+submit patches via mail as it automates a lot of the bookkeeping required by
+`git send-email`.
+
 Regardless of which method you choose, your engagement with reviewers will be
-the same; the review process will be covered after the sections on GitGitGadget
-and `git send-email`.
+the same; the review process will be covered after the sections on GitGitGadget,
+`git send-email` and `b4`.
 
 [[howto-ggg]]
 == Sending Patches via GitGitGadget
@@ -1296,6 +1301,76 @@ index 88f126184c..38da593a60 100644
 2.21.0.392.gf8f6787159e-goog
 ----
 
+[[howto-b4]]
+== Sending Patches with `b4`
+
+`b4` is a tool that builds on top of `git format-patch` and `git send-email`.
+It automates much of the bookkeeping involved in sending a patch series to a
+mailing-list-based project.
+
+Refer to the https://b4.docs.kernel.org/[b4 documentation] for a full reference.
+
+[[prep-b4]]
+=== Preparing a Patch Series
+
+`b4` tracks your patch series as a branch. To start tracking the `psuh` branch
+you have been working on, run:
+
+----
+$ b4 prep --enroll master
+----
+
+This enrolls the current branch, using `master` as the base of the topic. `b4`
+manages the cover letter as part of the branch, so you can edit it at any time
+with:
+
+----
+$ b4 prep --edit-cover
+----
+
+The cover letter not only tracks the content of the top-level mail, but also
+the set of recipients. You can add recipients by adding `To:` and `Cc:`
+trailer lines.
+
+[[send-b4]]
+=== Sending the Patches
+
+Before sending the series out for real, you can inspect what `b4` would send by
+passing `--dry-run`:
+
+----
+$ b4 send --dry-run
+----
+
+Once you are happy with the result, send the series with:
+
+----
+$ b4 send
+----
+
+[[v2-b4]]
+=== Sending v2
+
+When you are ready to send a new iteration of your series, refine your
+patches as usual using linkgit:git-rebase[1]. Note that you typically want to
+rebase on top of the cover letter. You can configure an alias to enable easy
+rebases going forward:
+
+---
+$ git config set alias.b4-rebase 'rebase "HEAD^{/--- b4-submit-tracking ---}"'
+$ git b4-rebase -i
+---
+
+Before sending out the new version you should also update the cover letter with
+`b4 prep --edit-cover` to note the relevant changes compared to the previous
+version. You can inspect the changes between the two versions with `b4 prep
+--compare-to=v1`.
+
+Same as with the first version, you can use `b4 send` to send out the second
+version. `b4` automatically bumps the version to `v2`, generates the range-diff
+against the previous iteration, and threads the new series as a reply to the
+cover letter of the first version.
+
 [[now-what]]
 == My Patch Got Emailed - Now What?
 
diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches
index d570184ec8..99427e1ee1 100644
--- a/Documentation/SubmittingPatches
+++ b/Documentation/SubmittingPatches
@@ -573,8 +573,10 @@ your existing e-mail client (often optimized for "multipart/*" MIME
 type e-mails) might render your patches unusable.
 
 NOTE: Here we outline the procedure using `format-patch` and
-`send-email`, but you can instead use GitGitGadget to send in your
-patches (see link:MyFirstContribution.html[MyFirstContribution]).
+`send-email`, but you can instead use GitGitGadget or `b4` to send in
+your patches (see link:MyFirstContribution.html[MyFirstContribution]).
+Contributors are encouraged to use `b4`, which automates much of the
+bookkeeping that is otherwise done by hand.
 
 People on the Git mailing list need to be able to read and
 comment on the changes you are submitting.  It is important for

-- 
2.54.0.1064.gd145956f57.dirty


^ permalink raw reply related

* [PATCH 0/2] Documentation: recommend the use of b4
From: Patrick Steinhardt @ 2026-06-02 11:59 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano

Hi,

this small patch series wires up b4 in Git and recommends the use
thereof via "MyFirstContribution", as discussed in [1].

Thanks!

Patrick

[1]: <xmqqik81xpqx.fsf@gitster.g>

---
Patrick Steinhardt (2):
      b4: introduce configuration for the Git project
      Documentation/MyFirstContribution: recommend the use of b4

 .b4-config                             |  3 ++
 .b4-cover-template                     | 11 +++++
 Documentation/MyFirstContribution.adoc | 81 ++++++++++++++++++++++++++++++++--
 Documentation/SubmittingPatches        |  6 ++-
 4 files changed, 96 insertions(+), 5 deletions(-)


---
base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
change-id: 20260602-pks-b4-31cc20d7f84b


^ permalink raw reply

* [PATCH 1/2] b4: introduce configuration for the Git project
From: Patrick Steinhardt @ 2026-06-02 11:59 UTC (permalink / raw)
  To: git; +Cc: Junio C Hamano
In-Reply-To: <20260602-pks-b4-v1-0-a7ae5a49e9cf@pks.im>

We're about to extend our documentation to recommend b4 for sending
patch series ot the mailing list. Prepare for this by introducing a b4
configuration so that the tool knows to honor our preferences. For now,
this configuration does two things:

  - It configures "send-same-thread = shallow", which tells b4 to always
    send subsequent versions of the same patch series as a reply to the
    cover letter of the first version.

  - It configures "prep-cover-template", which tells b4 to use a custom
    template for the cover letter. The most important change compared to
    the default template is that our custom template also includes a
    range-diff.

There's potentially more things that we may want to configure going
forward, like for example auto-configuration of folks to Cc on certain
patches. But these two tweaks feel like a good place to start.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 .b4-config         |  3 +++
 .b4-cover-template | 11 +++++++++++
 2 files changed, 14 insertions(+)

diff --git a/.b4-config b/.b4-config
new file mode 100644
index 0000000000..14124728ce
--- /dev/null
+++ b/.b4-config
@@ -0,0 +1,3 @@
+[b4]
+send-same-thread = shallow
+prep-cover-template = ./.b4-cover-template
diff --git a/.b4-cover-template b/.b4-cover-template
new file mode 100644
index 0000000000..ab864933b5
--- /dev/null
+++ b/.b4-cover-template
@@ -0,0 +1,11 @@
+${cover}
+
+---
+${shortlog}
+
+${diffstat}
+
+${range_diff}
+---
+base-commit: ${base_commit}
+${prerequisites}

-- 
2.54.0.1064.gd145956f57.dirty


^ permalink raw reply related

* Re: [PATCH 2/4] doc: replay: simplify replay.refAction description
From: Kristoffer Haugsbakk @ 2026-06-02 11:58 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, Siddharth Asthana
In-Reply-To: <xmqqy0h0ed7h.fsf@gitster.g>

On Sun, May 31, 2026, at 00:37, Junio C Hamano wrote:
> kristofferhaugsbakk@fastmail.com writes:
>
>>  replay.refAction::
>> -	Specifies the default mode for handling reference updates in
>> -	`git replay`. The value can be:
>> -+
>> ---
>> -	* `update`: Update refs directly using an atomic transaction (default behavior).
>> -	* `print`: Output update-ref commands for pipeline use.
>> ---
>> -+
>> -This setting can be overridden with the `--ref-action` command-line option.
>> -When not configured, `git replay` defaults to `update` mode.
>> +	Specifies the default mode for handling reference updates. Either `update` or `print`.
>> +ifdef::git-replay[]
>> +See `--ref-action`.
>> +endif::git-replay[]
>> +ifndef::git-replay[]
>> +See `--ref-action` for linkgit:git-replay[1] for details.
>> +endif::git-replay[]
>
> This makes it a bit roundabout for "git config --help" readers who
> wanted to figure out what value to set to the configuration
> variable, because the valid choices are no longer listed here.

That’s a good point. My thought process at the time was

• This description list needs to be changed
• But I also need to change it on git-replay(1)...
• So why not just gesture towards git-replay(1)?

But now I see that this does make it slightly worse. Which is not worth
the saved effort.

I was thinking that a shared file which is included in the config and
git-replay(1) could be used. That file would just contain the definition
list. I could also duplicate it manually and leave a comment about
keeping them in synch. What do you think?

Assuming that they should be equal, which I think right now although I
haven’t started on the next version yet.

>
> Finding `--ref-action=<mode>` and its description in the other page
> is straight-forward, so it may not be too bad, though.
>
>> diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
>> index f9ca2db2833..4de85088d6c 100644
>> --- a/Documentation/git-replay.adoc
>> +++ b/Documentation/git-replay.adoc
>> @@ -211,6 +211,7 @@ to use bare commit IDs instead of branch names.
>>
>>  CONFIGURATION
>>  -------------
>> +:git-replay: 1
>>  include::config/replay.adoc[]
>
> The use of conditional attributes (`ifdef::git-replay[]`) is a neat
> and standard way to tailor the description depending on whether it
> is read as part of `git-config(1)` or `git-replay(1)`. It correctly
> points the reader to `--ref-action` in the latter case, and provides
> a full `linkgit` reference in the former. Clean and correct.

Thanks for the thorough review.

^ permalink raw reply

* Re: What's cooking in git.git (Jun 2026, #01)
From: Kristoffer Haugsbakk @ 2026-06-02 11:53 UTC (permalink / raw)
  To: Junio C Hamano, git
In-Reply-To: <xmqqy0gxw5i9.fsf@gitster.g>

On Tue, Jun 2, 2026, at 13:23, Junio C Hamano wrote:
>[snip]
> * kh/doc-replay-config (2026-05-21) 4 commits
>  - doc: replay: move “default” to the right-hand-side
>  - doc: replay: use a nested definition list
>  - doc: replay: simplify replay.refAction description
>  - doc: link to config for git-replay(1)
>
>  Doc update for "git replay" to actually refer to its configuration
>  variables.
>
>  Will merge to 'next'.
>  cf. <xmqqpl2ced7d.fsf@gitster.g>
>  source: <CV_doc_replay_config.709@msgid.xyz>

Sorry, I meant to reply to your review but it fell out of the back of
my mind. I will do that now.

I think making a second version will make sense.

>[snip]

^ permalink raw reply

* Git for Windows Failing to Clone
From: Dylan Carlyle @ 2026-06-02 11:31 UTC (permalink / raw)
  To: git

Thank you for filling out a Git bug report!
Please answer the following questions to help us understand your issue.

What did you do before the bug happened? (Steps to reproduce your issue):

Ran git clone user@ip_addres:repo

What did you expect to happen? (Expected behavior):

The repo to be cloned

What happened instead? (Actual behavior):

remote: Enumerating objects: 57873, done.
remote: Counting objects: 100% (57873/57873), done.
remote: Compressing objects: 100% (32002/32002), done.
fatal: pack has bad object at offset 460179591: inflate returned 1
fatal: fetch-pack: invalid index-pack output

What's different between what you expected and what actually happened?

The clone never finishes on Windows.

Anything else you want to add:

Git version on the remote server is 2.47.3
This works fine from Linux but fails on Windows.

[System Info]
git version:
git version 2.54.0.windows.1
cpu: x86_64
built from commit: 2b8a3ab140826ac423c2845ef81d4c6ac4f7bf3c
sizeof-long: 4
sizeof-size_t: 8
shell-path: D:/git-sdk-64-build-installers/usr/bin/sh
rust: disabled
feature: fsmonitor--daemon

-- 
Kind Regards,

Dylan Carlyle
REFTEK Systems, Inc.
Systems Administrator

^ permalink raw reply

* What's cooking in git.git (Jun 2026, #01)
From: Junio C Hamano @ 2026-06-02 11:23 UTC (permalink / raw)
  To: git

Here are the topics that have been cooking in my tree.  Commits
prefixed with '+' are in 'next' (being in 'next' is a sign that a
topic is stable enough to be used and is a candidate to be in a
future release).  Commits prefixed with '-' are only in 'seen', and
aren't considered "accepted" at all and may be annotated with a URL
to a message that raises issues but they are by no means exhaustive.
A topic without enough support may be discarded after a long period
of no activity (of course they can be resubmitted when new interests
arise).

This week is expected to be slow, as I am officially on vacation.

Copies of the source code to Git live in many repositories, and the
following is a list of the ones I push into or their mirrors.  Some
repositories have only a subset of branches.

With maint, master, next, seen, todo:

	git://git.kernel.org/pub/scm/git/git.git/
	git://repo.or.cz/alt-git.git/
	https://kernel.googlesource.com/pub/scm/git/git/
	https://github.com/git/git/
	https://gitlab.com/git-scm/git/

With all the integration branches and topics broken out:

	https://github.com/gitster/git/

Even though the preformatted documentation in HTML and man format
are not sources, they are published in these repositories for
convenience (replace "htmldocs" with "manpages" for the manual
pages):

	git://git.kernel.org/pub/scm/git/git-htmldocs.git/
	https://github.com/gitster/git-htmldocs.git/

Release tarballs are available at:

	https://www.kernel.org/pub/software/scm/git/

--------------------------------------------------
[Graduated to 'master']

* ds/path-walk-filters (2026-05-22) 14 commits
  (merged to 'next' on 2026-05-25 at eccb829b10)
 + path-walk: support `combine` filter
 + path-walk: support `object:type` filter
 + path-walk: support `tree:0` filter
 + t6601: tag otherwise-unreachable trees
 + pack-objects: support sparse:oid filter with path-walk
 + path-walk: add pl_sparse_trees to control tree pruning
 + path-walk: support blob size limit filter
 + backfill: die on incompatible filter options
 + path-walk: support blobless filter
 + path-walk: always emit directly-requested objects
 + t/perf: add pack-objects filter and path-walk benchmark
 + pack-objects: pass --objects with --path-walk
 + t5620: make test work with path-walk var
 + Merge branch 'en/backfill-fixes-and-edges' into ds/path-walk-filters
 (this branch is used by tb/pack-path-walk-bitmap-delta-islands.)

 The "git pack-objects --path-walk" traversal has been integrated
 with several object filters, including blobless and sparse filters.
 source: <pull.2101.v5.git.1779474277.gitgitgadget@gmail.com>


* ed/check-connected-close-err-fd (2026-05-16) 1 commit
  (merged to 'next' on 2026-05-22 at 00d592399e)
 + Merge branch 'ed/check-connected-close-err-fd-2.53' into ed/check-connected-close-err-fd
 (this branch uses ed/check-connected-close-err-fd-2.53.)

 File descriptor leak fix.
 (this branch uses ed/check-connected-close-err-fd-2.53.)


* ed/check-connected-close-err-fd-2.53 (2026-05-14) 1 commit
  (merged to 'next' on 2026-05-22 at 1017d0e022)
 + connected: close err_fd in promisor fast-path
 (this branch is used by ed/check-connected-close-err-fd.)

 File descriptor leak fix (for 2.54 maintenance track).
 source: <pull.2303.git.git.1778827194448.gitgitgadget@gmail.com>


* jk/commit-graph-lazy-load-fallback (2026-05-18) 1 commit
  (merged to 'next' on 2026-05-22 at d1188df466)
 + commit: fall back to full read when maybe_tree is NULL

 The logic to lazy-load trees from the commit-graph has been made
 more robust by falling back to reading the commit object when
 the commit-graph is no longer available.
 source: <20260519061534.GA1709881@coredump.intra.peff.net>


* jk/connect-service-enum (2026-05-21) 2 commits
  (merged to 'next' on 2026-05-24 at 293561cbc5)
 + transport-helper: fix typo in BUG() message
  (merged to 'next' on 2026-05-21 at fd80c61e21)
 + connect: use "service" enum for "name" argument

 The "name" argument in git_connect() and related functions has been
 converted to a "service" enum to improve type safety and clarify its
 purpose.
 source: <20260519052219.GA1703179@coredump.intra.peff.net>
 source: <20260522044352.GA861761@coredump.intra.peff.net>


* jr/bisect-custom-terms-in-output (2026-05-14) 3 commits
  (merged to 'next' on 2026-05-22 at 1ccd1056c9)
 + rev-parse: use selected alternate terms to look up refs
 + bisect: print bisect terms in single quotes
 + bisect: use selected alternate terms in status output

 "git bisect" now uses the selected terms (e.g., old/new) more
 consistently in its output.
 source: <20260514-bisect-terms-v4-0-b3e3cf1b06ce@schlaraffenlan.de>


* kh/doc-hook (2026-05-21) 4 commits
  (merged to 'next' on 2026-05-25 at 5e41d13adf)
 + doc: hook: don’t self-link via config include
 + doc: config: include existing git-hook(1) section
 + doc: hook: consistently capitalize Git
 + doc: hook: remove stray backtick

 Doc updates.
 cf. <2832179.mvXUDI8C0e@piment-oiseau>
 source: <CV_doc_hook.6f0@msgid.xyz>


* kk/tips-reachable-from-bases-optim (2026-05-16) 2 commits
  (merged to 'next' on 2026-05-22 at 87d6b8e666)
 + t6600: add tests for duplicate tips in tips_reachable_from_bases()
 + commit-reach: use object flags for tips_reachable_from_bases()

 Revision traversal optimization.
 source: <pull.2116.v3.git.1778947182.gitgitgadget@gmail.com>


* ps/gitlab-ci-macOS-improvements (2026-05-21) 2 commits
  (merged to 'next' on 2026-05-22 at aaa3c7021e)
 + gitlab-ci: update macOS image
 + gitlab-ci: upgrade macOS runners

 Update GitLab CI jobs that exercise macOS.
 source: <20260521-b4-pks-gitlab-ci-updates-v1-0-53bb46ed33e0@pks.im>


* ps/graph-lane-limit (2026-03-27) 3 commits
  (merged to 'next' on 2026-05-22 at ca1c5e8432)
 + graph: add truncation mark to capped lanes
 + graph: add --graph-lane-limit option
 + graph: limit the graph width to a hard-coded max

 The graph output from commands like "git log --graph" can now be
 limited to a specified number of lanes, preventing overly wide output
 in repositories with many branches.
 cf. <bdff0a5d-b738-4053-9b72-08eba88156de@kdbg.org>
 source: <20260328001113.1275291-1-pabloosabaterr@gmail.com>


* pt/fsmonitor-linux (2026-04-15) 13 commits
  (merged to 'next' on 2026-05-22 at 5d99c1765d)
 + fsmonitor: convert shown khash to strset in do_handle_client
 + fsmonitor: add tests for Linux
 + fsmonitor: add timeout to daemon stop command
 + fsmonitor: close inherited file descriptors and detach in daemon
 + run-command: add close_fd_above_stderr option
 + fsmonitor: implement filesystem change listener for Linux
 + fsmonitor: rename fsm-settings-darwin.c to fsm-settings-unix.c
 + fsmonitor: rename fsm-ipc-darwin.c to fsm-ipc-unix.c
 + fsmonitor: use pthread_cond_timedwait for cookie wait
 + compat/win32: add pthread_cond_timedwait
 + fsmonitor: fix hashmap memory leak in fsmonitor_run_daemon
 + fsmonitor: fix khash memory leak in do_handle_client
 + t9210, t9211: disable GIT_TEST_SPLIT_INDEX for scalar clone tests

 The fsmonitor daemon has been implemented for Linux.
 cf. <xmqqa4u5nnxq.fsf@gitster.g>
 source: <pull.2147.v15.git.git.1776259657.gitgitgadget@gmail.com>


* sa/cat-file-batch-mailmap-switch (2026-04-15) 1 commit
  (merged to 'next' on 2026-05-22 at 197a9bad73)
 + cat-file: add mailmap subcommand to --batch-command

 "git cat-file --batch" learns an in-line command "mailmap"
 that lets the user toggle use of mailmap.
 cf. <xmqqwlwy4v7t.fsf@gitster.g>
 source: <20260416033250.4327-2-siddharthasthana31@gmail.com>


* sp/doc-range-diff-takes-notes (2026-05-20) 1 commit
  (merged to 'next' on 2026-05-22 at 020bec81b7)
 + Documentation/git-range-diff: add missing notes options in synopsis

 Docfix.
 source: <20260521052841.73775-1-siddh.raman.pant@oracle.com>


* ta/approxidate-noon-fix (2026-05-21) 4 commits
  (merged to 'next' on 2026-05-25 at 2dd9ce3c54)
 + approxidate: use deferred mday adjustments for "specials"
 + approxidate: make "specials" respect fixed day-of-month
 + t0006: add support for approxidate test date adjustment
 + approxidate: make "today" wrap to midnight

 "Friday noon" asked in the morning on Sunday was parsed to be one
 day before the specified time, which has been corrected.
 source: <20260521105408.8222-1-taahol@utu.fi>


* tc/generate-configlist-fix-for-older-ninja (2026-05-15) 1 commit
  (merged to 'next' on 2026-05-22 at 8322bfb8f2)
 + generate-configlist: collapse depfile for older Ninja

 Build update.
 source: <20260515-toon-fix-almalinux8-v3-1-b545a0647f0f@iotcl.com>

--------------------------------------------------
[New Topics]

* kh/free-commit-list (2026-05-28) 2 commits
  (merged to 'next' on 2026-05-31 at 154f83b192)
 + commit: remove deprecated functions
 + *: replace deprecated free_commit_list

 Code clean-up.

 Will merge to 'master'.
 source: <V2_CV_commit.h_remove_deprecated.732@msgid.xyz>


* kk/streaming-walk-pqueue (2026-05-27) 3 commits
 - revision: use priority queue for non-limited streaming walks
 - revision: introduce rev_walk_mode to clarify get_revision_1()
 - pack-objects: call release_revisions() after cruft traversal

 Streaming revision walks have been optimized by using a priority queue
 for date-sorting commits, speeding up walks repositories with many
 merges.

 Will merge to 'next'?
 source: <pull.2127.git.1779897003.gitgitgadget@gmail.com>


* kk/wildmatch-windows-ls-files-prereq (2026-05-28) 1 commit
 - t3070: skip ls-files tests with backslash patterns on Windows

 In t3070-wildmatch, "via ls-files" test variants with patterns
 containing backslash escapes are now skipped on Windows, avoiding 36
 test failures caused by pathspec separator conversion.

 Will merge to 'next'.
 cf. <xmqqecivjn7k.fsf@gitster.g>
 source: <pull.2128.git.1779958849319.gitgitgadget@gmail.com>


* sn/rebase-update-refs-symrefs (2026-05-27) 2 commits
 - rebase: skip branch symref aliases
 - t3404: add failing branch symref test

 "git rebase --update-refs" has been taught to resolve local branch
 symrefs to their referents before queuing updates. This correctly
 skips aliases of the current branch and avoids duplicate updates for
 underlying real branches, fixing failures when branch aliases (like a
 default branch rename) are present.

 Waiting for response(s) to review comment(s).
 cf. <xmqqwlwni7vk.fsf@gitster.g>
 cf. <f1b662d5-f9bb-4274-ad42-3a2227d2a060@gmail.com>
 source: <pull.2126.git.1779946921.gitgitgadget@gmail.com>


* lp/http-fetch-pack-index-leak-fix (2026-06-01) 2 commits
 - http: fix memory leak in fetch_and_setup_pack_index()
 - http: cleanup function fetch_and_setup_pack_index()

 A memory leak in `fetch_and_setup_pack_index()` when verification of
 the downloaded pack index fails has been plugged. Also an obsolete
 `unlink()` call on parse failure has been cleaned up.

 Will merge to 'next'.
 cf. <20260529053659.GC1099450@coredump.intra.peff.net>
 source: <cover.1780321770.git.lorenzo.pegorari2002@gmail.com>


* jk/describe-contains-all-match-fix (2026-06-01) 1 commit
 - describe: fix --exclude, --match with --contains and --all

 The 'git describe --contains --all' command has been fixed to
 properly honor the '--match' and '--exclude' options by passing
 them down to 'git name-rev' with the appropriate reference
 prefixes.

 Will merge to 'next'?
 source: <20260601233727.43558-1-jacob.e.keller@intel.com>


* wy/docs-typofixes (2026-05-29) 1 commit
 - docs: fix typos and grammar

 Various typos, grammatical errors, and duplicated words in both
 documentation and code comments have been corrected.

 Waiting for response(s) to review comment(s).
 cf. <xmqq8q8x3nox.fsf@gitster.g>
 source: <7b502e20e9495cd4720496bd6738a1fbeb453410.1780041658.git.wy@wyuan.org>


* ab/index-pack-retain-child-bases (2026-06-01) 1 commit
 - index-pack: retain child bases in delta cache

 "git index-pack" has been optimized by retaining child bases in the
 delta cache instead of immediately freeing them, letting the existing
 cache limit policy decide eviction.

 Comments?
 cf. <4882be43-9bc5-48cf-b74c-4a05453b2fef@gmail.com>
 cf. <20260602064519.GD695568@coredump.intra.peff.net>
 source: <pull.2131.v2.git.1780330402264.gitgitgadget@gmail.com>


* hn/macos-linker-warning (2026-06-02) 1 commit
 - config.mak.uname: avoid macOS linker warning on Xcode 16.3+

 A linker warning on macOS when building with Xcode 16.3 or newer has
 been avoided by passing -fno-common to the compiler when a
 sufficiently new linker is detected.

 Will merge to 'next'.
 source: <pull.2313.v3.git.git.1780385878555.gitgitgadget@gmail.com>


* mm/diff-process-hunks (2026-05-29) 6 commits
 - blame: consult diff process for no-hunk detection
 - diff: bypass diff process with --no-ext-diff and in format-patch
 - diff: add long-running diff process via diff.<driver>.process
 - sub-process: separate process lifecycle from hashmap management
 - userdiff: add diff.<driver>.process config
 - xdiff: support external hunks via xpparam_t

 A new `diff.<driver>.process` configuration has been introduced to
 allow a long-running external process to act as a hunk provider to
 allows external tools to control which lines Git considers changed
 while leaving all output formatting (word diff, color, blame, etc.) to
 Git's standard pipeline.

 Breaks CI.
 cf. <xmqq5x43dfk4.fsf@gitster.g>
 source: <pull.2120.v3.git.1780087700.gitgitgadget@gmail.com>


* tb/pack-path-walk-bitmap-delta-islands (2026-05-27) 4 commits
 - pack-objects: support `--delta-islands` with `--path-walk`
 - pack-objects: extract `record_tree_depth()` helper
 - pack-objects: support reachability bitmaps with `--path-walk`
 - Merge branch 'ds/path-walk-filters' into tb/pack-path-walk-bitmap-delta-islands

 The pack-objects command now supports using reachability bitmaps and
 delta-islands concurrently with the `--path-walk` option, allowing
 faster packaging by falling back to path-walk when bitmaps cannot
 fully satisfy the request.

 Expecting a reroll.
 cf. <ahoRQZX6OXYKPCmd@nand.local>
 source: <cover.1779923907.git.me@ttaylorr.com>


* ty/migrate-trust-executable-bit (2026-05-30) 4 commits
 - read-cache: pass 'istate' to stat/mode helper functions
 - environment: move 'trust_executable_bit' into repo_config_values
 - read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'
 - read-cache: remove redundant extern declarations

 The 'trust_executable_bit' (coming from 'core.filemode'
 configuration) has been migrated into 'repo_config_values' to tie it
 to a specific repository instance.

 Waiting for response(s) to review comment(s).
 cf. <CAP8UFD1GJ=caPh-M97KLCfB1ZKtpomzosYN0uYBOnay+G23GcA@mail.gmail.com>
 cf. <CAP8UFD20yij=1ZEYnR74DoCJ3g=b39yOsUxZecYuuf7nFGaKyA@mail.gmail.com>
 source: <20260530160520.77859-1-cat@malon.dev>


* ak/typofixes (2026-05-31) 2 commits
 - SQUASH???
 - doc: fix typos via codespell

 Typofixes.

 Waiting for response(s) to review comment(s).
 cf. <xmqqo6hv9i1w.fsf@gitster.g>
 source: <20260531184428.55905-1-algonell@gmail.com>


* kk/prio-queue-cascade-sift (2026-06-01) 1 commit
 - prio-queue: use cascade-down for faster extract-min

 prio_queue_get() has been optimized by using a cascade-down approach
 (promoting the smaller child at each level and sifting up the last
 element from the leaf vacancy), which halves the number of comparisons
 per extract-min operation in the common case.

 Comments?
 source: <pull.2132.v2.git.1780301856444.gitgitgadget@gmail.com>


* mm/subprocess-handshake-fix (2026-06-01) 1 commit
 - sub-process: use gentle handshake to avoid die() on startup failure

 The subprocess handshake during startup has been made gentler by using
 packet_read_line_gently() instead of packet_read_line() to prevent the
 parent Git process from dying abruptly when a configured subprocess
 (e.g., a clean/smudge filter) fails to start.

 Will merge to 'next'?
 source: <pull.2133.v2.git.1780348848489.gitgitgadget@gmail.com>


* jk/repo-info-path-keys (2026-06-01) 4 commits
 - repo: add path.commondir with absolute and relative suffix formatting
 - repo: add path.gitdir with absolute and relative suffix formatting
 - rev-parse: use strbuf_add_path for path formatting
 - path: add strbuf_add_path for formatting paths

 The "git repo info" command has been taught new keys to output both
 absolute and relative paths for "gitdir" and "commondir", supported by
 a new path-formatting helper extracted from "git rev-parse".
 source: <20260601151950.30686-1-jayatheerthkulkarni2005@gmail.com>


* ps/history-drop (2026-06-01) 2 commits
 - builtin/history: implement "drop" subcommand
 - builtin/history: split handling of ref updates into two phases

 The experimental "git history" command has been taught a new "drop"
 subcommand to remove a commit and replay its descendants onto its
 parent.

 Waiting for response(s) to review comment(s).
 cf. <xmqqbjdt25e3.fsf@gitster.g>
 source: <20260601-b4-pks-history-drop-v1-0-643e32340d55@pks.im>


* ls/doc-raw-timestamp-prefix (2026-06-02) 1 commit
 - doc: document and test `@` prefix for raw timestamps

 Documentation and tests have been added to clarify that Git's internal
 raw timestamp format requires a `@` prefix for values less than
 100,000,000 to prevent ambiguity with other formats like YYYYMMDD.

 Will merge to 'next'?
 cf. <xmqqmrxdxq1r.fsf@gitster.g>
 source: <20260602081924.673763-2-dev@luna.gl>


* jk/setup-gitfile-diag-fix (2026-06-01) 1 commit
 - read_gitfile_gently(): return non-repo path on error

 A regression in the error diagnosis code for invalid .git files has
 been fixed, avoiding a potential NULL-pointer crash when reporting
 that a .git file does not point to a valid repository.

 Comments?
 source: <20260602061159.GA693928@coredump.intra.peff.net>


* wy/typofixes (2026-05-29) 1 commit
 - docs: fix typos and grammar

 Typofixes.
 source: <7b502e20e9495cd4720496bd6738a1fbeb453410.1780041658.git.wy@wyuan.org>


* jc/submitting-patches-cover-letter (2026-06-02) 2 commits
 - SubmittingPatches: describe cover letter
 - SubmittingPatches: separate typofixes section

 Guidelines on how to write a cover letter for a multi-patch series
 have been added to SubmittingPatches, which also got a new marker
 to separate the section for typofixes.

 Comments?
 source: <20260602090808.87837-1-gitster@pobox.com>


* ps/t7527-fix-tap-output (2026-06-02) 4 commits
 - t: let prove fail when parsing invalid TAP output
 - t/lib-git-p4: silence output when killing p4d and its watchdog
 - t/test-lib: silence EBUSY errors on Windows during test cleanup
 - t7527: fix broken TAP output

 A recent regression in t7527 that broke TAP output has been fixed,
 some other test noise that also broke TAP output has been silenced,
 and 'prove' is now configured to fail on invalid TAP output to
 prevent future regressions.

 Waiting for response(s) to review comment(s).
 cf. <xmqqecipxp6g.fsf@gitster.g>
 source: <20260602-pks-t7527-fix-tap-output-v1-0-db3da2a1b137@pks.im>

--------------------------------------------------
[Stalled]

* jd/unpack-trees-wo-the-repository (2026-03-31) 2 commits
 - unpack-trees: use repository from index instead of global
 - unpack-trees: use repository from index instead of global

 A handful of inappropriate uses of the_repository have been
 rewritten to use the right repository structure instance in the
 unpack-trees.c codepath.

 Waiting for response(s) to review comment(s) for too long, consider discarding.
 cf. <xmqqldf7y95a.fsf@gitster.g>
 source: <pull.2258.v2.git.git.1774971267.gitgitgadget@gmail.com>


* cs/subtree-split-recursion (2026-03-05) 3 commits
 - contrib/subtree: reduce recursion during split
 - contrib/subtree: functionalize split traversal
 - contrib/subtree: reduce function side-effects

 When processing large history graphs on Debian or Ubuntu, "git
 subtree" can die with a "recursion depth reached" error.

 Waiting for response(s) to review comment(s) for too long, consider discarding.
 cf. <xmqqv7c13o5l.fsf@gitster.g>
 source: <20260305-cs-subtree-split-recursion-v2-0-7266be870ba9@howdoi.land>

--------------------------------------------------
[Cooking]

* ob/more-repo-config-values (2026-06-01) 8 commits
 - environment: move "warn_on_object_refname_ambiguity" into `struct repo_config_values`
 - environment: move "sparse_expect_files_outside_of_patterns" into `repo_config_values`
 - environment: move "core_sparse_checkout_cone" into `struct repo_config_values`
 - environment: move "precomposed_unicode" into `struct repo_config_values`
 - environment: move "pack_compression_level" into `struct repo_config_values`
 - environment: move `zlib_compression_level` into `struct repo_config_values`
 - environment: move "check_stat" into `struct repo_config_values`
 - environment: move "trust_ctime" into `struct repo_config_values`

 Many core configuration variables have been migrated from global
 variables into 'repo_config_values' to tie them to a specific
 repository instance, avoiding cross-repository state leakage.

 Waiting for response(s) to review comment(s).
 cf. <xmqqtsrlztzu.fsf@gitster.g>
 source: <20260601154211.82370-1-belkid98@gmail.com>


* kh/doc-trailers (2026-04-13) 9 commits
 - doc: interpret-trailers: document comment line treatment
 - doc: interpret-trailers: commit to “trailer block” term
 - doc: interpret-trailers: add key format example
 - doc: interpret-trailers: explain key format
 - doc: interpret-trailers: explain the format after the intro
 - doc: interpret-trailers: not just for commit messages
 - doc: interpret-trailers: use “metadata” in Name as well
 - doc: interpret-trailers: replace “lines” with “metadata”
 - doc: interpret-trailers: stop fixating on RFC 822

 Documentation updates.

 Expecting a reroll.
 cf. <5508ee49-2f78-4c3a-accf-a2350666bfb8@app.fastmail.com>
 source: <V2_CV_doc_int-tr_key_format.613@msgid.xyz>


* za/completion-hide-dotfiles (2026-05-26) 1 commit
 - completion: hide dotfiles for selected path completion

 The path completion for commands like `git rm` and `git mv`, is being
 updated to hide dotfiles by default, unless the user explicitly starts
 the path with a dot, matching standard shell-completion behavior.

 Comments?
 cf. <xmqqqzmxlep3.fsf@gitster.g>
 source: <pull.2311.v2.git.git.1779808987825.gitgitgadget@gmail.com>


* ds/restore-sparse-index (2026-05-26) 2 commits
  (merged to 'next' on 2026-05-31 at e85a961bc7)
 + restore: avoid sparse index expansion
 + t1092: test 'git restore' with sparse index

 'git restore --staged' has been optimized to avoid unnecessarily expanding
 the sparse index when operating on paths within the sparse checkout
 definition, by handling sparse directory entries at the tree level.

 Will merge to 'master'.
 source: <pull.2121.v2.git.1779827195.gitgitgadget@gmail.com>


* kk/commit-reach-optim (2026-05-25) 3 commits
  (merged to 'next' on 2026-05-31 at eeb8d0c207)
 + commit-reach: replace queue_has_nonstale() scan with O(1) tracking
 + commit-reach: deduplicate queue entries in paint_down_to_common
 + object.h: fix stale entries in object flag allocation table

 The check for non-stale commits in the priority queue used by
 `paint_down_to_common` and `ahead_behind` has been optimized by
 replacing an O(N) scan with an O(1) counter, yielding performance
 improvements in repositories with wide histories.

 Will merge to 'master'.
 cf. <xmqqzf1ncded.fsf@gitster.g>
 source: <pull.2124.v2.git.1779719286.gitgitgadget@gmail.com>


* ar/receive-pack-worktree-env (2026-05-25) 1 commit
  (merged to 'next' on 2026-05-27 at 9c246d1969)
 + receive-pack: fix updateInstead with core.worktree

 The GIT_WORK_TREE variable prepared to invoke the push-to-checkout
 hook was leaking into the environment even when there was no hook
 used and broke the default push-to-deploy (i.e., let "git checkout"
 update the working tree only when the working tree is clean).

 Will merge to 'master'.
 source: <20260525162311.66240-2-hi@alyssa.is>


* ib/doc-push-default-simple (2026-05-25) 1 commit
  (merged to 'next' on 2026-06-02 at 5c1ff2a769)
 + doc: clarify push.default=simple behavior

 The documentation for `push.default = simple` has been clarified to
 better explain its behavior, making it clear that it pushes the
 current branch to a same-named branch on the remote, and detailing
 the upstream requirements for centralized workflows.

 Will merge to 'master'.
 cf. <pull.2115.v2.git.1779767888508.gitgitgadget@gmail.com>
 source: <pull.2115.v2.git.1779767888508.gitgitgadget@gmail.com>


* jc/doc-monitor-ghci (2026-05-24) 1 commit
  (merged to 'next' on 2026-06-02 at 46fb5fe1c2)
 + SubmittingPatches: proactively monitor GHCI pages

 Encourage original authors to monitor the CI status.

 Will merge to 'master'.
 source: <xmqq1pf0gpp3.fsf@gitster.g>


* ec/commit-fixup-options (2026-05-26) 2 commits
 - commit: allow -c/-C for all kinds of --fixup
 - commit: allow -m/-F for all kinds of --fixup

 The -m/-F/-c/-C options to supply commit log message from outside the
 editor are now supported for all "git commit --fixup" variations.

 Comments?
 source: <cover.1779792311.git.erik@cervined.in>


* gh/jump-auto-mode (2026-05-21) 1 commit
  (merged to 'next' on 2026-06-02 at f70dd05c9c)
 + git-jump: pick a mode automatically when invoked without arguments

 The 'git-jump' command (in contrib/) has been taught to automatically
 pick a mode (merge, diff, or ws) when invoked without arguments.

 Will merge to 'master'.
 cf. <20260522052821.GC861761@coredump.intra.peff.net>
 source: <pull.2108.v3.git.1779371110195.gitgitgadget@gmail.com>


* ps/odb-source-loose (2026-06-01) 19 commits
 - odb/source-loose: drop pointer to the "files" source
 - odb/source-loose: stub out remaining callbacks
 - odb/source-loose: wire up `write_object_stream()` callback
 - object-file: refactor writing objects to use loose source
 - odb/source-loose: wire up `write_object()` callback
 - loose: refactor object map to operate on `struct odb_source_loose`
 - odb/source-loose: wire up `freshen_object()` callback
 - odb/source-loose: drop `odb_source_loose_has_object()`
 - odb/source-loose: wire up `count_objects()` callback
 - odb/source-loose: wire up `find_abbrev_len()` callback
 - odb/source-loose: wire up `for_each_object()` callback
 - odb/source-loose: wire up `read_object_stream()` callback
 - odb/source-loose: wire up `read_object_info()` callback
 - odb/source-loose: wire up `close()` callback
 - odb/source-loose: wire up `reprepare()` callback
 - odb/source-loose: start converting to a proper `struct odb_source`
 - odb/source-loose: store pointer to "files" instead of generic source
 - odb/source-loose: move loose source into "odb/" subsystem
 - Merge branch 'ps/odb-in-memory' into ps/odb-source-loose

 The loose object source has been refactored into a proper `struct
 odb_source`.

 Will merge to 'next'.
 source: <20260601-b4-pks-odb-source-loose-v2-0-90ff159430af@pks.im>


* ps/setup-centralize-odb-creation (2026-05-25) 9 commits
 - setup: construct object database in `apply_repository_format()`
 - repository: stop reading loose object map twice on repo init
 - setup: stop initializing object database without repository
 - setup: stop creating the object database in `setup_git_env()`
 - repository: stop initializing the object database in `repo_set_gitdir()`
 - setup: deduplicate logic to apply repository format
 - setup: drop `setup_git_env()`
 - t0001: plug test gaps for git-init(1) with GIT_OBJECT_DIRECTORY
 - Merge branch 'ps/setup-wo-the-repository' into ps/setup-centralize-odb-creation

 The setup logic to discover and configure repositories has been
 refactored, and the initialization of the object database has been
 centralized.

 Comments?
 source: <20260526-b4-pks-setup-centralize-odb-creation-v2-0-2fa5b385c13e@pks.im>


* kh/doc-replay-config (2026-05-21) 4 commits
 - doc: replay: move “default” to the right-hand-side
 - doc: replay: use a nested definition list
 - doc: replay: simplify replay.refAction description
 - doc: link to config for git-replay(1)

 Doc update for "git replay" to actually refer to its configuration
 variables.

 Will merge to 'next'.
 cf. <xmqqpl2ced7d.fsf@gitster.g>
 source: <CV_doc_replay_config.709@msgid.xyz>


* aj/stash-patch-optimize-temporary-index (2026-05-22) 1 commit
  (merged to 'next' on 2026-05-31 at d1b1dd94f5)
 + stash: reuse cached index entries in --patch temporary index

 "git stash -p" has been optimized by reusing cached index
 entries in its temporary index, avoiding unnecessary lstat()
 calls on unchanged files.

 Will merge to 'master'.
 cf. <xmqqse7m6deh.fsf@gitster.g>
 source: <pull.2306.v2.git.git.1779491545531.gitgitgadget@gmail.com>


* tb/bitmap-build-performance (2026-05-27) 9 commits
  (merged to 'next' on 2026-06-02 at d1a84a996a)
 + pack-bitmap: build pseudo-merge bitmaps after regular bitmaps
 + pack-bitmap: remember pseudo-merge parents
 + pack-bitmap: sort bitmaps before XORing
 + pack-bitmap: cache object positions during fill
 + pack-bitmap: consolidate `find_object_pos()` success path
 + pack-bitmap: reuse stored selected bitmaps
 + pack-bitmap: check subtree bits before recursing
 + pack-bitmap: pass object position to `fill_bitmap_tree()`
 + Merge branch 'tb/pseudo-merge-bugfixes' into tb/bitmap-build-performance

 Reachability bitmap generation has been significantly optimized. By
 reordering tree traversal, caching object positions, and refining how
 pseudo-merge bitmaps are constructed, the performance of "git repack
 --write-midx-bitmaps" is improved, especially for large repositories
 and when using pseudo-merges.

 Will merge to 'master'.
 cf. <20260529083439.GD1106035@coredump.intra.peff.net>
 source: <cover.1779911733.git.me@ttaylorr.com>


* hn/status-pull-advice-qualified (2026-05-21) 1 commit
 - remote: qualify "git pull" advice for non-upstream compareBranches

 Advice shown by "git status" when the local branch is behind or has
 diverged from its push branch has been updated to suggest "git pull
 <remote> <branch>".

 Comments?
 source: <pull.2301.v4.git.git.1779372367317.gitgitgadget@gmail.com>


* rs/strbuf-add-uint (2026-05-12) 4 commits
  (merged to 'next' on 2026-06-02 at f5be02d8ec)
 + ls-tree: use strbuf_add_uint()
 + ls-files: use strbuf_add_uint()
 + cat-file: use strbuf_add_uint()
 + strbuf: add strbuf_add_uint()

 Adding a decimal integer with strbuf_addf("%u") appears commonly;
 they have been optimized by using a custom formatter.

 Will merge to 'master'.
 cf. <20260512184619.GD70851@coredump.intra.peff.net>
 source: <20260512115603.80780-1-l.s.r@web.de>


* mm/doc-word-diff (2026-05-28) 1 commit
 - doc: clarify that --word-diff operates on line-level hunks

 The documentation for "--word-diff" has been extended with a bit of
 implementation detail of where these different words come from.

 Will merge to 'next'.
 source: <pull.2113.v2.git.1779996106005.gitgitgadget@gmail.com>


* rs/strbuf-add-oid-hex (2026-05-13) 1 commit
  (merged to 'next' on 2026-06-02 at 4876f95de0)
 + hex: add and use strbuf_add_oid_hex()

 Formatting object name in full hexadecimal form has been optimized
 by using a new strbuf_add_oid_hex() helper function.

 Will merge to 'master'.
 cf. <20260513160155.GA103037@coredump.intra.peff.net>
 source: <183aa0fd-d455-4ec9-9c42-d511fac8b3e4@web.de>


* hn/config-typo-advice (2026-05-26) 1 commit
 - config: improve diagnostic for "set" with missing value

 "git config foo.bar=baz" is not likely to be a request to read the
 value of such a variable with '=' in its name; rather it is plausible
 that the user meant "git config set foo.bar baz".  Give advice when
 giving an error message.

 Waiting for response(s) to review comment(s).
 cf. <xmqq33z524yh.fsf@gitster.g>
 source: <pull.2302.v4.git.git.1779823288005.gitgitgadget@gmail.com>


* ja/doc-synopsis-style-again (2026-05-25) 6 commits
  (merged to 'next' on 2026-05-31 at cc4fe82d87)
 + doc: convert git-imap-send synopsis and options to new style
 + doc: convert git-apply synopsis and options to new style
 + doc: convert git-am synopsis and options to new style
 + doc: convert git-grep synopsis and options to new style
 + doc: git bisect: clarify the usage of the synopsis vs actual command
 + doc: convert git-bisect to synopsis style

 A batch of documentation pages has been updated to use the modern
 synopsis style.

 Will merge to 'master'.
 cf. <pull.2117.v2.git.1779704908.gitgitgadget@gmail.com>
 source: <pull.2117.v2.git.1779704908.gitgitgadget@gmail.com>


* jt/config-lock-timeout (2026-05-17) 1 commit
 - config: retry acquiring config.lock, configurable via core.configLockTimeout

 Configuration file locking now retries for a short period, avoiding
 failures when multiple processes attempt to update the configuration
 simultaneously.

 Waiting for response(s) to review comment(s).
 cf. <agrIrGwSMFlKTx9x@pks.im>
 source: <20260517132111.1014901-1-joerg@thalheim.io>


* hn/branch-prune-merged (2026-05-22) 6 commits
 - branch: add --dry-run for --prune-merged
 - branch: add branch.<name>.pruneMerged opt-out
 - branch: add --prune-merged <branch>
 - branch: prepare delete_branches for a bulk caller
 - branch: let delete_branches warn instead of error on bulk refusal
 - branch: add --forked <branch>

 "git branch" command learned "--prune-merged" option to remove
 local branches that have already been merged to the remote-tracking
 branches they track.

 Comments?
 source: <pull.2285.v11.git.git.1779449498.gitgitgadget@gmail.com>


* st/daemon-sockaddr-fixes (2026-05-27) 3 commits
 - daemon: guard NULL REMOTE_PORT in execute() logging
 - daemon: fix IPv6 address truncation in ip2str()
 - daemon: fix IPv6 address corruption in lookup_hostname()

 Correct use of sockaddr API in "git daemon".

 Will merge to 'next'.
 source: <pull.2300.v3.git.git.1779937016.gitgitgadget@gmail.com>


* cc/promisor-auto-config-url-more (2026-05-27) 8 commits
 - doc: promisor: improve acceptFromServer entry
 - promisor-remote: auto-configure unknown remotes
 - promisor-remote: trust known remotes matching acceptFromServerUrl
 - promisor-remote: introduce promisor.acceptFromServerUrl
 - promisor-remote: add 'local_name' to 'struct promisor_info'
 - urlmatch: add url_normalize_pattern() helper
 - urlmatch: change 'allow_globs' arg to bool
 - t5710: simplify 'mkdir X' followed by 'git -C X init'

 The handling of promisor-remote protocol capability has been
 loosened to allow the other side to add to the list of promisor
 remotes via the promisor.acceptFromServerURL configuration
 variable.

 Comments?
 source: <20260527140820.1438165-1-christian.couder@gmail.com>


* hn/checkout-track-fetch (2026-05-23) 2 commits
 - checkout: extend --track with a "fetch" mode to refresh start-point
 - branch: expose helpers for finding the remote owning a tracking ref

 "git checkout --track=..." learned to optionally fetch the branch
 from the remote the new branch will work with.

 Comments?
 source: <pull.2281.v13.git.git.1779565714.gitgitgadget@gmail.com>


* mf/revision-max-count-oldest (2026-05-18) 1 commit
 - revision.c: implement --max-count-oldest

 "git rev-list" (and "git log" family of commands) learned a new "--max-count-oldest"
 that picks oldest N commits in the range instead of the usual newest.

 Waiting for response(s) to review comment(s).
 cf. <xmqq7boy4o05.fsf@gitster.g>
 source: <8210d60832b9a58aa4d71fc3790e44d8989564ce.1779152064.git.mroik@delayed.space>


* mm/line-log-cleanup (2026-05-28) 3 commits
 - line-log: allow non-patch diff formats with -L
 - line-log: integrate -L output with the standard log-tree pipeline
 - revision: move -L setup before output_format-to-diff derivation

 The `git log -L` implementation has been refactored to use the
 standard diff output pipeline, enabling pickaxe and diff-filter to
 work as expected. Additionally, metadata-only diff formats like
 --raw and --name-only are now supported with -L.

 Will merge to 'next'.
 cf. <B59BA5B1-184D-48A8-8BAD-11EB6F8EB50C@gmail.com>
 source: <pull.2094.v3.git.1780001267.gitgitgadget@gmail.com>


* en/ort-harden-against-corrupt-trees (2026-04-20) 5 commits
 - cache-tree: fix verify_cache() to catch non-adjacent D/F conflicts
 - merge-ort: abort merge when trees have duplicate entries
 - merge-ort: free diff pairs queue in clear_or_reinit_internal_opts()
 - merge-ort: drop unnecessary show_all_errors from collect_merge_info()
 - merge-ort: propagate callback errors from traverse_trees_wrapper()

 "ort" merge backend handles merging corrupt trees better by
 aborting when it should.

 Waiting for response(s) to review comment(s).
 cf. <xmqqldcy4f07.fsf@gitster.g>
 source: <pull.2096.git.1776731171.gitgitgadget@gmail.com>


* pw/status-rebase-todo (2026-05-01) 2 commits
 - status: improve rebase todo list parsing
 - sequencer: factor out parsing of todo commands

 The display of the rebase todo list in "git status" has been
 improved to correctly abbreviate object IDs for more commands and
 avoid misinterpreting refs as object IDs.

 Waiting for response(s) to review comment(s).
 cf. <xmqqbjdwcsno.fsf@gitster.g>
 source: <cover.1777648598.git.phillip.wood@dunelm.org.uk>


* cl/conditional-config-on-worktree-path (2026-05-24) 2 commits
 - config: add "worktree" and "worktree/i" includeIf conditions
 - config: refactor include_by_gitdir() into include_by_path()

 The [includeIf "condition"] conditional inclusion facility for
 configuration files has learned to use the location of worktree
 in its condition.

 Waiting for response(s) to review comment(s).
 cf. <xmqq8q97et9b.fsf@gitster.g>
 source: <20260525-includeif-worktree-v5-0-1efe525d025a@black-desk.cn>


* ps/shift-root-in-graph (2026-04-27) 1 commit
 - graph: add indentation for commits preceded by a parentless commit

 In a history with more than one root commit, "git log --graph
 --oneline" stuffed an unrelated commit immediately below a root
 commit, which has been corrected by making the spot below a root
 unavailable.

 Expecting a reroll.
 cf. <CAN5EUNQoKRqt3FGLmzRGpPU1nO5jCAogP8Wm9gBZXuPbMNbQAw@mail.gmail.com>
 source: <20260427102838.44867-2-pabloosabaterr@gmail.com>


* th/promisor-quiet-per-repo (2026-04-06) 1 commit
  (merged to 'next' on 2026-06-02 at 02a749d7fe)
 + promisor-remote: fix promisor.quiet to use the correct repository

 The "promisor.quiet" configuration variable was not used from
 relevant submodules when commands like "grep --recurse-submodules"
 triggered a lazy fetch, which has been corrected.

 Will merge to 'master'.
 cf. <c87f1f12-d0cc-4150-8f43-4dc9cc1fe24f@malon.dev>
 source: <20260406183041.783800-1-vikingtc4@gmail.com>


* ua/push-remote-group (2026-05-03) 3 commits
  (merged to 'next' on 2026-06-02 at ba5d6aebaa)
 + push: support pushing to a remote group
 + remote: move remote group resolution to remote.c
 + remote: fix sign-compare warnings in push_cas_option

 "git push" learned to take a "remote group" name to push to, which
 causes pushes to multiple places, just like "git fetch" would do.

 Will merge to 'master'.
 cf. <20260518182721.155070-1-usmanakinyemi202@gmail.com>
 source: <20260503153402.1333220-1-usmanakinyemi202@gmail.com>


* js/parseopt-subcommand-autocorrection (2026-04-27) 11 commits
 - SQUASH???
 - doc: document autocorrect API
 - parseopt: add tests for subcommand autocorrection
 - parseopt: enable subcommand autocorrection for git-remote and git-notes
 - parseopt: autocorrect mistyped subcommands
 - autocorrect: provide config resolution API
 - autocorrect: rename AUTOCORRECT_SHOW to AUTOCORRECT_HINT
 - autocorrect: use mode and delay instead of magic numbers
 - help: move tty check for autocorrection to autocorrect.c
 - help: make autocorrect handling reusable
 - parseopt: extract subcommand handling from parse_options_step()

 The parse-options library learned to auto-correct misspelled
 subcommand names.

 Expecting a reroll.
 cf. <SY0P300MB0801E50FCB7EB2F45CD15208CE042@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>
 source: <SY0P300MB0801677A2A1E0FD38D06A841CE2A2@SY0P300MB0801.AUSP300.PROD.OUTLOOK.COM>


* jc/neuter-sideband-post-3.0 (2026-03-05) 2 commits
 - sideband: delay sanitizing by default to Git v3.0
 - Merge branch 'jc/neuter-sideband-fixup' into jc/neuter-sideband-post-3.0

 The final step, split from earlier attempt by Dscho, to loosen the
 sideband restriction for now and tighten later at Git v3.0 boundary.

 On hold to help the base topic with wider exposure.
 (this branch uses jc/neuter-sideband-fixup.)
 source: <20260305233452.3727126-8-gitster@pobox.com>

--------------------------------------------------
[Discarded]

* kk/fetch-store-ref-optimization (2026-05-24) 1 commit
 - fetch: pass transport to post-fetch connectivity check

 When fetching from a transport that provides a self-contained pack,
 pass the transport pointer to the post-fetch `check_connected()` call
 to optimize connectivity check.

 Retracted.
 cf. <CAL71e4MrVqC1=AR6x0_8S=8kVqPdDkhgCZRb4etFsxTzd6s_8Q@mail.gmail.com>
 source: <pull.2123.git.1779625693328.gitgitgadget@gmail.com>


* lp/repack-propagate-promisor-debugging-info (2026-04-18) 6 commits
 - repack-promisor: add missing headers
 - t7703: test for promisor file content after geometric repack
 - t7700: test for promisor file content after repack
 - repack-promisor: preserve content of promisor files after repack
 - repack-promisor add helper to fill promisor file after repack
 - pack-write: add explanation to promisor file content

 When fetching objects into a lazily cloned repository, .promisor
 files are created with information meant to help debugging.  "git
 repack" has been taught to carry this information forward to
 packfiles that are newly created.

 Retracted.
 cf. <agx_GPfBKpkSc3Gx@lorenzo-VM>
 source: <cover.1776384902.git.lorenzo.pegorari2002@gmail.com>

^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox