{"thread":{"id":"63615","subject":"[PATCH] branch: move multiple branches in a single --force","startedAt":"2025-06-10T09:07:47Z","lastAt":"2025-06-12T16:51:38Z","messageCount":10,"participants":["Andrea Stacchiotti via GitGitGadget","Junio C Hamano","Andrea Stacchiotti"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"520029","messageId":"pull.1992.git.git.1749546464346.gitgitgadget@gmail.com","threadId":"63615","inReplyTo":null,"subject":"[PATCH] branch: move multiple branches in a single --force","fromName":"Andrea Stacchiotti via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-10T09:07:44Z","receivedAt":"2025-06-10T09:07:47Z","isPatch":true,"sender":{"key":"andreastacchiotti@gmail.com","avatar":null},"body":"From: Andrea Stacchiotti <andreastacchiotti@gmail.com>\n\nUsing either the 1-arg or 2-args form of --force\nit is possible to only move one branch at a time,\nto HEAD and <arg2> respectively.\n\nAllow moving multiple branches to a single target by giving\n'git branch --force b1 b2 b3 ... dest' cp-like semantics,\nall the branches are moved/created to 'dest'.\n\nThe convention extends the 2-args form in the same way\n'cp a b c ... dest' would do.\n\nThere could be another potential interpretation of\n`--force a b c`: moving all 3 to HEAD, but the 2-args\nform already changed the semantics to cp-like instead\nof appending an implicit HEAD, so this seems the least\nsurprising way to support multiple moves.\n\nNo such change is done to the move/copy paths,\nas such paths would error out anyway by trying\nto create multiple branches of the same name.\n\nSigned-off-by: Andrea Stacchiotti <andreastacchiotti@gmail.com>\n---\n    branch: Move multiple branches in a single --force\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1992%2F12345ieee%2Fmaster-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1992/12345ieee/master-v1\nPull-Request: https://github.com/git/git/pull/1992\n\n Documentation/git-branch.adoc |  2 +-\n builtin/branch.c              | 26 ++++++++++++++------------\n t/t3200-branch.sh             |  7 +++++++\n 3 files changed, 22 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc\nindex c0afddc424d..817b4a5d3f6 100644\n--- a/Documentation/git-branch.adoc\n+++ b/Documentation/git-branch.adoc\n@@ -17,7 +17,7 @@ git branch [--color[=<when>] | --no-color] [--show-current]\n \t   [(-r|--remotes) | (-a|--all)]\n \t   [--list] [<pattern>...]\n git branch [--track[=(direct|inherit)] | --no-track] [-f]\n-\t   [--recurse-submodules] <branch-name> [<start-point>]\n+\t   [--recurse-submodules] <branch-name>... [<start-point>]\n git branch (--set-upstream-to=<upstream>|-u <upstream>) [<branch-name>]\n git branch --unset-upstream [<branch-name>]\n git branch (-m|-M) [<old-branch>] <new-branch>\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex c150131bd9f..8ba04568c15 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -31,7 +31,7 @@\n \n static const char * const builtin_branch_usage[] = {\n \tN_(\"git branch [<options>] [-r | -a] [--merged] [--no-merged]\"),\n-\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]\"),\n+\tN_(\"git branch [<options>] [-f] [--recurse-submodules] <branch-name>... [<start-point>]\"),\n \tN_(\"git branch [<options>] [-l] [<pattern>...]\"),\n \tN_(\"git branch [<options>] [-r] (-d | -D) <branch-name>...\"),\n \tN_(\"git branch [<options>] (-m | -M) [<old-branch>] <new-branch>\"),\n@@ -992,9 +992,9 @@ int cmd_branch(int argc,\n \t\tstrbuf_addf(&buf, \"branch.%s.merge\", branch->name);\n \t\tgit_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE);\n \t\tstrbuf_release(&buf);\n-\t} else if (!noncreate_actions && argc > 0 && argc <= 2) {\n-\t\tconst char *branch_name = argv[0];\n-\t\tconst char *start_name = argc == 2 ? argv[1] : head;\n+\t} else if (!noncreate_actions && argc > 0) {\n+\t\tconst char *start_name = argc == 1 ? head : argv[argc - 1];\n+\t\tint iters = argc == 1 ? 1 : argc - 1;\n \n \t\tif (filter.kind != FILTER_REFS_BRANCHES)\n \t\t\tdie(_(\"the -a, and -r, options to 'git branch' do not take a branch name.\\n\"\n@@ -1003,15 +1003,17 @@ int cmd_branch(int argc,\n \t\tif (track == BRANCH_TRACK_OVERRIDE)\n \t\t\tdie(_(\"the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead\"));\n \n-\t\tif (recurse_submodules) {\n-\t\t\tcreate_branches_recursively(the_repository, branch_name,\n-\t\t\t\t\t\t    start_name, NULL, force,\n-\t\t\t\t\t\t    reflog, quiet, track, 0);\n-\t\t\tret = 0;\n-\t\t\tgoto out;\n+\t\tfor (int i = 0; i < iters; i++) {\n+\t\t\tconst char *branch_name = argv[i];\n+\n+\t\t\tif (recurse_submodules)\n+\t\t\t\tcreate_branches_recursively(the_repository, branch_name,\n+\t\t\t\t\t\t\t\tstart_name, NULL, force,\n+\t\t\t\t\t\t\t\treflog, quiet, track, 0);\n+\t\t\telse\n+\t\t\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n+\t\t\t\t\t\treflog, quiet, track, 0);\n \t\t}\n-\t\tcreate_branch(the_repository, branch_name, start_name, force, 0,\n-\t\t\t      reflog, quiet, track, 0);\n \t} else\n \t\tusage_with_options(builtin_branch_usage, options);\n \ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex f3e720dc10d..7cd31ca7820 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -60,6 +60,13 @@ test_expect_success 'git branch --force abc should succeed when abc exists' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'git branch --force br1 br2 abc should create 2 new branches' '\n+\tgit branch --force br1 br2 abc &&\n+\ttest_ref_exists refs/heads/br1 &&\n+\ttest_ref_exists refs/heads/br2 &&\n+\tgit branch -d br1 br2\n+'\n+\n test_expect_success 'git branch a/b/c should create a branch' '\n \tgit branch a/b/c &&\n \ttest_ref_exists refs/heads/a/b/c\n\nbase-commit: 4c0e625c091d4c648cec7319bafaed3cc81658e5\n-- \ngitgitgadget\n"},{"id":"520057","messageId":"xmqq7c1jmgpq.fsf@gitster.g","threadId":"63615","inReplyTo":"pull.1992.git.git.1749546464346.gitgitgadget@gmail.com","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-10T21:25:53Z","receivedAt":"2025-06-10T21:25:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Andrea Stacchiotti via GitGitGadget\" <gitgitgadget@gmail.com>\nwrites:\n\n> From: Andrea Stacchiotti <andreastacchiotti@gmail.com>\n>\n> Using either the 1-arg or 2-args form of --force\n> it is possible to only move one branch at a time,\n> to HEAD and <arg2> respectively.\n\nIf you are renaming (or \"moving\") a branch that is not checked out\nanywhere to a new name that is not in use, you do not even need to\nforce.  You can just do:\n\n    git branch -m old new\n\nYou are not moving branches without \"-m\".\n\nWhat you are doing is to point a branch A to point at a commit X\nwith\n\n    git branch A X\n\nYour proposed log message talks about \"--force\" too much; if you are\ncreating a branch, you need \"--force\" only when the name you want to\nuse is already taken.  Pointing the branch tip to a commit is not\ninherently tied to \"--force\", but your description gives a false\nimpression that you are adding a special feature when \"--force\" is\nused.  The proposed log message needs rewritten.\n\nIf there is not yet a branch A, you do not even need \"--force\" on\nthis command line.  Also take a special note that \"X\" does not have\nto be a branch name.  It only has to resolve to a commit, so this is\nalso valid:\n\n    git branch [--force] A X~4\n\nI can understand that it may appear to be handy to be able to set\nmultiple branches at the same time with\n\n    git branch A X~4 B X~3 C X~2\t\t(* does not exist *)\n\nwith or without \"--force\".  If none of A, B, or C exist, they can be\ncreated from these three comits X~4, X~3, and X~2.\n\nOr you could propose a different syntax to create branches pointing\nat the same commit\n\n    git branch A B C origin/master\t\t(* does not exist *)\n\nBut either syntax to create multiple branches feel somewhat\ninadequate.  What should happen to their associated configuration\ndata like branch.A.remote and branch.B.merge?  Should they all point\nat the same remote & a branch at the remote?  How would that make\nhaving multiple of them useful?\n\n"},{"id":"520061","messageId":"CAEgWtF-fNXaC88FWw5K_3ZpbvQSxAfeuCFy8kkrh_z16vD77=g@mail.gmail.com","threadId":"63615","inReplyTo":"xmqq7c1jmgpq.fsf@gitster.g","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Andrea Stacchiotti","fromEmail":"andreastacchiotti@gmail.com","sentAt":"2025-06-10T22:17:39Z","receivedAt":"2025-06-10T22:17:53Z","isPatch":true,"sender":{"key":"andreastacchiotti@gmail.com","avatar":null},"body":"Thank you for your review, the concerns are fair, I'll reply point-to-point.\n\nThe general issue is that AFAIK the only way git has to say:\n\"point non-checked-out branch B to commit-ish X, leave B's config intact,\nif B doesn't exist create it\" is `git branch --force B X`.\n\nThis is likely an abuse of `branch -f`, it might be better giving it\nanother flag\naltogether, like `-p/--point-to`.\n\n`branch --force` is massively overloaded and most of its functionality is\ntied to `--move` or `--copy`, so changing branch names, not repointing branches.\n\nThis patch aims to make repointing multiple branches to the same commit-ish\neasier, currently it needs a shell loop.\n\nI'd like this functionality for gitops repos, where each branch is an env\nand I want to update dozens of envs to the last config state.\n\nIl giorno mar 10 giu 2025 alle ore 23:25 Junio C Hamano\n<gitster@pobox.com> ha scritto:\n>\n> \"Andrea Stacchiotti via GitGitGadget\" <gitgitgadget@gmail.com>\n> writes:\n>\n> > From: Andrea Stacchiotti <andreastacchiotti@gmail.com>\n> >\n> > Using either the 1-arg or 2-args form of --force\n> > it is possible to only move one branch at a time,\n> > to HEAD and <arg2> respectively.\n>\n> If you are renaming (or \"moving\") a branch that is not checked out\n> anywhere to a new name that is not in use, you do not even need to\n> force.  You can just do:\n>\n>     git branch -m old new\n>\n> You are not moving branches without \"-m\".\n>\n> What you are doing is to point a branch A to point at a commit X\n> with\n>\n>     git branch A X\n\nIndeed, this is about repointing, not changing branch names.\n\n> Your proposed log message talks about \"--force\" too much; if you are\n> creating a branch, you need \"--force\" only when the name you want to\n> use is already taken.  Pointing the branch tip to a commit is not\n> inherently tied to \"--force\", but your description gives a false\n> impression that you are adding a special feature when \"--force\" is\n> used.  The proposed log message needs rewritten.\n\nRight, it's that I'm using \"force create branch A onto X, delete A if\nit existed\"\nas \"repoint branch A to X, create it if needed\", I likely have a skewed\nview on it.\nThis usecase might be better served by a new flag.\n\n> If there is not yet a branch A, you do not even need \"--force\" on\n> this command line.  Also take a special note that \"X\" does not have\n> to be a branch name.  It only has to resolve to a commit, so this is\n> also valid:\n>\n>     git branch [--force] A X~4\n\nYeah, it was not clear from my message, nothing about the\nchange is about renaming a branch, my usecase is about\nrepointing to a generic commit-ish, like you say.\n\n> I can understand that it may appear to be handy to be able to set\n> multiple branches at the same time with\n>\n>     git branch A X~4 B X~3 C X~2                (* does not exist *)\n>\n> with or without \"--force\".  If none of A, B, or C exist, they can be\n> created from these three commits X~4, X~3, and X~2.\n\nI'm not opposed to implement this if it's preferred, but I liked\nthe cp-like syntax as it's less repeating and the average shell user\nis already exposed to it via cp or mv.\n\n> Or you could propose a different syntax to create branches pointing\n> at the same commit\n>\n>     git branch A B C origin/master              (* does not exist *)\n>\n> But either syntax to create multiple branches feel somewhat\n> inadequate.  What should happen to their associated configuration\n> data like branch.A.remote and branch.B.merge?  Should they all point\n> at the same remote & a branch at the remote?  How would that make\n> having multiple of them useful?\n>\nThis is closer to the intended syntax and use of the feature, I'm more\nfocused on branch repoints but I'd also use this when I create, say,\nthe staging and prod env of client newclient via\n`git branch [-f] newclient-prod newclient-stag <commit-ish>`\n\nThe branches are useful because they are then supposed to evolve\nindependently and track the real world state of the two envs.\n\nIf <commit-ish> is a remote branch the implementation sets up\ntracking, but this is really not the intended usecase, once\nthe branches are created their remote tracking\nis set at push time to <default remote>/<branch name> as per\ndefault push config.\n\nThanks for your review, I hope my intent is clearer.\n"},{"id":"520064","messageId":"xmqq4iwnktyv.fsf@gitster.g","threadId":"63615","inReplyTo":"CAEgWtF-fNXaC88FWw5K_3ZpbvQSxAfeuCFy8kkrh_z16vD77=g@mail.gmail.com","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-11T00:22:32Z","receivedAt":"2025-06-11T00:22:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n\n> This patch aims to make repointing multiple branches to the same commit-ish\n> easier, currently it needs a shell loop.\n\nOr \"update-ref --stdin\"?\n"},{"id":"520078","messageId":"CAEgWtF_0JzZ24L+H-WoKFGaK6Hho-YYbutxSXRud4SK3HwOYXg@mail.gmail.com","threadId":"63615","inReplyTo":"xmqq4iwnktyv.fsf@gitster.g","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Andrea Stacchiotti","fromEmail":"andreastacchiotti@gmail.com","sentAt":"2025-06-11T08:34:38Z","receivedAt":"2025-06-11T08:34:51Z","isPatch":true,"sender":{"key":"andreastacchiotti@gmail.com","avatar":null},"body":"Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano\n<gitster@pobox.com> ha scritto:\n>\n> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n>\n> > This patch aims to make repointing multiple branches to the same commit-ish\n> > easier, currently it needs a shell loop.\n>\n> Or \"update-ref --stdin\"?\n\nI learned something new, but I'd still like to keep advocating for a syntax\nlike `branch --some-flag A B C X` instead of feeding by hand\nupdate-ref commands.\n"},{"id":"520106","messageId":"xmqqecvqjo46.fsf@gitster.g","threadId":"63615","inReplyTo":"CAEgWtF_0JzZ24L+H-WoKFGaK6Hho-YYbutxSXRud4SK3HwOYXg@mail.gmail.com","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-11T15:26:33Z","receivedAt":"2025-06-11T15:26:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n\n> Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano\n> <gitster@pobox.com> ha scritto:\n>>\n>> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n>>\n>> > This patch aims to make repointing multiple branches to the same commit-ish\n>> > easier, currently it needs a shell loop.\n>>\n>> Or \"update-ref --stdin\"?\n>\n> I learned something new, but I'd still like to keep advocating for a syntax\n> like `branch --some-flag A B C X` instead of feeding by hand\n> update-ref commands.\n\nI am personally not interested in such a mode, I do not know why you\nthink \"--some-flag\" is needed when the command can figure out from\nthe number of things on the command line being more than 2 just\nfine.\n\nBut my comment was targetted against \"it needs a shell loop\" in the\njustification in the proposed log message, which is not quite\ncorrect.\n\n"},{"id":"520138","messageId":"CAEgWtF9MRbRASg1Jb3n6Ggvh8viZOpyev+OyX5DSpWQ7bMF8dg@mail.gmail.com","threadId":"63615","inReplyTo":"xmqqecvqjo46.fsf@gitster.g","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Andrea Stacchiotti","fromEmail":"andreastacchiotti@gmail.com","sentAt":"2025-06-12T00:19:18Z","receivedAt":"2025-06-12T00:19:30Z","isPatch":true,"sender":{"key":"andreastacchiotti@gmail.com","avatar":null},"body":"So, if I may ask, is the proposed patch as written (branch -f A B C X)\nacceptable and you just need me to rewrite the commit message\nor are you not interested in it at all?\n\nIl giorno mer 11 giu 2025 alle ore 17:26 Junio C Hamano\n<gitster@pobox.com> ha scritto:\n>\n> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n>\n> > Il giorno mer 11 giu 2025 alle ore 02:22 Junio C Hamano\n> > <gitster@pobox.com> ha scritto:\n> >>\n> >> Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n> >>\n> >> > This patch aims to make repointing multiple branches to the same commit-ish\n> >> > easier, currently it needs a shell loop.\n> >>\n> >> Or \"update-ref --stdin\"?\n> >\n> > I learned something new, but I'd still like to keep advocating for a syntax\n> > like `branch --some-flag A B C X` instead of feeding by hand\n> > update-ref commands.\n>\n> I am personally not interested in such a mode, I do not know why you\n> think \"--some-flag\" is needed when the command can figure out from\n> the number of things on the command line being more than 2 just\n> fine.\n>\n> But my comment was targetted against \"it needs a shell loop\" in the\n> justification in the proposed log message, which is not quite\n> correct.\n>\n"},{"id":"520167","messageId":"xmqqjz5hffn0.fsf@gitster.g","threadId":"63615","inReplyTo":"CAEgWtF9MRbRASg1Jb3n6Ggvh8viZOpyev+OyX5DSpWQ7bMF8dg@mail.gmail.com","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-12T09:55:31Z","receivedAt":"2025-06-12T09:55:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Andrea Stacchiotti <andreastacchiotti@gmail.com> writes:\n\n> So, if I may ask, is the proposed patch as written (branch -f A B C X)\n> acceptable and you just need me to rewrite the commit message\n> or are you not interested in it at all?\n\nAs I said, I personally am not interested in such a mode, but you\nmay be able to interest other people in it, and with wider support\nI may change my mind.  But I do not think the feature should not be\ntied to \"--force\" option at all.  \"git branch A B C X\" should be\nable to create three new branches A B C that all tracks X if none of\nthem exist without \"--force\".\n\n\n\n"},{"id":"520173","messageId":"xmqqa56dezax.fsf@gitster.g","threadId":"63615","inReplyTo":"xmqqjz5hffn0.fsf@gitster.g","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2025-06-12T15:48:22Z","receivedAt":"2025-06-12T15:48:25Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I may change my mind.  But I do not think the feature should not be\n> tied to \"--force\" option at all.\n\nSorry for a double-negation failure.  What I think is that the\nfeature should be orthogonal to \"--force\".\n"},{"id":"520176","messageId":"CAEgWtF-uKdeJ_B4WUZFJQ7gziN+GjJ=GVNE1V6OebyoQAiiG3w@mail.gmail.com","threadId":"63615","inReplyTo":"xmqqa56dezax.fsf@gitster.g","subject":"Re: [PATCH] branch: move multiple branches in a single --force","fromName":"Andrea Stacchiotti","fromEmail":"andreastacchiotti@gmail.com","sentAt":"2025-06-12T16:51:25Z","receivedAt":"2025-06-12T16:51:38Z","isPatch":true,"sender":{"key":"andreastacchiotti@gmail.com","avatar":null},"body":"Il giorno gio 12 giu 2025 alle ore 17:48 Junio C Hamano\n<gitster@pobox.com> ha scritto:\n>\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n> > I may change my mind.  But I do not think the feature should not be\n> > tied to \"--force\" option at all.\n>\n> Sorry for a double-negation failure.  What I think is that the\n> feature should be orthogonal to \"--force\".\n\nNo worries, I got it, it makes sense.\nI can implement the revised request if I see some replies expressing interest.\n"}]}