* [PATCH v3 3/4] doc: replay: use a nested description list
From: kristofferhaugsbakk @ 2026-06-05 13:56 UTC (permalink / raw)
To: Junio C Hamano
Cc: Kristoffer Haugsbakk, Siddharth Asthana, git, Patrick Steinhardt
In-Reply-To: <V3_CV_doc_replay_config.780@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
This bullet list for `--ref-action` introduces a term with a colon.
This is exactly what a description list is, structurally. Let’s be
stylistically consistent and use the desc. list markup construct.
In short, just transform this unordered list in the same way that we
did for `replay.refAction` in the previous commit.
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3:
• Msg:[1] Fix typo: “stylistically”
• Msg: Simplify message. Devote one paragraph to † 1: Commit
explain the transformation. Then delegate to the message
previous patch since we did the same trans-
formation there.
---
v2:
• Msg: Mention that the explanation for the description list is the
same as in the previous commit
• Msg: It’s “description list”, not “definition list”
Documentation/git-replay.adoc | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 4de85088d6c..b4fe43ec687 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -80,10 +80,10 @@ incompatible with `--contained` (which is a modifier for `--onto` only).
Control how references are updated. The mode can be:
+
--
- * `update` (default): Update refs directly using an atomic transaction.
- All refs are updated or none are (all-or-nothing behavior).
- * `print`: Output update-ref commands for pipeline use. This is the
- traditional behavior where output can be piped to `git update-ref --stdin`.
+`update` (default);; Update refs directly using an atomic transaction.
+ All refs are updated or none are (all-or-nothing behavior).
+`print`;; Output update-ref commands for pipeline use. This is the
+ traditional behavior where output can be piped to `git update-ref --stdin`.
--
+
The default mode can be configured via the `replay.refAction` configuration variable.
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v3 2/4] doc: replay: improve config description
From: kristofferhaugsbakk @ 2026-06-05 13:56 UTC (permalink / raw)
To: Junio C Hamano
Cc: Kristoffer Haugsbakk, Siddharth Asthana, git, Patrick Steinhardt
In-Reply-To: <V3_CV_doc_replay_config.780@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
First of all, this unordered list for `replay.refAction` introduces
a term with a colon. This is exactly what a description list is,
structurally. Let’s be stylistically consistent and use the desc.
list markup construct. Let’s also drop the harmless but unneeded
indentation.
We can reuse the `::` delimiter since we use an open block.
But for consistency use the typical nested description list
delimiter, namely `;;`.
Second, let’s replace the inline-verbatim `git replay` with a link
to git-replay(1), since we are naming the command. But make that
conditional so that we avoid a self-link inside git-replay(1).[1]
† 1: See e.g. e7b3a768 (doc: git-init: rework config item
init.templateDir, 2024-03-10) for another example of
avoiding self-linking
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Notes (series):
v3:
• Msg:[1] typo, fix to “stylistically”
• Msg: Move the paragraph about delimiters (;;) from the *next*
patch over here instead. This is the first place we do it. In the
next patch we can just say that we are doing the same trans-
formation as here.
• Msg: Remove double-space to separate two sentences. That’s
inconsitent for me. I moved away from that because two-space
separation takes up too much space when linewrapping is set to 72.
• Msg: This isn’t the option, it is `replay.refAction`
• Copy–paste mistake? We don’t have to ask
• Msg: ... and it’s better to call it an unordered list rather than
bullet points
† 1: Commit message
---
v2:
• Keep the description list for `replay.refAction` (Junio)
• Now rewrite the description list like in patch 1/3 (it’s
technically an unordered list)
• Msg: mention a previous commit which also avoided self-linking.
This helps establish a bit more context for why we do this.
Documentation/config/replay.adoc | 16 ++++++++++------
Documentation/git-replay.adoc | 1 +
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
index 7d549d2f0e5..7328da9537d 100644
--- a/Documentation/config/replay.adoc
+++ b/Documentation/config/replay.adoc
@@ -1,11 +1,15 @@
replay.refAction::
- Specifies the default mode for handling reference updates in
- `git replay`. The value can be:
+ Specifies the default mode for handling reference updates.
+ The value can be:
+
--
- * `update`: Update refs directly using an atomic transaction (default behavior).
- * `print`: Output update-ref commands for pipeline use.
+`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.
+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[]
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[]
GIT
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v3 1/4] doc: link to config for git-replay(1)
From: kristofferhaugsbakk @ 2026-06-05 13:55 UTC (permalink / raw)
To: Junio C Hamano
Cc: Kristoffer Haugsbakk, Siddharth Asthana, git, Patrick Steinhardt
In-Reply-To: <V3_CV_doc_replay_config.780@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
This config doc was added in 336ac90c (replay: add replay.refAction
config option, 2025-11-06) but never included anywhere. Include it in
git-replay(1) and git-config(1).
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
---
Documentation/config.adoc | 2 ++
Documentation/git-replay.adoc | 4 ++++
2 files changed, 6 insertions(+)
diff --git a/Documentation/config.adoc b/Documentation/config.adoc
index 62eebe7c545..51fabecb9b0 100644
--- a/Documentation/config.adoc
+++ b/Documentation/config.adoc
@@ -511,6 +511,8 @@ include::config/remotes.adoc[]
include::config/repack.adoc[]
+include::config/replay.adoc[]
+
include::config/rerere.adoc[]
include::config/revert.adoc[]
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index a32f72aead3..f9ca2db2833 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -209,6 +209,10 @@ This replays the range `aabbcc..ddeeff` onto commit `112233` and updates
`refs/heads/mybranch` to point at the result. This can be useful when you want
to use bare commit IDs instead of branch names.
+CONFIGURATION
+-------------
+include::config/replay.adoc[]
+
GIT
---
Part of the linkgit:git[1] suite
--
2.54.0.22.g9e26862b904
^ permalink raw reply related
* [PATCH v3 0/4] doc: replay: fix config link
From: kristofferhaugsbakk @ 2026-06-05 13:55 UTC (permalink / raw)
To: Junio C Hamano
Cc: Kristoffer Haugsbakk, Siddharth Asthana, git, Patrick Steinhardt
In-Reply-To: <V2_CV_doc_replay_config.767@msgid.xyz>
From: Kristoffer Haugsbakk <code@khaugsbakk.name>
Topic name (applied): kh/doc-replay-config
Topic summary: link to the config for git-replay(1) (one variable) in
git-replay(1) and git-config(1). Also improve the doc for that config
variable and `--ref-action`.
§ Changes in v3
Fix a commit message typo to “stylistically”. Also improve (IMO) the commit
messages a bit. See the notes on the patches for details.
§ Link to v2
https://lore.kernel.org/git/V2_CV_doc_replay_config.767@msgid.xyz/
[1/4] doc: link to config for git-replay(1)
[2/4] doc: replay: improve config description
[3/4] doc: replay: use a nested description list
[4/4] doc: replay: move “default” to the right-hand side
Documentation/config.adoc | 2 ++
Documentation/config/replay.adoc | 19 +++++++++++++------
Documentation/git-replay.adoc | 16 ++++++++++++----
3 files changed, 27 insertions(+), 10 deletions(-)
Interdiff against v2:
Range-diff against v2:
1: ef8212a076a = 1: ef8212a076a doc: link to config for git-replay(1)
2: b60e2e02826 ! 2: 35b44b922e5 doc: replay: improve config description
@@ Metadata
## Commit message ##
doc: replay: improve config description
- First of all, this bullet list for `--ref-action` introduces a term with
- a colon. This is exactly what a description list is, structurally. Let’s
- be sylistically consistent and use the description list markup
- construct. Let’s also drop the harmless but unneeded indentation.
+ First of all, this unordered list for `replay.refAction` introduces
+ a term with a colon. This is exactly what a description list is,
+ structurally. Let’s be stylistically consistent and use the desc.
+ list markup construct. Let’s also drop the harmless but unneeded
+ indentation.
+
+ We can reuse the `::` delimiter since we use an open block.
+ But for consistency use the typical nested description list
+ delimiter, namely `;;`.
Second, let’s replace the inline-verbatim `git replay` with a link
to git-replay(1), since we are naming the command. But make that
3: d13cd39cb36 ! 3: 12c73641fb9 doc: replay: use a nested description list
@@ Commit message
This bullet list for `--ref-action` introduces a term with a colon.
This is exactly what a description list is, structurally. Let’s be
- sylistically consistent and use the desc. list markup construct.[1]
+ stylistically consistent and use the desc. list markup construct.
- We can reuse the `::` delimiter since we use an open block.
- But for consistency use the typical nested description list
- delimiter, namely `;;`.
-
- Also drop the harmless but unneeded indentation.
-
- † 1: Same explanation as in the previous commit
+ In short, just transform this unordered list in the same way that we
+ did for `replay.refAction` in the previous commit.
Signed-off-by: Kristoffer Haugsbakk <code@khaugsbakk.name>
4: 17804ea7afa = 4: e2191c723fc doc: replay: move “default” to the right-hand side
base-commit: a89346e34a937f001e5d397ee62224e3e9852040
--
2.54.0.22.g9e26862b904
^ permalink raw reply
* Re: [PATCH v12 4/6] branch: add --prune-merged <branch>
From: Phillip Wood @ 2026-06-05 13:50 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git
Cc: Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <cccfdb831cc8c4ca0844d5b4ba2b70f0c801fc59.1780477479.git.gitgitgadget@gmail.com>
Hi Harald
On 03/06/2026 10:04, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> git branch --prune-merged <branch>...
I'm not sure that synopsis is correct anymore as you need to repeat
"--prune-merged". As --prune-merged now takes an argument there is no
reason to forbid positional arguments so I think we should support
git branch --prune-merged origin/master 'feature*'
to delete all the branches beginning with "feature" that have the
upstream "origin/master" and have been merged.
I wonder about the name - the other options that delete branches are
called "delete", not "prune". Also "--prune-merged" does not delete the
branches listed by "--merged" so maybe "--delete-forked" would be better?
I've not commented in detail on the code as it will need to change a bit
once we match on full refnames and do the filtering in
apply_ref_filter() but I think the basics are sound.
I'll stop here - I did quickly scan the next two patches and they both
looked like sensible ideas.
Thanks
Phillip
> 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.
>
> Reachability is read from local refs; nothing is fetched. Users
> who want fresh upstream refs run "git fetch" first.
>
> Three classes of branches are spared:
>
> * 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.
>
> 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. We only act on upstream-merged status.
>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
> Documentation/git-branch.adoc | 23 +++++
> builtin/branch.c | 117 +++++++++++++++++++--
> t/t3200-branch.sh | 188 ++++++++++++++++++++++++++++++++++
> 3 files changed, 318 insertions(+), 10 deletions(-)
>
> diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
> index 8002d7f38c..f7942fcd7d 100644
> --- a/Documentation/git-branch.adoc
> +++ b/Documentation/git-branch.adoc
> @@ -25,6 +25,7 @@ git branch (-m|-M) [<old-branch>] <new-branch>
> git branch (-c|-C) [<old-branch>] <new-branch>
> git branch (-d|-D) [-r] <branch-name>...
> git branch --edit-description [<branch-name>]
> +git branch (--prune-merged <branch>)...
>
> DESCRIPTION
> -----------
> @@ -206,6 +207,28 @@ This option is only applicable in non-verbose mode.
> `master`) or a shell-style glob (e.g. `'origin/*'`). The
> option can be repeated to widen the filter.
>
> +`--prune-merged <branch>`::
> + Delete the local branches that `--forked` would list for the
> + same _<branch>_, 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. May be given more than once to
> + union the matches; positional arguments are not accepted.
> ++
> +Reachability is checked against whatever the upstream refs say
> +locally; nothing is fetched. Run `git fetch` first if you want
> +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
> +upstream (`<branch>@{upstream}`), so it cannot be distinguished
> +from a freshly pulled trunk that just looks "fully merged".
> ++
> +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`::
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 09afdd9257..736480b002 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -39,6 +39,7 @@ static const char * const builtin_branch_usage[] = {
> N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"),
> N_("git branch [<options>] [-r | -a] [--points-at]"),
> N_("git branch [<options>] [-r | -a] [--format]"),
> + N_("git branch [<options>] (--prune-merged <branch>)..."),
> NULL
> };
>
> @@ -782,17 +783,13 @@ static int upstream_matches(const char *short_upstream,
> return 0;
> }
>
> -static int branch_upstream_matches(const char *full_refname,
> +static int branch_upstream_matches(const char *short_branch_name,
> const struct upstream_pattern *patterns,
> size_t nr_patterns)
> {
> - const char *short_name;
> - struct branch *branch;
> + struct branch *branch = branch_get(short_branch_name);
> const char *upstream;
>
> - if (!skip_prefix(full_refname, "refs/heads/", &short_name))
> - return 0;
> - branch = branch_get(short_name);
> if (!branch)
> return 0;
> upstream = branch_get_upstream(branch, NULL);
> @@ -813,8 +810,9 @@ static void filter_array_by_forked(struct ref_array *array,
>
> for (i = 0; i < array->nr; i++) {
> struct ref_array_item *item = array->items[i];
> - if (branch_upstream_matches(item->refname,
> - patterns, nr_patterns))
> + const char *short_name;
> + if (skip_prefix(item->refname, "refs/heads/", &short_name) &&
> + branch_upstream_matches(short_name, patterns, nr_patterns))
> array->items[kept++] = item;
> else
> free_ref_array_item(item);
> @@ -824,6 +822,94 @@ static void filter_array_by_forked(struct ref_array *array,
> upstream_pattern_list_clear(patterns, nr_patterns);
> }
>
> +struct forked_cb {
> + const struct upstream_pattern *patterns;
> + size_t nr_patterns;
> + struct string_list *out;
> +};
> +
> +static int collect_forked_branch(const struct reference *ref, void *cb_data)
> +{
> + struct forked_cb *cb = cb_data;
> +
> + if (ref->flags & REF_ISSYMREF)
> + return 0;
> + if (branch_upstream_matches(ref->name, cb->patterns, cb->nr_patterns))
> + string_list_append(cb->out, ref->name);
> + return 0;
> +}
> +
> +static void collect_forked_set(const struct string_list *upstreams,
> + struct string_list *out)
> +{
> + struct upstream_pattern *patterns = NULL;
> + size_t nr_patterns = 0;
> + struct forked_cb cb;
> +
> + parse_forked_args(upstreams, &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);
> +
> + upstream_pattern_list_clear(patterns, nr_patterns);
> +}
> +
> +static int prune_merged_branches(const struct string_list *upstreams,
> + int quiet)
> +{
> + struct ref_store *refs = get_main_ref_store(the_repository);
> + struct string_list candidates = STRING_LIST_INIT_DUP;
> + struct strvec deletable = STRVEC_INIT;
> + struct string_list_item *item;
> + int ret = 0;
> +
> + if (!upstreams->nr)
> + die(_("--prune-merged requires at least one <branch>"));
> +
> + collect_forked_set(upstreams, &candidates);
> +
> + for_each_string_list_item(item, &candidates) {
> + const char *short_name = item->string;
> + 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;
> +
> + upstream = branch ? branch_get_upstream(branch, NULL) : NULL;
> + if (!upstream || !refs_ref_exists(refs, upstream))
> + continue;
> + push = branch ? branch_get_push(branch, NULL) : NULL;
> + if (!push || !strcmp(push, upstream))
> + continue;
> +
> + strvec_push(&deletable, short_name);
> + }
> +
> + if (deletable.nr)
> + ret = delete_branches(deletable.nr, deletable.v,
> + 0, /* force */
> + FILTER_REFS_BRANCHES,
> + quiet,
> + 1, /* warn_only */
> + 1, /* no_head_fallback */
> + 0 /* dry_run */);
> +
> + strvec_clear(&deletable);
> + string_list_clear(&candidates, 0);
> + return ret;
> +}
> +
> static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
>
> static int edit_branch_description(const char *branch_name)
> @@ -866,6 +952,7 @@ int cmd_branch(int argc,
> int delete = 0, rename = 0, copy = 0, list = 0,
> unset_upstream = 0, show_current = 0, edit_description = 0;
> struct string_list forked_upstreams = STRING_LIST_INIT_DUP;
> + struct string_list prune_merged_upstreams = STRING_LIST_INIT_DUP;
> const char *new_upstream = NULL;
> int noncreate_actions = 0;
> /* possible options */
> @@ -921,6 +1008,8 @@ int cmd_branch(int argc,
> N_("edit the description for the branch")),
> OPT_STRING_LIST(0, "forked", &forked_upstreams, N_("branch"),
> N_("list local branches whose upstream matches <branch> (repeatable)")),
> + OPT_STRING_LIST(0, "prune-merged", &prune_merged_upstreams, N_("branch"),
> + N_("delete local branches whose upstream matches <branch> and is merged (repeatable)")),
> 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")),
> @@ -965,7 +1054,8 @@ int cmd_branch(int argc,
> 0);
>
> if (!delete && !rename && !copy && !edit_description && !new_upstream &&
> - !show_current && !unset_upstream && argc == 0)
> + !show_current && !unset_upstream && !prune_merged_upstreams.nr &&
> + argc == 0)
> list = 1;
>
> if (filter.with_commit || filter.no_commit ||
> @@ -975,7 +1065,7 @@ int cmd_branch(int argc,
>
> noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
> !!show_current + !!list + !!edit_description +
> - !!unset_upstream;
> + !!unset_upstream + !!prune_merged_upstreams.nr;
> if (noncreate_actions > 1)
> usage_with_options(builtin_branch_usage, options);
>
> @@ -1016,6 +1106,12 @@ int cmd_branch(int argc,
> ret = delete_branches(argc, argv, delete > 1, filter.kind,
> quiet, 0, 0, 0);
> goto out;
> + } else if (prune_merged_upstreams.nr) {
> + if (argc)
> + die(_("--prune-merged does not take positional arguments; "
> + "repeat --prune-merged for each <branch>"));
> + ret = prune_merged_branches(&prune_merged_upstreams, quiet);
> + goto out;
> } else if (show_current) {
> print_current_branch_name();
> ret = 0;
> @@ -1178,5 +1274,6 @@ int cmd_branch(int argc,
> out:
> string_list_clear(&sorting_options, 0);
> string_list_clear(&forked_upstreams, 0);
> + string_list_clear(&prune_merged_upstreams, 0);
> return ret;
> }
> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
> index 4e7deddc04..beb86987ad 100755
> --- a/t/t3200-branch.sh
> +++ b/t/t3200-branch.sh
> @@ -1809,4 +1809,192 @@ test_expect_success '--forked requires a value' '
> test_grep "requires a value" err
> '
>
> +test_expect_success '--prune-merged: setup' '
> + test_create_repo pm-upstream &&
> + test_commit -C pm-upstream base &&
> + git -C pm-upstream checkout -b next &&
> + test_commit -C pm-upstream one-commit &&
> + test_commit -C pm-upstream two-commit &&
> + 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 &&
> + 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 &&
> + git -C pm-merged branch --set-upstream-to=origin/next two &&
> +
> + git -C pm-merged branch --prune-merged "origin/*" &&
> +
> + test_must_fail git -C pm-merged rev-parse --verify refs/heads/one &&
> + test_must_fail git -C pm-merged rev-parse --verify refs/heads/two
> +'
> +
> +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 --prune-merged origin/next &&
> +
> + 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 --prune-merged origin/main &&
> +
> + test_must_fail git -C pm-union rev-parse --verify refs/heads/one &&
> + test_must_fail git -C pm-union rev-parse --verify refs/heads/two
> +'
> +
> +test_expect_success '--prune-merged accepts a local upstream' '
> + test_when_finished "rm -rf pm-local" &&
> + 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 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 &&
> + git -C pm-unmerged checkout - &&
> +
> + git -C pm-unmerged branch --prune-merged "origin/*" 2>err &&
> + test_grep "not fully merged" 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 &&
> +
> + git -C pm-upstream-gone update-ref -d refs/remotes/origin/next &&
> + git -C pm-upstream-gone branch --prune-merged "origin/*" &&
> +
> + git -C pm-upstream-gone rev-parse --verify refs/heads/one
> +'
> +
> +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 &&
> +
> + git -C pm-head branch --prune-merged "origin/*" &&
> +
> + git -C pm-head rev-parse --verify refs/heads/one
> +'
> +
> +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 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 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 a value' '
> + test_must_fail git -C forked branch --prune-merged 2>err &&
> + test_grep "requires a value" err
> +'
> +
> +test_expect_success '--prune-merged rejects positional arguments' '
> + test_must_fail git -C forked branch --prune-merged origin/one other/foreign 2>err &&
> + test_grep "does not take positional arguments" err
> +'
> +
> test_done
^ permalink raw reply
* Re: [PATCH v12 3/6] branch: prepare delete_branches for a bulk caller
From: Phillip Wood @ 2026-06-05 13:49 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git
Cc: Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <004a96f7a447ad8dcbcabeb36502330c2399f829.1780477479.git.gitgitgadget@gmail.com>
Hi Harald
On 03/06/2026 10:04, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> Add no_head_fallback and dry_run flags to delete_branches() so a
> bulk caller (the upcoming --prune-merged) can ask strictly about
> merged-into-upstream without a silent fallback to HEAD, and
> rehearse deletions with the same "Would delete branch ..." wording
> as the live run. Existing callers pass 0 for both and keep current
> behavior.
>
> When no_head_fallback is set, head_rev stays NULL through to
> branch_merged(), whose "merged to X but not yet merged to HEAD"
> reminder otherwise compares against HEAD. For the bulk caller
> every candidate is known to have an upstream, so HEAD is
> irrelevant. Guard the block on head_rev so the NULL case skips
> it instead of treating "NULL != reference_rev" as "diverges from
> HEAD" and emitting a spurious warning.
Same comment as the last patch - use a flags argument rather than lots
of individual booleans that make the call sites hard to read.
Thanks
Phillip
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
> builtin/branch.c | 27 +++++++++++++++++++--------
> 1 file changed, 19 insertions(+), 8 deletions(-)
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 93d8eae891..09afdd9257 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -169,10 +169,13 @@ static int branch_merged(int kind, const char *name,
> * upstream, if any, otherwise with HEAD", we should just
> * return the result of the repo_in_merge_bases() above without
> * any of the following code, but during the transition period,
> - * a gentle reminder is in order.
> + * a gentle reminder is in order. Callers that opt out of the
> + * HEAD fallback by passing head_rev=NULL are not interested in
> + * the reminder either: they have already established that the
> + * branch has an upstream, so HEAD is irrelevant to the decision.
> */
> - 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)
> @@ -225,7 +228,8 @@ 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 quiet, int warn_only, int no_head_fallback,
> + int dry_run)
> {
> struct commit *head_rev = NULL;
> struct object_id oid;
> @@ -259,7 +263,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
> }
> branch_name_pos = strcspn(fmt, "%");
>
> - if (!force)
> + if (!force && !no_head_fallback)
> head_rev = lookup_commit_reference(the_repository, &head_oid);
>
> for (i = 0; i < argc; i++, strbuf_reset(&bname)) {
> @@ -330,13 +334,20 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
> free(target);
> }
>
> - if (refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
> + if (!dry_run &&
> + refs_delete_refs(get_main_ref_store(the_repository), NULL, &refs_to_delete, REF_NO_DEREF))
> ret = 1;
>
> for_each_string_list_item(item, &refs_to_delete) {
> char *describe_ref = item->util;
> char *name = item->string;
> - if (!refs_ref_exists(get_main_ref_store(the_repository), name)) {
> + if (dry_run) {
> + if (!quiet)
> + printf(remote_branch
> + ? _("Would delete remote-tracking branch %s (was %s).\n")
> + : _("Would delete branch %s (was %s).\n"),
> + name + branch_name_pos, describe_ref);
> + } else if (!refs_ref_exists(get_main_ref_store(the_repository), name)) {
> char *refname = name + branch_name_pos;
> if (!quiet)
> printf(remote_branch
> @@ -1003,7 +1014,7 @@ int cmd_branch(int argc,
> if (!argc)
> die(_("branch name required"));
> ret = delete_branches(argc, argv, delete > 1, filter.kind,
> - quiet, 0);
> + quiet, 0, 0, 0);
> goto out;
> } else if (show_current) {
> print_current_branch_name();
^ permalink raw reply
* Re: [PATCH v12 2/6] branch: let delete_branches warn instead of error on bulk refusal
From: Phillip Wood @ 2026-06-05 13:49 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git
Cc: Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <6c95e4e77cf555194b83eab92c7564e6b639f500.1780477479.git.gitgitgadget@gmail.com>
Hi Harald
On 03/06/2026 10:04, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> Add a warn_only flag to delete_branches() and check_branch_commit()
> so a bulk caller can report not-fully-merged branches as one-line
> warnings and continue, instead of erroring with the four-line "use
> 'git branch -D'" advice that the standalone "git branch -d" path
> emits. Default callers pass 0 and are unaffected.
>
> Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
> ---
> builtin/branch.c | 26 +++++++++++++++++---------
> 1 file changed, 17 insertions(+), 9 deletions(-)
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 12711b29cf..93d8eae891 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -192,7 +192,7 @@ 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)
We've already got two boolean parameters, lets replace those with an
"unsigned int flags" parameter rather than adding a third. That way we
can avoid having to comment each argument as you do in a later patch.
Thanks
Phillip
> {
> struct commit *rev = lookup_commit_reference(the_repository, oid);
> if (!force && !rev) {
> @@ -200,10 +200,16 @@ 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);
> + }
> return -1;
> }
> return 0;
> @@ -219,7 +225,7 @@ 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)
> {
> struct commit *head_rev = NULL;
> struct object_id oid;
> @@ -309,8 +315,9 @@ 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)) {
> + if (!warn_only)
> + ret = 1;
> goto next;
> }
>
> @@ -995,7 +1002,8 @@ 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);
> goto out;
> } else if (show_current) {
> print_current_branch_name();
^ permalink raw reply
* Re: [PATCH v12 1/6] branch: add --forked filter for --list mode
From: Phillip Wood @ 2026-06-05 13:48 UTC (permalink / raw)
To: Harald Nordgren via GitGitGadget, git
Cc: Kristoffer Haugsbakk, Johannes Sixt, Harald Nordgren
In-Reply-To: <8834c424fbd27800636fe21ae73e9cdce75b558a.1780477479.git.gitgitgadget@gmail.com>
Hi Harald
On 03/06/2026 10:04, Harald Nordgren via GitGitGadget wrote:
> From: Harald Nordgren <haraldnordgren@gmail.com>
>
> Add a --forked option to "git branch" list mode that keeps only
> branches whose configured upstream matches <branch>. The argument
> can be a ref (e.g. "origin/main", "master") or a shell-style
> glob (e.g. "origin/*"). The option can be repeated to widen the
> filter.
Do we want to support a remote name as an alias for $remote/HEAD to
match "git checkout -b $remote"?
> Because it is a filter on list mode, --forked composes with the
> existing list-mode filters, so
>
> git branch --merged origin/main --forked 'origin/*'
>
> lists branches forked from origin that have already been
> integrated into origin/main, and --no-merged inverts the question.
Nice
> 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 | 7 ++
> builtin/branch.c | 147 +++++++++++++++++++++++++++++++++-
> ref-filter.c | 10 +--
> ref-filter.h | 2 +
> t/t3200-branch.sh | 92 +++++++++++++++++++++
> 5 files changed, 249 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
> index c0afddc424..8002d7f38c 100644
> --- a/Documentation/git-branch.adoc
> +++ b/Documentation/git-branch.adoc
> @@ -14,6 +14,7 @@ git branch [--color[=<when>] | --no-color] [--show-current]
> [--merged [<commit>]] [--no-merged [<commit>]]
> [--contains [<commit>]] [--no-contains [<commit>]]
> [--points-at <object>] [--format=<format>]
> + [(--forked <branch>)...]
Should this come before --format? I think it logically belongs with
--merged and --contains which also filter the output.
> [(-r|--remotes) | (-a|--all)]
> [--list] [<pattern>...]
> git branch [--track[=(direct|inherit)] | --no-track] [-f]
> @@ -199,6 +200,12 @@ This option is only applicable in non-verbose mode.
> Print the name of the current branch. In detached `HEAD` state,
> nothing is printed.
>
> +`--forked <branch>`::
> + List only branches whose configured upstream matches
> + _<branch>_. The argument can be a ref (e.g. `origin/main`,
> + `master`) or a shell-style glob (e.g. `'origin/*'`). The
> + option can be repeated to widen the filter.
This is fine but do we want to add a sentence to the DESCRIPTION as well
where it talks about "--contains" and "--merged"?
> `-v`::
> `-vv`::
> `--verbose`::
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 1572a4f9ef..12711b29cf 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -28,9 +28,10 @@
> #include "help.h"
> #include "advice.h"
> #include "commit-reach.h"
> +#include "wildmatch.h"
>
> static const char * const builtin_branch_usage[] = {
> - N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
> + N_("git branch [<options>] [-r | -a] [--merged] [--no-merged] [(--forked <branch>)...]"),
> N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
> N_("git branch [<options>] [-l] [<pattern>...]"),
> N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
> @@ -442,8 +443,12 @@ static char *build_format(struct ref_filter *filter, int maxwidth, const char *r
> return strbuf_detach(&fmt, NULL);
> }
>
> +static void filter_array_by_forked(struct ref_array *array,
> + const struct string_list *upstreams);
We try to avoid forward declarations unless they're really needed - can
we add the new functions up here instead?
> static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting,
> - struct ref_format *format, struct string_list *output)
> + struct ref_format *format, struct string_list *output,
> + const struct string_list *forked_upstreams)
> {
> int i;
> struct ref_array array;
> @@ -463,6 +468,9 @@ static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sortin
>
> filter_refs(&array, filter, filter->kind);
>
> + if (forked_upstreams->nr)
> + filter_array_by_forked(&array, forked_upstreams);
This gets a bit messy below where free elements when we filter "array".
It would be much nicer to do the filtering in apply_ref_filter() so that
we don't have to allocate those in the first place. I think it would
make it simpler to implement --prune-merged as collect_forked_set()
would become a call to filter_refs() and we could support --forked in
"git for-each-ref".
> +static int parse_one_forked_arg(const char *arg, struct upstream_pattern *out)
> +{
> + struct object_id oid;
> + char *full_ref = NULL;
> +
> + if (has_glob_specials(arg)) {
> + out->name = xstrdup(arg);
> + out->is_wildcard = 1;
> + return 0;
> + }
> +
> + 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));
I don't think abbreviating the refname here is a good idea as short
names are inherently ambiguous - in principle you could have a remote
tracking branch and a local branch with the same short name. It also
means we end up reconstructing the full name in a later patch, instead
we should just call short_upstream_name() where we need the abbreviated
name.
> +static int upstream_matches(const char *short_upstream,
> + const struct upstream_pattern *patterns,
> + size_t nr)
> +{
> + size_t i;
> +
> + 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;
> + }
> + }
This is quadratic but maybe we can assume the user wont pass "--forked"
too many times. If this ever becomes a problem we could use an strset
for the exact matches and then we only need to loop over the wildmatch
patterns but we probably don't need to worry about that now.
> +static int branch_upstream_matches(const char *full_refname,
> + const struct upstream_pattern *patterns,
> + size_t nr_patterns)
> +{
> + const char *short_name;
> + struct branch *branch;
> + const char *upstream;
> +
> + if (!skip_prefix(full_refname, "refs/heads/", &short_name))
> + return 0;
> + branch = branch_get(short_name);
> + if (!branch)
> + return 0;
> + upstream = branch_get_upstream(branch, NULL);
> + if (!upstream)
> + return 0;
> + return upstream_matches(short_upstream_name(upstream),
This would be simpler if we matched on full names.
> +static void filter_array_by_forked(struct ref_array *array,
> + const struct string_list *upstreams)
> +{
> + struct upstream_pattern *patterns = NULL;
> + size_t nr_patterns = 0;
> + int i, kept = 0;
> +
> + parse_forked_args(upstreams, &patterns, &nr_patterns);
> +
> + for (i = 0; i < array->nr; i++) {
> + struct ref_array_item *item = array->items[i];
> + if (branch_upstream_matches(item->refname,
> + patterns, nr_patterns))
> + array->items[kept++] = item;
> + else
> + free_ref_array_item(item);
> + }
> + array->nr = kept;
As I said above this would be nicer if it was implemented in
apply_ref_filter().
> @@ -714,6 +847,7 @@ int cmd_branch(int argc,
> /* possible actions */
> int delete = 0, rename = 0, copy = 0, list = 0,
> unset_upstream = 0, show_current = 0, edit_description = 0;
> + struct string_list forked_upstreams = STRING_LIST_INIT_DUP;
Personally I'd use a strvec here as we don't need the "util" member of
the string list but I'm probably biased as I don't really like the
string list api.
I like the idea of making this just another filter to "--list". The
basics of the implementation look reasonable - it should be straight
forward to match on full refs and move the relavent code into filter-refs.c
Thanks
Phillip
> const char *new_upstream = NULL;
> int noncreate_actions = 0;
> /* possible options */
> @@ -767,6 +901,8 @@ int cmd_branch(int argc,
> OPT_BOOL(0, "create-reflog", &reflog, N_("create the branch's reflog")),
> OPT_BOOL(0, "edit-description", &edit_description,
> N_("edit the description for the branch")),
> + OPT_STRING_LIST(0, "forked", &forked_upstreams, N_("branch"),
> + N_("list local branches whose upstream matches <branch> (repeatable)")),
> 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")),
> @@ -815,7 +951,8 @@ int cmd_branch(int argc,
> list = 1;
>
> if (filter.with_commit || filter.no_commit ||
> - filter.reachable_from || filter.unreachable_from || filter.points_at.nr)
> + filter.reachable_from || filter.unreachable_from ||
> + filter.points_at.nr || forked_upstreams.nr)
> list = 1;
>
> noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream +
> @@ -880,7 +1017,8 @@ int cmd_branch(int argc,
> ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
> ref_sorting_set_sort_flags_all(
> sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1);
> - print_ref_list(&filter, sorting, &format, &output);
> + print_ref_list(&filter, sorting, &format, &output,
> + &forked_upstreams);
> print_columns(&output, colopts, NULL);
> string_list_clear(&output, 0);
> ref_sorting_release(sorting);
> @@ -1020,5 +1158,6 @@ int cmd_branch(int argc,
>
> out:
> string_list_clear(&sorting_options, 0);
> + string_list_clear(&forked_upstreams, 0);
> return ret;
> }
> diff --git a/ref-filter.c b/ref-filter.c
> index 1da4c0e60d..65e7bc6785 100644
> --- a/ref-filter.c
> +++ b/ref-filter.c
> @@ -3035,7 +3035,7 @@ static int filter_one(const struct reference *ref, void *cb_data)
> }
>
> /* Free memory allocated for a ref_array_item */
> -static void free_array_item(struct ref_array_item *item)
> +void free_ref_array_item(struct ref_array_item *item)
> {
> free((char *)item->symref);
> if (item->value) {
> @@ -3078,7 +3078,7 @@ static int filter_and_format_one(const struct reference *ref, void *cb_data)
>
> strbuf_release(&output);
> strbuf_release(&err);
> - free_array_item(item);
> + free_ref_array_item(item);
>
> /*
> * Increment the running count of refs that match the filter. If
> @@ -3098,7 +3098,7 @@ void ref_array_clear(struct ref_array *array)
> int i;
>
> for (i = 0; i < array->nr; i++)
> - free_array_item(array->items[i]);
> + free_ref_array_item(array->items[i]);
> FREE_AND_NULL(array->items);
> array->nr = array->alloc = 0;
>
> @@ -3171,7 +3171,7 @@ static void reach_filter(struct ref_array *array,
> if (is_merged == include_reached)
> array->items[array->nr++] = array->items[i];
> else
> - free_array_item(item);
> + free_ref_array_item(item);
> }
>
> clear_commit_marks_many(old_nr, to_clear, ALL_REV_FLAGS);
> @@ -3667,7 +3667,7 @@ void pretty_print_ref(const char *name, const struct object_id *oid,
>
> strbuf_release(&err);
> strbuf_release(&output);
> - free_array_item(ref_item);
> + free_ref_array_item(ref_item);
> }
>
> static int parse_sorting_atom(const char *atom)
> diff --git a/ref-filter.h b/ref-filter.h
> index 120221b47f..3883b9dc62 100644
> --- a/ref-filter.h
> +++ b/ref-filter.h
> @@ -155,6 +155,8 @@ void filter_and_format_refs(struct ref_filter *filter, unsigned int type,
> struct ref_format *format);
> /* Clear all memory allocated to ref_array */
> void ref_array_clear(struct ref_array *array);
> +/* Free a single item from a ref_array */
> +void free_ref_array_item(struct ref_array_item *item);
> /* Used to verify if the given format is correct and to parse out the used atoms */
> int verify_ref_format(struct ref_format *format);
> /* Sort the given ref_array as per the ref_sorting provided */
> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
> index e7829c2c4b..4e7deddc04 100755
> --- a/t/t3200-branch.sh
> +++ b/t/t3200-branch.sh
> @@ -1717,4 +1717,96 @@ test_expect_success 'errors if given a bad branch name' '
> test_cmp expect actual
> '
>
> +test_expect_success '--forked: setup' '
> + test_create_repo forked-upstream &&
> + test_commit -C forked-upstream base &&
> + git -C forked-upstream branch one base &&
> + git -C forked-upstream branch two base &&
> +
> + test_create_repo forked-other &&
> + test_commit -C forked-other other-base &&
> + git -C forked-other branch foreign other-base &&
> +
> + 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 local-trunk local-base
> +'
> +
> +test_expect_success '--forked <upstream-tracking-branch> filters by upstream' '
> + git -C forked branch --forked origin/one --format="%(refname:short)" >actual &&
> + echo local-one >expect &&
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked <glob> filters by wildmatch' '
> + git -C forked branch --forked "origin/*" --format="%(refname:short)" >actual &&
> + cat >expect <<-\EOF &&
> + local-one
> + local-two
> + main
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked <local-branch> matches branches with local upstream' '
> + git -C forked branch --forked local-base --format="%(refname:short)" >actual &&
> + echo local-trunk >expect &&
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked can be repeated to widen the filter' '
> + git -C forked branch --forked origin/one --forked other/foreign --format="%(refname:short)" >actual &&
> + cat >expect <<-\EOF &&
> + local-foreign
> + local-one
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked combines literal and glob arguments' '
> + git -C forked branch --forked local-base --forked "other/*" --format="%(refname:short)" >actual &&
> + cat >expect <<-\EOF &&
> + local-foreign
> + local-trunk
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked "*/*" covers every remote-tracking upstream' '
> + git -C forked branch --forked "*/*" --format="%(refname:short)" >actual &&
> + cat >expect <<-\EOF &&
> + local-foreign
> + local-one
> + local-two
> + main
> + EOF
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked composes with --no-merged' '
> + test_when_finished "git -C forked checkout detached" &&
> + git -C forked checkout local-one &&
> + test_commit -C forked local-only &&
> + git -C forked branch --forked "origin/*" --no-merged origin/one \
> + --format="%(refname:short)" >actual &&
> + echo local-one >expect &&
> + test_cmp expect actual
> +'
> +
> +test_expect_success '--forked rejects unknown branch/pattern' '
> + test_must_fail git -C forked branch --forked nope 2>err &&
> + test_grep "not a valid branch or pattern" err
> +'
> +
> +test_expect_success '--forked requires a value' '
> + test_must_fail git -C forked branch --forked 2>err &&
> + test_grep "requires a value" err
> +'
> +
> test_done
^ permalink raw reply
* Re: [PATCH v2] compat/posix.h: enable UNUSED warning messages for Clang
From: Patrick Steinhardt @ 2026-06-05 13:22 UTC (permalink / raw)
To: Dominik Loidolt; +Cc: gitster, git, asedeno, asedeno, avarab
In-Reply-To: <aiK4BR86cuq5bmCe@four.local>
On Fri, Jun 05, 2026 at 01:50:29PM +0200, Dominik Loidolt wrote:
> Thanks for the review!
>
> I noticed that the version-check style now differs between GCC and the newly
> introduced Clang checks, would it make sense to make them consistent? Like:
>
> diff --git a/compat/posix.h b/compat/posix.h
> index faaae1b655..e20f8ec61e 100644
> --- a/compat/posix.h
> +++ b/compat/posix.h
> @@ -17,7 +17,8 @@
> */
> #if defined(__GNUC__) && defined(__GNUC_MINOR__)
> # define GIT_GNUC_PREREQ(maj, min) \
> - ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
> + ((__GNUC__ > (maj)) || \
> + (__GNUC__ == (maj) && (__GNUC_MINOR__ >= (min))))
> #else
> #define GIT_GNUC_PREREQ(maj, min) 0
> #endif
>
> I think the current GCC bit-shift check is harder to read.
> If you agree, I could send a 2-patch v3 series, which would also clean up the
> comment style nit.
I was wondering about that, too. The question that I have is whether
there's any particular reason why the check was written that way. So in
the best case we'd do some digging into the history to figure out why
this looks the way it looks like.
Patrick
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Simon Richter @ 2026-06-05 12:10 UTC (permalink / raw)
To: Benson Muite; +Cc: git
In-Reply-To: <87h5nhr2zp.fsf@emailplus.org>
Hi,
On 6/5/26 2:05 PM, Benson Muite wrote:
> Simon Richter <Simon.Richter@hogyros.de> writes:
>> On the other hand, this can be used to construct a stable relative
>> submodule URL.
> For submodules, the metadata consists of the url of the repository to
> clone from.
That is precisely what precludes mirroring: if I clone and republish a
repository, people can clone from that repository, but will still fetch
submodules from the URLs listed in the .gitmodules file.
If that is a relative URL, then all is (mostly) well: they will also ask
my mirror server for the submodule, and all I have to do is make it
available.
If it is an absolute URL, then I need a side channel to communicate to
the client "you can also get this repository from me." This could, for
example, generate an insteadOf config, but that would be a horrible hack
that becomes unmanageable pretty quickly (updates? security implications?)
Hence this thread: is there a way to represent submodules so that their
identity is independent from the hosting location -- and this ties into
the other thread from last week, giving projects a stable identity that
follows them through clones (or, if someone is using a forge, forks).
The download location for a project is project metadata that lives
outside the project view of time, but it is expressed as (versioned)
data in git, in the .gitmodules file, so if hosting for a project
changes, projects referring to them must either rewrite all of their
history, accept that old versions will no longer be buildable because
they contain a broken link, or expect people/CI to manually generate
insteadOf entries.
So the problem here is that we are treating metadata as data.
Simon
^ permalink raw reply
* Re: [PATCH v2] compat/posix.h: enable UNUSED warning messages for Clang
From: Dominik Loidolt @ 2026-06-05 11:50 UTC (permalink / raw)
To: Patrick Steinhardt; +Cc: gitster, git, asedeno, asedeno, avarab
In-Reply-To: <aiKnqlI7WdcskDAs@pks.im>
Thanks for the review!
I noticed that the version-check style now differs between GCC and the newly
introduced Clang checks, would it make sense to make them consistent? Like:
diff --git a/compat/posix.h b/compat/posix.h
index faaae1b655..e20f8ec61e 100644
--- a/compat/posix.h
+++ b/compat/posix.h
@@ -17,7 +17,8 @@
*/
#if defined(__GNUC__) && defined(__GNUC_MINOR__)
# define GIT_GNUC_PREREQ(maj, min) \
- ((__GNUC__ << 16) + __GNUC_MINOR__ >= ((maj) << 16) + (min))
+ ((__GNUC__ > (maj)) || \
+ (__GNUC__ == (maj) && (__GNUC_MINOR__ >= (min))))
#else
#define GIT_GNUC_PREREQ(maj, min) 0
#endif
I think the current GCC bit-shift check is harder to read.
If you agree, I could send a 2-patch v3 series, which would also clean up the
comment style nit.
Dominik
^ permalink raw reply related
* Re: [PATCH v2] compat/posix.h: enable UNUSED warning messages for Clang
From: Patrick Steinhardt @ 2026-06-05 10:40 UTC (permalink / raw)
To: Dominik Loidolt; +Cc: gitster, git, asedeno, asedeno, avarab
In-Reply-To: <20260605094647.94805-1-dominik.loidolt@univie.ac.at>
On Fri, Jun 05, 2026 at 11:46:47AM +0200, Dominik Loidolt wrote:
> Use a dedicated Clang version check for the UNUSED macro.
>
> Commit 7c07f36ad2 (git-compat-util.h: GCC deprecated message arg only in
> GCC 4.5+, 2022-10-05) restricted use of the deprecated attribute's
> message argument in the UNUSED macro to GCC 4.5 or newer.
Ah. I was briefly wondering about this because the UNUSED macro already
works. But the important part here is that it's really only about better
diagnostics via the attribute message.
> Clang identifies itself as GNUC 4.2.1 for compatibility, so
> GIT_GNUC_PREREQ(4, 5) does not detect whether Clang supports the
> deprecated("...") form. Add GIT_CLANG_PREREQ() macro and use it to
> enable the UNUSED warning message for Clang 2.9 and newer.
There's a second user of `GIT_GNUC_PREREQ` in "git-compat-util.h", but
that user checks for GCC 3.1. And as Clang identifies as a newer version
we don't have to adapt any other callsites.
> diff --git a/compat/posix.h b/compat/posix.h
> index faaae1b655..88ad29d74b 100644
> --- a/compat/posix.h
> +++ b/compat/posix.h
> @@ -22,6 +22,17 @@
> #define GIT_GNUC_PREREQ(maj, min) 0
> #endif
>
> +/*
> + * Similar for Clang
> + */
Micronit, not worth rerolling over: this could have easily been a single
line: `/* Similar for Clang. */`
> +#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_major__)
> +# define GIT_CLANG_PREREQ(maj, min) \
> + ((__clang_major__ > (maj)) || \
> + (__clang_major__ == (maj) && (__clang_minor__ >= (min))))
> +#else
> +# define GIT_CLANG_PREREQ(maj, min) 0
> +#endif
> +
> /*
> * UNUSED marks a function parameter that is always unused. It also
> * can be used to annotate a function, a variable, or a type that is
> @@ -35,7 +46,7 @@
> * When a parameter may be used or unused, depending on conditional
> * compilation, consider using MAYBE_UNUSED instead.
> */
> -#if GIT_GNUC_PREREQ(4, 5)
> +#if GIT_GNUC_PREREQ(4, 5) || GIT_CLANG_PREREQ(2, 9)
> #define UNUSED __attribute__((unused)) \
> __attribute__((deprecated ("parameter declared as UNUSED")))
> #elif defined(__GNUC__)
Makes sense, thanks!
Patrick
^ permalink raw reply
* [PATCH v2] compat/posix.h: enable UNUSED warning messages for Clang
From: Dominik Loidolt @ 2026-06-05 9:46 UTC (permalink / raw)
To: gitster; +Cc: git, asedeno, asedeno, avarab, Dominik Loidolt
In-Reply-To: <20260503151210.36036-1-dominik.loidolt@univie.ac.at>
Use a dedicated Clang version check for the UNUSED macro.
Commit 7c07f36ad2 (git-compat-util.h: GCC deprecated message arg only in
GCC 4.5+, 2022-10-05) restricted use of the deprecated attribute's
message argument in the UNUSED macro to GCC 4.5 or newer.
Clang identifies itself as GNUC 4.2.1 for compatibility, so
GIT_GNUC_PREREQ(4, 5) does not detect whether Clang supports the
deprecated("...") form. Add GIT_CLANG_PREREQ() macro and use it to
enable the UNUSED warning message for Clang 2.9 and newer.
Signed-off-by: Dominik Loidolt <dominik.loidolt@univie.ac.at>
---
v2:
- add GIT_CLANG_PREREQ()
- require Clang 2.9+ for deprecated("...") in UNUSED
compat/posix.h | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/compat/posix.h b/compat/posix.h
index faaae1b655..88ad29d74b 100644
--- a/compat/posix.h
+++ b/compat/posix.h
@@ -22,6 +22,17 @@
#define GIT_GNUC_PREREQ(maj, min) 0
#endif
+/*
+ * Similar for Clang
+ */
+#if defined(__clang__) && defined(__clang_minor__) && defined(__clang_major__)
+# define GIT_CLANG_PREREQ(maj, min) \
+ ((__clang_major__ > (maj)) || \
+ (__clang_major__ == (maj) && (__clang_minor__ >= (min))))
+#else
+# define GIT_CLANG_PREREQ(maj, min) 0
+#endif
+
/*
* UNUSED marks a function parameter that is always unused. It also
* can be used to annotate a function, a variable, or a type that is
@@ -35,7 +46,7 @@
* When a parameter may be used or unused, depending on conditional
* compilation, consider using MAYBE_UNUSED instead.
*/
-#if GIT_GNUC_PREREQ(4, 5)
+#if GIT_GNUC_PREREQ(4, 5) || GIT_CLANG_PREREQ(2, 9)
#define UNUSED __attribute__((unused)) \
__attribute__((deprecated ("parameter declared as UNUSED")))
#elif defined(__GNUC__)
base-commit: a89346e34a937f001e5d397ee62224e3e9852040
--
2.54.0
^ permalink raw reply related
* Re: Mirror repositories for submodules
From: Matt Hunter @ 2026-06-05 9:34 UTC (permalink / raw)
To: Benson Muite, Simon Richter, Junio C Hamano; +Cc: git
In-Reply-To: <87pl25r3tz.fsf@emailplus.org>
On Fri Jun 5, 2026 at 12:47 AM EDT, Benson Muite wrote:
>
> For submodules, the metadata consists of the url of the repository to
> clone from. One could have a list of absolute URLs. The default would
> be to assume that the URLs are tried in order, and if a URL times out,
> the next one would be tried. One may want to change the default
> ordering as a user setting, or do a ping test to get obtain content from
> the closest repository.
Another idea is for the client to attempt in a random order as a kind of
load balancing.
>
> As an example, for linphone-desktop, the first part of the .gitmodules
> file contains:
>
> [submodule "linphone-sdk"]
> path = external/linphone-sdk
> url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
> [submodule "external/google/gn"]
>
> This could be updated to
>
> [submodule "linphone-sdk"]
> path = external/linphone-sdk
> url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
> url = https://github.com/BelledonneCommunications/linphone-sdk.git
> [submodule "external/google/gn"]
Relevant to Junio's earlier comment about aiming for a general solution:
It looks like the remote URL configuration already supports multiple
URLs per a single entry. It's just that git only considers the first in
the list for fetching, and will push to all.
Your proposed change to .gitmodules may be used to initialize the list
of URLs for a cloned submodule's (single) initial remote? At this
point, any kind of mirror management logic could generically operate on
each remote's URL list.
From git-config(1):
remote.<name>.url
The URL of a remote repository. See git-fetch(1) or git-push(1).
A configured remote can have multiple URLs; in this case the first
is used for fetching, and all are used for pushing (assuming no
remote.<name>.pushurl is defined). Setting this key to the empty
string clears the list of urls, allowing you to override earlier
config.
From gitmodules(5):
submodule.<name>.url
Defines a URL from which the submodule repository can be cloned.
This may be either an absolute URL ready to be passed to
git-clone(1) or (if it begins with ./ or ../) a location relative
to the superproject’s origin repository.
Note that the submodule format seems to explicitly support only a single
value right now. Of course, I am assuming that the implementation
matches the documentation.
^ permalink raw reply
* Re: [PATCH] compat/posix.h: enable UNUSED warning messages for Clang
From: Dominik Loidolt @ 2026-06-05 8:44 UTC (permalink / raw)
To: Junio C Hamano
Cc: git, Alejandro R Sedeño, Alejandro R. Sedeño,
Ævar Arnfjörð Bjarmason
In-Reply-To: <xmqqqznsossj.fsf@gitster.g>
On Mon, May 04, 2026 at 10:41:16AM +0900, Junio C Hamano wrote:
> > Does this "drop support" because you force _all_ versions of Clang
> > to use the "deprecated" attribute, even though you _know_ some older
> > versions do not understand it? Don't these versions identify
> > themselves so that you can do
> >
> > #if defined(__clang__) && CLANG_VERSION >= 2.9
>
> IOW, something like this, perhaps?
Yes, you're right. My original patch breaks older Clang versions for no
good reason.
I'll send a v2 with an explicit Clang version check, as you suggested.
Thanks,
Dominik
^ permalink raw reply
* Re: Message from Debaashish Nandi
From: Weijie Yuan @ 2026-06-05 6:43 UTC (permalink / raw)
To: Debaashish Nandi; +Cc: git
In-Reply-To: <fcb34925-f10c-4fc4-826d-a55217101ec8@localhost>
On Thu, Jun 04, 2026 at 06:37:12PM +0000, Debaashish Nandi wrote:
> Hello, is there any game playing which l can understand how git works ?
You may try this:
https://learngitbranching.js.org/
source code:
https://github.com/pcottle/learnGitBranching
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Benson Muite @ 2026-06-05 5:05 UTC (permalink / raw)
To: Simon Richter, Junio C Hamano; +Cc: git
In-Reply-To: <d64e7f31-4e00-478c-ab31-b671242865fb@hogyros.de>
Simon Richter <Simon.Richter@hogyros.de> writes:
> Hi,
>
> On 6/4/26 10:09 AM, Junio C Hamano wrote:
>
>> So, no, I do not think a contribution to add mirror repositories as
>> alternate submodule sources should be considered for inclusion, as
>> it artificially limits usefulness of the feature. A feature to add
>> mirror repositories as alternate sources might be worth considering,
>> though.
>
> This is relevant to the Debian use case: we run a git server that
> archives git trees for Debian packages, and ideally the objects on this
> server should be identical to what you get from upstream projects.
>
> This is a big problem for archiving projects that use submodules,
> because we cannot alter the reference URLs.
>
> Cloning from our server will, depending on what upstream uses, either a
> relative URL (which will go to our server, but we have little control
> over what the name part of the repository base URL is going to be), or
> an absolute URL that instructs clients to pull from another place, which
> conflicts with our goal to have a self-contained archive.
>
> The idea posited earlier, to have a "repository identity" that remains
> the same across forks and clones, is somewhat appealing, but the best
> idea I can come up with is generating some kind of repository UUID, and
> adding a symlink -- not a great design because it pollutes outside the repo:
>
> $ mkdir myproject
> $ cd myproject
> $ git init
> $ ls -l ..
> lrwxrwxrwx 1 simon simon 9 Jun 4 14:05
> 12345678-9abc-def0-1234-56789abcdef0.git -> myproject
> drwxrwxr-x 2 simon simon 40 Jun 4 14:04 myproject
>
> On the other hand, this can be used to construct a stable relative
> submodule URL.
>
> Making the symlinks optional would require keeping a list of local
> clones and their UUIDs, and resolving them.
>
> I don't like that design, but as I said it's the best idea I have for now.
>
For submodules, the metadata consists of the url of the repository to
clone from. One could have a list of absolute URLs. The default would
be to assume that the URLs are tried in order, and if a URL times out,
the next one would be tried. One may want to change the default
ordering as a user setting, or do a ping test to get obtain content from
the closest repository.
As an example, for linphone-desktop, the first part of the .gitmodules
file contains:
[submodule "linphone-sdk"]
path = external/linphone-sdk
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
[submodule "external/google/gn"]
This could be updated to
[submodule "linphone-sdk"]
path = external/linphone-sdk
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
url = https://github.com/BelledonneCommunications/linphone-sdk.git
[submodule "external/google/gn"]
> I also fully expect that Debian's servers will be used by a lot of
> people outside the project as soon as it becomes a convenient fallback,
> in the same way people are pulling .orig.tar.gz archives from Debian
> mirrors, so we need to make it easy to set up a mirror, to allow this to
> scale.
>
> Simon
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Benson Muite @ 2026-06-05 4:57 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqqcxy7qfgk.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> writes:
> Benson Muite <benson_muite@emailplus.org> writes:
>
>> Would a contribution to add mirror repositories as alternate submodule
>> sources be considered for inclusion? Some projects have mirror
>> repositories on other hosting services, and may have bandwidth limits on
>> their primary hosting service. Being able to indicate mirror
>> repositories for where to check for updates and sources for submodules
>> when doing `git clone --recurse-submodules https://my.repo ` or `git
>> submodule update --init --recursive` would be helpful when there is a
>> timeout.
>
> I do not see why such a "oh, the repository at $URL1 seems to be
> down, but we know $URL2 serves the equivalent information, so let's
> go there instead" feature has to be limited to submodule use case.
>
> So, no, I do not think a contribution to add mirror repositories as
> alternate submodule sources should be considered for inclusion, as
> it artificially limits usefulness of the feature. A feature to add
> mirror repositories as alternate sources might be worth considering,
> though.
Thanks for the feedback. This was motivated by problems when trying to
recursively clone, but a more general solution is also fine.
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Benson Muite @ 2026-06-05 4:54 UTC (permalink / raw)
To: Jeff King, Simon Richter; +Cc: Junio C Hamano, git
In-Reply-To: <20260604061605.GA3194609@coredump.intra.peff.net>
Jeff King <peff@peff.net> writes:
> On Thu, Jun 04, 2026 at 02:11:38PM +0900, Simon Richter wrote:
>
>> Cloning from our server will, depending on what upstream uses, either a
>> relative URL (which will go to our server, but we have little control over
>> what the name part of the repository base URL is going to be), or an
>> absolute URL that instructs clients to pull from another place, which
>> conflicts with our goal to have a self-contained archive.
>>
>> The idea posited earlier, to have a "repository identity" that remains the
>> same across forks and clones, is somewhat appealing, but the best idea I can
>> come up with is generating some kind of repository UUID, and adding a
>> symlink -- not a great design because it pollutes outside the repo:
>>
>> $ mkdir myproject
>> $ cd myproject
>> $ git init
>> $ ls -l ..
>> lrwxrwxrwx 1 simon simon 9 Jun 4 14:05
>> 12345678-9abc-def0-1234-56789abcdef0.git -> myproject
>> drwxrwxr-x 2 simon simon 40 Jun 4 14:04 myproject
>>
>> On the other hand, this can be used to construct a stable relative submodule
>> URL.
>
> Here's a thought experiment. What if you put the UUID into a URL, like:
>
> repoid://123456789.git
>
> Then your in-repo .gitconfig would point to that repo id and be
> consistent. Of course you need some way to tell Git how to retrieve
> repoid:// URLs. You could do so with a custom remote helper
> (git-remote-repoid), but presumably that helper is eventually going to
> end up going over one of the normal Git protocols.
>
> So we just need to tell Git how to resolve repo id URLs into concrete
> URLs. And indeed, we have url.*.insteadOf to do rewriting already. So
> for example, you can add a submodule but convert it into a uuid like
> this:
>
> $ git submodule add https://github.com/git/git.git
> $ git config -f .gitmodules submodule.git.url
> https://github.com/git/git.git
> $ git config -f .gitmodules submodule.git.url repoid://123456789.git
> $ git commit -am 'add submodule with magic repoid'
>
> Now if somebody else comes along and clones it naively, the repo uuid is
> not useful to git by itself:
>
> $ git clone --recurse-submodules repo
> Submodule 'git' (repoid://123456789.git) registered for path 'git'
> Cloning into '/home/peff/tmp/repo/git'...
> fatal: transport 'repoid' not allowed
> fatal: clone of 'repoid://123456789.git' into submodule path '/home/peff/tmp/repo/git' failed
>
> But imagine that "somehow" they have learned that 123456789.git can be
> found at some URL. You can do this:
>
> git -c url.https://github.com/git/git.git.insteadOf=repoid://123456789.git \
> clone --recurse-submodules repo.git
>
> which would clone from the original URL. Or you could even imagine that
> they have a cache of repositories named by uuid, and then:
>
> git -c url.https://my/cache/.insteadOf=repoid:// ...
>
> would rewrite all repoid://'s automatically.
>
> The use of "-c" here is mostly for illustration. It is a per-command
> config, so when you later try to update the submodule, you'd run into
> the same problem. Probably you'd want to stuff your mapping into on-disk
> config (either ~/.gitconfig, or if you have a lot of them, perhaps some
> file included from there).
>
> It would be nice if you could use "git clone -c" (note "-c" as an option
> to "clone", not to "git") to set a permanent per-repo config variable.
> But sadly the URL rewriting happens in the submodule repository, not the
> parent. So it has to be a per-user setting.
>
>
> Now, all of that said, do we still need uuids at all? If the canonical
> submodule name is https://github.com/git/git.git, then anybody can just
> rewrite that locally in the same way using url.*.insteadOf config. And I
> think this is a pretty standard way of using submodules. E.g., you might
> rewrite https:// into ssh:// if you prefer that protocol. Or point to a
> local server if it's faster for you.
>
> Which makes me wonder if I am missing something about the original
> request that started this thread. But it sounds to me like it is just
> asking for the existing URL-rewriting feature.
>
The problem is that one might have multiple repositories, submodules
may themselves have submodules. Typically a primary development
organization will have its own host, but may also have mirrors on other
services which maybe more convenient for others to use. A recursive
clone could give upto 20 repositories not all of which are maintained by
the same organization. URL-rewriting each of them can be inefficient,
especially when the upstream maintains the mirror repositories and can
indicate that in the source repositories.
> -Peff
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Benson Muite @ 2026-06-05 4:47 UTC (permalink / raw)
To: Simon Richter, Junio C Hamano; +Cc: git
In-Reply-To: <d64e7f31-4e00-478c-ab31-b671242865fb@hogyros.de>
Simon Richter <Simon.Richter@hogyros.de> writes:
> Hi,
>
> On 6/4/26 10:09 AM, Junio C Hamano wrote:
>
>> So, no, I do not think a contribution to add mirror repositories as
>> alternate submodule sources should be considered for inclusion, as
>> it artificially limits usefulness of the feature. A feature to add
>> mirror repositories as alternate sources might be worth considering,
>> though.
>
> This is relevant to the Debian use case: we run a git server that
> archives git trees for Debian packages, and ideally the objects on this
> server should be identical to what you get from upstream projects.
>
> This is a big problem for archiving projects that use submodules,
> because we cannot alter the reference URLs.
>
> Cloning from our server will, depending on what upstream uses, either a
> relative URL (which will go to our server, but we have little control
> over what the name part of the repository base URL is going to be), or
> an absolute URL that instructs clients to pull from another place, which
> conflicts with our goal to have a self-contained archive.
>
> The idea posited earlier, to have a "repository identity" that remains
> the same across forks and clones, is somewhat appealing, but the best
> idea I can come up with is generating some kind of repository UUID, and
> adding a symlink -- not a great design because it pollutes outside the repo:
>
> $ mkdir myproject
> $ cd myproject
> $ git init
> $ ls -l ..
> lrwxrwxrwx 1 simon simon 9 Jun 4 14:05
> 12345678-9abc-def0-1234-56789abcdef0.git -> myproject
> drwxrwxr-x 2 simon simon 40 Jun 4 14:04 myproject
>
> On the other hand, this can be used to construct a stable relative
> submodule URL.
>
> Making the symlinks optional would require keeping a list of local
> clones and their UUIDs, and resolving them.
>
> I don't like that design, but as I said it's the best idea I have for now.
>
> I also fully expect that Debian's servers will be used by a lot of
> people outside the project as soon as it becomes a convenient fallback,
> in the same way people are pulling .orig.tar.gz archives from Debian
> mirrors, so we need to make it easy to set up a mirror, to allow this to
> scale.
>
For submodules, the metadata consists of the url of the repository to
clone from. One could have a list of absolute URLs. The default would
be to assume that the URLs are tried in order, and if a URL times out,
the next one would be tried. One may want to change the default
ordering as a user setting, or do a ping test to get obtain content from
the closest repository.
As an example, for linphone-desktop, the first part of the .gitmodules
file contains:
[submodule "linphone-sdk"]
path = external/linphone-sdk
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
[submodule "external/google/gn"]
This could be updated to
[submodule "linphone-sdk"]
path = external/linphone-sdk
url = https://gitlab.linphone.org/BC/public/linphone-sdk.git
url = https://github.com/BelledonneCommunications/linphone-sdk.git
[submodule "external/google/gn"]
^ permalink raw reply
* Re: Mirror repositories for submodules
From: Benson Muite @ 2026-06-05 4:37 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
In-Reply-To: <xmqqcxy7qfgk.fsf@gitster.g>
Junio C Hamano <gitster@pobox.com> writes:
> Benson Muite <benson_muite@emailplus.org> writes:
>
>> Would a contribution to add mirror repositories as alternate submodule
>> sources be considered for inclusion? Some projects have mirror
>> repositories on other hosting services, and may have bandwidth limits on
>> their primary hosting service. Being able to indicate mirror
>> repositories for where to check for updates and sources for submodules
>> when doing `git clone --recurse-submodules https://my.repo ` or `git
>> submodule update --init --recursive` would be helpful when there is a
>> timeout.
>
> I do not see why such a "oh, the repository at $URL1 seems to be
> down, but we know $URL2 serves the equivalent information, so let's
> go there instead" feature has to be limited to submodule use case.
>
> So, no, I do not think a contribution to add mirror repositories as
> alternate submodule sources should be considered for inclusion, as
> it artificially limits usefulness of the feature. A feature to add
> mirror repositories as alternate sources might be worth considering,
> though.
Thanks for the feedback. This was motivated by problems when trying to
recursively clone, but a more general solution is also fine.
^ permalink raw reply
* Re: [PATCH] Documentation: remove redundant 'instead' in --subject-prefix
From: Weijie Yuan @ 2026-06-05 4:11 UTC (permalink / raw)
To: Lucas Seiki Oshiro; +Cc: git
In-Reply-To: <20260604163510.36687-2-lucasseikioshiro@gmail.com>
On Thu, Jun 04, 2026 at 01:34:42PM -0300, Lucas Seiki Oshiro wrote:
> --subject-prefix=<subject-prefix>::
> - Instead of the standard '[PATCH]' prefix in the subject
> - line, instead use '[<subject-prefix>]'. This can be used
> - to name a patch series, and can be combined with the
> - `--numbered` option.
> + Use '[<subject-prefix>]' instead of the standard '[PATCH]'
> + prefix in the subject line. This can be used to name a patch
> + series, and can be combined with the `--numbered` option.
Agreed, this reads more smoothly.
^ permalink raw reply
* [PATCH v2] Makefile: dedup archives in $(LIBS) so link recipes don't repeat them
From: Harald Nordgren via GitGitGadget @ 2026-06-04 22:03 UTC (permalink / raw)
To: git; +Cc: Harald Nordgren, Harald Nordgren
In-Reply-To: <pull.2314.git.git.1780269406949.gitgitgadget@gmail.com>
From: Harald Nordgren <haraldnordgren@gmail.com>
A handful of link recipes listed archive files twice: once explicitly
via $(filter %.a,$^) and again implicitly through $(LIBS), which
expanded to $(filter-out %.o,$(GITLIBS)) $(EXTLIBS). On macOS the
linker warned about the duplicates:
ld: warning: ignoring duplicate libraries: 'libgit.a', 'target/release/libgitcore.a'
Redefine $(LIBS) to list archive prerequisites from $^ first, then
the rest of the library list with those archives filtered out so each
appears only once.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
---
Makefile: drop duplicate %.a from test-helper link rule
Redefine $(LIBS) to list archive prerequisites from $^ first, then the
rest of the library list to avoid brittleness in the future.
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2314%2FHaraldNordgren%2Fmakefile-test-helper-dedup-libs-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2314/HaraldNordgren/makefile-test-helper-dedup-libs-v2
Pull-Request: https://github.com/git/git/pull/2314
Range-diff vs v1:
1: f6166450b0 ! 1: 0ef442ea05 Makefile: drop duplicate %.a from link recipes
@@ Metadata
Author: Harald Nordgren <haraldnordgren@gmail.com>
## Commit message ##
- Makefile: drop duplicate %.a from link recipes
+ Makefile: dedup archives in $(LIBS) so link recipes don't repeat them
- Three link recipes list archive files twice on the link line: once
- via $(filter %.a,$^) and again through $(LIBS), which expands to
- $(filter-out %.o,$(GITLIBS)) $(EXTLIBS). On macOS the linker warns
- about the duplicates:
+ A handful of link recipes listed archive files twice: once explicitly
+ via $(filter %.a,$^) and again implicitly through $(LIBS), which
+ expanded to $(filter-out %.o,$(GITLIBS)) $(EXTLIBS). On macOS the
+ linker warned about the duplicates:
ld: warning: ignoring duplicate libraries: 'libgit.a', 'target/release/libgitcore.a'
- Drop the redundant filter from the test-helper, fuzz-program, and
- unit-test recipes so they match the pattern used by other link
- recipes in the file.
+ Redefine $(LIBS) to list archive prerequisites from $^ first, then
+ the rest of the library list with those archives filtered out so each
+ appears only once.
Signed-off-by: Harald Nordgren <haraldnordgren@gmail.com>
## Makefile ##
+@@ Makefile: endif
+ #
+ # where we use it as a dependency. Since we also pull object files
+ # from the dependency list, that would make each entry appear twice.
+-LIBS = $(filter-out %.o, $(GITLIBS)) $(EXTLIBS)
++# Archives from $^ come first, then the rest with those archives
++# filtered out so each appears only once.
++LIBS = $(filter %.a,$^) $(filter-out $(filter %.a,$^),$(filter-out %.o,$(GITLIBS)) $(EXTLIBS))
+
+ BASIC_CFLAGS += $(COMPAT_CFLAGS)
+ LIB_OBJS += $(COMPAT_OBJS)
@@ Makefile: perf: all
t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
Makefile | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/Makefile b/Makefile
index b31ecb0756..a828a66f28 100644
--- a/Makefile
+++ b/Makefile
@@ -2503,7 +2503,9 @@ endif
#
# where we use it as a dependency. Since we also pull object files
# from the dependency list, that would make each entry appear twice.
-LIBS = $(filter-out %.o, $(GITLIBS)) $(EXTLIBS)
+# Archives from $^ come first, then the rest with those archives
+# filtered out so each appears only once.
+LIBS = $(filter %.a,$^) $(filter-out $(filter %.a,$^),$(filter-out %.o,$(GITLIBS)) $(EXTLIBS))
BASIC_CFLAGS += $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
@@ -3392,7 +3394,7 @@ perf: all
t/helper/test-tool$X: $(patsubst %,t/helper/%,$(TEST_BUILTINS_OBJS)) $(UNIT_TEST_DIR)/test-lib.o
t/helper/test-%$X: t/helper/test-%.o GIT-LDFLAGS $(GITLIBS)
- $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+ $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
check-sha1:: t/helper/test-tool$X
t/helper/test-sha1.sh
@@ -4015,13 +4017,13 @@ fuzz-all: $(FUZZ_PROGRAMS)
$(FUZZ_PROGRAMS): %: %.o oss-fuzz/dummy-cmd-main.o $(GITLIBS) GIT-LDFLAGS
$(QUIET_LINK)$(FUZZ_CXX) $(FUZZ_CXXFLAGS) -o $@ $(ALL_LDFLAGS) \
-Wl,--allow-multiple-definition \
- $(filter %.o,$^) $(filter %.a,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
+ $(filter %.o,$^) $(LIBS) $(LIB_FUZZING_ENGINE)
$(UNIT_TEST_PROGS): $(UNIT_TEST_BIN)/%$X: $(UNIT_TEST_DIR)/%.o $(UNIT_TEST_OBJS) \
$(GITLIBS) GIT-LDFLAGS
$(call mkdir_p_parent_template)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) \
- $(filter %.o,$^) $(filter %.a,$^) $(LIBS)
+ $(filter %.o,$^) $(LIBS)
GIT-TEST-SUITES: FORCE
@FLAGS='$(CLAR_TEST_SUITES)'; \
base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
--
gitgitgadget
^ permalink raw reply related
* Re: [PATCH 0/6] Support hashing objects larger than 4GB on Windows
From: Philip Oakley @ 2026-06-04 21:56 UTC (permalink / raw)
To: Johannes Schindelin via GitGitGadget, git; +Cc: Johannes Schindelin
In-Reply-To: <pull.2138.git.1780593313.gitgitgadget@gmail.com>
On 04/06/2026 18:15, Johannes Schindelin via GitGitGadget wrote:
> Philip Oakley has contributed these patches ~4.5 years ago, and they have
> been carried in Git for Windows ever since.
>
> Now that there are already other patch series flying around that try to
> address various aspects about >4GB objects (which aren't handled well by Git
> until it stops forcing unsigned long to do size_t's job), it seems a good
> time to upstream these patches, too, at long last.
Yay. I approve this message ;-)
Philip
>
> Philip Oakley (6):
> hash-object: demonstrate a >4GB/LLP64 problem
> object-file.c: use size_t for header lengths
> hash algorithms: use size_t for section lengths
> hash-object --stdin: verify that it works with >4GB/LLP64
> hash-object: add another >4GB/LLP64 test case
> hash-object: add a >4GB/LLP64 test case using filtered input
>
> object-file.c | 18 +++++++++---------
> object-file.h | 4 ++--
> sha1dc_git.c | 3 +--
> sha1dc_git.h | 2 +-
> t/t1007-hash-object.sh | 39 +++++++++++++++++++++++++++++++++++++++
> 5 files changed, 52 insertions(+), 14 deletions(-)
>
>
> base-commit: 9ac3f193c05c2237e2b14ebaa1149e9fc8a1abe0
> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2138%2Fdscho%2FPhilipOakley%2Fhashliteral_t-v1
> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2138/dscho/PhilipOakley/hashliteral_t-v1
> Pull-Request: https://github.com/gitgitgadget/git/pull/2138
^ permalink raw reply
* trailers: --only-trailers normalizes URLs to trailers
From: Kristoffer Haugsbakk @ 2026-06-04 21:27 UTC (permalink / raw)
To: git
The following is a bug that follows straightforwardly from the documented
or discussed behavior. In that sense it is not a bug. But it is a bug in
the sense that it makes things inconvenient and violates a design goal.
> 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 what is the equivalent of
git interpret-trailers --only-trailers
With
git log --format="%(trailers:only)"
> What did you expect to happen? (Expected behavior)
For URLs like https://www.digsm.xyz/ to be left intact.
(Well, did I expect that? It follows from the discussed behavior...)
> What happened instead? (Actual behavior)
URLs on a line by themselves in eligible trailer blocks get
normalized/canonicalized to a “trailer” with key e.g. `https`:
https: //www.digsm.xyz/
> What's different between what you expected and what actually happened?
In an ideal world to have some special-casing of URLs so that they are
not detected as trailers. Does anyone realistically want trailers like
this?:
file: //...
http: //...
https: //...
Maybe a C-style comment?
https: // I changed my mind about providing a URL here.
This comment is a placeholder.
Comment: // But next up we have a URL
https: https://protocoltwiceover.net
And this is where my imagination ends.
Just special-casing `https` would go a long way.
> Anything else you want to add:
Yes, after this [System Info] part.
> Please review the rest of the bug report below.
> You can delete any lines you don't wish to share.
[System Info]
git version:
git version 2.54.0
cpu: x86_64
built from commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0
sizeof-long: 8
sizeof-size_t: 8
shell-path: /bin/sh
rust: disabled
gettext: enabled
libcurl: 7.81.0
OpenSSL: OpenSSL 3.0.2 15 Mar 2022
zlib: 1.2.11
SHA-1: SHA1_DC
SHA-256: SHA256_BLK
default-ref-format: files
default-hash: sha1
uname: Linux 6.8.0-117-generic #117~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu May 7 22:17:46 UTC x86_64
compiler info: gnuc: 11.4
libc info: glibc: 2.35
$SHELL (typically, interactive shell): /bin/bash
[Enabled Hooks]
commit-msg
post-applypatch
post-commit
sendemail-validate
***
That things like `--format='%(trailers:only)'` normalize trailers is
known and has been discussed before.[1] There’s been discussion around
the key capitalization and prefix normalization. But this is not about
that. This is just about normalizing the separator part.
🔗 1: https://lore.kernel.org/git/87blk0rjob.fsf@0x63.nu/
One design goal for trailers (either by implementers or reviewers or both)
has been to avoid false positives.[2] That meant trying to avoid
detecting trailers that were not intended. For example:
Everything was better in the past. Let me not even start on this
rant: it is not good for my blood pressure.
This will not be picked up as a trailer block unless `rant` is configured
as a trailer key.
† 2: See e.g. Jonathan Tan’s series about among other things adding the
25% rule
https://lore.kernel.org/git/xmqq7f96sa9i.fsf@gitster.mtv.corp.google.com/
But that’s pretty innocuous. Just a misplaced rant. The topic of this
bug report is not a big deal either, but it is:
1. Structured data that gets mangled in this normalize mode
2. That can naturally go at the end of the message on its own line
And these two points are very relevant for people who never use
trailers. Or, wait. I guess it isn’t if they don’t use trailers and thus
will never normalize them. But it is relevant if they work on a project
where someone else does that.
IN INTENDED TRAILER BLOCKS [3]
And then there are things that can go wrong if you intend to write trailer blocks:
1. “Non-trailer lines” that are URLs get normalized as trailers (NTL for
short)
2. User error line wrapping turns one trailer into an empty trailer plus
a `https` trailer (LW for short)
3. Normalizing trailers along the way (as in patches in flight or
something) introduces this strange lossiness (NL for short)
I did (2) (LW for short) four years ago it seems:
See:
https://digsm.yxz/blog/important-context/?bigtechtracker=86b0c5a1e2b73b08fd54c727f4458649ed9fe3ad1b6e8ac9460c070113509a1e
† 3: Are all-caps titles good or bad? Let me know.
IN THE LINUX KERNEL
There are some hits for the `http` and `https` trailers when trailers
are normalized. The baseline:
$ git log --extended-regexp --grep='https?: //' --oneline | wc -l
12
With normalization:
$ git log --format='%(trailers:only)' |
grep --extended-regexp '^https?: //' | wc -l
245
Note that I have no idea how the Linux Kernel is run. But I don’t
imagine that there are uses for `https: //...` trailers.
And trailer usage is complicated. There are for example on-purpose
indented `Link` “trailers”, presumably for the purpose of *excluding*
them as `Link` trailers. See:
commit d80a9cb1a64ab9c817b6262c7e4e433b6a3581a0
<body>
[ljs@kernel.org: avoid bisection hazard]
Link: https://lkml.kernel.org/r/d0cc6161-77a4-42ba-a411-96c23c78df1b@lucifer.local
Link: https://lkml.kernel.org/r/c2be872d64ef9573b80727d9ab5446cf002f17b5.1774029655.git.ljs@kernel.org
Signed-off-by: Lorenzo Stoakes (Oracle) <ljs@kernel.org>
[MORE BELOW]
Is that indented link for that `[]` comment? I dunno.
But what’s the main topic here are intended non-trailer lines which are
URLs that get treated as trailers (NTL). Like this invented example:
Reported-by: ...
https://digsm.xyz/?avastvirus=5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03
Signed-off-by: ...
Or this real example where the URLs are clearly part of a “comment”
non-trailer run.
8236fc613d44e59f6736d6c3e9efffaf26ab7f00
Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
[bhelgaas: squash fixes:
https://lore.kernel.org/r/20260108013956.14351-2-bagasdotme@gmail.com
https://lore.kernel.org/r/20260108013956.14351-3-bagasdotme@gmail.com]
Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
Reviewed-by: Ilpo Järvinen <ilpo.jarvinen@linux.intel.com>
Link: https://patch.msgid.link/20251210132907.58799-4-xueshuai@linux.alibaba.com
(These are shown as they are written in the commit message. Normalizing
the messages would create `https` trailers.)
Here are examples of line-wrapping mistake commits (LW) for `Link`,
`Closes`, or `Fixes` (sometimes these point to bug URLs and not
commits):
5bd97f5c5f241a5610c4412d1b93995a26241f81
Link: https://patch.msgid.link/20260216-work-xattr-socket-v1-4-c2efa4f74cb7@kernel.org
Link:
https://lore.kernel.org/3cnmtqmakpbb2uwhenrj7kdqu3uefykiykjllgfbtpkiwhaa4s@sghkevv7jned [1]
Acked-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner <brauner@kernel.org>
and:
• 24abe1f238e7d7ac56be6374c52a3c13dab84f69
• 27e21516914dc130a79aa895a5a26e18f0213a5a
• be3536a4bdda53ff5a91b7e542b167d12bddb317
Finally there is this commit which has a trailer in the commit message
itself with the key `https` (NL).
commit 496c0c4c53bbe1bad97e82cd12103df61a6e459d
...
...
net: wan: fsl_ucc_hdlc: free tx_skbuff in uhdlc_memclean
<body>
https: //sashiko.dev/#/patchset/20260429114208.941011-1-holger.brunck%40hitachienergy.com
Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
Link: https://patch.msgid.link/20260507155332.3452319-1-holger.brunck@hitachienergy.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
How could this have happened? Follow the patch-id link.
https://patch.msgid.link/20260507155332.3452319-1-holger.brunck@hitachienergy.com
https://sashiko.dev/#/patchset/20260429114208.941011-1-holger.brunck%40hitachienergy.com
Fixes: c19b6d246a35 ("drivers/net: support hdlc function for QE-UCC")
Signed-off-by: Holger Brunck <holger.brunck@hitachienergy.com>
So it was just a non-trailer URL line as this person submitted it. But
presumably the person who applied it put the message through a round of
normalization.
Cheers, good night
--
Kristoffer
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox