threads / patch / 63615

patchbranch: move multiple branches in a single --force

Subject: [PATCH] branch: move multiple branches in a single --force

## tl;dr

10 messages between Jun 10, 2025 and Jun 12, 2025. Diffs are folded; open one to read it.

replies: 9people: 2as markdown or json

Andrea Stacchiotti via GitGitGadget· Jun 10, 2025, 09:07 UTC · lore
From: Andrea Stacchiotti <andreastacchiotti@gmail.com>

Using either the 1-arg or 2-args form of --force it is possible to only move one branch at a time, to HEAD and <arg2> respectively.

Allow moving multiple branches to a single target by giving 'git branch --force b1 b2 b3 ... dest' cp-like semantics, all the branches are moved/created to 'dest'.

The convention extends the 2-args form in the same way 'cp a b c ... dest' would do.

There could be another potential interpretation of `--force a b c`: moving all 3 to HEAD, but the 2-args form already changed the semantics to cp-like instead of appending an implicit HEAD, so this seems the least surprising way to support multiple moves.

No such change is done to the move/copy paths, as such paths would error out anyway by trying to create multiple branches of the same name.

Signed-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>
---
    branch: Move multiple branches in a single --force
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1992%2F12345ieee%2Fmaster-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1992/12345ieee/master-v1
Pull-Request: https://github.com/git/git/pull/1992
 Documentation/git-branch.adoc |  2 +-
 builtin/branch.c              | 26 ++++++++++++++------------
 t/t3200-branch.sh             |  7 +++++++
 3 files changed, 22 insertions(+), 13 deletions(-)
Show changes to 3 files +22 −13

Documentation/git-branch.adoc, builtin/branch.c, t/t3200-branch.sh

diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
index c0afddc424d..817b4a5d3f6 100644
--- a/Documentation/git-branch.adoc
+++ b/Documentation/git-branch.adoc
@@ -17,7 +17,7 @@ git branch [--color[=<when>] | --no-color] [--show-current]
 	   [(-r|--remotes) | (-a|--all)]
 	   [--list] [<pattern>...]
 git branch [--track[=(direct|inherit)] | --no-track] [-f]
-	   [--recurse-submodules] <branch-name> [<start-point>]
+	   [--recurse-submodules] <branch-name>... [<start-point>]
 git branch (--set-upstream-to=<upstream>|-u <upstream>) [<branch-name>]
 git branch --unset-upstream [<branch-name>]
 git branch (-m|-M) [<old-branch>] <new-branch>
diff --git a/builtin/branch.c b/builtin/branch.c
index c150131bd9f..8ba04568c15 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -31,7 +31,7 @@
 
 static const char * const builtin_branch_usage[] = {
 	N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"),
-	N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"),
+	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>..."),
 	N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
@@ -992,9 +992,9 @@ int cmd_branch(int argc,
 		strbuf_addf(&buf, "branch.%s.merge", branch->name);
 		git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);
 		strbuf_release(&buf);
-	} else if (!noncreate_actions && argc > 0 && argc <= 2) {
-		const char *branch_name = argv[0];
-		const char *start_name = argc == 2 ? argv[1] : head;
+	} else if (!noncreate_actions && argc > 0) {
+		const char *start_name = argc == 1 ? head : argv[argc - 1];
+		int iters = argc == 1 ? 1 : argc - 1;
 
 		if (filter.kind != FILTER_REFS_BRANCHES)
 			die(_("the -a, and -r, options to 'git branch' do not take a branch name.\n"
@@ -1003,15 +1003,17 @@ int cmd_branch(int argc,
 		if (track == BRANCH_TRACK_OVERRIDE)
 			die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead"));
 
-		if (recurse_submodules) {
-			create_branches_recursively(the_repository, branch_name,
-						    start_name, NULL, force,
-						    reflog, quiet, track, 0);
-			ret = 0;
-			goto out;
+		for (int i = 0; i < iters; i++) {
+			const char *branch_name = argv[i];
+
+			if (recurse_submodules)
+				create_branches_recursively(the_repository, branch_name,
+								start_name, NULL, force,
+								reflog, quiet, track, 0);
+			else
+				create_branch(the_repository, branch_name, start_name, force, 0,
+						reflog, quiet, track, 0);
 		}
-		create_branch(the_repository, branch_name, start_name, force, 0,
-			      reflog, quiet, track, 0);
 	} else
 		usage_with_options(builtin_branch_usage, options);
 
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index f3e720dc10d..7cd31ca7820 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -60,6 +60,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '
 	test_cmp expect actual
 '
 
+test_expect_success 'git branch --force br1 br2 abc should create 2 new branches' '
+	git branch --force br1 br2 abc &&
+	test_ref_exists refs/heads/br1 &&
+	test_ref_exists refs/heads/br2 &&
+	git branch -d br1 br2
+'
+
 test_expect_success 'git branch a/b/c should create a branch' '
 	git branch a/b/c &&
 	test_ref_exists refs/heads/a/b/c

base-commit: 4c0e625c091d4c648cec7319bafaed3cc81658e5
-- 
gitgitgadget
Junio C Hamano· Jun 10, 2025, 21:25 UTC · re: Andrea Stacchiotti via GitGitGadget · lore

Re: [PATCH] branch: move multiple branches in a single --force

"Andrea Stacchiotti via GitGitGadget" <gitgitgadget@gmail.com> writes:

Show 5 quoted lines
> From: Andrea Stacchiotti <andreastacchiotti@gmail.com>
>
> Using either the 1-arg or 2-args form of --force
> it is possible to only move one branch at a time,
> to HEAD and <arg2> respectively.

If you are renaming (or "moving") a branch that is not checked out anywhere to a new name that is not in use, you do not even need to force. You can just do:

    git branch -m old new
You are not moving branches without "-m".

What you are doing is to point a branch A to point at a commit X with

    git branch A X

Your proposed log message talks about "--force" too much; if you are creating a branch, you need "--force" only when the name you want to use is already taken. Pointing the branch tip to a commit is not inherently tied to "--force", but your description gives a false impression that you are adding a special feature when "--force" is used. The proposed log message needs rewritten.

If there is not yet a branch A, you do not even need "--force" on this command line. Also take a special note that "X" does not have to be a branch name. It only has to resolve to a commit, so this is also valid:

    git branch [--force] A X~4

I can understand that it may appear to be handy to be able to set multiple branches at the same time with

    git branch A X~4 B X~3 C X~2		(* does not exist *)

with or without "--force". If none of A, B, or C exist, they can be created from these three comits X~4, X~3, and X~2.

Or you could propose a different syntax to create branches pointing at the same commit

    git branch A B C origin/master		(* does not exist *)

But either syntax to create multiple branches feel somewhat inadequate. What should happen to their associated configuration data like branch.A.remote and branch.B.merge? Should they all point at the same remote & a branch at the remote? How would that make having multiple of them useful?

Andrea Stacchiotti· Jun 10, 2025, 22:17 UTC · re: Junio C Hamano · lore

Re: [PATCH] branch: move multiple branches in a single --force

Thank you for your review, the concerns are fair, I'll reply point-to-point.

The general issue is that AFAIK the only way git has to say: "point non-checked-out branch B to commit-ish X, leave B's config intact, if B doesn't exist create it" is `git branch --force B X`.

This is likely an abuse of `branch -f`, it might be better giving it another flag altogether, like `-p/--point-to`.

`branch --force` is massively overloaded and most of its functionality is tied to `--move` or `--copy`, so changing branch names, not repointing branches.

This patch aims to make repointing multiple branches to the same commit-ish easier, currently it needs a shell loop.

I'd like this functionality for gitops repos, where each branch is an env and I want to update dozens of envs to the last config state.

Il giorno mar 10 giu 2025 alle ore 23:25 Junio C Hamano <gitster@pobox.com> ha scritto:

Show 22 quoted lines
>
> "Andrea Stacchiotti via GitGitGadget" <gitgitgadget@gmail.com>
> writes:
>
> > From: Andrea Stacchiotti <andreastacchiotti@gmail.com>
> >
> > Using either the 1-arg or 2-args form of --force
> > it is possible to only move one branch at a time,
> > to HEAD and <arg2> respectively.
>
> If you are renaming (or "moving") a branch that is not checked out
> anywhere to a new name that is not in use, you do not even need to
> force.  You can just do:
>
>     git branch -m old new
>
> You are not moving branches without "-m".
>
> What you are doing is to point a branch A to point at a commit X
> with
>
>     git branch A X
Indeed, this is about repointing, not changing branch names.
Show 6 quoted lines
> Your proposed log message talks about "--force" too much; if you are
> creating a branch, you need "--force" only when the name you want to
> use is already taken.  Pointing the branch tip to a commit is not
> inherently tied to "--force", but your description gives a false
> impression that you are adding a special feature when "--force" is
> used.  The proposed log message needs rewritten.

Right, it's that I'm using "force create branch A onto X, delete A if it existed" as "repoint branch A to X, create it if needed", I likely have a skewed view on it. This usecase might be better served by a new flag.

Show 6 quoted lines
> If there is not yet a branch A, you do not even need "--force" on
> this command line.  Also take a special note that "X" does not have
> to be a branch name.  It only has to resolve to a commit, so this is
> also valid:
>
>     git branch [--force] A X~4

Yeah, it was not clear from my message, nothing about the change is about renaming a branch, my usecase is about repointing to a generic commit-ish, like you say.

Show 7 quoted lines
> I can understand that it may appear to be handy to be able to set
> multiple branches at the same time with
>
>     git branch A X~4 B X~3 C X~2                (* does not exist *)
>
> with or without "--force".  If none of A, B, or C exist, they can be
> created from these three commits X~4, X~3, and X~2.

I'm not opposed to implement this if it's preferred, but I liked the cp-like syntax as it's less repeating and the average shell user is already exposed to it via cp or mv.

Show 11 quoted lines
> Or you could propose a different syntax to create branches pointing
> at the same commit
>
>     git branch A B C origin/master              (* does not exist *)
>
> But either syntax to create multiple branches feel somewhat
> inadequate.  What should happen to their associated configuration
> data like branch.A.remote and branch.B.merge?  Should they all point
> at the same remote & a branch at the remote?  How would that make
> having multiple of them useful?
>

This is closer to the intended syntax and use of the feature, I'm more focused on branch repoints but I'd also use this when I create, say, the staging and prod env of client newclient via `git branch [-f] newclient-prod newclient-stag <commit-ish>`

The branches are useful because they are then supposed to evolve independently and track the real world state of the two envs.

If <commit-ish> is a remote branch the implementation sets up tracking, but this is really not the intended usecase, once the branches are created their remote tracking is set at push time to <default remote>/<branch name> as per default push config.

Thanks for your review, I hope my intent is clearer.
Junio C Hamano· Jun 11, 2025, 00:22 UTC · re: Andrea Stacchiotti · lore

Re: [PATCH] branch: move multiple branches in a single --force

Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
> This patch aims to make repointing multiple branches to the same commit-ish
> easier, currently it needs a shell loop.
Or "update-ref --stdin"?
Andrea Stacchiotti· Jun 11, 2025, 08:34 UTC · re: Junio C Hamano · lore

Re: [PATCH] branch: move multiple branches in a single --force

Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano <gitster@pobox.com> ha scritto:

Show 7 quoted lines
>
> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
>
> > This patch aims to make repointing multiple branches to the same commit-ish
> > easier, currently it needs a shell loop.
>
> Or "update-ref --stdin"?

I learned something new, but I'd still like to keep advocating for a syntax like `branch --some-flag A B C X` instead of feeding by hand update-ref commands.

Junio C Hamano· Jun 11, 2025, 15:26 UTC · re: Andrea Stacchiotti · lore

Re: [PATCH] branch: move multiple branches in a single --force

Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
Show 13 quoted lines
> Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano
> <gitster@pobox.com> ha scritto:
>>
>> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
>>
>> > This patch aims to make repointing multiple branches to the same commit-ish
>> > easier, currently it needs a shell loop.
>>
>> Or "update-ref --stdin"?
>
> I learned something new, but I'd still like to keep advocating for a syntax
> like `branch --some-flag A B C X` instead of feeding by hand
> update-ref commands.

I am personally not interested in such a mode, I do not know why you think "--some-flag" is needed when the command can figure out from the number of things on the command line being more than 2 just fine.

But my comment was targetted against "it needs a shell loop" in the justification in the proposed log message, which is not quite correct.

Andrea Stacchiotti· Jun 12, 2025, 00:19 UTC · re: Junio C Hamano · lore

Re: [PATCH] branch: move multiple branches in a single --force

So, if I may ask, is the proposed patch as written (branch -f A B C X) acceptable and you just need me to rewrite the commit message or are you not interested in it at all?

Il giorno mer 11 giu 2025 alle ore 17:26 Junio C Hamano <gitster@pobox.com> ha scritto:

Show 26 quoted lines
>
> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
>
> > Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano
> > <gitster@pobox.com> ha scritto:
> >>
> >> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
> >>
> >> > This patch aims to make repointing multiple branches to the same commit-ish
> >> > easier, currently it needs a shell loop.
> >>
> >> Or "update-ref --stdin"?
> >
> > I learned something new, but I'd still like to keep advocating for a syntax
> > like `branch --some-flag A B C X` instead of feeding by hand
> > update-ref commands.
>
> I am personally not interested in such a mode, I do not know why you
> think "--some-flag" is needed when the command can figure out from
> the number of things on the command line being more than 2 just
> fine.
>
> But my comment was targetted against "it needs a shell loop" in the
> justification in the proposed log message, which is not quite
> correct.
>
Junio C Hamano· Jun 12, 2025, 09:55 UTC · re: Andrea Stacchiotti · lore

Re: [PATCH] branch: move multiple branches in a single --force

Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:
> So, if I may ask, is the proposed patch as written (branch -f A B C X)
> acceptable and you just need me to rewrite the commit message
> or are you not interested in it at all?

As I said, I personally am not interested in such a mode, but you may be able to interest other people in it, and with wider support I may change my mind. But I do not think the feature should not be tied to "--force" option at all. "git branch A B C X" should be able to create three new branches A B C that all tracks X if none of them exist without "--force".

Junio C Hamano· Jun 12, 2025, 15:48 UTC · re: Junio C Hamano · lore

Re: [PATCH] branch: move multiple branches in a single --force

Junio C Hamano <gitster@pobox.com> writes:
> I may change my mind.  But I do not think the feature should not be
> tied to "--force" option at all.

Sorry for a double-negation failure. What I think is that the feature should be orthogonal to "--force".

Andrea Stacchiotti· Jun 12, 2025, 16:51 UTC · re: Junio C Hamano · lore

Re: [PATCH] branch: move multiple branches in a single --force

Il giorno gio 12 giu 2025 alle ore 17:48 Junio C Hamano <gitster@pobox.com> ha scritto:

Show 8 quoted lines
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > I may change my mind.  But I do not think the feature should not be
> > tied to "--force" option at all.
>
> Sorry for a double-negation failure.  What I think is that the
> feature should be orthogonal to "--force".

No worries, I got it, it makes sense. I can implement the revised request if I see some replies expressing interest.

← back to recent threads