{"thread":{"id":"56468","subject":"[RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","startedAt":"2021-09-08T20:15:52Z","lastAt":"2022-01-21T21:12:19Z","messageCount":103,"participants":["Josh Steadmon","Junio C Hamano","Glen Choo","Ævar Arnfjörð Bjarmason","Johannes Schindelin","René Scharfe","Andreas Schwab"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"435175","messageId":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","threadId":"56468","inReplyTo":null,"subject":"[RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-09-08T20:15:45Z","receivedAt":"2021-09-08T20:15:52Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach branch.autoSetupMerge a new \"inherit\" option. When this is set,\ncreating a new branch will cause the tracking configuration to default\nto the configuration of the branch point, if set.\n\nNEEDS WORK:\n* this breaks `git checkout -b new-branch --recurse-submodules`\n* add documentation\n* add tests\n* check corner cases, including whether this plays well with related\n  cmd-line options (switch, checkout, branch)\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\nI'll be looking into the --recurse-submodules breakage today, and then\nI'll work on polishing the patch after that's fixed. But I thought it's\nworth getting an idea of how the list feels about the feature in general\nwhile I sort through the issues.\n\n branch.c | 36 +++++++++++++++++++++++++++++++++++-\n branch.h |  3 ++-\n config.c |  3 +++\n 3 files changed, 40 insertions(+), 2 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..17d4cc5128 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,6 +126,38 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tchar *remote;\n+\tconst char *bare_ref;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n+\tif (git_config_get_string(key.buf, &remote)) {\n+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n+\t\t\tkey.buf);\n+\t\tstrbuf_release(&key);\n+\t\treturn 1;\n+\t}\n+\ttracking->remote = remote;\n+\n+\tstrbuf_reset(&key);\n+\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n+\tif (git_config_get_string(key.buf, &tracking->src)) {\n+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n+\t\t\tkey.buf);\n+\t\tstrbuf_release(&key);\n+\t\treturn 1;\n+\t}\n+\n+\ttracking->matches = 1;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -139,7 +171,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\tif (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))\n+\t\treturn;\n+\telse if (for_each_remote(find_tracked_branch, &tracking))\n \t\treturn;\n \n \tif (!tracking.matches)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..6484bda8a2 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..4bd5a18faf 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\n-- \n2.33.0.153.gba50c8fa24-goog\n\n"},{"id":"435176","messageId":"YTkgyQLtYZp1L5JE@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"Re: [RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-09-08T20:44:57Z","receivedAt":"2021-09-08T20:45:09Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Actually, this patch probably makes zero sense without [1], an old RFC\npatch to use a more repo-like workflow that we've been using internally\nat $job. Hopefully I can get a v2 that doesn't depend on this.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n"},{"id":"435491","messageId":"0346f447548cfd11307173aaa3284d86a2ef689c.1631319742.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v2] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-09-11T00:25:20Z","receivedAt":"2021-09-11T00:25:25Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach branch.autoSetupMerge a new \"inherit\" option. When this is set,\ncreating a new branch will cause the tracking configuration to default\nto the configuration of the branch point, if set.\n\nFor example, if branch.autoSetupMerge=inherit, branch \"main\" tracks\n\"origin/main\", and we run `git checkout -b feature main`, then branch\n\"feature\" will track \"origin/main\". Thus, `git status` will show us how\nfar ahead/behind we are from origin, and `git pull` will pull from\norigin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\nAfter a bit of testing, I've verified that this still works as intended\neven without the extra patch [1] linked above. I've added documentation\nand tests.\n\nRange-diff against v1:\n1:  9628d14588 ! 1:  0346f44754 branch: add \"inherit\" option for branch.autoSetupMerge\n    @@ Commit message\n         creating a new branch will cause the tracking configuration to default\n         to the configuration of the branch point, if set.\n     \n    -    NEEDS WORK:\n    -    * this breaks `git checkout -b new-branch --recurse-submodules`\n    -    * add documentation\n    -    * add tests\n    -    * check corner cases, including whether this plays well with related\n    -      cmd-line options (switch, checkout, branch)\n    +    For example, if branch.autoSetupMerge=inherit, branch \"main\" tracks\n    +    \"origin/main\", and we run `git checkout -b feature main`, then branch\n    +    \"feature\" will track \"origin/main\". Thus, `git status` will show us how\n    +    far ahead/behind we are from origin, and `git pull` will pull from\n    +    origin.\n    +\n    +    This is particularly useful when creating branches across many\n    +    submodules, such as with `git submodule foreach ...` (or if running with\n    +    a patch such as [1], which we use at $job), as it avoids having to\n    +    manually set tracking info for each submodule.\n    +\n    +    [1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n     \n     \n    + ## Documentation/config/branch.txt ##\n    +@@ Documentation/config/branch.txt: branch.autoSetupMerge::\n    + \tautomatic setup is done; `true` -- automatic setup is done when the\n    + \tstarting point is a remote-tracking branch; `always` --\n    + \tautomatic setup is done when the starting point is either a\n    +-\tlocal branch or remote-tracking\n    ++\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n    ++\thas a tracking configuration, it is copied to the new\n    + \tbranch. This option defaults to true.\n    + \n    + branch.autoSetupRebase::\n    +\n    + ## Documentation/git-branch.txt ##\n    +@@ Documentation/git-branch.txt: This behavior is the default when the start point is a remote-tracking branch.\n    + Set the branch.autoSetupMerge configuration variable to `false` if you\n    + want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n    + were given. Set it to `always` if you want this behavior when the\n    +-start-point is either a local or remote-tracking branch.\n    ++start-point is either a local or remote-tracking branch. Set it to\n    ++`inherit` if you want to copy the tracking configuration from the\n    ++start point.\n    + \n    + --no-track::\n    + \tDo not set up \"upstream\" configuration, even if the\n    +\n      ## branch.c ##\n     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const\n      \treturn -1;\n    @@ config.c: static int git_default_branch_config(const char *var, const char *valu\n      \t\t}\n      \t\tgit_branch_track = git_config_bool(var, value);\n      \t\treturn 0;\n    +\n    + ## t/t2017-checkout-orphan.sh ##\n    +@@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '\n    + \tgit checkout --orphan gamma &&\n    + \ttest -z \"$(git config branch.gamma.merge)\" &&\n    + \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n    ++\ttest_must_fail git rev-parse --verify HEAD^ &&\n    ++\tgit checkout main &&\n    ++\tgit config branch.autosetupmerge inherit &&\n    ++\tgit checkout --orphan eta &&\n    ++\ttest -z \"$(git config branch.eta.merge)\" &&\n    ++\ttest -z \"$(git config branch.eta.remote)\" &&\n    ++\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n    + \ttest_must_fail git rev-parse --verify HEAD^\n    + '\n    + \n    +\n    + ## t/t2027-checkout-track.sh ##\n    +@@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an extra path argument' '\n    + \ttest_i18ngrep \"cannot be used with updating paths\" err\n    + '\n    + \n    ++test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n    ++\t# Set up tracking config on main\n    ++\tgit config branch.main.remote origin &&\n    ++\tgit config branch.main.merge refs/heads/main &&\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\t# With branch.autoSetupMerge=inherit, we copy the tracking config\n    ++\tgit checkout -b b1 main &&\n    ++\ttest_cmp_config origin branch.b1.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.b1.merge &&\n    ++\t# But --track overrides this\n    ++\tgit checkout --track -b b2 main &&\n    ++\ttest_cmp_config . branch.b2.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.b2.merge\n    ++'\n    ++\n    + test_done\n    +\n    + ## t/t2060-switch.sh ##\n    +@@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progress' '\n    + \ttest_must_fail git switch -d @^\n    + '\n    + \n    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n    ++\t# default config does not copy tracking info\n    ++\tgit switch -c foo-no-inherit foo &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\t# with autoSetupMerge=inherit, we copy tracking info from foo\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\tgit switch -c foo2 foo &&\n    ++\ttest_cmp_config origin branch.foo2.remote &&\n    ++\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n    ++\t# no tracking info to inherit from main\n    ++\tgit switch -c main2 main &&\n    ++\ttest -z \"$(git config branch.main2.remote)\" &&\n    ++\ttest -z \"$(git config branch.main2.merge)\"\n    ++'\n    ++\n    + test_done\n    +\n    + ## t/t3200-branch.sh ##\n    +@@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration' '\n    + \t)\n    + '\n    + \n    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n    ++\ttest_unconfig branch.autoSetupMerge &&\n    ++\t# default config does not copy tracking info\n    ++\tgit branch foo-no-inherit my1 &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\t# with autoSetupMerge=inherit, we copy tracking info from my1\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\tgit branch foo2 my1 &&\n    ++\ttest_cmp_config local branch.foo2.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.foo2.merge &&\n    ++\t# no tracking info to inherit from main\n    ++\tgit branch main2 main &&\n    ++\ttest -z \"$(git config branch.main2.remote)\" &&\n    ++\ttest -z \"$(git config branch.main2.merge)\"\n    ++'\n    ++\n    + test_done\n    +\n    + ## t/t7201-co.sh ##\n    +@@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '\n    + \ttest_cmp expect arm\n    + '\n    + \n    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n    ++\tgit reset --hard main &&\n    ++\t# default config does not copy tracking info\n    ++\tgit checkout -b foo-no-inherit koala/bear &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    ++\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\tgit checkout -b foo koala/bear &&\n    ++\ttest_cmp_config origin branch.foo.remote &&\n    ++\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n    ++\t# no tracking info to inherit from main\n    ++\tgit checkout -b main2 main &&\n    ++\ttest -z \"$(git config branch.main2.remote)\" &&\n    ++\ttest -z \"$(git config branch.main2.merge)\"\n    ++'\n    ++\n    + test_done\n\n Documentation/config/branch.txt |  3 ++-\n Documentation/git-branch.txt    |  4 +++-\n branch.c                        | 36 ++++++++++++++++++++++++++++++++-\n branch.h                        |  3 ++-\n config.c                        |  3 +++\n t/t2017-checkout-orphan.sh      |  7 +++++++\n t/t2027-checkout-track.sh       | 15 ++++++++++++++\n t/t2060-switch.sh               | 16 +++++++++++++++\n t/t3200-branch.sh               | 17 ++++++++++++++++\n t/t7201-co.sh                   | 17 ++++++++++++++++\n 10 files changed, 117 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..81e901b8e8 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -218,7 +218,9 @@ This behavior is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+start point.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..17d4cc5128 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,6 +126,38 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tchar *remote;\n+\tconst char *bare_ref;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n+\tif (git_config_get_string(key.buf, &remote)) {\n+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n+\t\t\tkey.buf);\n+\t\tstrbuf_release(&key);\n+\t\treturn 1;\n+\t}\n+\ttracking->remote = remote;\n+\n+\tstrbuf_reset(&key);\n+\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n+\tif (git_config_get_string(key.buf, &tracking->src)) {\n+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n+\t\t\tkey.buf);\n+\t\tstrbuf_release(&key);\n+\t\treturn 1;\n+\t}\n+\n+\ttracking->matches = 1;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -139,7 +171,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\tif (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))\n+\t\treturn;\n+\telse if (for_each_remote(find_tracked_branch, &tracking))\n \t\treturn;\n \n \tif (!tracking.matches)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..6484bda8a2 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..4bd5a18faf 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..31fb64c5be 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout --orphan gamma &&\n \ttest -z \"$(git config branch.gamma.merge)\" &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest -z \"$(git config branch.eta.merge)\" &&\n+\ttest -z \"$(git config branch.eta.remote)\" &&\n+\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..4805965872 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,19 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\tgit config branch.main.remote origin &&\n+\tgit config branch.main.merge refs/heads/main &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With branch.autoSetupMerge=inherit, we copy the tracking config\n+\tgit checkout -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b2 main &&\n+\ttest_cmp_config . branch.b2.remote &&\n+\ttest_cmp_config refs/heads/main branch.b2.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..f9972e2841 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,20 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from foo\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..8005a5ccc6 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,21 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..ae9f8d02c2 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\n-- \n2.33.0.309.g3052b89438-goog\n\n"},{"id":"435494","messageId":"xmqqwnnondba.fsf@gitster.g","threadId":"56468","inReplyTo":"0346f447548cfd11307173aaa3284d86a2ef689c.1631319742.git.steadmon@google.com","subject":"Re: [PATCH v2] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-09-11T00:52:41Z","receivedAt":"2021-09-11T00:52:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> It can be helpful when creating a new branch to use the existing\n> tracking configuration from the branch point. However, there is\n> currently not a method to automatically do so.\n>\n> Teach branch.autoSetupMerge a new \"inherit\" option. When this is set,\n> creating a new branch will cause the tracking configuration to default\n> to the configuration of the branch point, if set.\n\nSo, when a new branch N is forked from an existing branch A that\nbuilds on branch B (which could be a local branch under refs/heads/\nor a remote-tracking branch under refs/remotes/), a plain-vanilla\nauto-setup-merge makes N build on A but with 'inherit', N is marked\nto build on B instead?\n\nI do not think it is wise to hide this useful feature behind a\nconfiguration variable.  \n\nRather, this should be made available first to users who do not even\nset the configuration and then as a convenience measure, made usable\nvia the configuration mechanism as well.\n\nThe current \"git branch --track N A\" makes N build on A, so perhaps\n\"git branch --track=inherit N A\" should make N build on whatever A\nbuilds on.  We may need to give a synonym --track=direct to the\ntraditional \"build on the original branch that was used to specfy\nthe fork point\" while doing so.\n\nAnd then on top of that, we can add configuration variable handling.\n\nDepending on the value of branch.autoSetupMerge, \"git branch -b\" and\n\"git checkout -b\" would pretend as if \"--track\" or \"--track=inherit\"\nwere given, or something along that line.  The end result may be the\nsame for those who only use the configuration variables, but it\nwould give us some flexibility to countermand the configuration from\nthe command line.  Those who set branch.autoSetupMerge to 'inherit'\ncannot say \"git checkout -b N --track=direct A\" to express \"With\nthis single invocation alone I am making N build on A, even though I\nknow I usually make N build on whatever A builds on\" if you give\nonly the configuration variable.\n\nThanks.\n"},{"id":"438960","messageId":"YWun9j7L5uNlVHkW@google.com","threadId":"56468","inReplyTo":"xmqqwnnondba.fsf@gitster.g","subject":"Re: [PATCH v2] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-10-17T04:35:02Z","receivedAt":"2021-10-17T04:35:14Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.09.10 17:52, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > It can be helpful when creating a new branch to use the existing\n> > tracking configuration from the branch point. However, there is\n> > currently not a method to automatically do so.\n> >\n> > Teach branch.autoSetupMerge a new \"inherit\" option. When this is set,\n> > creating a new branch will cause the tracking configuration to default\n> > to the configuration of the branch point, if set.\n> \n> So, when a new branch N is forked from an existing branch A that\n> builds on branch B (which could be a local branch under refs/heads/\n> or a remote-tracking branch under refs/remotes/), a plain-vanilla\n> auto-setup-merge makes N build on A but with 'inherit', N is marked\n> to build on B instead?\n> \n> I do not think it is wise to hide this useful feature behind a\n> configuration variable.  \n> \n> Rather, this should be made available first to users who do not even\n> set the configuration and then as a convenience measure, made usable\n> via the configuration mechanism as well.\n> \n> The current \"git branch --track N A\" makes N build on A, so perhaps\n> \"git branch --track=inherit N A\" should make N build on whatever A\n> builds on.  We may need to give a synonym --track=direct to the\n> traditional \"build on the original branch that was used to specfy\n> the fork point\" while doing so.\n> \n> And then on top of that, we can add configuration variable handling.\n> \n> Depending on the value of branch.autoSetupMerge, \"git branch -b\" and\n> \"git checkout -b\" would pretend as if \"--track\" or \"--track=inherit\"\n> were given, or something along that line.  The end result may be the\n> same for those who only use the configuration variables, but it\n> would give us some flexibility to countermand the configuration from\n> the command line.  Those who set branch.autoSetupMerge to 'inherit'\n> cannot say \"git checkout -b N --track=direct A\" to express \"With\n> this single invocation alone I am making N build on A, even though I\n> know I usually make N build on whatever A builds on\" if you give\n> only the configuration variable.\n> \n> Thanks.\n\nThanks for the feedback. I've added \"--track=direct\" and\n\"--track=inherit\" flags in V3, which I'll send out shortly. I am a bit\nskeptical of the value of having \"--track=direct\" when just \"--track\"\nstill works, but I'll leave it up to the list to decide.\n"},{"id":"438961","messageId":"b9356d9837479914bcf9a265f52afe170be7e2e2.1634445482.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-10-17T04:45:00Z","receivedAt":"2021-10-17T04:45:07Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\nRange-diff against v2:\n1:  0346f44754 ! 1:  b9356d9837 branch: add \"inherit\" option for branch.autoSetupMerge\n    @@ Metadata\n     Author: Josh Steadmon <steadmon@google.com>\n     \n      ## Commit message ##\n    -    branch: add \"inherit\" option for branch.autoSetupMerge\n    +    branch: add flags and config to inherit tracking\n     \n         It can be helpful when creating a new branch to use the existing\n         tracking configuration from the branch point. However, there is\n         currently not a method to automatically do so.\n     \n    -    Teach branch.autoSetupMerge a new \"inherit\" option. When this is set,\n    -    creating a new branch will cause the tracking configuration to default\n    -    to the configuration of the branch point, if set.\n    +    Teach git-{branch,checkout,switch} an \"inherit\" argument to the\n    +    \"--track\" option. When this is set, creating a new branch will cause the\n    +    tracking configuration to default to the configuration of the branch\n    +    point, if set.\n     \n    -    For example, if branch.autoSetupMerge=inherit, branch \"main\" tracks\n    -    \"origin/main\", and we run `git checkout -b feature main`, then branch\n    -    \"feature\" will track \"origin/main\". Thus, `git status` will show us how\n    -    far ahead/behind we are from origin, and `git pull` will pull from\n    -    origin.\n    +    For example, if branch \"main\" tracks \"origin/main\", and we run\n    +    `git checkout --track=inherit -b feature main`, then branch \"feature\"\n    +    will track \"origin/main\". Thus, `git status` will show us how far\n    +    ahead/behind we are from origin, and `git pull` will pull from origin.\n     \n         This is particularly useful when creating branches across many\n         submodules, such as with `git submodule foreach ...` (or if running with\n         a patch such as [1], which we use at $job), as it avoids having to\n         manually set tracking info for each submodule.\n     \n    +    Since we've added an argument to \"--track\", also add \"--track=direct\" as\n    +    another way to explicitly get the original \"--track\" behavior (\"--track\"\n    +    without an argument still works as well).\n    +\n    +    Finally, teach branch.autoSetupMerge a new \"inherit\" option. When this\n    +    is set, \"--track=inherit\" becomes the default behavior.\n    +\n         [1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n     \n    @@ Documentation/config/branch.txt: branch.autoSetupMerge::\n      branch.autoSetupRebase::\n     \n      ## Documentation/git-branch.txt ##\n    -@@ Documentation/git-branch.txt: This behavior is the default when the start point is a remote-tracking branch.\n    +@@ Documentation/git-branch.txt: SYNOPSIS\n    + \t[--points-at <object>] [--format=<format>]\n    + \t[(-r | --remotes) | (-a | --all)]\n    + \t[--list] [<pattern>...]\n    +-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n    ++'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n    + 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n    + 'git branch' --unset-upstream [<branchname>]\n    + 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n    +@@ Documentation/git-branch.txt: This option is only applicable in non-verbose mode.\n    + \tDisplay the full sha1s in the output listing rather than abbreviating them.\n    + \n    + -t::\n    +---track::\n    ++--track [inherit|direct]::\n    + \tWhen creating a new branch, set up `branch.<name>.remote` and\n    +-\t`branch.<name>.merge` configuration entries to mark the\n    +-\tstart-point branch as \"upstream\" from the new branch. This\n    ++\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n    ++\tconfiguration for the new branch. This\n    + \tconfiguration will tell git to show the relationship between the\n    + \ttwo branches in `git status` and `git branch -v`. Furthermore,\n    + \tit directs `git pull` without arguments to pull from the\n    + \tupstream when the new branch is checked out.\n    + +\n    +-This behavior is the default when the start point is a remote-tracking branch.\n    ++The exact upstream branch is chosen depending on the optional argument:\n    ++`--track` or `--track direct` means to use the start-point branch itself as the\n    ++upstream; `--track inherit` means to copy the upstream configuration of the\n    ++start-point branch.\n    +++\n    ++`--track direct` is the default when the start point is a remote-tracking branch.\n      Set the branch.autoSetupMerge configuration variable to `false` if you\n      want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n      were given. Set it to `always` if you want this behavior when the\n     -start-point is either a local or remote-tracking branch.\n     +start-point is either a local or remote-tracking branch. Set it to\n     +`inherit` if you want to copy the tracking configuration from the\n    -+start point.\n    ++branch point.\n      \n      --no-track::\n      \tDo not set up \"upstream\" configuration, even if the\n    +-\tbranch.autoSetupMerge configuration variable is true.\n    ++\tbranch.autoSetupMerge configuration variable is set.\n    + \n    + --set-upstream::\n    + \tAs this option had confusing syntax, it is no longer supported.\n    +\n    + ## Documentation/git-checkout.txt ##\n    +@@ Documentation/git-checkout.txt: of it\").\n    + \tlinkgit:git-branch[1] for details.\n    + \n    + -t::\n    +---track::\n    ++--track [direct|inherit]::\n    + \tWhen creating a new branch, set up \"upstream\" configuration. See\n    + \t\"--track\" in linkgit:git-branch[1] for details.\n    + +\n    +\n    + ## Documentation/git-switch.txt ##\n    +@@ Documentation/git-switch.txt: should result in deletion of the path).\n    + \tattached to a terminal, regardless of `--quiet`.\n    + \n    + -t::\n    +---track::\n    ++--track [direct|inherit]::\n    + \tWhen creating a new branch, set up \"upstream\" configuration.\n    + \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n    + \tdetails.\n     \n      ## branch.c ##\n     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     +\n     +\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n     +\tif (git_config_get_string(key.buf, &remote)) {\n    -+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n    -+\t\t\tkey.buf);\n    ++\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n    ++\t\t\tbare_ref, key.buf);\n     +\t\tstrbuf_release(&key);\n    -+\t\treturn 1;\n    ++\t\treturn -1;\n     +\t}\n    -+\ttracking->remote = remote;\n     +\n     +\tstrbuf_reset(&key);\n     +\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n     +\tif (git_config_get_string(key.buf, &tracking->src)) {\n    -+\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n    -+\t\t\tkey.buf);\n    ++\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n    ++\t\t\tbare_ref, key.buf);\n     +\t\tstrbuf_release(&key);\n    -+\t\treturn 1;\n    ++\t\tfree(remote);\n    ++\t\treturn -1;\n     +\t}\n     +\n    ++\ttracking->remote = remote;\n     +\ttracking->matches = 1;\n     +\tstrbuf_release(&key);\n     +\treturn 0;\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \tmemset(&tracking, 0, sizeof(tracking));\n      \ttracking.spec.dst = (char *)orig_ref;\n     -\tif (for_each_remote(find_tracked_branch, &tracking))\n    -+\tif (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))\n    -+\t\treturn;\n    -+\telse if (for_each_remote(find_tracked_branch, &tracking))\n    ++\tif (track != BRANCH_TRACK_INHERIT) {\n    ++\t\tfor_each_remote(find_tracked_branch, &tracking);\n    ++\t} else if (inherit_tracking(&tracking, orig_ref))\n      \t\treturn;\n      \n      \tif (!tracking.matches)\n    @@ branch.h: enum branch_track {\n      \n      extern enum branch_track git_branch_track;\n     \n    + ## builtin/branch.c ##\n    +@@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)\n    + \t\tOPT__VERBOSE(&filter.verbose,\n    + \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n    + \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n    +-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n    +-\t\t\tBRANCH_TRACK_EXPLICIT),\n    ++\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n    ++\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n    ++\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n    ++\t\t\tparse_opt_tracking_mode),\n    + \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n    + \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n    + \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\n    +\n    + ## builtin/checkout.c ##\n    +@@ builtin/checkout.c: static struct option *add_common_switch_branch_options(\n    + {\n    + \tstruct option options[] = {\n    + \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n    +-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n    +-\t\t\tBRANCH_TRACK_EXPLICIT),\n    ++\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n    ++\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n    ++\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n    ++\t\t\tparse_opt_tracking_mode),\n    + \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n    + \t\t\t   PARSE_OPT_NOCOMPLETE),\n    + \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n    +\n      ## config.c ##\n     @@ config.c: static int git_default_branch_config(const char *var, const char *value)\n      \t\tif (value && !strcasecmp(value, \"always\")) {\n    @@ config.c: static int git_default_branch_config(const char *var, const char *valu\n      \t\tgit_branch_track = git_config_bool(var, value);\n      \t\treturn 0;\n     \n    + ## parse-options-cb.c ##\n    +@@\n    + #include \"git-compat-util.h\"\n    + #include \"parse-options.h\"\n    ++#include \"branch.h\"\n    + #include \"cache.h\"\n    + #include \"commit.h\"\n    + #include \"color.h\"\n    +@@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n    + \n    + \treturn 0;\n    + }\n    ++\n    ++int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n    ++\tif (unset)\n    ++\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n    ++\telse if (!arg || !strcmp(arg, \"direct\"))\n    ++\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n    ++\telse if (!strcmp(arg, \"inherit\"))\n    ++\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n    ++\telse\n    ++\t\treturn error(_(\"option `%s' expects \\\"direct\\\" or \\\"inherit\\\"\"),\n    ++\t\t\t     opt->long_name);\n    ++\n    ++\treturn 0;\n    ++}\n    +\n    + ## parse-options.h ##\n    +@@ parse-options.h: enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n    + \t\t\t\t\t   const char *, int);\n    + int parse_opt_passthru(const struct option *, const char *, int);\n    + int parse_opt_passthru_argv(const struct option *, const char *, int);\n    ++/* value is enum branch_track* */\n    ++int parse_opt_tracking_mode(const struct option *, const char *, int);\n    + \n    + #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n    + #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\n    +\n      ## t/t2017-checkout-orphan.sh ##\n     @@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '\n      \tgit checkout --orphan gamma &&\n    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e\n     +\tgit config branch.main.remote origin &&\n     +\tgit config branch.main.merge refs/heads/main &&\n     +\ttest_config branch.autoSetupMerge inherit &&\n    -+\t# With branch.autoSetupMerge=inherit, we copy the tracking config\n    -+\tgit checkout -b b1 main &&\n    ++\t# With --track=inherit, we copy the tracking config from main\n    ++\tgit checkout --track=inherit -b b1 main &&\n     +\ttest_cmp_config origin branch.b1.remote &&\n     +\ttest_cmp_config refs/heads/main branch.b1.merge &&\n    ++\t# With branch.autoSetupMerge=inherit, we do the same\n    ++\tgit checkout -b b2 main &&\n    ++\ttest_cmp_config origin branch.b2.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.b2.merge &&\n     +\t# But --track overrides this\n    -+\tgit checkout --track -b b2 main &&\n    -+\ttest_cmp_config . branch.b2.remote &&\n    -+\ttest_cmp_config refs/heads/main branch.b2.merge\n    ++\tgit checkout --track -b b3 main &&\n    ++\ttest_cmp_config . branch.b3.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.b3.merge &&\n    ++\t# And --track=direct does as well\n    ++\tgit checkout --track=direct -b b4 main &&\n    ++\ttest_cmp_config . branch.b4.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.b4.merge\n     +'\n     +\n      test_done\n    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr\n     +\tgit switch -c foo-no-inherit foo &&\n     +\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n     +\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    -+\t# with autoSetupMerge=inherit, we copy tracking info from foo\n    -+\ttest_config branch.autoSetupMerge inherit &&\n    -+\tgit switch -c foo2 foo &&\n    ++\t# with --track=inherit, we copy tracking info from foo\n    ++\tgit switch --track=inherit -c foo2 foo &&\n     +\ttest_cmp_config origin branch.foo2.remote &&\n     +\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n    ++\t# with autoSetupMerge=inherit, we do the same\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\tgit switch -c foo3 foo &&\n    ++\ttest_cmp_config origin branch.foo3.remote &&\n    ++\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n    ++\t# with --track, we override autoSetupMerge\n    ++\tgit switch --track -c foo4 foo &&\n    ++\ttest_cmp_config . branch.foo4.remote &&\n    ++\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n    ++\t# and --track=direct does as well\n    ++\tgit switch --track=direct -c foo5 foo &&\n    ++\ttest_cmp_config . branch.foo5.remote &&\n    ++\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n     +\t# no tracking info to inherit from main\n     +\tgit switch -c main2 main &&\n     +\ttest -z \"$(git config branch.main2.remote)\" &&\n    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'\n      \t)\n      '\n      \n    ++test_expect_success 'tracking info copied with --track=inherit' '\n    ++\tgit branch --track=inherit foo2 my1 &&\n    ++\ttest_cmp_config local branch.foo2.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.foo2.merge\n    ++'\n    ++\n     +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n     +\ttest_unconfig branch.autoSetupMerge &&\n     +\t# default config does not copy tracking info\n    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'\n     +\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n     +\t# with autoSetupMerge=inherit, we copy tracking info from my1\n     +\ttest_config branch.autoSetupMerge inherit &&\n    -+\tgit branch foo2 my1 &&\n    -+\ttest_cmp_config local branch.foo2.remote &&\n    -+\ttest_cmp_config refs/heads/main branch.foo2.merge &&\n    ++\tgit branch foo3 my1 &&\n    ++\ttest_cmp_config local branch.foo3.remote &&\n    ++\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n     +\t# no tracking info to inherit from main\n     +\tgit branch main2 main &&\n     +\ttest -z \"$(git config branch.main2.remote)\" &&\n     +\ttest -z \"$(git config branch.main2.merge)\"\n     +'\n    ++\n    ++test_expect_success '--track overrides branch.autoSetupMerge' '\n    ++\ttest_config branch.autoSetupMerge inherit &&\n    ++\tgit branch --track=direct foo4 my1 &&\n    ++\ttest_cmp_config . branch.foo4.remote &&\n    ++\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n    ++\tgit branch --no-track foo5 my1 &&\n    ++\ttest -z \"$(git config branch.foo5.remote)\" &&\n    ++\ttest -z \"$(git config branch.foo5.merge)\"\n    ++'\n     +\n      test_done\n     \n\n Documentation/config/branch.txt |  3 ++-\n Documentation/git-branch.txt    | 21 ++++++++++++-------\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 37 ++++++++++++++++++++++++++++++++-\n branch.h                        |  3 ++-\n builtin/branch.c                |  6 ++++--\n builtin/checkout.c              |  6 ++++--\n config.c                        |  3 +++\n parse-options-cb.c              | 15 +++++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      |  7 +++++++\n t/t2027-checkout-track.sh       | 23 ++++++++++++++++++++\n t/t2060-switch.sh               | 28 +++++++++++++++++++++++++\n t/t3200-branch.sh               | 33 +++++++++++++++++++++++++++++\n t/t7201-co.sh                   | 17 +++++++++++++++\n 16 files changed, 192 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..e484a3803a 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,31 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..eef31f373d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,6 +126,39 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tstruct strbuf key = STRBUF_INIT;\n+\tchar *remote;\n+\tconst char *bare_ref;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n+\tif (git_config_get_string(key.buf, &remote)) {\n+\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n+\t\t\tbare_ref, key.buf);\n+\t\tstrbuf_release(&key);\n+\t\treturn -1;\n+\t}\n+\n+\tstrbuf_reset(&key);\n+\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n+\tif (git_config_get_string(key.buf, &tracking->src)) {\n+\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n+\t\t\tbare_ref, key.buf);\n+\t\tstrbuf_release(&key);\n+\t\tfree(remote);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = remote;\n+\ttracking->matches = 1;\n+\tstrbuf_release(&key);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -139,7 +172,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\tif (track != BRANCH_TRACK_INHERIT) {\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\t} else if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..6484bda8a2 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..15f70fe3fa 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..4bd5a18faf 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..b1a1ab797e 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,17 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `%s' expects \\\"direct\\\" or \\\"inherit\\\"\"),\n+\t\t\t     opt->long_name);\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..31fb64c5be 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout --orphan gamma &&\n \ttest -z \"$(git config branch.gamma.merge)\" &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest -z \"$(git config branch.eta.merge)\" &&\n+\ttest -z \"$(git config branch.eta.remote)\" &&\n+\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..e87d77f629 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\tgit config branch.main.remote origin &&\n+\tgit config branch.main.merge refs/heads/main &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..76e9d12e36 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..bc547b08e1 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest -z \"$(git config branch.foo5.remote)\" &&\n+\ttest -z \"$(git config branch.foo5.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..ae9f8d02c2 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.33.0.1079.g6e70778dc9-goog\n\n"},{"id":"438963","messageId":"xmqqee8k19or.fsf@gitster.g","threadId":"56468","inReplyTo":"YWun9j7L5uNlVHkW@google.com","subject":"Re: [PATCH v2] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-17T05:50:44Z","receivedAt":"2021-10-17T05:50:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Thanks for the feedback. I've added \"--track=direct\" and\n> \"--track=inherit\" flags in V3, which I'll send out shortly. I am a bit\n> skeptical of the value of having \"--track=direct\" when just \"--track\"\n> still works, but I'll leave it up to the list to decide.\n\nAh, I assumed that with the configuration variable set to inherit,\nyou'd make a command line --track to use the upstream of the fork\npoint branch as the upstream of the new branch, but if your\nintention is that (1) without --track or --no-track on the command\nline, if configuration is set to inherit, the new branch will track\nthe upstream of the original, and (2) with --track on the command\nline, the new branch will track the original without any \"inherit\"\nmagic, then I agree that there is no need for a way to explicitly\nchoose between --track={direct,inherit} from the command line.  The\nchoice would be, for those with remote.autosetupmerge=inherit, to\nuse --no-track (to not track anything), --track (to track directly\nthe original branch), or say nothing (to inherit tracking), and for\nthose without that, --no-track (to not track) and --track (to track\ndirectly) are the same but there is no way (other than \"git -c\nvar=val\") to do \"inherited tracking\".\n\nOne advantage of allowing to be explicit is that you do not have to\nremember (or know --- if you are visiting somebody else's repository\nto help them out) how remote.autosetupmerge is configured (or\nunconfigured).  From the command line, without having to worry about\nconfigured values to interfere, you can control which tracking mode\nis used.\n\n\n"},{"id":"438994","messageId":"kl6lwnmaz0gy.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"Re: [RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-10-18T17:50:21Z","receivedAt":"2021-10-18T17:50:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n> +{\n> +\tstruct strbuf key = STRBUF_INIT;\n> +\tchar *remote;\n> +\tconst char *bare_ref;\n> +\n> +\tbare_ref = orig_ref;\n> +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n> +\n> +\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n> +\tif (git_config_get_string(key.buf, &remote)) {\n> +\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n> +\t\t\tkey.buf);\n> +\t\tstrbuf_release(&key);\n> +\t\treturn 1;\n> +\t}\n> +\ttracking->remote = remote;\n> +\n> +\tstrbuf_reset(&key);\n> +\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n> +\tif (git_config_get_string(key.buf, &tracking->src)) {\n> +\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n> +\t\t\tkey.buf);\n> +\t\tstrbuf_release(&key);\n> +\t\treturn 1;\n> +\t}\n> +\n> +\ttracking->matches = 1;\n> +\tstrbuf_release(&key);\n> +\treturn 0;\n> +}\n\nI believe that we can get the branch remote via struct branch. Instead\nof reading the config, we could do something along the lines of:\n\n   int *explicit;\n   struct branch *branch = branch_get();\n   char *remote = remote_for_branch(branch, explicit);\n   /* Optionally check explicit if we don't want to fall back to\n    * \"origin\" */\n\nI'm not sure which is the idiomatic way to get the branch remote, feel\nfree to correct me.\n"},{"id":"438995","messageId":"kl6ltuheyzmk.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"kl6lwnmaz0gy.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-10-18T18:08:35Z","receivedAt":"2021-10-18T18:08:40Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> I believe that we can get the branch remote via struct branch. Instead\n> of reading the config, we could do something along the lines of:\n>\n>    int *explicit;\n>    struct branch *branch = branch_get();\n>    char *remote = remote_for_branch(branch, explicit);\n>    /* Optionally check explicit if we don't want to fall back to\n>     * \"origin\" */\n>\n> I'm not sure which is the idiomatic way to get the branch remote, feel\n> free to correct me.\n\nGah, I read and responded to v1 thinking it was v3. Reading v3, it looks\nlike the feedback is still relevant, so please pretend I responded to\nthe right email :P\n"},{"id":"438997","messageId":"87a6j6tbsv.fsf@gmgdl.gmail.com","threadId":"56468","inReplyTo":"b9356d9837479914bcf9a265f52afe170be7e2e2.1634445482.git.steadmon@google.com","subject":"Re: [PATCH v3] branch: add flags and config to inherit tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-18T18:31:58Z","receivedAt":"2021-10-18T18:42:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Oct 16 2021, Josh Steadmon wrote:\n\n> It can be helpful when creating a new branch to use the existing\n> tracking configuration from the branch point. However, there is\n> currently not a method to automatically do so.\n\nThere's no method to get *only* that config, but this use-case is why\nthe \"-c\" option (copy branch) was added.\n\nI haven't looked at this in any detail, but the seeming lack of mention\nof it in the commit message & docs makes me wonder if you missed that\nthat option could do what you wanted (but granted, it does a lot more,\nwhich maybe you don't want).\n\nBut in terms of implementation can't this share more code with the copy\nmode? I.e. I'd think that this would just be a limited mode of that,\nwhere we pass some whitelist of specific config to copy over, instead\nthe current \"all the config\" with \"copy\".\n\nAnd should these options be made to work together somehow? I.e. if you\nwant to copy branch A to B, but copy tracking info from C?\n\n> [...]\n>  -t::\n> ---track::\n> +--track [inherit|direct]::\n>  \tWhen creating a new branch, set up `branch.<name>.remote` and\n> -\t`branch.<name>.merge` configuration entries to mark the\n> -\tstart-point branch as \"upstream\" from the new branch. This\n> +\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n> +\tconfiguration for the new branch. This\n\nSetting up \".remote\" is what --tracke does, but doesn't it make sense\nfor such an option to copy over any other config related to that area,\ne.g. also .pushRemote, as a user may have edited it since the creation\nof the copied-from branch?\n\nMaybe, maybe not. But this & the above comparison with copy makes me\nwonder if we'd be better off with some mode similar to the matching\nregexes \"git config\", i.e. you could do a \"copy\" but only on a list of\nmatching variables.\n\nThen the --track mode could just be implemented in terms of that, no?\n"},{"id":"439008","messageId":"xmqqk0iauhwu.fsf@gitster.g","threadId":"56468","inReplyTo":"87a6j6tbsv.fsf@gmgdl.gmail.com","subject":"Re: [PATCH v3] branch: add flags and config to inherit tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-10-18T21:44:49Z","receivedAt":"2021-10-18T21:44:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Sat, Oct 16 2021, Josh Steadmon wrote:\n>\n>> It can be helpful when creating a new branch to use the existing\n>> tracking configuration from the branch point. However, there is\n>> currently not a method to automatically do so.\n>\n> There's no method to get *only* that config, but this use-case is why\n> the \"-c\" option (copy branch) was added.\n\nHmph, I doubt the claim about the original motivation behind \"-c\",\nbut it sure sounds like an interesting point of view.  The commit at\nthe tip, as well as configurations are copied, which is most of the\nway there, but I suspect that the --track=inherit is mostly to be\nused in the context of \"git checkout -b\" and the mention of \"branch\"\nis merely for simplicity of the description of this topic, no?  And\nyou cannot say \"git checkout --clone-branch original\" (yet).\n\nBut it is a very interesting way to twist the point of view.\n\n\n"},{"id":"439009","messageId":"87sfwyx8ux.fsf@gmgdl.gmail.com","threadId":"56468","inReplyTo":"xmqqk0iauhwu.fsf@gitster.g","subject":"Re: [PATCH v3] branch: add flags and config to inherit tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-10-18T22:11:54Z","receivedAt":"2021-10-18T22:32:12Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Oct 18 2021, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> On Sat, Oct 16 2021, Josh Steadmon wrote:\n>>\n>>> It can be helpful when creating a new branch to use the existing\n>>> tracking configuration from the branch point. However, there is\n>>> currently not a method to automatically do so.\n>>\n>> There's no method to get *only* that config, but this use-case is why\n>> the \"-c\" option (copy branch) was added.\n>\n> Hmph, I doubt the claim about the original motivation behind \"-c\",\n> but it sure sounds like an interesting point of view.\n\nWhile I didn't implement --copy, a co-worker of mine at the time did (in\n52d59cc6452 (branch: add a --copy (-c) option to go with --move (-m),\n2017-06-18)).\n\nHe wanted to try to submit a patch series to git.git, and came to me\nasking for ideas for things to work on. I suggested that he come up with\nsomething that had personally irked him, was lacking from his POV etc.\n\nAfter coming up blank there I suggested something to the effect of\n\"well, if you want to just hack on git.git, but can't come up with\nanything, I've got a pretty long personal TODO list in that area,\nso...\".\n\nThe feature is the result of that discussion & my mentoring it. So yes,\nit's useful for other things as a side-effect of scratching that\nparticular itch. But in terms of why we ended up with it that's the\nexact use-case it's intended for.\n\n> The commit at the tip, as well as configurations are copied, which is\n> most of the way there, but I suspect that the --track=inherit is\n> mostly to be used in the context of \"git checkout -b\" and the mention\n> of \"branch\" is merely for simplicity of the description of this topic,\n> no?  And you cannot say \"git checkout --clone-branch original\" (yet).\n\nYes, this has integration with \"git checkout\", but \"git branch --copy\"\ndoesn't.\n\nThe original iteration of \"git branch --copy\" that didn't make it into\ngit.git would switch you to the branch (so behave like checkout). So\nthere is a \"git checkout --clone-branch\" equivalent out there in the ML\narchive, it was just invoked via \"git branch\".\n\nI think at the time I advocated for having those \"checkout\" semantics,\nbut in retrospect I'm happy we didn't go with that, per the table I\nlater posted at [1]. That UX would be very confusing.\n\nBut we should have some way of doing \"copy config and switch\", and per\n[1] we don't have that yet.\n\nSo now with this patch we'll have \"copy remote config [and switch]\"? I'm\nnot saying that's a bad thing as as incremental step, but it might be\nworth thinking of the UX here more holistically. And Josh: I think some\nextension/update of that ASCII table I posted in[1] would be very useful\nto explain this change (and compare it to \"branch -c\").\n\n1. https://lore.kernel.org/git/877dkdwgfe.fsf@evledraar.gmail.com/\n"},{"id":"441168","messageId":"YZLX08noFD0MhmIf@google.com","threadId":"56468","inReplyTo":"xmqqee8k19or.fsf@gitster.g","subject":"Re: [PATCH v2] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-15T21:57:39Z","receivedAt":"2021-11-15T22:52:29Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.10.16 22:50, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > Thanks for the feedback. I've added \"--track=direct\" and\n> > \"--track=inherit\" flags in V3, which I'll send out shortly. I am a bit\n> > skeptical of the value of having \"--track=direct\" when just \"--track\"\n> > still works, but I'll leave it up to the list to decide.\n> \n> Ah, I assumed that with the configuration variable set to inherit,\n> you'd make a command line --track to use the upstream of the fork\n> point branch as the upstream of the new branch, but if your\n> intention is that (1) without --track or --no-track on the command\n> line, if configuration is set to inherit, the new branch will track\n> the upstream of the original, and (2) with --track on the command\n> line, the new branch will track the original without any \"inherit\"\n> magic, then I agree that there is no need for a way to explicitly\n> choose between --track={direct,inherit} from the command line.\n\nYes, I intend for this to be similar to `branch.autoSetupmerge=always`\nsuch that users who prefer this behavior can set it in their global\nconfigs and then not need to remember to pass --track=inherit on the\ncommand line.\n\n> The\n> choice would be, for those with remote.autosetupmerge=inherit, to\n> use --no-track (to not track anything), --track (to track directly\n> the original branch), or say nothing (to inherit tracking), and for\n> those without that, --no-track (to not track) and --track (to track\n> directly) are the same but there is no way (other than \"git -c\n> var=val\") to do \"inherited tracking\".\n> \n> One advantage of allowing to be explicit is that you do not have to\n> remember (or know --- if you are visiting somebody else's repository\n> to help them out) how remote.autosetupmerge is configured (or\n> unconfigured).  From the command line, without having to worry about\n> configured values to interfere, you can control which tracking mode\n> is used.\n\n\n"},{"id":"441172","messageId":"YZLUoi3aBxb9kFLz@google.com","threadId":"56468","inReplyTo":"kl6lwnmaz0gy.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [RFC PATCH] branch: add \"inherit\" option for branch.autoSetupMerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-15T21:44:02Z","receivedAt":"2021-11-15T23:03:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.10.18 10:50, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n> > +{\n> > +\tstruct strbuf key = STRBUF_INIT;\n> > +\tchar *remote;\n> > +\tconst char *bare_ref;\n> > +\n> > +\tbare_ref = orig_ref;\n> > +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n> > +\n> > +\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n> > +\tif (git_config_get_string(key.buf, &remote)) {\n> > +\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n> > +\t\t\tkey.buf);\n> > +\t\tstrbuf_release(&key);\n> > +\t\treturn 1;\n> > +\t}\n> > +\ttracking->remote = remote;\n> > +\n> > +\tstrbuf_reset(&key);\n> > +\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n> > +\tif (git_config_get_string(key.buf, &tracking->src)) {\n> > +\t\twarning(\"branch.autoSetupMerge=inherit, but could not find %s\",\n> > +\t\t\tkey.buf);\n> > +\t\tstrbuf_release(&key);\n> > +\t\treturn 1;\n> > +\t}\n> > +\n> > +\ttracking->matches = 1;\n> > +\tstrbuf_release(&key);\n> > +\treturn 0;\n> > +}\n> \n> I believe that we can get the branch remote via struct branch. Instead\n> of reading the config, we could do something along the lines of:\n> \n>    int *explicit;\n>    struct branch *branch = branch_get();\n>    char *remote = remote_for_branch(branch, explicit);\n>    /* Optionally check explicit if we don't want to fall back to\n>     * \"origin\" */\n> \n> I'm not sure which is the idiomatic way to get the branch remote, feel\n> free to correct me.\n\nWill fix in V4, thanks.\n"},{"id":"441191","messageId":"YZLduLeRlXKfPjtw@google.com","threadId":"56468","inReplyTo":"87a6j6tbsv.fsf@gmgdl.gmail.com","subject":"Re: [PATCH v3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-15T22:22:48Z","receivedAt":"2021-11-15T23:20:05Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.10.18 20:31, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Sat, Oct 16 2021, Josh Steadmon wrote:\n> \n> > It can be helpful when creating a new branch to use the existing\n> > tracking configuration from the branch point. However, there is\n> > currently not a method to automatically do so.\n> \n> There's no method to get *only* that config, but this use-case is why\n> the \"-c\" option (copy branch) was added.\n> \n> I haven't looked at this in any detail, but the seeming lack of mention\n> of it in the commit message & docs makes me wonder if you missed that\n> that option could do what you wanted (but granted, it does a lot more,\n> which maybe you don't want).\n\nIndeed, I did miss that option. Thank you for the pointer. I am\nconflicted about whether or not we want to copy all the branch\nconfiguration. Most of the options do seem useful to copy, but the\nexisting config values available for `branch.autoSetupMerge` are\nstrictly about setting up `branch.<name>.remote`, `branch.<name>.merge`,\nand `branch.<name>.rebase`. Adding a new value here that additionally\npulls in all the rest of the config may be confusing. Alternatively we\ncould add an entirely new option, but then its interaction with\n`branch.autoSetupMerge` would be confusing as well.\n\n> But in terms of implementation can't this share more code with the copy\n> mode? I.e. I'd think that this would just be a limited mode of that,\n> where we pass some whitelist of specific config to copy over, instead\n> the current \"all the config\" with \"copy\".\n\nI will look into the copy machinery and see what can be reused in V4.\n\n> And should these options be made to work together somehow? I.e. if you\n> want to copy branch A to B, but copy tracking info from C?\n\nI am skeptical of the benefit here, but I'm certainly willing to hear\narguments in favor.\n\nThe motivation for this series is for Git users (who are not necessarily\nGit experts) to have a simple config they can tune to make reduce\nfriction for the use case of having large repositories with many\nsubmodules (see Emily's discussion [1]). The idea is that we have many\npeople with a workflow where they'd have `submodule.recurse=true` and\n`branch.autoSetupMerge=inherit`. When they checkout a new branch in the\nsuperproject, branches would also be checked out in the submodules, and\nappropriate tracking information would also be inherited so that they\ncan later `git push` without having to manually configure tracking for\nevery submodule.\n\nThis would be a very common operation for these users, and should\ntherefore require as little friction as possible. While I can see use\ncases for your \"copy A to B but copy tracking from C\", it seems to me\nthat this would be a much less common situation, and is probably going\nto be needed by Git experts who are capable of setting this manually\nwithout relying on configs to make it the default behavior.\n\n\n[1]: https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/\n\n> > [...]\n> >  -t::\n> > ---track::\n> > +--track [inherit|direct]::\n> >  \tWhen creating a new branch, set up `branch.<name>.remote` and\n> > -\t`branch.<name>.merge` configuration entries to mark the\n> > -\tstart-point branch as \"upstream\" from the new branch. This\n> > +\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n> > +\tconfiguration for the new branch. This\n> \n> Setting up \".remote\" is what --tracke does, but doesn't it make sense\n> for such an option to copy over any other config related to that area,\n> e.g. also .pushRemote, as a user may have edited it since the creation\n> of the copied-from branch?\n\nYes, .pushRemote and .mergeOptions both seem like they'd be useful to\ncopy here.\n\n> Maybe, maybe not. But this & the above comparison with copy makes me\n> wonder if we'd be better off with some mode similar to the matching\n> regexes \"git config\", i.e. you could do a \"copy\" but only on a list of\n> matching variables.\n> \n> Then the --track mode could just be implemented in terms of that, no?\n\nUsing config matching to only copy portions of the branch config seems\noverkill to me. IMO it would be better to get agreement for which of the\nbranch.<name>.* variables to copy, and then use that consistently for\nall possible settings of `branch.autoSetupMerge` and\n`branch.autoSetupRebase`. If that allows us to reuse the existing copy\nmachinery, then so much the better.\n"},{"id":"441341","messageId":"7ad7507f183332cb2b5fdf2eb76fbbc9dd7199ef.1637085915.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v4] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-16T18:25:31Z","receivedAt":"2021-11-16T18:25:35Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\nI've addressed Glen's feedback from V3. However, this brings up a new\nissue that was not obvious before: \"branch.<name>.merge\" can be\nspecified more than once. On the other hand, the existing tracking setup\ncode supports only a single merge entry. For now I'm defaulting to use\nthe first merge entry listed in the branch struct, but I'm curious what\npeople think the best solution would be. This may be another point in\nfavor of Ævar's suggestion to reuse the copy-branch-config machinery.\n\nChanges since V3:\n* Use branch_get() instead of git_config_get_string() to look up branch\n  configuration.\n* Remove unnecessary string formatting in new error message in\n  parse-options-cb.c.\n\nRange-diff against v3:\n1:  b9356d9837 ! 1:  7ad7507f18 branch: add flags and config to inherit tracking\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n      \n     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n     +{\n    -+\tstruct strbuf key = STRBUF_INIT;\n    -+\tchar *remote;\n     +\tconst char *bare_ref;\n    ++\tstruct branch *branch;\n     +\n     +\tbare_ref = orig_ref;\n     +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n     +\n    -+\tstrbuf_addf(&key, \"branch.%s.remote\", bare_ref);\n    -+\tif (git_config_get_string(key.buf, &remote)) {\n    -+\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n    -+\t\t\tbare_ref, key.buf);\n    -+\t\tstrbuf_release(&key);\n    ++\tbranch = branch_get(bare_ref);\n    ++\tif (!branch->remote_name) {\n    ++\t\twarning(_(\"asked to inherit tracking from %s, but no remote is set\"),\n    ++\t\t\tbare_ref);\n     +\t\treturn -1;\n     +\t}\n     +\n    -+\tstrbuf_reset(&key);\n    -+\tstrbuf_addf(&key, \"branch.%s.merge\", bare_ref);\n    -+\tif (git_config_get_string(key.buf, &tracking->src)) {\n    -+\t\twarning(_(\"asked to inherit tracking from %s, but could not find %s\"),\n    -+\t\t\tbare_ref, key.buf);\n    -+\t\tstrbuf_release(&key);\n    -+\t\tfree(remote);\n    ++\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n    ++\t\twarning(_(\"asked to inherit tracking from %s, but no merge configuration is set\"),\n    ++\t\t\tbare_ref);\n     +\t\treturn -1;\n     +\t}\n     +\n    -+\ttracking->remote = remote;\n    ++\ttracking->remote = xstrdup(branch->remote_name);\n    ++\ttracking->src = xstrdup(branch->merge_name[0]);\n     +\ttracking->matches = 1;\n    -+\tstrbuf_release(&key);\n     +\treturn 0;\n     +}\n     +\n    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const\n     +\telse if (!strcmp(arg, \"inherit\"))\n     +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n     +\telse\n    -+\t\treturn error(_(\"option `%s' expects \\\"direct\\\" or \\\"inherit\\\"\"),\n    -+\t\t\t     opt->long_name);\n    ++\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n     +\n     +\treturn 0;\n     +}\n\n Documentation/config/branch.txt |  3 ++-\n Documentation/git-branch.txt    | 21 ++++++++++++++-------\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 31 ++++++++++++++++++++++++++++++-\n branch.h                        |  3 ++-\n builtin/branch.c                |  6 ++++--\n builtin/checkout.c              |  6 ++++--\n config.c                        |  3 +++\n parse-options-cb.c              | 14 ++++++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      |  7 +++++++\n t/t2027-checkout-track.sh       | 23 +++++++++++++++++++++++\n t/t2060-switch.sh               | 28 ++++++++++++++++++++++++++++\n t/t3200-branch.sh               | 33 +++++++++++++++++++++++++++++++++\n t/t7201-co.sh                   | 17 +++++++++++++++++\n 16 files changed, 185 insertions(+), 16 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..e484a3803a 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,31 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..f018e440a6 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -126,6 +126,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \treturn -1;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tconst char *bare_ref;\n+\tstruct branch *branch;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tbranch = branch_get(bare_ref);\n+\tif (!branch->remote_name) {\n+\t\twarning(_(\"asked to inherit tracking from %s, but no remote is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n+\t\twarning(_(\"asked to inherit tracking from %s, but no merge configuration is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = xstrdup(branch->remote_name);\n+\ttracking->src = xstrdup(branch->merge_name[0]);\n+\ttracking->matches = 1;\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\tif (track != BRANCH_TRACK_INHERIT) {\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\t} else if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..6484bda8a2 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..15f70fe3fa 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..4bd5a18faf 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..91261d8c41 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,16 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..31fb64c5be 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout --orphan gamma &&\n \ttest -z \"$(git config branch.gamma.merge)\" &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest -z \"$(git config branch.eta.merge)\" &&\n+\ttest -z \"$(git config branch.eta.remote)\" &&\n+\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..e87d77f629 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\tgit config branch.main.remote origin &&\n+\tgit config branch.main.merge refs/heads/main &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..76e9d12e36 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..bc547b08e1 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest -z \"$(git config branch.foo5.remote)\" &&\n+\ttest -z \"$(git config branch.foo5.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..ae9f8d02c2 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.34.0.rc1.387.gb447b232ab-goog\n\n"},{"id":"441375","messageId":"kl6lczmzipsf.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"7ad7507f183332cb2b5fdf2eb76fbbc9dd7199ef.1637085915.git.steadmon@google.com","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-11-17T00:33:52Z","receivedAt":"2021-11-17T00:34:02Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> I've addressed Glen's feedback from V3. However, this brings up a new\n> issue that was not obvious before: \"branch.<name>.merge\" can be\n> specified more than once. On the other hand, the existing tracking setup\n> code supports only a single merge entry.\n\nYes, for istance, install_branch_config() uses git_config_set_gently(),\nwhich will override duplicate values.\n\n> For now I'm defaulting to use the first merge entry listed in the\n> branch struct, but I'm curious what people think the best solution\n> would be.\n\nI can think of at least two possibilities:\n\nThe first would be to parse the information into our native data\nstructures. This is pretty much what you've done in v4, but insteaed of\ndefaulting to the first merge entry, we would iterate over all of the\npossible merge entries and...\n\n> @@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \n>  \tmemset(&tracking, 0, sizeof(tracking));\n>  \ttracking.spec.dst = (char *)orig_ref;\n> -\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\tif (track != BRANCH_TRACK_INHERIT) {\n> +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> +\t} else if (inherit_tracking(&tracking, orig_ref))\n>  \t\treturn;\n>  \n>  \tif (!tracking.matches)\n\nwe get rid of the assumption that we can use a single 'struct tracking'.\nwhen track=BRANCH_TRACK_INHERIT. Of course, this isn't as simple as\ncalling install_branch_config() repeatedly, because that would override\n\"branch.<name>.merge\" over and over.\n\n> This may be another point in favor of Ævar's suggestion to\n> reuse the copy-branch-config machinery.\n\nThis is the second option, which is pretty simple. Inheriting the branch\ntracking info is a matter of copying the config, which we already do\nwhen we copy branches in builtin/branch.c:\n\n\tstrbuf_addf(&oldsection, \"branch.%s\", interpreted_oldname);\n\tstrbuf_release(&oldref);\n\tstrbuf_addf(&newsection, \"branch.%s\", interpreted_newname);\n\tstrbuf_release(&newref);\n\tif (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)\n\t\tdie(_(\"Branch is renamed, but update of config-file failed\"));\n\nBetween these two options, I think the first is a better long-term\nsolution because I think that parsing the config into our own data\nstructures is generally less error-prone than operating directly on a\nfile (e.g. using the data structures was what made this bug obvious to\nus in the first place, using repo->config will handle multiple config\nfiles correctly). I don't see '--track=inherit' as being _that_\nconceptually similar to copying a branch; I see it as a different mode\nof tracking that just so happens to be implementable by copying some\nsections in the branch configuration.\n\nBut as a practical matter, I don't see any obviously terrible short-term\ndownsides to just copying the config. It's no less correct than our\nbranch copying logic and I'm afaid of introducing unintended\nconsequences by mucking around with install_branch_config().\n"},{"id":"441670","messageId":"xmqqbl2hw10h.fsf@gitster.g","threadId":"56468","inReplyTo":"7ad7507f183332cb2b5fdf2eb76fbbc9dd7199ef.1637085915.git.steadmon@google.com","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-11-18T22:29:50Z","receivedAt":"2021-11-18T22:29:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> I've addressed Glen's feedback from V3. However, this brings up a new\n> issue that was not obvious before: \"branch.<name>.merge\" can be\n> specified more than once. On the other hand, the existing tracking setup\n> code supports only a single merge entry. For now I'm defaulting to use\n> the first merge entry listed in the branch struct, but I'm curious what\n> people think the best solution would be. This may be another point in\n> favor of Ævar's suggestion to reuse the copy-branch-config machinery.\n\nOr we can extend \"existing tracking setup code\" to support multiple\nmerge sources.\n\nHow does the \"git pull\" machinery react to them, by the way?  I\nthink the original intention is to support pulling multiple branches\nfrom the (single) remote configured for the branch with a single\ninvocation of \"git pull\", creating an octopus merge, but does it\nstill work, or nobody uses such a crazy curiosity anymore and it was\nonce broken and left in non-working state ever since?  What I am\ndreaming here is if we can safely ignore all but one of them, taking\nthe usual \"last-one-wins\" rule, after some transition period.\n\n> +int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n> +\tif (unset)\n> +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n> +\telse if (!arg || !strcmp(arg, \"direct\"))\n> +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n> +\telse if (!strcmp(arg, \"inherit\"))\n> +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n> +\telse\n> +\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n\nAccording to recent discussion in another thread,\n\n\terror(_(\"option '--%s` expects '%s' or '%s'\"),\n\t\t\"track\", \"direct\", \"inherit\");\n\nwould be more translater friendly, as these three words are not\nsubject to translation?  I am not sure if it is really worth it,\nthough.\n\n"},{"id":"441697","messageId":"211119.86v90o4oqw.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"7ad7507f183332cb2b5fdf2eb76fbbc9dd7199ef.1637085915.git.steadmon@google.com","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-11-19T06:47:30Z","receivedAt":"2021-11-19T06:57:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 16 2021, Josh Steadmon wrote:\n\n> I've addressed Glen's feedback from V3. However, this brings up a new\n> issue that was not obvious before: \"branch.<name>.merge\" can be\n> specified more than once. On the other hand, the existing tracking setup\n> code supports only a single merge entry. For now I'm defaulting to use\n> the first merge entry listed in the branch struct, but I'm curious what\n> people think the best solution would be. This may be another point in\n> favor of Ævar's suggestion to reuse the copy-branch-config machinery.\n\nI haven't looked in any detail now at the \"should we copy the config?\"\nquestions. Just some quick comments/nits below:\n\n\n> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n> +{\n> +\tconst char *bare_ref;\n> +\tstruct branch *branch;\n> +\n> +\tbare_ref = orig_ref;\n> +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n> +\n> +\tbranch = branch_get(bare_ref);\n> +\tif (!branch->remote_name) {\n> +\t\twarning(_(\"asked to inherit tracking from %s, but no remote is set\"),\n> +\t\t\tbare_ref);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n> +\t\twarning(_(\"asked to inherit tracking from %s, but no merge configuration is set\"),\n> +\t\t\tbare_ref);\n\nShould quote ('%s') the %s in both here.\n\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttracking->remote = xstrdup(branch->remote_name);\n> +\ttracking->src = xstrdup(branch->merge_name[0]);\n> +\ttracking->matches = 1;\n> +\treturn 0;\n> +}\n> +\n>  /*\n>   * This is called when new_ref is branched off of orig_ref, and tries\n>   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> @@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \n>  \tmemset(&tracking, 0, sizeof(tracking));\n>  \ttracking.spec.dst = (char *)orig_ref;\n> -\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\tif (track != BRANCH_TRACK_INHERIT) {\n> +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> +\t} else if (inherit_tracking(&tracking, orig_ref))\n>  \t\treturn;\n\nStyle: Dangling braces, can just skip the braces here.\n\n> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tOPT__VERBOSE(&filter.verbose,\n>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n\nHrm, should we say \"git help pull\" here, on just not reference it at all\nand have a linkgit:git-pull[1]?\n\nOr maybe git-branch.txt and git-pull.txt should be including a template?\nAs we do with Documentation/rev-list-options.txt, then this\ncross-reference wouldn't be needed.\n\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n>  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n>  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n>  \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index b5d477919a..45dab414ea 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n>  {\n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n> +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n>  \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n>  \t\t\t   PARSE_OPT_NOCOMPLETE),\n>  \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n> diff --git a/config.c b/config.c\n> index cb4a8058bf..4bd5a18faf 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n>  \t\tif (value && !strcasecmp(value, \"always\")) {\n>  \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n>  \t\t\treturn 0;\n> +\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n> +\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n> +\t\t\treturn 0;\n>  \t\t}\n\nLooks like an existing issue, but we just document \"inherit\", not\n\"INHERIT\", \"iNhErIt\" etc. I.e. should it being strcasecmp()\nv.s. strcmp() be documented?\n\n> +\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n\nAlready commented-on by Junio.\n\n> +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n> +\t# Set up tracking config on main\n> +\tgit config branch.main.remote origin &&\n> +\tgit config branch.main.merge refs/heads/main &&\n> +\ttest_config branch.autoSetupMerge inherit &&\n> +\t# With --track=inherit, we copy the tracking config from main\n> +\tgit checkout --track=inherit -b b1 main &&\n> +\ttest_cmp_config origin branch.b1.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b1.merge &&\n> +\t# With branch.autoSetupMerge=inherit, we do the same\n> +\tgit checkout -b b2 main &&\n> +\ttest_cmp_config origin branch.b2.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b2.merge &&\n> +\t# But --track overrides this\n> +\tgit checkout --track -b b3 main &&\n> +\ttest_cmp_config . branch.b3.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b3.merge &&\n> +\t# And --track=direct does as well\n> +\tgit checkout --track=direct -b b4 main &&\n> +\ttest_cmp_config . branch.b4.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b4.merge\n> +'\n> +\n\nThis is the last test, we can use test_config instead of \"git config\"\nthere I think, i.e. it's not setting up config for subseuent tests.\n"},{"id":"442693","messageId":"YaaYyFhDrvEbenWa@google.com","threadId":"56468","inReplyTo":"211119.86v90o4oqw.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-30T21:34:00Z","receivedAt":"2021-11-30T21:34:10Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.11.19 07:47, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Nov 16 2021, Josh Steadmon wrote:\n> \n> > I've addressed Glen's feedback from V3. However, this brings up a new\n> > issue that was not obvious before: \"branch.<name>.merge\" can be\n> > specified more than once. On the other hand, the existing tracking setup\n> > code supports only a single merge entry. For now I'm defaulting to use\n> > the first merge entry listed in the branch struct, but I'm curious what\n> > people think the best solution would be. This may be another point in\n> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.\n> \n> I haven't looked in any detail now at the \"should we copy the config?\"\n> questions. Just some quick comments/nits below:\n\nThanks for the comments. They're all fixed in V5, which I'll be sending\nout soon.\n\n[snip]\n\n> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n> >  \t\tOPT__VERBOSE(&filter.verbose,\n> >  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n> >  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> > -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> > -\t\t\tBRANCH_TRACK_EXPLICIT),\n> > +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> > +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n> \n> Hrm, should we say \"git help pull\" here, on just not reference it at all\n> and have a linkgit:git-pull[1]?\n> \n> Or maybe git-branch.txt and git-pull.txt should be including a template?\n> As we do with Documentation/rev-list-options.txt, then this\n> cross-reference wouldn't be needed.\n\nYeah, there's nothing really helpful in git-pull(1) about \"--track\"\nthat's easily searchable (i.e. without reading it all straight through),\nso I just removed the pointer in the option help string, add added\nlinkgit:git-pull(1) and linkgit:git-config(1) to git-branch.txt.\n\nI briefly looked at writing a common template for both git-branch.txt\nand git-pull.txt but I feel like the git-pull discussion of tracking is\nso spread out in that doc that it would require a significant rewrite to\nmake a common template work.\n"},{"id":"442710","messageId":"YaagLkQOUia7RsYq@google.com","threadId":"56468","inReplyTo":"xmqqbl2hw10h.fsf@gitster.g","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-11-30T22:05:34Z","receivedAt":"2021-11-30T22:05:45Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.11.18 14:29, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > I've addressed Glen's feedback from V3. However, this brings up a new\n> > issue that was not obvious before: \"branch.<name>.merge\" can be\n> > specified more than once. On the other hand, the existing tracking setup\n> > code supports only a single merge entry. For now I'm defaulting to use\n> > the first merge entry listed in the branch struct, but I'm curious what\n> > people think the best solution would be. This may be another point in\n> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.\n> \n> Or we can extend \"existing tracking setup code\" to support multiple\n> merge sources.\n> \n> How does the \"git pull\" machinery react to them, by the way?  I\n> think the original intention is to support pulling multiple branches\n> from the (single) remote configured for the branch with a single\n> invocation of \"git pull\", creating an octopus merge, but does it\n> still work, or nobody uses such a crazy curiosity anymore and it was\n> once broken and left in non-working state ever since?  What I am\n> dreaming here is if we can safely ignore all but one of them, taking\n> the usual \"last-one-wins\" rule, after some transition period.\n\nIt does still work and creates an octopus merge. Tested as follows:\n\n$ git clone https://github.com/gitster/git\n$ cd git\n$ git config pull.rebase false\n$ git checkout -b test-branch v2.30.0\n$ cat >>.git/config <<EOF\n[branch \"test-branch\"]\n\tremote = origin\n\tmerge = refs/heads/master\n\tmerge = refs/heads/ab/ambiguous-object-name\n\tmerge = refs/heads/js/scalar\nEOF\n$ git pull\nFast-forwarding to: 4ef9e1ce4ac4ad79f99f5c5712146254b4cca530\nTrying simple merge with 352f8e8fcba4726340c946200149e6285c514fc0\nTrying simple merge with abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nSimple merge did not work, trying automatic merge.\nAuto-merging Makefile\nAuto-merging contrib/buildsystems/CMakeLists.txt\nMerge made by the 'octopus' strategy.\n[...]\n\n$ git cat-file commit HEAD\ntree 78bd62c62edfc6e51beb956e548b92b97210ddd4\nparent 4ef9e1ce4ac4ad79f99f5c5712146254b4cca530\nparent 352f8e8fcba4726340c946200149e6285c514fc0\nparent abe6bb3905392d5eb6b01fa6e54d7e784e0522aa\nauthor Josh Steadmon <josh@steadmon.net> 1638309707 -0800\ncommitter Josh Steadmon <josh@steadmon.net> 1638309707 -0800\n\nMerge branches 'ab/ambiguous-object-name', 'js/scalar' and 'master' of https://github.com/gitster/git into test-branch\n\n\n\n> > +int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n> > +\tif (unset)\n> > +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n> > +\telse if (!arg || !strcmp(arg, \"direct\"))\n> > +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n> > +\telse if (!strcmp(arg, \"inherit\"))\n> > +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n> > +\telse\n> > +\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n> \n> According to recent discussion in another thread,\n> \n> \terror(_(\"option '--%s` expects '%s' or '%s'\"),\n> \t\t\"track\", \"direct\", \"inherit\");\n> \n> would be more translater friendly, as these three words are not\n> subject to translation?  I am not sure if it is really worth it,\n> though.\n\nI don't really feel strongly either way, so I switched to the more\ntranslatable version. I was originally following some other examples in\nthe file, where it seemed that most strings that would require more than\none field expansion were just hardcoded instead.\n"},{"id":"442763","messageId":"211201.86tufsbsfc.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"YaaYyFhDrvEbenWa@google.com","subject":"Re: [PATCH v4] branch: add flags and config to inherit tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-01T09:11:03Z","receivedAt":"2021-12-01T09:13:46Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Nov 30 2021, Josh Steadmon wrote:\n\n> On 2021.11.19 07:47, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Tue, Nov 16 2021, Josh Steadmon wrote:\n>> \n>> > I've addressed Glen's feedback from V3. However, this brings up a new\n>> > issue that was not obvious before: \"branch.<name>.merge\" can be\n>> > specified more than once. On the other hand, the existing tracking setup\n>> > code supports only a single merge entry. For now I'm defaulting to use\n>> > the first merge entry listed in the branch struct, but I'm curious what\n>> > people think the best solution would be. This may be another point in\n>> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.\n>> \n>> I haven't looked in any detail now at the \"should we copy the config?\"\n>> questions. Just some quick comments/nits below:\n>\n> Thanks for the comments. They're all fixed in V5, which I'll be sending\n> out soon.\n>\n> [snip]\n\nThanks, happy that it helped.\n\n>> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>> >  \t\tOPT__VERBOSE(&filter.verbose,\n>> >  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>> >  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n>> > -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n>> > -\t\t\tBRANCH_TRACK_EXPLICIT),\n>> > +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n>> > +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n>> \n>> Hrm, should we say \"git help pull\" here, on just not reference it at all\n>> and have a linkgit:git-pull[1]?\n>> \n>> Or maybe git-branch.txt and git-pull.txt should be including a template?\n>> As we do with Documentation/rev-list-options.txt, then this\n>> cross-reference wouldn't be needed.\n>\n> Yeah, there's nothing really helpful in git-pull(1) about \"--track\"\n> that's easily searchable (i.e. without reading it all straight through),\n> so I just removed the pointer in the option help string, add added\n> linkgit:git-pull(1) and linkgit:git-config(1) to git-branch.txt.\n>\n> I briefly looked at writing a common template for both git-branch.txt\n> and git-pull.txt but I feel like the git-pull discussion of tracking is\n> so spread out in that doc that it would require a significant rewrite to\n> make a common template work.\n\n*nod*. I didn't look into if it was easy/doable, just a hint in case\nthat direction was fruitful. Makes sense.\n"},{"id":"443239","messageId":"cover.1638859949.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v5 0/2] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-07T07:12:06Z","receivedAt":"2021-12-07T07:12:14Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least\nslightly) prefer handling multiple upstream branches in the existing\ntracking setup, I've gone that direction rather than repurposing the\nbranch copy code. None of the other issues were controversial.\n\nIn this version, I'd appreciate feedback mainly on patch 1:\n* Is the combination of `git_config_set_gently()` +\n  `git_config_set_multivar_gently() the best way to write multiple\n  config entries for the same key?\n* Does the reorganization of the BRANCH_CONFIG_VERBOSE output make\n  things more readable, or less? Should I try to simplify the output\n  here so that we don't end up with so many translatable variants of the\n  same message?\n\nAlso, a question specifically for Junio: this will conflict with\ngc/branch-recurse-submodules; should I rebase on that, or wait till it\nhits next, or just ignore it for now?\n\nChanges since V4:\n* Add new patch (1/2) to refactor branch.c:install_branch_config() to\n  accept multiple upstream refs\n* When multiple upstream branches are set in the parent branch, inherit\n  them all, instead of just the first\n* Break out error string arguments for easier translation\n* Don't ignore case for values of branch.autosetupmerge\n* Move reference to git-pull out of usage string for --track into\n  git-branch.txt\n* Use test_config instead of `git config` in t2027\n* Style fixes: add single-quotes around warning string arguments, remove\n  unnecessary braces\n\nChanges since V3:\n* Use branch_get() instead of git_config_get_string() to look up branch\n  configuration.\n* Remove unnecessary string formatting in new error message in\n  parse-options-cb.c.\n\nJosh Steadmon (2):\n  branch: accept multiple upstream branches for tracking\n  branch: add flags and config to inherit tracking\n\n Documentation/config/branch.txt |   3 +-\n Documentation/git-branch.txt    |  24 +++--\n Documentation/git-checkout.txt  |   2 +-\n Documentation/git-switch.txt    |   2 +-\n branch.c                        | 169 ++++++++++++++++++++++++--------\n branch.h                        |   3 +-\n builtin/branch.c                |   6 +-\n builtin/checkout.c              |   6 +-\n config.c                        |   5 +-\n parse-options-cb.c              |  15 +++\n parse-options.h                 |   2 +\n t/t2017-checkout-orphan.sh      |   7 ++\n t/t2027-checkout-track.sh       |  23 +++++\n t/t2060-switch.sh               |  28 ++++++\n t/t3200-branch.sh               |  33 +++++++\n t/t7201-co.sh                   |  17 ++++\n 16 files changed, 289 insertions(+), 56 deletions(-)\n\nRange-diff against v4:\n-:  ---------- > 1:  ba7d557725 branch: accept multiple upstream branches for tracking\n1:  7ad7507f18 ! 2:  c7e4af9a36 branch: add flags and config to inherit tracking\n    @@ Documentation/git-branch.txt: This option is only applicable in non-verbose mode\n     +start-point is either a local or remote-tracking branch. Set it to\n     +`inherit` if you want to copy the tracking configuration from the\n     +branch point.\n    +++\n    ++See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on\n    ++how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n      \n      --no-track::\n      \tDo not set up \"upstream\" configuration, even if the\n    @@ Documentation/git-switch.txt: should result in deletion of the path).\n      \tdetails.\n     \n      ## branch.c ##\n    +@@\n    + \n    + struct tracking {\n    + \tstruct refspec_item spec;\n    +-\tchar *src;\n    ++\tstruct string_list *srcs;\n    + \tconst char *remote;\n    + \tint matches;\n    + };\n    +@@ branch.c: static int find_tracked_branch(struct remote *remote, void *priv)\n    + \n    + \tif (!remote_find_tracking(remote, &tracking->spec)) {\n    + \t\tif (++tracking->matches == 1) {\n    +-\t\t\ttracking->src = tracking->spec.src;\n    ++\t\t\tstring_list_append(tracking->srcs, tracking->spec.src);\n    + \t\t\ttracking->remote = remote->name;\n    + \t\t} else {\n    + \t\t\tfree(tracking->spec.src);\n    +-\t\t\tFREE_AND_NULL(tracking->src);\n    ++\t\t\tstring_list_clear(tracking->srcs, 0);\n    + \t\t}\n    + \t\ttracking->spec.src = NULL;\n    + \t}\n     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const\n    - \treturn -1;\n    + \tstring_list_clear(&remotes, 0);\n      }\n      \n     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n     +{\n     +\tconst char *bare_ref;\n     +\tstruct branch *branch;\n    ++\tint i;\n     +\n     +\tbare_ref = orig_ref;\n     +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n     +\n     +\tbranch = branch_get(bare_ref);\n     +\tif (!branch->remote_name) {\n    -+\t\twarning(_(\"asked to inherit tracking from %s, but no remote is set\"),\n    ++\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n     +\t\t\tbare_ref);\n     +\t\treturn -1;\n     +\t}\n     +\n     +\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n    -+\t\twarning(_(\"asked to inherit tracking from %s, but no merge configuration is set\"),\n    ++\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n     +\t\t\tbare_ref);\n     +\t\treturn -1;\n     +\t}\n     +\n     +\ttracking->remote = xstrdup(branch->remote_name);\n    -+\ttracking->src = xstrdup(branch->merge_name[0]);\n    ++\tfor (i = 0; i < branch->merge_nr; i++)\n    ++\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n     +\ttracking->matches = 1;\n     +\treturn 0;\n     +}\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n       * This is called when new_ref is branched off of orig_ref, and tries\n       * to infer the settings for branch.<new_ref>.{remote,merge} from the\n     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    + \t\t\t   enum branch_track track, int quiet)\n    + {\n    + \tstruct tracking tracking;\n    ++\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n    + \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n      \n      \tmemset(&tracking, 0, sizeof(tracking));\n      \ttracking.spec.dst = (char *)orig_ref;\n     -\tif (for_each_remote(find_tracked_branch, &tracking))\n    -+\tif (track != BRANCH_TRACK_INHERIT) {\n    ++\ttracking.srcs = &tracking_srcs;\n    ++\tif (track != BRANCH_TRACK_INHERIT)\n     +\t\tfor_each_remote(find_tracked_branch, &tracking);\n    -+\t} else if (inherit_tracking(&tracking, orig_ref))\n    ++\telse if (inherit_tracking(&tracking, orig_ref))\n      \t\treturn;\n      \n      \tif (!tracking.matches)\n    +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    + \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n    + \t\t    orig_ref);\n    + \n    +-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n    +-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n    ++\tif (tracking.srcs->nr < 1)\n    ++\t\tstring_list_append(tracking.srcs, orig_ref);\n    ++\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n    ++\t\t\t      tracking.srcs) < 0)\n    + \t\texit(-1);\n    + \n    +-\tfree(tracking.src);\n    ++\tstring_list_clear(tracking.srcs, 0);\n    + }\n    + \n    + int read_branch_desc(struct strbuf *buf, const char *branch_name)\n     \n      ## branch.h ##\n     @@ branch.h: enum branch_track {\n    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix\n     -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n     -\t\t\tBRANCH_TRACK_EXPLICIT),\n     +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n    -+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n    ++\t\t\tN_(\"set branch tracking configuration\"),\n     +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n     +\t\t\tparse_opt_tracking_mode),\n      \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n    @@ builtin/checkout.c: static struct option *add_common_switch_branch_options(\n      \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n     \n      ## config.c ##\n    -@@ config.c: static int git_default_branch_config(const char *var, const char *value)\n    - \t\tif (value && !strcasecmp(value, \"always\")) {\n    +@@ config.c: static int git_default_i18n_config(const char *var, const char *value)\n    + static int git_default_branch_config(const char *var, const char *value)\n    + {\n    + \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n    +-\t\tif (value && !strcasecmp(value, \"always\")) {\n    ++\t\tif (value && !strcmp(value, \"always\")) {\n      \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n      \t\t\treturn 0;\n    -+\t\t} else if (value && !strcasecmp(value, \"inherit\")) {\n    ++\t\t} else if (value && !strcmp(value, \"inherit\")) {\n     +\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n     +\t\t\treturn 0;\n      \t\t}\n    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const\n     +\telse if (!strcmp(arg, \"inherit\"))\n     +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n     +\telse\n    -+\t\treturn error(_(\"option `--track' expects \\\"direct\\\" or \\\"inherit\\\"\"));\n    ++\t\treturn error(_(\"option `%s' expects \\\"%s\\\" or \\\"%s\\\"\"),\n    ++\t\t\t     \"--track\", \"direct\", \"inherit\");\n     +\n     +\treturn 0;\n     +}\n    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e\n      \n     +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n     +\t# Set up tracking config on main\n    -+\tgit config branch.main.remote origin &&\n    -+\tgit config branch.main.merge refs/heads/main &&\n    ++\ttest_config branch.main.remote origin &&\n    ++\ttest_config branch.main.merge refs/heads/main &&\n     +\ttest_config branch.autoSetupMerge inherit &&\n     +\t# With --track=inherit, we copy the tracking config from main\n     +\tgit checkout --track=inherit -b b1 main &&\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.34.1.400.ga245620fadb-goog\n\n"},{"id":"443240","messageId":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1638859949.git.steadmon@google.com","subject":"[PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-07T07:12:07Z","receivedAt":"2021-12-07T07:12:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Add a new static variant of install_branch_config() that accepts\nmultiple remote branch names for tracking. This will be used in an\nupcoming commit that enables inheriting the tracking configuration from\na parent branch.\n\nCurrently, all callers of install_branch_config() pass only a single\nremote. Make install_branch_config() a small wrapper around\ninstall_branch_config_multiple_remotes() so that existing callers do not\nneed to be changed.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------\n 1 file changed, 87 insertions(+), 33 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..1aabef4de0 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -55,19 +55,24 @@ N_(\"\\n\"\n \"the remote tracking information by invoking\\n\"\n \"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n \n-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n+static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,\n+\t\tstruct string_list *remotes)\n {\n \tconst char *shortname = NULL;\n \tstruct strbuf key = STRBUF_INIT;\n-\tint rebasing = should_setup_rebase(origin);\n-\n-\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n-\t    && !strcmp(local, shortname)\n-\t    && !origin) {\n-\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n-\t\t\tlocal);\n-\t\treturn 0;\n-\t}\n+\tint i, rebasing = should_setup_rebase(origin);\n+\n+\tif (remotes->nr < 1)\n+\t\tBUG(\"must provide at least one remote for branch config\");\n+\n+\tif (!origin)\n+\t\tfor (i = 0; i < remotes->nr; i++)\n+\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)\n+\t\t\t    && !strcmp(local, shortname)) {\n+\t\t\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n+\t\t\t\t\tlocal);\n+\t\t\t\treturn 0;\n+\t\t\t}\n \n \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n@@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \n \tstrbuf_reset(&key);\n \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n-\tif (git_config_set_gently(key.buf, remote) < 0)\n+\t/*\n+\t * We want to overwrite any existing config with all the branches in\n+\t * \"remotes\". Override any existing config with the first branch, but if\n+\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n+\t * we've written so far.\n+\t */\n+\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n \t\tgoto out_err;\n+\tfor (i = 1; i < remotes->nr; i++)\n+\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n+\t\t\tgoto out_err;\n \n \tif (rebasing) {\n \t\tstrbuf_reset(&key);\n@@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \n \tif (flag & BRANCH_CONFIG_VERBOSE) {\n-\t\tif (shortname) {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n-\t\t\t\t\t  local, shortname, origin);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n-\t\t\t\t\t  local, shortname);\n+\t\tint plural = remotes->nr > 1;\n+\t\tint all_shortnames = 1;\n+\t\tconst char *msg_fmt;\n+\t\tstruct strbuf ref_string = STRBUF_INIT;\n+\n+\t\tfor (i = 0; i < remotes->nr; i++)\n+\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)) {\n+\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", shortname);\n+\t\t\t} else {\n+\t\t\t\tall_shortnames = 0;\n+\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", remotes->items[i].string);\n+\t\t\t}\n+\t\t/* The last two characters are an extraneous \", \", so trim those. */\n+\t\tstrbuf_setlen(&ref_string, ref_string.len - 2);\n+\n+\t\tif (all_shortnames && origin) {\n+\t\t\tif (rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n+\t\t\telse if (rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n+\t\t\telse if (!rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n+\t\t\telse if (!rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n+\n+\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n \t\t} else {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\tif (all_shortnames && !origin && rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s by rebasing.\";\n+\t\t\tif (all_shortnames && !origin && rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s by rebasing.\";\n+\t\t\tif (all_shortnames && !origin && !rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s.\";\n+\t\t\tif (all_shortnames && !origin && !rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s.\";\n+\t\t\tif (!all_shortnames && origin && rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s by rebasing.\";\n+\t\t\tif (!all_shortnames && origin && rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s by rebasing.\";\n+\t\t\tif (!all_shortnames && origin && !rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s.\";\n+\t\t\tif (!all_shortnames && origin && !rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s.\";\n+\t\t\tif (!all_shortnames && !origin && rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s by rebasing.\";\n+\t\t\tif (!all_shortnames && !origin && rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s by rebasing.\";\n+\t\t\tif (!all_shortnames && !origin && !rebasing && plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s.\";\n+\t\t\tif (!all_shortnames && !origin && !rebasing && !plural)\n+\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s.\";\n+\n+\t\t\tprintf_ln(_(msg_fmt), local, ref_string);\n \t\t}\n+\n+\t\tstrbuf_release(&ref_string);\n \t}\n \n \treturn 0;\n@@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tadvise(_(tracking_advice),\n \t       origin ? origin : \"\",\n \t       origin ? \"/\" : \"\",\n-\t       shortname ? shortname : remote);\n+\t       remotes->items[0].string);\n \n \treturn -1;\n }\n \n+int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n+\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n+\tstring_list_append(&remotes, remote);\n+\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n+\tstring_list_clear(&remotes, 0);\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n-- \n2.34.1.400.ga245620fadb-goog\n\n"},{"id":"443241","messageId":"c7e4af9a365cc61e682c3c73045fb1ee4b79cfbf.1638859949.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1638859949.git.steadmon@google.com","subject":"[PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-07T07:12:08Z","receivedAt":"2021-12-07T07:12:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Documentation/config/branch.txt |  3 +-\n Documentation/git-branch.txt    | 24 +++++++++++-----\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 49 ++++++++++++++++++++++++++++-----\n branch.h                        |  3 +-\n builtin/branch.c                |  6 ++--\n builtin/checkout.c              |  6 ++--\n config.c                        |  5 +++-\n parse-options-cb.c              | 15 ++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      |  7 +++++\n t/t2027-checkout-track.sh       | 23 ++++++++++++++++\n t/t2060-switch.sh               | 28 +++++++++++++++++++\n t/t3200-branch.sh               | 33 ++++++++++++++++++++++\n t/t7201-co.sh                   | 17 ++++++++++++\n 16 files changed, 202 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..c8b393e51c 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n++\n+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on\n+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex 1aabef4de0..8b17dd5c05 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -11,7 +11,7 @@\n \n struct tracking {\n \tstruct refspec_item spec;\n-\tchar *src;\n+\tstruct string_list *srcs;\n \tconst char *remote;\n \tint matches;\n };\n@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tif (++tracking->matches == 1) {\n-\t\t\ttracking->src = tracking->spec.src;\n+\t\t\tstring_list_append(tracking->srcs, tracking->spec.src);\n \t\t\ttracking->remote = remote->name;\n \t\t} else {\n \t\t\tfree(tracking->spec.src);\n-\t\t\tFREE_AND_NULL(tracking->src);\n+\t\t\tstring_list_clear(tracking->srcs, 0);\n \t\t}\n \t\ttracking->spec.src = NULL;\n \t}\n@@ -180,6 +180,35 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstring_list_clear(&remotes, 0);\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tconst char *bare_ref;\n+\tstruct branch *branch;\n+\tint i;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tbranch = branch_get(bare_ref);\n+\tif (!branch->remote_name) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = xstrdup(branch->remote_name);\n+\tfor (i = 0; i < branch->merge_nr; i++)\n+\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n+\ttracking->matches = 1;\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n {\n \tstruct tracking tracking;\n+\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\ttracking.srcs = &tracking_srcs;\n+\tif (track != BRANCH_TRACK_INHERIT)\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\telse if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\n@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n \t\t    orig_ref);\n \n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n+\tif (tracking.srcs->nr < 1)\n+\t\tstring_list_append(tracking.srcs, orig_ref);\n+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n+\t\t\t      tracking.srcs) < 0)\n \t\texit(-1);\n \n-\tfree(tracking.src);\n+\tstring_list_clear(tracking.srcs, 0);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..6484bda8a2 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..ebde5023c3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set branch tracking configuration\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..54c0e7d98e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1577,9 +1577,12 @@ static int git_default_i18n_config(const char *var, const char *value)\n static int git_default_branch_config(const char *var, const char *value)\n {\n \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n-\t\tif (value && !strcasecmp(value, \"always\")) {\n+\t\tif (value && !strcmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..306194206f 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,17 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `%s' expects \\\"%s\\\" or \\\"%s\\\"\"),\n+\t\t\t     \"--track\", \"direct\", \"inherit\");\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..31fb64c5be 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout --orphan gamma &&\n \ttest -z \"$(git config branch.gamma.merge)\" &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest -z \"$(git config branch.eta.merge)\" &&\n+\ttest -z \"$(git config branch.eta.remote)\" &&\n+\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..49c7def21c 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\ttest_config branch.main.remote origin &&\n+\ttest_config branch.main.merge refs/heads/main &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..76e9d12e36 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..bc547b08e1 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest -z \"$(git config branch.foo5.remote)\" &&\n+\ttest -z \"$(git config branch.foo5.merge)\"\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..ae9f8d02c2 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest -z \"$(git config branch.main2.remote)\" &&\n+\ttest -z \"$(git config branch.main2.merge)\"\n+'\n+\n test_done\n-- \n2.34.1.400.ga245620fadb-goog\n\n"},{"id":"443248","messageId":"211207.86mtlcpyu4.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-07T08:57:22Z","receivedAt":"2021-12-07T09:07:53Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Dec 06 2021, Josh Steadmon wrote:\n\n> Add a new static variant of install_branch_config() that accepts\n> multiple remote branch names for tracking. This will be used in an\n> upcoming commit that enables inheriting the tracking configuration from\n> a parent branch.\n>\n> Currently, all callers of install_branch_config() pass only a single\n> remote. Make install_branch_config() a small wrapper around\n> install_branch_config_multiple_remotes() so that existing callers do not\n> need to be changed.\n>\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------\n>  1 file changed, 87 insertions(+), 33 deletions(-)\n>\n> diff --git a/branch.c b/branch.c\n> index 7a88a4861e..1aabef4de0 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -55,19 +55,24 @@ N_(\"\\n\"\n>  \"the remote tracking information by invoking\\n\"\n>  \"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n>  \n> -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n> +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,\n> +\t\tstruct string_list *remotes)\n>  {\n>  \tconst char *shortname = NULL;\n>  \tstruct strbuf key = STRBUF_INIT;\n> -\tint rebasing = should_setup_rebase(origin);\n> -\n> -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> -\t    && !strcmp(local, shortname)\n> -\t    && !origin) {\n> -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> -\t\t\tlocal);\n> -\t\treturn 0;\n> -\t}\n> +\tint i, rebasing = should_setup_rebase(origin);\n> +\n> +\tif (remotes->nr < 1)\n> +\t\tBUG(\"must provide at least one remote for branch config\");\n\nSince it's unsigned IMO this would be clearer: if (!remotes->nr)\n\n> +\n> +\tif (!origin)\n> +\t\tfor (i = 0; i < remotes->nr; i++)\n> +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)\n\nFor this and others, since you don't use the [i] for anything except\ngetting the current item using for_each_string_list_item() would be\nbetter.\n\nPartially a nit, partially that I've got another WIP\nsoon-to-be-submitted topic to fix overflow bugs in that API, and not\nhaving \"int i\" etc. hardcoded in various places\nhelps. I.e. for_each_string_list_item() is future-proof.\n\n> +\t\t\t    && !strcmp(local, shortname)) {\n> +\t\t\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n\nBetter to quote '%s', also s/Not/not/ (lower-case) for all error/warning/die etc.\n\n> +\t\t\t\t\tlocal);\n> +\t\t\t\treturn 0;\n> +\t\t\t}\n>  \n>  \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n>  \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config with the first branch, but if\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n> +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n>  \t\tgoto out_err;\n> +\tfor (i = 1; i < remotes->nr; i++)\n> +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n>  \n>  \tif (rebasing) {\n>  \t\tstrbuf_reset(&key);\n> @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tstrbuf_release(&key);\n>  \n>  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> -\t\tif (shortname) {\n> -\t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> -\t\t\t\t\t  local, shortname, origin);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> -\t\t\t\t\t  local, shortname);\n> +\t\tint plural = remotes->nr > 1;\n\nThis....\n\n> +\t\tint all_shortnames = 1;\n> +\t\tconst char *msg_fmt;\n> +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> +\n> +\t\tfor (i = 0; i < remotes->nr; i++)\n> +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)) {\n> +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", shortname);\n> +\t\t\t} else {\n> +\t\t\t\tall_shortnames = 0;\n> +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", remotes->items[i].string);\n> +\t\t\t}\n> +\t\t/* The last two characters are an extraneous \", \", so trim those. */\n> +\t\tstrbuf_setlen(&ref_string, ref_string.len - 2);\n\nlanguages RTL in around way wrong the be to going is thing of sort This.\n\n:)\n\nWe deal with this in most other places by just formatting a list of\nbranches. E.g. in the ambiguous object output:\n\n    https://lore.kernel.org/git/patch-v5-4.6-36b6b440c37-20211125T215529Z-avarab@gmail.com/\n\n> +\n> +\t\tif (all_shortnames && origin) {\n> +\t\t\tif (rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n> +\t\t\telse if (rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n> +\t\t\telse if (!rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n> +\t\t\telse if (!rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n\n...and this is hardcoding plural rules used in English that don't apply\nin a lot of other languages...\n\n> +\n> +\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n>  \t\t} else {\n> -\t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> +\t\t\tif (all_shortnames && !origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s by rebasing.\";\n> +\t\t\tif (all_shortnames && !origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s by rebasing.\";\n> +\t\t\tif (all_shortnames && !origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s.\";\n> +\t\t\tif (all_shortnames && !origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s.\";\n> +\t\t\tif (!all_shortnames && origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s.\";\n> +\t\t\tif (!all_shortnames && origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s.\";\n> +\t\t\tif (!all_shortnames && !origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && !origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && !origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s.\";\n> +\t\t\tif (!all_shortnames && !origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s.\";\n\n...in English you've got one dog, then dogs, so == 1 and >1, but in\nvarious other languages it's:\n\n    git grep Plural-Forms -- po\n\nAnyway, this is easily solved, and even with less verbosity, see:\n\n    git grep -E -W '\\bQ_\\('\n\nFor examples of how to use the magic of libintl to do this for you.\n\n> +\n> +\t\t\tprintf_ln(_(msg_fmt), local, ref_string);\n\n...also s/Branch/branch/ for all of them/.\n\n>  \t\t}\n> +\n> +\t\tstrbuf_release(&ref_string);\n>  \t}\n>  \n>  \treturn 0;\n> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tadvise(_(tracking_advice),\n>  \t       origin ? origin : \"\",\n>  \t       origin ? \"/\" : \"\",\n> -\t       shortname ? shortname : remote);\n> +\t       remotes->items[0].string);\n>  \n>  \treturn -1;\n>  }\n>  \n> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n\nnit: overly long line..\n> +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n\nnit: an extra \\n after variable decls...\n\n> +\tstring_list_append(&remotes, remote);\n> +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n> +\tstring_list_clear(&remotes, 0);\n> +}\n> +\n>  /*\n>   * This is called when new_ref is branched off of orig_ref, and tries\n>   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n\n"},{"id":"443249","messageId":"211207.86ilw0pydg.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"c7e4af9a365cc61e682c3c73045fb1ee4b79cfbf.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-07T09:08:14Z","receivedAt":"2021-12-07T09:17:52Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Dec 06 2021, Josh Steadmon wrote:\n\n> It can be helpful when creating a new branch to use the existing\n> tracking configuration from the branch point. However, there is\n> currently not a method to automatically do so.\n>\n> Teach git-{branch,checkout,switch} an \"inherit\" argument to the\n> \"--track\" option. When this is set, creating a new branch will cause the\n> tracking configuration to default to the configuration of the branch\n> point, if set.\n>\n> For example, if branch \"main\" tracks \"origin/main\", and we run\n> `git checkout --track=inherit -b feature main`, then branch \"feature\"\n> will track \"origin/main\". Thus, `git status` will show us how far\n> ahead/behind we are from origin, and `git pull` will pull from origin.\n>\n> This is particularly useful when creating branches across many\n> submodules, such as with `git submodule foreach ...` (or if running with\n> a patch such as [1], which we use at $job), as it avoids having to\n> manually set tracking info for each submodule.\n>\n> Since we've added an argument to \"--track\", also add \"--track=direct\" as\n> another way to explicitly get the original \"--track\" behavior (\"--track\"\n> without an argument still works as well).\n> @@ -10,7 +10,8 @@ enum branch_track {\n>  \tBRANCH_TRACK_REMOTE,\n>  \tBRANCH_TRACK_ALWAYS,\n>  \tBRANCH_TRACK_EXPLICIT,\n> -\tBRANCH_TRACK_OVERRIDE\n> +\tBRANCH_TRACK_OVERRIDE,\n> +\tBRANCH_TRACK_INHERIT\n>  };\n\nSo we've got 5 items in this enum...\n\n>  \n>  extern enum branch_track git_branch_track;\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index b23b1d1752..ebde5023c3 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tOPT__VERBOSE(&filter.verbose,\n>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> +\t\t\tN_(\"set branch tracking configuration\"),\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n>  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n>  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n\nBut map --track, --track=direct --track=inherit to 3/5 of them. Will it\never make sense to do the oher 2/5 (I really haven't checked)....\n\n>  \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index b5d477919a..45dab414ea 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n>  {\n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n> +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n>  \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n>  \t\t\t   PARSE_OPT_NOCOMPLETE),\n>  \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n\nI wonder if this interface wouldn't be a lot simpler as:\n\n    --track\n    --track-explicit --track-direct --track-inherit\n\nBoth because it'll work better for auto-complete, and we can (and\npresumably some will want) just make --track mean whatever configured\n--track-THING you want.\n\nin any case, isn't there a NONEG missing here, or is --no-track-direct\netc. handled by OPT_CALLBACK_F() (I forget...).\n\n>  \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n> -\t\tif (value && !strcasecmp(value, \"always\")) {\n> +\t\tif (value && !strcmp(value, \"always\")) {\n\n...This probably makes sense, but it seems like the behavior change of\n\"let's not take this case-insensitive\" should be split up into its own\nchange...\n\n> +\ttest_must_fail git rev-parse --verify HEAD^ &&\n> +\tgit checkout main &&\n> +\tgit config branch.autosetupmerge inherit &&\n> +\tgit checkout --orphan eta &&\n> +\ttest -z \"$(git config branch.eta.merge)\" &&\n> +\ttest -z \"$(git config branch.eta.remote)\" &&\n\nBetter with the test_must_be_empty etc. helpers.\n\n> +\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n\nand this with test_cmp.\n\n(ditto occurances below)\n"},{"id":"443337","messageId":"xmqq4k7k8cz3.fsf@gitster.g","threadId":"56468","inReplyTo":"cover.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 0/2] branch: inherit tracking configs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T18:52:00Z","receivedAt":"2021-12-07T18:52:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least\n> slightly) prefer handling multiple upstream branches in the existing\n> tracking setup, I've gone that direction rather than repurposing the\n> branch copy code. None of the other issues were controversial.\n>\n> In this version, I'd appreciate feedback mainly on patch 1:\n> * Is the combination of `git_config_set_gently()` +\n>   `git_config_set_multivar_gently() the best way to write multiple\n>   config entries for the same key?\n\nIIRC git_config_set_*() is Dscho's brainchild.  If he is available\nto comment, it may be a valuable input.\n\n> * Does the reorganization of the BRANCH_CONFIG_VERBOSE output make\n>   things more readable, or less? Should I try to simplify the output\n>   here so that we don't end up with so many translatable variants of the\n>   same message?\n\nLet me find time to take a look.\n\n> Also, a question specifically for Junio: this will conflict with\n> gc/branch-recurse-submodules; should I rebase on that, or wait till it\n> hits next, or just ignore it for now?\n\nCan you two work out the preferred plan, taking relative importance,\npriority, and difficulty between the topics into account, and report\nto us how you want to proceed and why you chose the route once you\nare done?\n\nUnless the plan you two come up with is outrageously bad, such a\ndecision by stakeholders would be far more acceptable by the\ncommunity than going by my gut feeling.  In short, I'd prefer\ndecentralization ;-)\n\nHaving said that, I think this one is a simpler topic that is closer\nto become stable enough than the other one, so it could be that the\nrebases want to go the other direction.\n\nThanks.\n"},{"id":"443339","messageId":"xmqqk0gg6wqn.fsf@gitster.g","threadId":"56468","inReplyTo":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T19:28:00Z","receivedAt":"2021-12-07T19:28:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,\n> +\t\tstruct string_list *remotes)\n\nThe line got overly long so perhaps cut the line after \"*local,\",\nas \"origin\" and \"remotes\" conceptually are closer together.\n\nWhat is in the string list?  Names of refs at the remote \"origin\",\ninstead of a single ref there?\n\n>  {\n>  \tconst char *shortname = NULL;\n>  \tstruct strbuf key = STRBUF_INIT;\n> -\tint rebasing = should_setup_rebase(origin);\n> -\n> -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> -\t    && !strcmp(local, shortname)\n> -\t    && !origin) {\n> -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> -\t\t\tlocal);\n\nWhen 'origin' is NULL in the original caller, it means a local\ntracking, and making sure we do not say \"my 'master' branch builds\non top of itself\" makes sense.\n\n> -\t\treturn 0;\n> -\t}\n> +\tint i, rebasing = should_setup_rebase(origin);\n> +\n> +\tif (remotes->nr < 1)\n> +\t\tBUG(\"must provide at least one remote for branch config\");\n> +\n> +\tif (!origin)\n> +\t\tfor (i = 0; i < remotes->nr; i++)\n> +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)\n> +\t\t\t    && !strcmp(local, shortname)) {\n> +\t\t\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> +\t\t\t\t\tlocal);\n> +\t\t\t\treturn 0;\n\nI am a bit surprised with this warning and early return before\ninspecting the remainder of the list.  When 'origin' is NULL,\ni.e. we are talking about the local building on top of another local\nbranch, if the function is called for the local branch 'main' with\n'main' in the remotes list alone, we do want to issue the warning\nand exit without doing anything (i.e. degenerating to the original\nbehaviour of taking a single string variable, when a string list\nwith a single element is given).  But if the remotes list has 'main'\nand 'master', would we want to just \"skip\" the same one, but still\nhandle the other ones as if the \"same\" branch were not in the list?\n\n> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config with the first branch, but if\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n> +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n>  \t\tgoto out_err;\n> +\tfor (i = 1; i < remotes->nr; i++)\n> +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n>  \n>  \tif (rebasing) {\n>  \t\tstrbuf_reset(&key);\n> @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tstrbuf_release(&key);\n>  \n>  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> -\t\tif (shortname) {\n> -\t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> -\t\t\t\t\t  local, shortname, origin);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> -\t\t\t\t\t  local, shortname);\n> +\t\tint plural = remotes->nr > 1;\n> +\t\tint all_shortnames = 1;\n> +\t\tconst char *msg_fmt;\n> +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> +\n> +\t\tfor (i = 0; i < remotes->nr; i++)\n> +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)) {\n> +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", shortname);\n> +\t\t\t} else {\n> +\t\t\t\tall_shortnames = 0;\n> +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", remotes->items[i].string);\n\nSo, all_shortnames == true means everything was a local branch in\nthe 'origin' remote, and when it has a non-branch (like a tag),\nall_shortnames becomes false?\n\n> +\t\t\t}\n> +\t\t/* The last two characters are an extraneous \", \", so trim those. */\n> +\t\tstrbuf_setlen(&ref_string, ref_string.len - 2);\n\nAs you are starting from an empty ref_string, a more idiomatic way\nto build concatenated string would be to prefix when you add a new\nitem, e.g.\n\n\tloop {\n\t\tif (ref_string already has items)\n\t\t\tref_string.append(\", \");\n\t\tref_string.append(this_item);\n\t}\n\n> +\t\tif (all_shortnames && origin) {\n> +\t\t\tif (rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n\nWhat does it mean to keep my 'topic' branch up-to-date by rebasing\non top of more than one remote sources?  By merging, I can sort-of\nunderstand (i.e. creating an octopus), but would it make sense to\ntrack more than one remote sources in general?  Is it common?\n\nWhen the benefit is not clear, it might make more sense not to do\nthis when there are already multiple tracking sources defined for\nthe original; it might be a mistake that we may not want to spread\nwith the new option.\n\nOf course, it is very possible that I am missing a perfectly valid\nuse case where having more than one makes good sense.  If so, please\ndo not take the above comments as an objection, but adding some\ncomments before the function to explain when having remote list with\nmore than one items makes sense and how such a setting can be used\nto avoid future readers asking the same (stupid) question as I just\ndid.\n\n\n> +\t\t\telse if (rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n> +\t\t\telse if (!rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n> +\t\t\telse if (!rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n> +\n> +\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n\nI am not sure how well the \"plural\" thing works with i18n.  It may\nsuffice for the original in English to have only two choices between\none or more-than-one, but not all languages are English.  Counting\nthe actual number (I guess remotes->nr is it) and using Q_() to\nchoose between the possible variants.  I think Ævar knows about this\nmuch better than I do.\n\nBut if we are not doing this \"set multiple\" and instead go the\n\"detect existing multiple and refrain from spreading the damage\"\nroute, all of that is moot.\n\nThanks.\n"},{"id":"443341","messageId":"xmqq7dcg6w43.fsf@gitster.g","threadId":"56468","inReplyTo":"c7e4af9a365cc61e682c3c73045fb1ee4b79cfbf.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-07T19:41:32Z","receivedAt":"2021-12-07T19:41:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n>  --no-track::\n>  \tDo not set up \"upstream\" configuration, even if the\n> -\tbranch.autoSetupMerge configuration variable is true.\n> +\tbranch.autoSetupMerge configuration variable is set.\n\nI guess \"inherit\" is different from \"true\".\nNice to see an attention to the details.  \n\n> diff --git a/branch.h b/branch.h\n> index df0be61506..6484bda8a2 100644\n> --- a/branch.h\n> +++ b/branch.h\n> @@ -10,7 +10,8 @@ enum branch_track {\n>  \tBRANCH_TRACK_REMOTE,\n>  \tBRANCH_TRACK_ALWAYS,\n>  \tBRANCH_TRACK_EXPLICIT,\n> -\tBRANCH_TRACK_OVERRIDE\n> +\tBRANCH_TRACK_OVERRIDE,\n> +\tBRANCH_TRACK_INHERIT\n>  };\n\nUnless INHERIT must stay to be at the end of the enumeration even\nwhen we add more of these, let's leave a common at the end to help\nfuture developers.\n\n> -\t\tif (value && !strcasecmp(value, \"always\")) {\n> +\t\tif (value && !strcmp(value, \"always\")) {\n\nThis is belatedly correcting the mistake we made 5 years ago, which\ncan break existing users who used \"[branch]autosetupmerge=Always\" in\ntheir configuration files.\n\nI'm OK to fix it to accept only lowercase as written here, see if\nanybody screams, and tell them that the all-lowercase is the only\ndocumented way to spell this word.\n\n>  \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n>  \t\t\treturn 0;\n> +\t\t} else if (value && !strcmp(value, \"inherit\")) {\n> +\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n> +\t\t\treturn 0;\n>  \t\t}\n>  \t\tgit_branch_track = git_config_bool(var, value);\n>  \t\treturn 0;\n\nThanks.\n"},{"id":"443389","messageId":"kl6lmtlcar2j.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T00:16:52Z","receivedAt":"2021-12-08T00:16:58Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config with the first branch, but if\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n> +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n>  \t\tgoto out_err;\n> +\tfor (i = 1; i < remotes->nr; i++)\n> +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n\nI think that instead of overriding all config with the first value and\nthen appending every value after that, it'll be more obvious to readers\nif we first unset all of the config, then write every value (then the\ncomment wouldn't have to justify why we make two calls and iteration\nstarts at 1).\n\nI believe that unsetting all values for a key is supported by\ngit_config_set_multivar_gently() with value == NULL, i.e.\n\n  /*\n   * unset with value = NULL, not sure how this interacts with\n   * CONFIG_REGEX_NONE\n   */\n  if (git_config_set_multivar_gently(key.buf, NULL,\n    CONFIG_REGEX_NONE, 0))\n    goto out_err;\n\n  for_each_string_list_item(item, remotes) {\n    git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);\n  }\n\n> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tadvise(_(tracking_advice),\n>  \t       origin ? origin : \"\",\n>  \t       origin ? \"/\" : \"\",\n> -\t       shortname ? shortname : remote);\n> +\t       remotes->items[0].string);\n>  \n>  \treturn -1;\n>  }\n\nWhen there is more than one item in remotes->items, this advice is\n_technically_ incorrect because --set-upstream-to only takes a single\nupstream branch. I think that supporting multiple upstreams in\n--set-upstream-to is a fairly niche use case and is out of scope of this\nseries, so let's not pursue that option.\n\nAnother option would be to replace the mention of --set-upstream-to with\n\"git config add\", but that's unfriendly to the >90% of the user\npopulation that doesn't want multiple merge entries.\n\nIf we leave the advice as-is, even though it is misleading, a user who\nis sophisticated enough to set up multiple merge entries should also\nknow that --set-upstream-to won't solve their problems, and would\nprobably be able to fix their problems by mucking around with\n.git/config or git config.\n\nSo I think it is ok to not change the advice and to only mention the\nfirst merge item. However, it might be worth marking this as NEEDSWORK\nso that subsequent readers of this file understand that this advice is\noverly-simplistic and might be worth fixing.\n\n>  \n> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n> +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> +\tstring_list_append(&remotes, remote);\n> +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n> +\tstring_list_clear(&remotes, 0);\n> +}\n\nstring_list_clear() is being called after `return`.\n\nÆvar and Junio have commented on i18n, but I'm unfamiliar with that, so\nI won't comment on that :)\n"},{"id":"443390","messageId":"kl6llf0war1x.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T00:17:14Z","receivedAt":"2021-12-08T00:17:17Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config with the first branch, but if\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n> +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n>  \t\tgoto out_err;\n> +\tfor (i = 1; i < remotes->nr; i++)\n> +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n\nI think that instead of overriding all config with the first value and\nthen appending every value after that, it'll be more obvious to readers\nif we first unset all of the config, then write every value (then the\ncomment wouldn't have to justify why we make two calls and iteration\nstarts at 1).\n\nI believe that unsetting all values for a key is supported by\ngit_config_set_multivar_gently() with value == NULL, i.e.\n\n  /*\n   * unset with value = NULL, not sure how this interacts with\n   * CONFIG_REGEX_NONE\n   */\n  if (git_config_set_multivar_gently(key.buf, NULL,\n    CONFIG_REGEX_NONE, 0))\n    goto out_err;\n\n  for_each_string_list_item(item, remotes) {\n    git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);\n  }\n\n> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tadvise(_(tracking_advice),\n>  \t       origin ? origin : \"\",\n>  \t       origin ? \"/\" : \"\",\n> -\t       shortname ? shortname : remote);\n> +\t       remotes->items[0].string);\n>  \n>  \treturn -1;\n>  }\n\nWhen there is more than one item in remotes->items, this advice is\n_technically_ incorrect because --set-upstream-to only takes a single\nupstream branch. I think that supporting multiple upstreams in\n--set-upstream-to is a fairly niche use case and is out of scope of this\nseries, so let's not pursue that option.\n\nAnother option would be to replace the mention of --set-upstream-to with\n\"git config add\", but that's unfriendly to the >90% of the user\npopulation that doesn't want multiple merge entries.\n\nIf we leave the advice as-is, even though it is misleading, a user who\nis sophisticated enough to set up multiple merge entries should also\nknow that --set-upstream-to won't solve their problems, and would\nprobably be able to fix their problems by mucking around with\n.git/config or git config.\n\nSo I think it is ok to not change the advice and to only mention the\nfirst merge item. However, it might be worth marking this as NEEDSWORK\nso that subsequent readers of this file understand that this advice is\noverly-simplistic and might be worth fixing.\n\n>  \n> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n> +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> +\tstring_list_append(&remotes, remote);\n> +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n> +\tstring_list_clear(&remotes, 0);\n> +}\n\nstring_list_clear() is being called after `return`.\n\nÆvar and Junio have commented on i18n, but I'm unfamiliar with that, so\nI won't comment on that :)\n"},{"id":"443391","messageId":"kl6lilw0aq76.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"211207.86ilw0pydg.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T00:35:41Z","receivedAt":"2021-12-08T00:35:45Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n>> @@ -10,7 +10,8 @@ enum branch_track {\n>>  \tBRANCH_TRACK_REMOTE,\n>>  \tBRANCH_TRACK_ALWAYS,\n>>  \tBRANCH_TRACK_EXPLICIT,\n>> -\tBRANCH_TRACK_OVERRIDE\n>> +\tBRANCH_TRACK_OVERRIDE,\n>> +\tBRANCH_TRACK_INHERIT\n>>  };\n>\n> So we've got 5 items in this enum...\n>\n>>  \n>>  extern enum branch_track git_branch_track;\n>> diff --git a/builtin/branch.c b/builtin/branch.c\n>> index b23b1d1752..ebde5023c3 100644\n>> --- a/builtin/branch.c\n>> +++ b/builtin/branch.c\n>> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>>  \t\tOPT__VERBOSE(&filter.verbose,\n>>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n>> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n>> -\t\t\tBRANCH_TRACK_EXPLICIT),\n>> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n>> +\t\t\tN_(\"set branch tracking configuration\"),\n>> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n>> +\t\t\tparse_opt_tracking_mode),\n>>  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n>>  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n>\n> But map --track, --track=direct --track=inherit to 3/5 of them. Will it\n> ever make sense to do the oher 2/5 (I really haven't checked)....\n\nReasonable question, but I believe the answer is no, it doesn't make\nsense to map all the values:\n\n* BRANCH_TRACK_REMOTE is just a default value as far as I can tell (I\n  don't think this does anything?)\n* BRANCH_TRACK_ALWAYS behaves like BRANCH_TRACK_EXPLICIT but it's only\n  meant to be set from config files, see 9ed36cfa35 (branch: optionally\n  setup branch.*.merge from upstream local branches, 2008-02-19). We're\n  more lenient with _ALWAYS than with _EXPLICIT; e.g. we don't die()\n  when the upstream doesn't exist.\n\nEven one of the other options doesn't really make that much sense...\n\n* BRANCH_TRACK_OVERRIDE used to be used to implement --set-upstream, but\n  that's not necessary any more. Now it's used to make create_branch()\n  *not* create a branch sometimes, but that's going away if I get my\n  refactor of create_branch()\n  (https://lore.kernel.org/git/xmqq1r2pcnyw.fsf@gitster.g/T/#u) :)\n"},{"id":"443393","messageId":"kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"c7e4af9a365cc61e682c3c73045fb1ee4b79cfbf.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T01:02:20Z","receivedAt":"2021-12-08T01:02:24Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n> +{\n> +\tconst char *bare_ref;\n> +\tstruct branch *branch;\n> +\tint i;\n> +\n> +\tbare_ref = orig_ref;\n> +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n> +\n> +\tbranch = branch_get(bare_ref);\n> +\tif (!branch->remote_name) {\n> +\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n> +\t\t\tbare_ref);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n> +\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n> +\t\t\tbare_ref);\n> +\t\treturn -1;\n> +\t}\n> +\n> +\ttracking->remote = xstrdup(branch->remote_name);\n> +\tfor (i = 0; i < branch->merge_nr; i++)\n> +\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n> +\ttracking->matches = 1;\n> +\treturn 0;\n> +}\n\ntracking->matches is used to keep track of the number of matched remote\nrefs. I believe we set tracking->matches = 1 to fulfill two specific\nconditions in setup_tracking()...\n\n> +\n>  /*\n>   * This is called when new_ref is branched off of orig_ref, and tries\n>   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> @@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\t\t   enum branch_track track, int quiet)\n>  {\n>  \tstruct tracking tracking;\n> +\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n>  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n>  \n>  \tmemset(&tracking, 0, sizeof(tracking));\n>  \ttracking.spec.dst = (char *)orig_ref;\n> -\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\ttracking.srcs = &tracking_srcs;\n> +\tif (track != BRANCH_TRACK_INHERIT)\n> +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> +\telse if (inherit_tracking(&tracking, orig_ref))\n>  \t\treturn;\n>  \n>  \tif (!tracking.matches)\n\n*extra context*\n\tif (!tracking.matches)\n\t\tswitch (track) {\n\t\tcase BRANCH_TRACK_ALWAYS:\n\t\tcase BRANCH_TRACK_EXPLICIT:\n\t\tcase BRANCH_TRACK_OVERRIDE:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn;\n\t\t}\n\nFirst, tracking.matches > 0, because we want to do work if there are\nbranches to track.\n\nSecondly,\n\n*extra context*\n\tif (tracking.matches > 1)\n\t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n\t\t    orig_ref);\n\ntracking.matches <= 1, because we don't want to set up tracking if it's\nnot obvious what ref we want to track.\n\nBut as I understand it, BRANCH_TRACK_INHERIT should be unconditional, so\ninstead of fudging this behavior by setting the correct value for\ntracking.matches (which is meant for matching remote refs), we can just\ndo what the other unconditional BRANCH_TRACK_* options do, which is to\nto break instead of return, i.e.\n\n\tif (!tracking.matches)\n\t\tswitch (track) {\n\t\tcase BRANCH_TRACK_ALWAYS:\n\t\tcase BRANCH_TRACK_EXPLICIT:\n\t\tcase BRANCH_TRACK_OVERRIDE:\n+   case BRANCH_TRACK_INHERIT:\n\t\t\tbreak;\n\t\tdefault:\n\t\t\treturn;\n\t\t}\n\nand BRANCH_TRACK_INHERIT won't have to pretend that tracking.matches is\nmeaningful to it.\n\n>@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> \t\t    orig_ref);\n> \n>-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n>-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n>+\tif (tracking.srcs->nr < 1)\n>+\t\tstring_list_append(tracking.srcs, orig_ref);\n>+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n>+\t\t\t      tracking.srcs) < 0)\n> \t\texit(-1);\n> \n>-\tfree(tracking.src);\n>+\tstring_list_clear(tracking.srcs, 0);\n> }\n\nIt looks like install_branch_config_multiple_remotes() can just replace\ninstall_branch_config() in setup_tracking(), nice. This should make it\npretty easy for me to rebase gc/branch-recurse-submodules onto this.\n"},{"id":"443485","messageId":"kl6lbl1rauw3.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"xmqq4k7k8cz3.fsf@gitster.g","subject":"Re: [PATCH v5 0/2] branch: inherit tracking configs","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T17:06:36Z","receivedAt":"2021-12-08T17:06:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Can you two work out the preferred plan, taking relative importance,\n> priority, and difficulty between the topics into account, and report\n> to us how you want to proceed and why you chose the route once you\n> are done?\n>\n> Unless the plan you two come up with is outrageously bad, such a\n> decision by stakeholders would be far more acceptable by the\n> community than going by my gut feeling.  In short, I'd prefer\n> decentralization ;-)\n>\n> Having said that, I think this one is a simpler topic that is closer\n> to become stable enough than the other one, so it could be that the\n> rebases want to go the other direction.\n\nJosh and I have discussed this, and yes, we agree with your assessment.\n\nRebasing my changes on top of this is also easier from a dependency\nperspective because this series has a very obvious interface that I can\nuse.\n\nI'll send a re-roll soon. Thanks!\n"},{"id":"443547","messageId":"kl6lr1amac23.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"ba7d557725e70f2ae8f10ae5992c8168eb97f2fc.1638859949.git.steadmon@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-08T23:53:24Z","receivedAt":"2021-12-08T23:53:27Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"\nI noticed some test failures due to the printf_ln(msg_fmt) region. Since\nyou are reworking this, the problem might just go away, but I thought I\nshould mention it just in case.\n\n> +\t\tif (all_shortnames && origin) {\n> +\t\t\tif (rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n> +\t\t\telse if (rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n> +\t\t\telse if (!rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n> +\t\t\telse if (!rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n> +\n> +\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n\nHere \n\n>  \t\t} else {\n> -\t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> +\t\t\tif (all_shortnames && !origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s by rebasing.\";\n> +\t\t\tif (all_shortnames && !origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s by rebasing.\";\n> +\t\t\tif (all_shortnames && !origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s.\";\n> +\t\t\tif (all_shortnames && !origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s.\";\n> +\t\t\tif (!all_shortnames && origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s.\";\n> +\t\t\tif (!all_shortnames && origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s.\";\n> +\t\t\tif (!all_shortnames && !origin && rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && !origin && rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s by rebasing.\";\n> +\t\t\tif (!all_shortnames && !origin && !rebasing && plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s.\";\n> +\t\t\tif (!all_shortnames && !origin && !rebasing && !plural)\n> +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s.\";\n> +\n> +\t\t\tprintf_ln(_(msg_fmt), local, ref_string);\n\nand here\n\n>  \t\t}\n> +\n> +\t\tstrbuf_release(&ref_string);\n>  \t}\n\nWe print ref_string, which is a strbuf. This causes t/t3200-branch.sh to\nsegfault on my mac + clang, but inconsistently! With -O2, it doesn't\nalways segfault, but the wrong memory is read:\n\n  Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.\n\nWith -O0, it always segfaults.\n\nYou can see this in the osx-clang run in [1], but it looks like the gcc\nones refuse to build.\n\ns/ref_string/ref_string.buf should fix the problem.\n\n[1] https://github.com/chooglen/git/runs/4464134763?check_suite_focus=true\n"},{"id":"443548","messageId":"kl6lo85qabch.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"kl6lr1amac23.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T00:08:46Z","receivedAt":"2021-12-09T00:08:49Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to\n> segfault on my mac + clang, but inconsistently! With -O2, it doesn't\n> always segfault, but the wrong memory is read:\n>\n>   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.\n\nI forgot to mention this earlier but in this example, the test *passes*\neven though the stderr message is obviously wrong. I don't see any\ncoverage of the help message in t3200, which is a bit worrying to me.\n\nAfter this series is done, is it worth adding test coverage of the help\nmessage?\n"},{"id":"443712","messageId":"YbKHBsl7w1uNhLb6@google.com","threadId":"56468","inReplyTo":"kl6llf0war1x.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-09T22:45:26Z","receivedAt":"2021-12-09T22:45:35Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 16:17, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \n> >  \tstrbuf_reset(&key);\n> >  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> > -\tif (git_config_set_gently(key.buf, remote) < 0)\n> > +\t/*\n> > +\t * We want to overwrite any existing config with all the branches in\n> > +\t * \"remotes\". Override any existing config with the first branch, but if\n> > +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> > +\t * we've written so far.\n> > +\t */\n> > +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n> >  \t\tgoto out_err;\n> > +\tfor (i = 1; i < remotes->nr; i++)\n> > +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> > +\t\t\tgoto out_err;\n> \n> I think that instead of overriding all config with the first value and\n> then appending every value after that, it'll be more obvious to readers\n> if we first unset all of the config, then write every value (then the\n> comment wouldn't have to justify why we make two calls and iteration\n> starts at 1).\n> \n> I believe that unsetting all values for a key is supported by\n> git_config_set_multivar_gently() with value == NULL, i.e.\n> \n>   /*\n>    * unset with value = NULL, not sure how this interacts with\n>    * CONFIG_REGEX_NONE\n>    */\n>   if (git_config_set_multivar_gently(key.buf, NULL,\n>     CONFIG_REGEX_NONE, 0))\n>     goto out_err;\n> \n>   for_each_string_list_item(item, remotes) {\n>     git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);\n>   }\n\nFixed in V6, thanks for the suggestion.\n\n\n> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tadvise(_(tracking_advice),\n> >  \t       origin ? origin : \"\",\n> >  \t       origin ? \"/\" : \"\",\n> > -\t       shortname ? shortname : remote);\n> > +\t       remotes->items[0].string);\n> >  \n> >  \treturn -1;\n> >  }\n> \n> When there is more than one item in remotes->items, this advice is\n> _technically_ incorrect because --set-upstream-to only takes a single\n> upstream branch. I think that supporting multiple upstreams in\n> --set-upstream-to is a fairly niche use case and is out of scope of this\n> series, so let's not pursue that option.\n> \n> Another option would be to replace the mention of --set-upstream-to with\n> \"git config add\", but that's unfriendly to the >90% of the user\n> population that doesn't want multiple merge entries.\n> \n> If we leave the advice as-is, even though it is misleading, a user who\n> is sophisticated enough to set up multiple merge entries should also\n> know that --set-upstream-to won't solve their problems, and would\n> probably be able to fix their problems by mucking around with\n> .git/config or git config.\n> \n> So I think it is ok to not change the advice and to only mention the\n> first merge item. However, it might be worth marking this as NEEDSWORK\n> so that subsequent readers of this file understand that this advice is\n> overly-simplistic and might be worth fixing.\n\nSounds like we should just have separate advice strings for single vs.\nmultiple merge configs?\n\n\n> >  \n> > +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n> > +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> > +\tstring_list_append(&remotes, remote);\n> > +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n> > +\tstring_list_clear(&remotes, 0);\n> > +}\n> \n> string_list_clear() is being called after `return`.\n\nThat's an embarrassing bug, thank you for catching it.\n\n\n> Ævar and Junio have commented on i18n, but I'm unfamiliar with that, so\n> I won't comment on that :)\n"},{"id":"443714","messageId":"YbKH6CSmfBOsTajW@google.com","threadId":"56468","inReplyTo":"kl6lo85qabch.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-09T22:49:12Z","receivedAt":"2021-12-09T22:49:20Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.08 16:08, Glen Choo wrote:\n> Glen Choo <chooglen@google.com> writes:\n> \n> > We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to\n> > segfault on my mac + clang, but inconsistently! With -O2, it doesn't\n> > always segfault, but the wrong memory is read:\n> >\n> >   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.\n> \n> I forgot to mention this earlier but in this example, the test *passes*\n> even though the stderr message is obviously wrong. I don't see any\n> coverage of the help message in t3200, which is a bit worrying to me.\n> \n> After this series is done, is it worth adding test coverage of the help\n> message?\n\nYeah, I caught this earlier while reworking this section based on Ævar's\nreview, but thank you for pointing it out. I'm unsure about checking\nformatting of message strings in tests; it would certainly have caught\nthis bug but it seems that more often they're just \"change detectors\"\nrather than good tests. But I could be swayed if you or others feel it's\nimportant.\n"},{"id":"443715","messageId":"YbKLL2cQCxXQeQ5J@google.com","threadId":"56468","inReplyTo":"211207.86mtlcpyu4.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-09T23:03:11Z","receivedAt":"2021-12-09T23:03:21Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 09:57, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Dec 06 2021, Josh Steadmon wrote:\n> \n> > Add a new static variant of install_branch_config() that accepts\n> > multiple remote branch names for tracking. This will be used in an\n> > upcoming commit that enables inheriting the tracking configuration from\n> > a parent branch.\n> >\n> > Currently, all callers of install_branch_config() pass only a single\n> > remote. Make install_branch_config() a small wrapper around\n> > install_branch_config_multiple_remotes() so that existing callers do not\n> > need to be changed.\n> >\n> > Signed-off-by: Josh Steadmon <steadmon@google.com>\n> > ---\n> >  branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------\n> >  1 file changed, 87 insertions(+), 33 deletions(-)\n> >\n> > diff --git a/branch.c b/branch.c\n> > index 7a88a4861e..1aabef4de0 100644\n> > --- a/branch.c\n> > +++ b/branch.c\n> > @@ -55,19 +55,24 @@ N_(\"\\n\"\n> >  \"the remote tracking information by invoking\\n\"\n> >  \"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n> >  \n> > -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n> > +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,\n> > +\t\tstruct string_list *remotes)\n> >  {\n> >  \tconst char *shortname = NULL;\n> >  \tstruct strbuf key = STRBUF_INIT;\n> > -\tint rebasing = should_setup_rebase(origin);\n> > -\n> > -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> > -\t    && !strcmp(local, shortname)\n> > -\t    && !origin) {\n> > -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> > -\t\t\tlocal);\n> > -\t\treturn 0;\n> > -\t}\n> > +\tint i, rebasing = should_setup_rebase(origin);\n> > +\n> > +\tif (remotes->nr < 1)\n> > +\t\tBUG(\"must provide at least one remote for branch config\");\n> \n> Since it's unsigned IMO this would be clearer: if (!remotes->nr)\n\nFixed in v6.\n\n\n> > +\n> > +\tif (!origin)\n> > +\t\tfor (i = 0; i < remotes->nr; i++)\n> > +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)\n> \n> For this and others, since you don't use the [i] for anything except\n> getting the current item using for_each_string_list_item() would be\n> better.\n> \n> Partially a nit, partially that I've got another WIP\n> soon-to-be-submitted topic to fix overflow bugs in that API, and not\n> having \"int i\" etc. hardcoded in various places\n> helps. I.e. for_each_string_list_item() is future-proof.\n\nThanks. I somehow missed for_each_string_list_item() when I checked the\nheader file. Fixed in v6.\n\n> > +\t\t\t    && !strcmp(local, shortname)) {\n> > +\t\t\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> \n> Better to quote '%s', also s/Not/not/ (lower-case) for all error/warning/die etc.\n\nDone (for now) in v6, but this might become moot as I address Junio's\nreview comments.\n\n\n> > +\t\t\t\t\tlocal);\n> > +\t\t\t\treturn 0;\n> > +\t\t\t}\n> >  \n> >  \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n> >  \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \n> >  \tstrbuf_reset(&key);\n> >  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> > -\tif (git_config_set_gently(key.buf, remote) < 0)\n> > +\t/*\n> > +\t * We want to overwrite any existing config with all the branches in\n> > +\t * \"remotes\". Override any existing config with the first branch, but if\n> > +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> > +\t * we've written so far.\n> > +\t */\n> > +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n> >  \t\tgoto out_err;\n> > +\tfor (i = 1; i < remotes->nr; i++)\n> > +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> > +\t\t\tgoto out_err;\n> >  \n> >  \tif (rebasing) {\n> >  \t\tstrbuf_reset(&key);\n> > @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tstrbuf_release(&key);\n> >  \n> >  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> > -\t\tif (shortname) {\n> > -\t\t\tif (origin)\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> > -\t\t\t\t\t  local, shortname, origin);\n> > -\t\t\telse\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> > -\t\t\t\t\t  local, shortname);\n> > +\t\tint plural = remotes->nr > 1;\n> \n> This....\n> \n> > +\t\tint all_shortnames = 1;\n> > +\t\tconst char *msg_fmt;\n> > +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> > +\n> > +\t\tfor (i = 0; i < remotes->nr; i++)\n> > +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)) {\n> > +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", shortname);\n> > +\t\t\t} else {\n> > +\t\t\t\tall_shortnames = 0;\n> > +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", remotes->items[i].string);\n> > +\t\t\t}\n> > +\t\t/* The last two characters are an extraneous \", \", so trim those. */\n> > +\t\tstrbuf_setlen(&ref_string, ref_string.len - 2);\n> \n> languages RTL in around way wrong the be to going is thing of sort This.\n> \n> :)\n> \n> We deal with this in most other places by just formatting a list of\n> branches. E.g. in the ambiguous object output:\n> \n>     https://lore.kernel.org/git/patch-v5-4.6-36b6b440c37-20211125T215529Z-avarab@gmail.com/\n\nDone, thanks for the suggestion.\n\n> > +\n> > +\t\tif (all_shortnames && origin) {\n> > +\t\t\tif (rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n> > +\t\t\telse if (rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n> > +\t\t\telse if (!rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n> > +\t\t\telse if (!rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n> \n> ...and this is hardcoding plural rules used in English that don't apply\n> in a lot of other languages...\n> \n> > +\n> > +\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n> >  \t\t} else {\n> > -\t\t\tif (origin)\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> > -\t\t\t\t\t  local, remote);\n> > -\t\t\telse\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> > -\t\t\t\t\t  local, remote);\n> > +\t\t\tif (all_shortnames && !origin && rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s by rebasing.\";\n> > +\t\t\tif (all_shortnames && !origin && rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s by rebasing.\";\n> > +\t\t\tif (all_shortnames && !origin && !rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branches %s.\";\n> > +\t\t\tif (all_shortnames && !origin && !rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local branch %s.\";\n> > +\t\t\tif (!all_shortnames && origin && rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s by rebasing.\";\n> > +\t\t\tif (!all_shortnames && origin && rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s by rebasing.\";\n> > +\t\t\tif (!all_shortnames && origin && !rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote refs %s.\";\n> > +\t\t\tif (!all_shortnames && origin && !rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote ref %s.\";\n> > +\t\t\tif (!all_shortnames && !origin && rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s by rebasing.\";\n> > +\t\t\tif (!all_shortnames && !origin && rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s by rebasing.\";\n> > +\t\t\tif (!all_shortnames && !origin && !rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local refs %s.\";\n> > +\t\t\tif (!all_shortnames && !origin && !rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track local ref %s.\";\n> \n> ...in English you've got one dog, then dogs, so == 1 and >1, but in\n> various other languages it's:\n> \n>     git grep Plural-Forms -- po\n> \n> Anyway, this is easily solved, and even with less verbosity, see:\n> \n>     git grep -E -W '\\bQ_\\('\n> \n> For examples of how to use the magic of libintl to do this for you.\n\nThank you for the pointer. I looked specifically for dealing with plural\nforms in our docs, but the referenced \"Preparing Strings\" gettext docs\nwere not helpful for this. (Although I see now I should have read\nfurther in po/README.md to find the relevant advice).  I may send a\nseparate change to po/README.md to make it easier to find in the future.\n\n> > +\n> > +\t\t\tprintf_ln(_(msg_fmt), local, ref_string);\n> \n> ...also s/Branch/branch/ for all of them/.\n\nDone.\n\n> >  \t\t}\n> > +\n> > +\t\tstrbuf_release(&ref_string);\n> >  \t}\n> >  \n> >  \treturn 0;\n> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tadvise(_(tracking_advice),\n> >  \t       origin ? origin : \"\",\n> >  \t       origin ? \"/\" : \"\",\n> > -\t       shortname ? shortname : remote);\n> > +\t       remotes->items[0].string);\n> >  \n> >  \treturn -1;\n> >  }\n> >  \n> > +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {\n> \n> nit: overly long line..\n\nFixed.\n\n\n> > +\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n> \n> nit: an extra \\n after variable decls...\n\nFixed.\n\n\n> > +\tstring_list_append(&remotes, remote);\n> > +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n> > +\tstring_list_clear(&remotes, 0);\n> > +}\n> > +\n> >  /*\n> >   * This is called when new_ref is branched off of orig_ref, and tries\n> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> \n\n\nThanks for the review!\n"},{"id":"443718","messageId":"kl6l5yrx9wfv.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"YbKH6CSmfBOsTajW@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T23:43:00Z","receivedAt":"2021-12-09T23:43:04Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n>> > We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to\n>> > segfault on my mac + clang, but inconsistently! With -O2, it doesn't\n>> > always segfault, but the wrong memory is read:\n>> >\n>> >   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.\n>> \n>> I forgot to mention this earlier but in this example, the test *passes*\n>> even though the stderr message is obviously wrong. I don't see any\n>> coverage of the help message in t3200, which is a bit worrying to me.\n>> \n>> After this series is done, is it worth adding test coverage of the help\n>> message?\n>\n> Yeah, I caught this earlier while reworking this section based on Ævar's\n> review, but thank you for pointing it out. I'm unsure about checking\n> formatting of message strings in tests; it would certainly have caught\n> this bug but it seems that more often they're just \"change detectors\"\n> rather than good tests.\n\nI agree, although such a test might still be beneficial on the whole if\nthe change detector is easy to update.\n\n> But I could be swayed if you or others feel it's important.\n\nBecause this is rather \"change detector\"y, I don't think it's important\neither, but I'm also open to being convinced.\n"},{"id":"443719","messageId":"kl6l35n19w97.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"YbKHBsl7w1uNhLb6@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-09T23:47:00Z","receivedAt":"2021-12-09T23:47:06Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>> >  \tadvise(_(tracking_advice),\n>> >  \t       origin ? origin : \"\",\n>> >  \t       origin ? \"/\" : \"\",\n>> > -\t       shortname ? shortname : remote);\n>> > +\t       remotes->items[0].string);\n>> >  \n>> >  \treturn -1;\n>> >  }\n>> \n>> When there is more than one item in remotes->items, this advice is\n>> _technically_ incorrect because --set-upstream-to only takes a single\n>> upstream branch. I think that supporting multiple upstreams in\n>> --set-upstream-to is a fairly niche use case and is out of scope of this\n>> series, so let's not pursue that option.\n>> \n>> Another option would be to replace the mention of --set-upstream-to with\n>> \"git config add\", but that's unfriendly to the >90% of the user\n>> population that doesn't want multiple merge entries.\n>> \n>> If we leave the advice as-is, even though it is misleading, a user who\n>> is sophisticated enough to set up multiple merge entries should also\n>> know that --set-upstream-to won't solve their problems, and would\n>> probably be able to fix their problems by mucking around with\n>> .git/config or git config.\n>> \n>> So I think it is ok to not change the advice and to only mention the\n>> first merge item. However, it might be worth marking this as NEEDSWORK\n>> so that subsequent readers of this file understand that this advice is\n>> overly-simplistic and might be worth fixing.\n>\n> Sounds like we should just have separate advice strings for single vs.\n> multiple merge configs?\n\nThat sounds like a good idea if it's not too much work. Otherwise, a\nNEEDSWORK is still acceptable to me (but that said, I'm not an authority\non this matter).\n"},{"id":"443721","messageId":"211210.86mtl9e0ii.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"YbKLL2cQCxXQeQ5J@google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-10T01:00:36Z","receivedAt":"2021-12-10T01:01:31Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Dec 09 2021, Josh Steadmon wrote:\n\n> On 2021.12.07 09:57, Ævar Arnfjörð Bjarmason wrote:\n>> \n>> On Mon, Dec 06 2021, Josh Steadmon wrote:\n>> ...in English you've got one dog, then dogs, so == 1 and >1, but in\n>> various other languages it's:\n>> \n>>     git grep Plural-Forms -- po\n>> \n>> Anyway, this is easily solved, and even with less verbosity, see:\n>> \n>>     git grep -E -W '\\bQ_\\('\n>> \n>> For examples of how to use the magic of libintl to do this for you.\n>\n> Thank you for the pointer. I looked specifically for dealing with plural\n> forms in our docs, but the referenced \"Preparing Strings\" gettext docs\n> were not helpful for this. (Although I see now I should have read\n> further in po/README.md to find the relevant advice).  I may send a\n> separate change to po/README.md to make it easier to find in the future.\n\nThanks, that would be really helpful.\n\n>> > +\tstring_list_append(&remotes, remote);\n>> > +\treturn install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n>> > +\tstring_list_clear(&remotes, 0);\n>> > +}\n>> > +\n>> >  /*\n>> >   * This is called when new_ref is branched off of orig_ref, and tries\n>> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n>> \n>\n>\n> Thanks for the review!\n\nHappy to help, cheers!\n"},{"id":"443722","messageId":"211210.86ilvxe09w.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"kl6l35n19w97.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-10T01:03:35Z","receivedAt":"2021-12-10T01:06:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Dec 09 2021, Glen Choo wrote:\n\n> Josh Steadmon <steadmon@google.com> writes:\n>\n>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>>> >  \tadvise(_(tracking_advice),\n>>> >  \t       origin ? origin : \"\",\n>>> >  \t       origin ? \"/\" : \"\",\n>>> > -\t       shortname ? shortname : remote);\n>>> > +\t       remotes->items[0].string);\n>>> >  \n>>> >  \treturn -1;\n>>> >  }\n>>> \n>>> When there is more than one item in remotes->items, this advice is\n>>> _technically_ incorrect because --set-upstream-to only takes a single\n>>> upstream branch. I think that supporting multiple upstreams in\n>>> --set-upstream-to is a fairly niche use case and is out of scope of this\n>>> series, so let's not pursue that option.\n>>> \n>>> Another option would be to replace the mention of --set-upstream-to with\n>>> \"git config add\", but that's unfriendly to the >90% of the user\n>>> population that doesn't want multiple merge entries.\n>>> \n>>> If we leave the advice as-is, even though it is misleading, a user who\n>>> is sophisticated enough to set up multiple merge entries should also\n>>> know that --set-upstream-to won't solve their problems, and would\n>>> probably be able to fix their problems by mucking around with\n>>> .git/config or git config.\n>>> \n>>> So I think it is ok to not change the advice and to only mention the\n>>> first merge item. However, it might be worth marking this as NEEDSWORK\n>>> so that subsequent readers of this file understand that this advice is\n>>> overly-simplistic and might be worth fixing.\n>>\n>> Sounds like we should just have separate advice strings for single vs.\n>> multiple merge configs?\n>\n> That sounds like a good idea if it's not too much work. Otherwise, a\n> NEEDSWORK is still acceptable to me (but that said, I'm not an authority\n> on this matter).\n\nWe haven't used Q_() with advise() yet, but there's no reason not to:\n\n\tadvise(Q_(\"fix your branch by doing xyz\",\n\t\t  \"fix your branches by doing xyz\",\n                  branches_nr));\n"},{"id":"443826","messageId":"kl6lzgp88ixb.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"211210.86ilvxe09w.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-10T17:32:32Z","receivedAt":"2021-12-10T17:32:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Thu, Dec 09 2021, Glen Choo wrote:\n>\n>> Josh Steadmon <steadmon@google.com> writes:\n>>\n>>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>>>> >  \tadvise(_(tracking_advice),\n>>>> >  \t       origin ? origin : \"\",\n>>>> >  \t       origin ? \"/\" : \"\",\n>>>> > -\t       shortname ? shortname : remote);\n>>>> > +\t       remotes->items[0].string);\n>>>> >  \n>>>> >  \treturn -1;\n>>>> >  }\n>>>> \n>>>> When there is more than one item in remotes->items, this advice is\n>>>> _technically_ incorrect because --set-upstream-to only takes a single\n>>>> upstream branch. I think that supporting multiple upstreams in\n>>>> --set-upstream-to is a fairly niche use case and is out of scope of this\n>>>> series, so let's not pursue that option.\n>>>> \n>>>> Another option would be to replace the mention of --set-upstream-to with\n>>>> \"git config add\", but that's unfriendly to the >90% of the user\n>>>> population that doesn't want multiple merge entries.\n>>>> \n>>>> If we leave the advice as-is, even though it is misleading, a user who\n>>>> is sophisticated enough to set up multiple merge entries should also\n>>>> know that --set-upstream-to won't solve their problems, and would\n>>>> probably be able to fix their problems by mucking around with\n>>>> .git/config or git config.\n>>>> \n>>>> So I think it is ok to not change the advice and to only mention the\n>>>> first merge item. However, it might be worth marking this as NEEDSWORK\n>>>> so that subsequent readers of this file understand that this advice is\n>>>> overly-simplistic and might be worth fixing.\n>>>\n>>> Sounds like we should just have separate advice strings for single vs.\n>>> multiple merge configs?\n>>\n>> That sounds like a good idea if it's not too much work. Otherwise, a\n>> NEEDSWORK is still acceptable to me (but that said, I'm not an authority\n>> on this matter).\n>\n> We haven't used Q_() with advise() yet, but there's no reason not to:\n>\n> \tadvise(Q_(\"fix your branch by doing xyz\",\n> \t\t  \"fix your branches by doing xyz\",\n>                   branches_nr));\n\nNeat, that should do it in most cases. I think this one is a little\ntrickier because the plural advice messages requires constructing a\nlist, e.g.\n\nSingular: \n  \"\\n\"\n  \"After fixing the error cause you may try to fix up\\n\"\n  \"the remote tracking information by invoking\\n\"\n  \"\\\"git branch --set-upstream-to=%s%s%s\\\".\"\n\nPlural:\n  \"\\n\"\n  \"After fixing the error cause you may try to fix up\\n\"\n  \"the remote tracking information by invoking\\n\"\n  \"\\\"git config --add my_new_remote remote_name\\\"\n  \"\\\"git config --add my_new_upstream1 upstream_name1\\\"\n  \"\\\"git config --add my_new_upstream2 upstream_name2\\\"\n\nBut perhaps this is not too hard since you've already included examples\nof how to format lists of strings [1]\n\n[1] https://lore.kernel.org/git/211207.86mtlcpyu4.gmgdl@evledraar.gmail.com\n"},{"id":"443844","messageId":"nycvar.QRO.7.76.6.2112102344370.90@tvgsbejvaqbjf.bet","threadId":"56468","inReplyTo":"xmqq4k7k8cz3.fsf@gitster.g","subject":"Re: [PATCH v5 0/2] branch: inherit tracking configs","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2021-12-10T22:48:59Z","receivedAt":"2021-12-10T22:49:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Tue, 7 Dec 2021, Junio C Hamano wrote:\n\n> Josh Steadmon <steadmon@google.com> writes:\n>\n> > I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least\n> > slightly) prefer handling multiple upstream branches in the existing\n> > tracking setup, I've gone that direction rather than repurposing the\n> > branch copy code. None of the other issues were controversial.\n> >\n> > In this version, I'd appreciate feedback mainly on patch 1:\n> > * Is the combination of `git_config_set_gently()` +\n> >   `git_config_set_multivar_gently() the best way to write multiple\n> >   config entries for the same key?\n>\n> IIRC git_config_set_*() is Dscho's brainchild.  If he is available\n> to comment, it may be a valuable input.\n\nThe `git_config_set_multivar_gently()` function was really only intended\nto add one key/value pair.\n\nCurrently, there is no function to add multiple key/value pairs, and while\nit is slightly wasteful to lock the config multiple times to write a bunch\nof key/value pairs, it is not the worst in the world for a small use case\nlike this one.\n\nSo yes, for the moment I would go with the suggested design.\n\nOne thing you might want to do is to avoid the extra\n`git_config_set_gently()` before the `for` loop, simply by passing `i == 0\n? 0 : CONFIG_FLAGS_MULTI_REPLACE` as `flags` parameter to the multivar\nversion of the function.\n\nBut that would optimize for code size rather than for readability, and I\nwould actually prefer the more verbose version.\n\nCiao,\nDscho\n"},{"id":"443858","messageId":"211211.86ilvvc1zn.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"kl6lzgp88ixb.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-12-11T02:18:32Z","receivedAt":"2021-12-11T02:24:47Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Dec 10 2021, Glen Choo wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> On Thu, Dec 09 2021, Glen Choo wrote:\n>>\n>>> Josh Steadmon <steadmon@google.com> writes:\n>>>\n>>>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>>>>> >  \tadvise(_(tracking_advice),\n>>>>> >  \t       origin ? origin : \"\",\n>>>>> >  \t       origin ? \"/\" : \"\",\n>>>>> > -\t       shortname ? shortname : remote);\n>>>>> > +\t       remotes->items[0].string);\n>>>>> >  \n>>>>> >  \treturn -1;\n>>>>> >  }\n>>>>> \n>>>>> When there is more than one item in remotes->items, this advice is\n>>>>> _technically_ incorrect because --set-upstream-to only takes a single\n>>>>> upstream branch. I think that supporting multiple upstreams in\n>>>>> --set-upstream-to is a fairly niche use case and is out of scope of this\n>>>>> series, so let's not pursue that option.\n>>>>> \n>>>>> Another option would be to replace the mention of --set-upstream-to with\n>>>>> \"git config add\", but that's unfriendly to the >90% of the user\n>>>>> population that doesn't want multiple merge entries.\n>>>>> \n>>>>> If we leave the advice as-is, even though it is misleading, a user who\n>>>>> is sophisticated enough to set up multiple merge entries should also\n>>>>> know that --set-upstream-to won't solve their problems, and would\n>>>>> probably be able to fix their problems by mucking around with\n>>>>> .git/config or git config.\n>>>>> \n>>>>> So I think it is ok to not change the advice and to only mention the\n>>>>> first merge item. However, it might be worth marking this as NEEDSWORK\n>>>>> so that subsequent readers of this file understand that this advice is\n>>>>> overly-simplistic and might be worth fixing.\n>>>>\n>>>> Sounds like we should just have separate advice strings for single vs.\n>>>> multiple merge configs?\n>>>\n>>> That sounds like a good idea if it's not too much work. Otherwise, a\n>>> NEEDSWORK is still acceptable to me (but that said, I'm not an authority\n>>> on this matter).\n>>\n>> We haven't used Q_() with advise() yet, but there's no reason not to:\n>>\n>> \tadvise(Q_(\"fix your branch by doing xyz\",\n>> \t\t  \"fix your branches by doing xyz\",\n>>                   branches_nr));\n>\n> Neat, that should do it in most cases. I think this one is a little\n> trickier because the plural advice messages requires constructing a\n> list, e.g.\n>\n> Singular: \n>   \"\\n\"\n>   \"After fixing the error cause you may try to fix up\\n\"\n>   \"the remote tracking information by invoking\\n\"\n>   \"\\\"git branch --set-upstream-to=%s%s%s\\\".\"\n>\n> Plural:\n>   \"\\n\"\n>   \"After fixing the error cause you may try to fix up\\n\"\n>   \"the remote tracking information by invoking\\n\"\n>   \"\\\"git config --add my_new_remote remote_name\\\"\n>   \"\\\"git config --add my_new_upstream1 upstream_name1\\\"\n>   \"\\\"git config --add my_new_upstream2 upstream_name2\\\"\n>\n> But perhaps this is not too hard since you've already included examples\n> of how to format lists of strings [1]\n>\n> [1] https://lore.kernel.org/git/211207.86mtlcpyu4.gmgdl@evledraar.gmail.com\n\nYou don't need to use the plural facility for that sort of\nmessage.\n\nPlurals in translated messages are specifically for the case where you\nneed to compose a sentence like:\n\n    I had %d glasses of water with breakfast\n\nBut it's not needed for a message that can be rehrased as a heading\nfollowed by a list of 1 or more items, e.g.:\n\n    Things I've consumed in liquid form during breakfast this week:\n    - Water\n    - Tea\n    - Coffe\n\nIf that list stopped at \"Water\" it would be OK. Every language has some\nway of referring to items on a list in general terms, without knowing if\nthat list is composed of only one item, two etc.\n"},{"id":"444121","messageId":"Ybj//dzRfvyuPZOv@google.com","threadId":"56468","inReplyTo":"xmqqk0gg6wqn.fsf@gitster.g","subject":"Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T20:35:09Z","receivedAt":"2021-12-14T20:35:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 11:28, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,\n> > +\t\tstruct string_list *remotes)\n> \n> The line got overly long so perhaps cut the line after \"*local,\",\n> as \"origin\" and \"remotes\" conceptually are closer together.\n> \n> What is in the string list?  Names of refs at the remote \"origin\",\n> instead of a single ref there?\n\nAdded a comment explaining the purpose and arguments.\n\n\n> >  {\n> >  \tconst char *shortname = NULL;\n> >  \tstruct strbuf key = STRBUF_INIT;\n> > -\tint rebasing = should_setup_rebase(origin);\n> > -\n> > -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> > -\t    && !strcmp(local, shortname)\n> > -\t    && !origin) {\n> > -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> > -\t\t\tlocal);\n> \n> When 'origin' is NULL in the original caller, it means a local\n> tracking, and making sure we do not say \"my 'master' branch builds\n> on top of itself\" makes sense.\n> \n> > -\t\treturn 0;\n> > -\t}\n> > +\tint i, rebasing = should_setup_rebase(origin);\n> > +\n> > +\tif (remotes->nr < 1)\n> > +\t\tBUG(\"must provide at least one remote for branch config\");\n> > +\n> > +\tif (!origin)\n> > +\t\tfor (i = 0; i < remotes->nr; i++)\n> > +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)\n> > +\t\t\t    && !strcmp(local, shortname)) {\n> > +\t\t\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> > +\t\t\t\t\tlocal);\n> > +\t\t\t\treturn 0;\n> \n> I am a bit surprised with this warning and early return before\n> inspecting the remainder of the list.  When 'origin' is NULL,\n> i.e. we are talking about the local building on top of another local\n> branch, if the function is called for the local branch 'main' with\n> 'main' in the remotes list alone, we do want to issue the warning\n> and exit without doing anything (i.e. degenerating to the original\n> behaviour of taking a single string variable, when a string list\n> with a single element is given).  But if the remotes list has 'main'\n> and 'master', would we want to just \"skip\" the same one, but still\n> handle the other ones as if the \"same\" branch were not in the list?\n\nThe inheritance case when creating a new branch is the only time we get\nmultiple branches, and I think it is a sign of a wider misconfiguration\nif we have our own branch name listed as upstream in this case.\n\nWhen inheriting, `local` should always be a new branch, and `remotes`\nshould contain the `branch.<name>.merge` entries of the branch we're\ninheriting from. For this check to trigger would mean that the parent\nbranch has configured a local upstream branch that doesn't actually\nexist. So it seems that something has gone wrong; perhaps we can assume\nwhat the user wanted such as in your case above, but it seems to me that\nit's safer to warn when this happens.\n\n> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \n> >  \tstrbuf_reset(&key);\n> >  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> > -\tif (git_config_set_gently(key.buf, remote) < 0)\n> > +\t/*\n> > +\t * We want to overwrite any existing config with all the branches in\n> > +\t * \"remotes\". Override any existing config with the first branch, but if\n> > +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> > +\t * we've written so far.\n> > +\t */\n> > +\tif (git_config_set_gently(key.buf, remotes->items[0].string) < 0)\n> >  \t\tgoto out_err;\n> > +\tfor (i = 1; i < remotes->nr; i++)\n> > +\t\tif (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)\n> > +\t\t\tgoto out_err;\n> >  \n> >  \tif (rebasing) {\n> >  \t\tstrbuf_reset(&key);\n> > @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tstrbuf_release(&key);\n> >  \n> >  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> > -\t\tif (shortname) {\n> > -\t\t\tif (origin)\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> > -\t\t\t\t\t  local, shortname, origin);\n> > -\t\t\telse\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> > -\t\t\t\t\t  local, shortname);\n> > +\t\tint plural = remotes->nr > 1;\n> > +\t\tint all_shortnames = 1;\n> > +\t\tconst char *msg_fmt;\n> > +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> > +\n> > +\t\tfor (i = 0; i < remotes->nr; i++)\n> > +\t\t\tif (skip_prefix(remotes->items[i].string, \"refs/heads/\", &shortname)) {\n> > +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", shortname);\n> > +\t\t\t} else {\n> > +\t\t\t\tall_shortnames = 0;\n> > +\t\t\t\tstrbuf_addf(&ref_string, \"'%s', \", remotes->items[i].string);\n> \n> So, all_shortnames == true means everything was a local branch in\n> the 'origin' remote, and when it has a non-branch (like a tag),\n> all_shortnames becomes false?\n> \n> > +\t\t\t}\n> > +\t\t/* The last two characters are an extraneous \", \", so trim those. */\n> > +\t\tstrbuf_setlen(&ref_string, ref_string.len - 2);\n> \n> As you are starting from an empty ref_string, a more idiomatic way\n> to build concatenated string would be to prefix when you add a new\n> item, e.g.\n> \n> \tloop {\n> \t\tif (ref_string already has items)\n> \t\t\tref_string.append(\", \");\n> \t\tref_string.append(this_item);\n> \t}\n\nAck, although changes to address other review feedback has made this\npoint moot.\n\n\n> > +\t\tif (all_shortnames && origin) {\n> > +\t\t\tif (rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s' by rebasing.\";\n> \n> What does it mean to keep my 'topic' branch up-to-date by rebasing\n> on top of more than one remote sources?  By merging, I can sort-of\n> understand (i.e. creating an octopus), but would it make sense to\n> track more than one remote sources in general?  Is it common?\n> \n> When the benefit is not clear, it might make more sense not to do\n> this when there are already multiple tracking sources defined for\n> the original; it might be a mistake that we may not want to spread\n> with the new option.\n> \n> Of course, it is very possible that I am missing a perfectly valid\n> use case where having more than one makes good sense.  If so, please\n> do not take the above comments as an objection, but adding some\n> comments before the function to explain when having remote list with\n> more than one items makes sense and how such a setting can be used\n> to avoid future readers asking the same (stupid) question as I just\n> did.\n\nNo, that's an oversight on my part. This will now exit with an error if\nwe try to rebase onto multiple branches.\n\n\n> > +\t\t\telse if (rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s' by rebasing.\";\n> > +\t\t\telse if (!rebasing && plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branches %s from '%s'.\";\n> > +\t\t\telse if (!rebasing && !plural)\n> > +\t\t\t\tmsg_fmt = \"Branch '%s' set up to track remote branch %s from '%s'.\";\n> > +\n> > +\t\t\tprintf_ln(_(msg_fmt), local, ref_string, origin);\n> \n> I am not sure how well the \"plural\" thing works with i18n.  It may\n> suffice for the original in English to have only two choices between\n> one or more-than-one, but not all languages are English.  Counting\n> the actual number (I guess remotes->nr is it) and using Q_() to\n> choose between the possible variants.  I think Ævar knows about this\n> much better than I do.\n> \n> But if we are not doing this \"set multiple\" and instead go the\n> \"detect existing multiple and refrain from spreading the damage\"\n> route, all of that is moot.\n> \n> Thanks.\n\nThanks for the feedback!\n"},{"id":"444122","messageId":"YbkAfk1r/ONeYyIN@google.com","threadId":"56468","inReplyTo":"xmqq7dcg6w43.fsf@gitster.g","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T20:37:18Z","receivedAt":"2021-12-14T20:37:27Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 11:41, Junio C Hamano wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> >  --no-track::\n> >  \tDo not set up \"upstream\" configuration, even if the\n> > -\tbranch.autoSetupMerge configuration variable is true.\n> > +\tbranch.autoSetupMerge configuration variable is set.\n> \n> I guess \"inherit\" is different from \"true\".\n> Nice to see an attention to the details.  \n> \n> > diff --git a/branch.h b/branch.h\n> > index df0be61506..6484bda8a2 100644\n> > --- a/branch.h\n> > +++ b/branch.h\n> > @@ -10,7 +10,8 @@ enum branch_track {\n> >  \tBRANCH_TRACK_REMOTE,\n> >  \tBRANCH_TRACK_ALWAYS,\n> >  \tBRANCH_TRACK_EXPLICIT,\n> > -\tBRANCH_TRACK_OVERRIDE\n> > +\tBRANCH_TRACK_OVERRIDE,\n> > +\tBRANCH_TRACK_INHERIT\n> >  };\n> \n> Unless INHERIT must stay to be at the end of the enumeration even\n> when we add more of these, let's leave a common at the end to help\n> future developers.\n\nFixed in V6.\n\n\n> > -\t\tif (value && !strcasecmp(value, \"always\")) {\n> > +\t\tif (value && !strcmp(value, \"always\")) {\n> \n> This is belatedly correcting the mistake we made 5 years ago, which\n> can break existing users who used \"[branch]autosetupmerge=Always\" in\n> their configuration files.\n> \n> I'm OK to fix it to accept only lowercase as written here, see if\n> anybody screams, and tell them that the all-lowercase is the only\n> documented way to spell this word.\n> \n> >  \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n> >  \t\t\treturn 0;\n> > +\t\t} else if (value && !strcmp(value, \"inherit\")) {\n> > +\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n> > +\t\t\treturn 0;\n> >  \t\t}\n> >  \t\tgit_branch_track = git_config_bool(var, value);\n> >  \t\treturn 0;\n> \n> Thanks.\n"},{"id":"444127","messageId":"YbkWY9cI/fZMMbPa@google.com","threadId":"56468","inReplyTo":"kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T22:10:43Z","receivedAt":"2021-12-14T22:10:52Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 17:02, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n> > +{\n> > +\tconst char *bare_ref;\n> > +\tstruct branch *branch;\n> > +\tint i;\n> > +\n> > +\tbare_ref = orig_ref;\n> > +\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n> > +\n> > +\tbranch = branch_get(bare_ref);\n> > +\tif (!branch->remote_name) {\n> > +\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n> > +\t\t\tbare_ref);\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n> > +\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n> > +\t\t\tbare_ref);\n> > +\t\treturn -1;\n> > +\t}\n> > +\n> > +\ttracking->remote = xstrdup(branch->remote_name);\n> > +\tfor (i = 0; i < branch->merge_nr; i++)\n> > +\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n> > +\ttracking->matches = 1;\n> > +\treturn 0;\n> > +}\n> \n> tracking->matches is used to keep track of the number of matched remote\n> refs. I believe we set tracking->matches = 1 to fulfill two specific\n> conditions in setup_tracking()...\n> \n> > +\n> >  /*\n> >   * This is called when new_ref is branched off of orig_ref, and tries\n> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the\n> > @@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> >  \t\t\t   enum branch_track track, int quiet)\n> >  {\n> >  \tstruct tracking tracking;\n> > +\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n> >  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> >  \n> >  \tmemset(&tracking, 0, sizeof(tracking));\n> >  \ttracking.spec.dst = (char *)orig_ref;\n> > -\tif (for_each_remote(find_tracked_branch, &tracking))\n> > +\ttracking.srcs = &tracking_srcs;\n> > +\tif (track != BRANCH_TRACK_INHERIT)\n> > +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> > +\telse if (inherit_tracking(&tracking, orig_ref))\n> >  \t\treturn;\n> >  \n> >  \tif (!tracking.matches)\n> \n> *extra context*\n> \tif (!tracking.matches)\n> \t\tswitch (track) {\n> \t\tcase BRANCH_TRACK_ALWAYS:\n> \t\tcase BRANCH_TRACK_EXPLICIT:\n> \t\tcase BRANCH_TRACK_OVERRIDE:\n> \t\t\tbreak;\n> \t\tdefault:\n> \t\t\treturn;\n> \t\t}\n> \n> First, tracking.matches > 0, because we want to do work if there are\n> branches to track.\n> \n> Secondly,\n> \n> *extra context*\n> \tif (tracking.matches > 1)\n> \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> \t\t    orig_ref);\n> \n> tracking.matches <= 1, because we don't want to set up tracking if it's\n> not obvious what ref we want to track.\n> \n> But as I understand it, BRANCH_TRACK_INHERIT should be unconditional, so\n> instead of fudging this behavior by setting the correct value for\n> tracking.matches (which is meant for matching remote refs), we can just\n> do what the other unconditional BRANCH_TRACK_* options do, which is to\n> to break instead of return, i.e.\n> \n> \tif (!tracking.matches)\n> \t\tswitch (track) {\n> \t\tcase BRANCH_TRACK_ALWAYS:\n> \t\tcase BRANCH_TRACK_EXPLICIT:\n> \t\tcase BRANCH_TRACK_OVERRIDE:\n> +   case BRANCH_TRACK_INHERIT:\n> \t\t\tbreak;\n> \t\tdefault:\n> \t\t\treturn;\n> \t\t}\n> \n> and BRANCH_TRACK_INHERIT won't have to pretend that tracking.matches is\n> meaningful to it.\n\nDone in V6.\n\n> >@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> > \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> > \t\t    orig_ref);\n> > \n> >-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n> >-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n> >+\tif (tracking.srcs->nr < 1)\n> >+\t\tstring_list_append(tracking.srcs, orig_ref);\n> >+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n> >+\t\t\t      tracking.srcs) < 0)\n> > \t\texit(-1);\n> > \n> >-\tfree(tracking.src);\n> >+\tstring_list_clear(tracking.srcs, 0);\n> > }\n> \n> It looks like install_branch_config_multiple_remotes() can just replace\n> install_branch_config() in setup_tracking(), nice. This should make it\n> pretty easy for me to rebase gc/branch-recurse-submodules onto this.\n"},{"id":"444128","messageId":"YbkWpIQSpT+7b31z@google.com","threadId":"56468","inReplyTo":"nycvar.QRO.7.76.6.2112102344370.90@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v5 0/2] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T22:11:48Z","receivedAt":"2021-12-14T22:12:11Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.10 23:48, Johannes Schindelin wrote:\n> Hi,\n> \n> On Tue, 7 Dec 2021, Junio C Hamano wrote:\n> \n> > Josh Steadmon <steadmon@google.com> writes:\n> >\n> > > I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least\n> > > slightly) prefer handling multiple upstream branches in the existing\n> > > tracking setup, I've gone that direction rather than repurposing the\n> > > branch copy code. None of the other issues were controversial.\n> > >\n> > > In this version, I'd appreciate feedback mainly on patch 1:\n> > > * Is the combination of `git_config_set_gently()` +\n> > >   `git_config_set_multivar_gently() the best way to write multiple\n> > >   config entries for the same key?\n> >\n> > IIRC git_config_set_*() is Dscho's brainchild.  If he is available\n> > to comment, it may be a valuable input.\n> \n> The `git_config_set_multivar_gently()` function was really only intended\n> to add one key/value pair.\n> \n> Currently, there is no function to add multiple key/value pairs, and while\n> it is slightly wasteful to lock the config multiple times to write a bunch\n> of key/value pairs, it is not the worst in the world for a small use case\n> like this one.\n> \n> So yes, for the moment I would go with the suggested design.\n> \n> One thing you might want to do is to avoid the extra\n> `git_config_set_gently()` before the `for` loop, simply by passing `i == 0\n> ? 0 : CONFIG_FLAGS_MULTI_REPLACE` as `flags` parameter to the multivar\n> version of the function.\n> \n> But that would optimize for code size rather than for readability, and I\n> would actually prefer the more verbose version.\n\nSounds good, thanks for the advice!\n\n> Ciao,\n> Dscho\n"},{"id":"444129","messageId":"YbkXlTdGBemY5nRi@google.com","threadId":"56468","inReplyTo":"kl6lilw0aq76.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T22:15:49Z","receivedAt":"2021-12-14T22:16:00Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 16:35, Glen Choo wrote:\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n> \n> >> @@ -10,7 +10,8 @@ enum branch_track {\n> >>  \tBRANCH_TRACK_REMOTE,\n> >>  \tBRANCH_TRACK_ALWAYS,\n> >>  \tBRANCH_TRACK_EXPLICIT,\n> >> -\tBRANCH_TRACK_OVERRIDE\n> >> +\tBRANCH_TRACK_OVERRIDE,\n> >> +\tBRANCH_TRACK_INHERIT\n> >>  };\n> >\n> > So we've got 5 items in this enum...\n> >\n> >>  \n> >>  extern enum branch_track git_branch_track;\n> >> diff --git a/builtin/branch.c b/builtin/branch.c\n> >> index b23b1d1752..ebde5023c3 100644\n> >> --- a/builtin/branch.c\n> >> +++ b/builtin/branch.c\n> >> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n> >>  \t\tOPT__VERBOSE(&filter.verbose,\n> >>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n> >>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> >> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> >> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> >> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> >> +\t\t\tN_(\"set branch tracking configuration\"),\n> >> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> >> +\t\t\tparse_opt_tracking_mode),\n> >>  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n> >>  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n> >\n> > But map --track, --track=direct --track=inherit to 3/5 of them. Will it\n> > ever make sense to do the oher 2/5 (I really haven't checked)....\n> \n> Reasonable question, but I believe the answer is no, it doesn't make\n> sense to map all the values:\n> \n> * BRANCH_TRACK_REMOTE is just a default value as far as I can tell (I\n>   don't think this does anything?)\n> * BRANCH_TRACK_ALWAYS behaves like BRANCH_TRACK_EXPLICIT but it's only\n>   meant to be set from config files, see 9ed36cfa35 (branch: optionally\n>   setup branch.*.merge from upstream local branches, 2008-02-19). We're\n>   more lenient with _ALWAYS than with _EXPLICIT; e.g. we don't die()\n>   when the upstream doesn't exist.\n> \n> Even one of the other options doesn't really make that much sense...\n> \n> * BRANCH_TRACK_OVERRIDE used to be used to implement --set-upstream, but\n>   that's not necessary any more. Now it's used to make create_branch()\n>   *not* create a branch sometimes, but that's going away if I get my\n>   refactor of create_branch()\n>   (https://lore.kernel.org/git/xmqq1r2pcnyw.fsf@gitster.g/T/#u) :)\n\nAgreed, thank you for stating things better than I could have :)\n"},{"id":"444130","messageId":"YbkaT2Aexerzc6sD@google.com","threadId":"56468","inReplyTo":"211207.86ilw0pydg.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T22:27:27Z","receivedAt":"2021-12-14T22:27:36Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.07 10:08, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Dec 06 2021, Josh Steadmon wrote:\n> \n> > It can be helpful when creating a new branch to use the existing\n> > tracking configuration from the branch point. However, there is\n> > currently not a method to automatically do so.\n> >\n> > Teach git-{branch,checkout,switch} an \"inherit\" argument to the\n> > \"--track\" option. When this is set, creating a new branch will cause the\n> > tracking configuration to default to the configuration of the branch\n> > point, if set.\n> >\n> > For example, if branch \"main\" tracks \"origin/main\", and we run\n> > `git checkout --track=inherit -b feature main`, then branch \"feature\"\n> > will track \"origin/main\". Thus, `git status` will show us how far\n> > ahead/behind we are from origin, and `git pull` will pull from origin.\n> >\n> > This is particularly useful when creating branches across many\n> > submodules, such as with `git submodule foreach ...` (or if running with\n> > a patch such as [1], which we use at $job), as it avoids having to\n> > manually set tracking info for each submodule.\n> >\n> > Since we've added an argument to \"--track\", also add \"--track=direct\" as\n> > another way to explicitly get the original \"--track\" behavior (\"--track\"\n> > without an argument still works as well).\n> > @@ -10,7 +10,8 @@ enum branch_track {\n> >  \tBRANCH_TRACK_REMOTE,\n> >  \tBRANCH_TRACK_ALWAYS,\n> >  \tBRANCH_TRACK_EXPLICIT,\n> > -\tBRANCH_TRACK_OVERRIDE\n> > +\tBRANCH_TRACK_OVERRIDE,\n> > +\tBRANCH_TRACK_INHERIT\n> >  };\n> \n> So we've got 5 items in this enum...\n> \n> >  \n> >  extern enum branch_track git_branch_track;\n> > diff --git a/builtin/branch.c b/builtin/branch.c\n> > index b23b1d1752..ebde5023c3 100644\n> > --- a/builtin/branch.c\n> > +++ b/builtin/branch.c\n> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n> >  \t\tOPT__VERBOSE(&filter.verbose,\n> >  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n> >  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> > -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> > -\t\t\tBRANCH_TRACK_EXPLICIT),\n> > +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> > +\t\t\tN_(\"set branch tracking configuration\"),\n> > +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> > +\t\t\tparse_opt_tracking_mode),\n> >  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n> >  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n> \n> But map --track, --track=direct --track=inherit to 3/5 of them. Will it\n> ever make sense to do the oher 2/5 (I really haven't checked)....\n> \n> >  \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\n> > diff --git a/builtin/checkout.c b/builtin/checkout.c\n> > index b5d477919a..45dab414ea 100644\n> > --- a/builtin/checkout.c\n> > +++ b/builtin/checkout.c\n> > @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n> >  {\n> >  \tstruct option options[] = {\n> >  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> > -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n> > -\t\t\tBRANCH_TRACK_EXPLICIT),\n> > +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n> > +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n> > +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> > +\t\t\tparse_opt_tracking_mode),\n> >  \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n> >  \t\t\t   PARSE_OPT_NOCOMPLETE),\n> >  \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n> \n> I wonder if this interface wouldn't be a lot simpler as:\n> \n>     --track\n>     --track-explicit --track-direct --track-inherit\n> \n> Both because it'll work better for auto-complete, and we can (and\n> presumably some will want) just make --track mean whatever configured\n> --track-THING you want.\n\nIf I understand you correctly, I disagree here. I think you're saying\nthat if you always (or usually) want a specific tracking mode, you have\nto both set the config appropriately and remember to pass `--track` on\nthe command line? Seems simpler to just let the config take precedence\nin the absence of a flag. But I think I may have misunderstood you.\n\n> in any case, isn't there a NONEG missing here, or is --no-track-direct\n> etc. handled by OPT_CALLBACK_F() (I forget...).\n\nYeah, --no-track is correctly handled. It sets BRANCH_TRACK_NEVER, so\nthat you can override a branch.autosetupmerge=always config if\nnecessary.\n\n\n> >  \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n> > -\t\tif (value && !strcasecmp(value, \"always\")) {\n> > +\t\tif (value && !strcmp(value, \"always\")) {\n> \n> ...This probably makes sense, but it seems like the behavior change of\n> \"let's not take this case-insensitive\" should be split up into its own\n> change...\n\nDone in V6.\n\n\n> > +\ttest_must_fail git rev-parse --verify HEAD^ &&\n> > +\tgit checkout main &&\n> > +\tgit config branch.autosetupmerge inherit &&\n> > +\tgit checkout --orphan eta &&\n> > +\ttest -z \"$(git config branch.eta.merge)\" &&\n> > +\ttest -z \"$(git config branch.eta.remote)\" &&\n> \n> Better with the test_must_be_empty etc. helpers.\n> \n> > +\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n> \n> and this with test_cmp.\n> \n> (ditto occurances below)\n"},{"id":"444133","messageId":"cover.1639524556.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v6 0/3] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T23:44:53Z","receivedAt":"2021-12-14T23:45:01Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Changes since V5:\n* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n  so many conditionals.\n* Note that rebasing is not compatible with inheriting multiple upstream\n  branches.\n* Moved the change to case-sensitivity for branch.autosetupmerge to its\n  own commit.\n* Improve advice on failed tracking setup when multiple branches are\n  involved.\n* Make better use of string_list API.\n* Make better use of config API.\n* More straight-forward use of the `struct tracking` API.\n* Numerous style fixes.\n\nChanges since V4:\n* Add new patch (1/2) to refactor branch.c:install_branch_config() to\n  accept multiple upstream refs\n* When multiple upstream branches are set in the parent branch, inherit\n  them all, instead of just the first\n* Break out error string arguments for easier translation\n* Don't ignore case for values of branch.autosetupmerge\n* Move reference to git-pull out of usage string for --track into\n  git-branch.txt\n* Use test_config instead of `git config` in t2027\n* Style fixes: add single-quotes around warning string arguments, remove\n  unnecessary braces\n\nChanges since V3:\n* Use branch_get() instead of git_config_get_string() to look up branch\n  configuration.\n* Remove unnecessary string formatting in new error message in\n  parse-options-cb.c.\n\nJosh Steadmon (3):\n  branch: accept multiple upstream branches for tracking\n  branch: add flags and config to inherit tracking\n  config: require lowercase for branch.*.autosetupmerge\n\n Documentation/config/branch.txt |   3 +-\n Documentation/git-branch.txt    |  24 +++--\n Documentation/git-checkout.txt  |   2 +-\n Documentation/git-switch.txt    |   2 +-\n branch.c                        | 184 ++++++++++++++++++++++++--------\n branch.h                        |   3 +-\n builtin/branch.c                |   6 +-\n builtin/checkout.c              |   6 +-\n config.c                        |   5 +-\n parse-options-cb.c              |  16 +++\n parse-options.h                 |   2 +\n t/t2017-checkout-orphan.sh      |  11 +-\n t/t2027-checkout-track.sh       |  23 ++++\n t/t2060-switch.sh               |  28 +++++\n t/t3200-branch.sh               |  39 ++++++-\n t/t7201-co.sh                   |  17 +++\n 16 files changed, 305 insertions(+), 66 deletions(-)\n\nRange-diff against v5:\n1:  ba7d557725 < -:  ---------- branch: accept multiple upstream branches for tracking\n-:  ---------- > 1:  43d6f83fed branch: accept multiple upstream branches for tracking\n2:  c7e4af9a36 ! 2:  57e57e6e6a branch: add flags and config to inherit tracking\n    @@ branch.c: static int find_tracked_branch(struct remote *remote, void *priv)\n      \t\t}\n      \t\ttracking->spec.src = NULL;\n      \t}\n    -@@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const\n    - \tstring_list_clear(&remotes, 0);\n    +@@ branch.c: int install_branch_config(int flag, const char *local, const char *origin,\n    + \treturn ret;\n      }\n      \n     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     +\ttracking->remote = xstrdup(branch->remote_name);\n     +\tfor (i = 0; i < branch->merge_nr; i++)\n     +\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n    -+\ttracking->matches = 1;\n     +\treturn 0;\n     +}\n     +\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \t\treturn;\n      \n      \tif (!tracking.matches)\n    +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n    + \t\tcase BRANCH_TRACK_ALWAYS:\n    + \t\tcase BRANCH_TRACK_EXPLICIT:\n    + \t\tcase BRANCH_TRACK_OVERRIDE:\n    ++\t\tcase BRANCH_TRACK_INHERIT:\n    + \t\t\tbreak;\n    + \t\tdefault:\n    + \t\t\treturn;\n     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n      \t\t    orig_ref);\n      \n     -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n     -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n    -+\tif (tracking.srcs->nr < 1)\n    ++\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n     +\t\tstring_list_append(tracking.srcs, orig_ref);\n     +\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n     +\t\t\t      tracking.srcs) < 0)\n    @@ branch.h: enum branch_track {\n      \tBRANCH_TRACK_EXPLICIT,\n     -\tBRANCH_TRACK_OVERRIDE\n     +\tBRANCH_TRACK_OVERRIDE,\n    -+\tBRANCH_TRACK_INHERIT\n    ++\tBRANCH_TRACK_INHERIT,\n      };\n      \n      extern enum branch_track git_branch_track;\n    @@ builtin/checkout.c: static struct option *add_common_switch_branch_options(\n      \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\n     \n      ## config.c ##\n    -@@ config.c: static int git_default_i18n_config(const char *var, const char *value)\n    - static int git_default_branch_config(const char *var, const char *value)\n    - {\n    - \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n    --\t\tif (value && !strcasecmp(value, \"always\")) {\n    -+\t\tif (value && !strcmp(value, \"always\")) {\n    +@@ config.c: static int git_default_branch_config(const char *var, const char *value)\n    + \t\tif (value && !strcasecmp(value, \"always\")) {\n      \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n      \t\t\treturn 0;\n     +\t\t} else if (value && !strcmp(value, \"inherit\")) {\n    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const\n      \treturn 0;\n      }\n     +\n    -+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {\n    ++int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)\n    ++{\n     +\tif (unset)\n     +\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n     +\telse if (!arg || !strcmp(arg, \"direct\"))\n    @@ parse-options.h: enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx\n     \n      ## t/t2017-checkout-orphan.sh ##\n     @@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '\n    + \tgit checkout main &&\n    + \tgit config branch.autosetupmerge always &&\n      \tgit checkout --orphan gamma &&\n    - \ttest -z \"$(git config branch.gamma.merge)\" &&\n    +-\ttest -z \"$(git config branch.gamma.merge)\" &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.gamma.merge &&\n      \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n     +\ttest_must_fail git rev-parse --verify HEAD^ &&\n     +\tgit checkout main &&\n     +\tgit config branch.autosetupmerge inherit &&\n     +\tgit checkout --orphan eta &&\n    -+\ttest -z \"$(git config branch.eta.merge)\" &&\n    -+\ttest -z \"$(git config branch.eta.remote)\" &&\n    -+\ttest refs/heads/eta = \"$(git symbolic-ref HEAD)\" &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.eta.merge &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.eta.remote &&\n    ++\techo refs/heads/eta >expected &&\n    ++\tgit symbolic-ref HEAD >actual &&\n    ++\ttest_cmp expected actual &&\n      \ttest_must_fail git rev-parse --verify HEAD^\n      '\n      \n    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr\n     +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n     +\t# default config does not copy tracking info\n     +\tgit switch -c foo-no-inherit foo &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n     +\t# with --track=inherit, we copy tracking info from foo\n     +\tgit switch --track=inherit -c foo2 foo &&\n     +\ttest_cmp_config origin branch.foo2.remote &&\n    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr\n     +\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n     +\t# no tracking info to inherit from main\n     +\tgit switch -c main2 main &&\n    -+\ttest -z \"$(git config branch.main2.remote)\" &&\n    -+\ttest -z \"$(git config branch.main2.merge)\"\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n     +'\n     +\n      test_done\n    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'\n     +\ttest_unconfig branch.autoSetupMerge &&\n     +\t# default config does not copy tracking info\n     +\tgit branch foo-no-inherit my1 &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n     +\t# with autoSetupMerge=inherit, we copy tracking info from my1\n     +\ttest_config branch.autoSetupMerge inherit &&\n     +\tgit branch foo3 my1 &&\n    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'\n     +\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n     +\t# no tracking info to inherit from main\n     +\tgit branch main2 main &&\n    -+\ttest -z \"$(git config branch.main2.remote)\" &&\n    -+\ttest -z \"$(git config branch.main2.merge)\"\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n     +'\n     +\n     +test_expect_success '--track overrides branch.autoSetupMerge' '\n    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'\n     +\ttest_cmp_config . branch.foo4.remote &&\n     +\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n     +\tgit branch --no-track foo5 my1 &&\n    -+\ttest -z \"$(git config branch.foo5.remote)\" &&\n    -+\ttest -z \"$(git config branch.foo5.merge)\"\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo5.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo5.merge\n     +'\n     +\n      test_done\n    @@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '\n     +\tgit reset --hard main &&\n     +\t# default config does not copy tracking info\n     +\tgit checkout -b foo-no-inherit koala/bear &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.remote)\" &&\n    -+\ttest -z \"$(git config branch.foo-no-inherit.merge)\" &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n     +\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n     +\ttest_config branch.autoSetupMerge inherit &&\n     +\tgit checkout -b foo koala/bear &&\n    @@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '\n     +\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n     +\t# no tracking info to inherit from main\n     +\tgit checkout -b main2 main &&\n    -+\ttest -z \"$(git config branch.main2.remote)\" &&\n    -+\ttest -z \"$(git config branch.main2.merge)\"\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n    ++\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n     +'\n     +\n      test_done\n-:  ---------- > 3:  f79d27dc24 config: require lowercase for branch.*.autosetupmerge\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444134","messageId":"43d6f83fedc022c44d6a3be249e7fd8cd2a25007.1639524556.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639524556.git.steadmon@google.com","subject":"[PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T23:44:54Z","receivedAt":"2021-12-14T23:45:05Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Add a new static variant of install_branch_config() that accepts\nmultiple remote branch names for tracking. This will be used in an\nupcoming commit that enables inheriting the tracking configuration from\na parent branch.\n\nCurrently, all callers of install_branch_config() pass only a single\nremote. Make install_branch_config() a small wrapper around\ninstall_branch_config_multiple_remotes() so that existing callers do not\nneed to be changed.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n branch.c          | 135 ++++++++++++++++++++++++++++++++--------------\n t/t3200-branch.sh |   6 +--\n 2 files changed, 99 insertions(+), 42 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..fa165ebdab 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -49,25 +49,41 @@ static int should_setup_rebase(const char *origin)\n \treturn 0;\n }\n \n-static const char tracking_advice[] =\n-N_(\"\\n\"\n-\"After fixing the error cause you may try to fix up\\n\"\n-\"the remote tracking information by invoking\\n\"\n-\"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n-\n-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n+/**\n+ * Install upstream tracking configuration for a branch; specifically, add\n+ * `branch.<name>.remote` and `branch.<name>.merge` entries.\n+ *\n+ * `flag` contains integer flags for options; currently only\n+ * BRANCH_CONFIG_VERBOSE is checked.\n+ *\n+ * `local` is the name of the branch whose configuration we're installing.\n+ *\n+ * `origin` is the name of the remote owning the upstream branches. NULL means\n+ * the upstream branches are local to this repo.\n+ *\n+ * `remotes` is a list of refs that are upstream of local\n+ */\n+static int install_branch_config_multiple_remotes(int flag, const char *local,\n+\t\tconst char *origin, struct string_list *remotes)\n {\n \tconst char *shortname = NULL;\n \tstruct strbuf key = STRBUF_INIT;\n+\tstruct string_list_item *item;\n \tint rebasing = should_setup_rebase(origin);\n \n-\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n-\t    && !strcmp(local, shortname)\n-\t    && !origin) {\n-\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n-\t\t\tlocal);\n-\t\treturn 0;\n-\t}\n+\tif (!remotes->nr)\n+\t\tBUG(\"must provide at least one remote for branch config\");\n+\tif (rebasing && remotes->nr > 1)\n+\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n+\n+\tif (!origin)\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n+\t\t\t    && !strcmp(local, shortname)) {\n+\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n+\t\t\t\t\tlocal);\n+\t\t\t\treturn 0;\n+\t\t\t}\n \n \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n@@ -75,8 +91,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \n \tstrbuf_reset(&key);\n \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n-\tif (git_config_set_gently(key.buf, remote) < 0)\n+\t/*\n+\t * We want to overwrite any existing config with all the branches in\n+\t * \"remotes\". Override any existing config, then write our branches. If\n+\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n+\t * we've written so far.\n+\t */\n+\tif (git_config_set_gently(key.buf, NULL) < 0)\n \t\tgoto out_err;\n+\tfor_each_string_list_item(item, remotes)\n+\t\tif (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)\n+\t\t\tgoto out_err;\n \n \tif (rebasing) {\n \t\tstrbuf_reset(&key);\n@@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \n \tif (flag & BRANCH_CONFIG_VERBOSE) {\n-\t\tif (shortname) {\n+\t\tconst char *name;\n+\t\tstruct strbuf ref_string = STRBUF_INIT;\n+\n+\t\tfor_each_string_list_item(item, remotes) {\n+\t\t\tname = item->string;\n+\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n+\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n+\t\t}\n+\n+\t\tif (remotes->nr == 1) {\n+\t\t\tstruct strbuf refname = STRBUF_INIT;\n+\n \t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n-\t\t\t\t\t  local, shortname, origin);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n-\t\t\t\t\t  local, shortname);\n+\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n+\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n+\n+\t\t\t/*\n+\t\t\t * Rebasing is only allowed in the case of a single\n+\t\t\t * upstream branch.\n+\t\t\t */\n+\t\t\tprintf_ln(rebasing ?\n+\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n+\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n+\t\t\t\tlocal, refname.buf);\n+\n+\t\t\tstrbuf_release(&refname);\n+\t\t} else if (origin) {\n+\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n+\t\t\t\tlocal, origin);\n+\t\t\tprintf(\"%s\", ref_string.buf);\n \t\t} else {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n+\t\t\tprintf(\"%s\", ref_string.buf);\n \t\t}\n+\n+\t\tstrbuf_release(&ref_string);\n \t}\n \n \treturn 0;\n@@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \terror(_(\"Unable to write upstream branch configuration\"));\n \n-\tadvise(_(tracking_advice),\n-\t       origin ? origin : \"\",\n-\t       origin ? \"/\" : \"\",\n-\t       shortname ? shortname : remote);\n+\tadvise(_(\"\\nAfter fixing the error cause you may try to fix up\\n\"\n+\t\t\"the remote tracking information by invoking:\"));\n+\tif (remotes->nr == 1)\n+\t\tadvise(\"  git branch --set-upstream-to=%s%s%s\",\n+\t\t\torigin ? origin : \"\",\n+\t\t\torigin ? \"/\" : \"\",\n+\t\t\tremotes->items[0].string);\n+\telse\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n+\t\t\t\tlocal, item->string);\n \n \treturn -1;\n }\n \n+int install_branch_config(int flag, const char *local, const char *origin,\n+\t\tconst char *remote)\n+{\n+\tint ret;\n+\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n+\n+\tstring_list_append(&remotes, remote);\n+\tret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n+\tstring_list_clear(&remotes, 0);\n+\treturn ret;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..4b0ef35913 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '\n \ttest_must_fail git branch --set-upstream origin/main\n '\n \n-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '\n+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' \"\n \tgit branch --set-upstream-to refs/heads/my13 my13 2>actual &&\n \tcat >expect <<-\\EOF &&\n-\twarning: Not setting branch my13 as its own upstream.\n+\twarning: not setting branch 'my13' as its own upstream.\n \tEOF\n \ttest_expect_code 1 git config branch.my13.remote &&\n \ttest_expect_code 1 git config branch.my13.merge &&\n \ttest_cmp expect actual\n-'\n+\"\n \n # Keep this test last, as it changes the current branch\n cat >expect <<EOF\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444135","messageId":"57e57e6e6a0735341a86f7b5dc52d477d8789578.1639524556.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639524556.git.steadmon@google.com","subject":"[PATCH v6 2/3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T23:44:55Z","receivedAt":"2021-12-14T23:45:06Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Documentation/config/branch.txt |  3 +-\n Documentation/git-branch.txt    | 24 +++++++++++-----\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 49 ++++++++++++++++++++++++++++-----\n branch.h                        |  3 +-\n builtin/branch.c                |  6 ++--\n builtin/checkout.c              |  6 ++--\n config.c                        |  3 ++\n parse-options-cb.c              | 16 +++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      | 11 +++++++-\n t/t2027-checkout-track.sh       | 23 ++++++++++++++++\n t/t2060-switch.sh               | 28 +++++++++++++++++++\n t/t3200-branch.sh               | 33 ++++++++++++++++++++++\n t/t7201-co.sh                   | 17 ++++++++++++\n 16 files changed, 205 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..c8b393e51c 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n++\n+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on\n+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex fa165ebdab..7e0c29957c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -11,7 +11,7 @@\n \n struct tracking {\n \tstruct refspec_item spec;\n-\tchar *src;\n+\tstruct string_list *srcs;\n \tconst char *remote;\n \tint matches;\n };\n@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tif (++tracking->matches == 1) {\n-\t\t\ttracking->src = tracking->spec.src;\n+\t\t\tstring_list_append(tracking->srcs, tracking->spec.src);\n \t\t\ttracking->remote = remote->name;\n \t\t} else {\n \t\t\tfree(tracking->spec.src);\n-\t\t\tFREE_AND_NULL(tracking->src);\n+\t\t\tstring_list_clear(tracking->srcs, 0);\n \t\t}\n \t\ttracking->spec.src = NULL;\n \t}\n@@ -183,6 +183,34 @@ int install_branch_config(int flag, const char *local, const char *origin,\n \treturn ret;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tconst char *bare_ref;\n+\tstruct branch *branch;\n+\tint i;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tbranch = branch_get(bare_ref);\n+\tif (!branch->remote_name) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = xstrdup(branch->remote_name);\n+\tfor (i = 0; i < branch->merge_nr; i++)\n+\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n {\n \tstruct tracking tracking;\n+\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\ttracking.srcs = &tracking_srcs;\n+\tif (track != BRANCH_TRACK_INHERIT)\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\telse if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\n@@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_ALWAYS:\n \t\tcase BRANCH_TRACK_EXPLICIT:\n \t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n \t\t\treturn;\n@@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n \t\t    orig_ref);\n \n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n+\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n+\t\tstring_list_append(tracking.srcs, orig_ref);\n+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n+\t\t\t      tracking.srcs) < 0)\n \t\texit(-1);\n \n-\tfree(tracking.src);\n+\tstring_list_clear(tracking.srcs, 0);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..815dcd40c0 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT,\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..ebde5023c3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set branch tracking configuration\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..152c94f29d 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..d346dbe210 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `%s' expects \\\"%s\\\" or \\\"%s\\\"\"),\n+\t\t\t     \"--track\", \"direct\", \"inherit\");\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..4d689bd377 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout main &&\n \tgit config branch.autosetupmerge always &&\n \tgit checkout --orphan gamma &&\n-\ttest -z \"$(git config branch.gamma.merge)\" &&\n+\ttest_cmp_config \"\" --default \"\" branch.gamma.merge &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.merge &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.remote &&\n+\techo refs/heads/eta >expected &&\n+\tgit symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..49c7def21c 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\ttest_config branch.main.remote origin &&\n+\ttest_config branch.main.merge refs/heads/main &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..ebb961be29 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 4b0ef35913..a049276439 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.merge\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..4fdf88ba46 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444136","messageId":"f79d27dc243dee281b879e358e8609aba39b3fe3.1639524556.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639524556.git.steadmon@google.com","subject":"[PATCH v6 3/3] config: require lowercase for branch.autosetupmerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-14T23:44:56Z","receivedAt":"2021-12-14T23:45:07Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Although we only documented that branch.autosetupmerge would accept\n\"always\" as a value, the actual implementation would accept any\ncombination of upper- or lower-case. Fix this to be consistent with\ndocumentation and with other values of this config variable.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 152c94f29d..54c0e7d98e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)\n static int git_default_branch_config(const char *var, const char *value)\n {\n \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n-\t\tif (value && !strcasecmp(value, \"always\")) {\n+\t\tif (value && !strcmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n \t\t} else if (value && !strcmp(value, \"inherit\")) {\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444138","messageId":"Ybk6QsMdeBl6IweW@google.com","threadId":"56468","inReplyTo":"cover.1639524556.git.steadmon@google.com","subject":"Re: [PATCH v6 0/3] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-15T00:43:46Z","receivedAt":"2021-12-15T00:43:57Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.14 15:44, Josh Steadmon wrote:\n> Changes since V5:\n> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n>   so many conditionals.\n\nI meant to expand on this but forgot before sending the series. I\nremoved as many distinctions as possible, as most can still be inferred\nfrom context. For example, previously the output specifically called out\nbranches vs. tags, but this is obvious in the name itself: \"some-branch\"\nvs. \"refs/tags/some-tag\" for example. Likewise, we don't need to specify\nwhether refs are remote or local: \"some-remote/some-branch\" vs.\n\"a-local-branch\" should be understandable without us spelling it out.\n\nOf course, if people feel like I've over-simplified here, I'm happy to\nrevert this change.\n"},{"id":"444171","messageId":"xmqqlf0l4kuu.fsf@gitster.g","threadId":"56468","inReplyTo":"43d6f83fedc022c44d6a3be249e7fd8cd2a25007.1639524556.git.steadmon@google.com","subject":"Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-15T21:30:17Z","receivedAt":"2021-12-15T21:30:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> + * `origin` is the name of the remote owning the upstream branches. NULL means\n> + * the upstream branches are local to this repo.\n> + *\n> + * `remotes` is a list of refs that are upstream of local\n> + */\n> +static int install_branch_config_multiple_remotes(int flag, const char *local,\n> +\t\tconst char *origin, struct string_list *remotes)\n>  {\n>  \tconst char *shortname = NULL;\n>  \tstruct strbuf key = STRBUF_INIT;\n> +\tstruct string_list_item *item;\n>  \tint rebasing = should_setup_rebase(origin);\n>  \n> -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> -\t    && !strcmp(local, shortname)\n> -\t    && !origin) {\n> -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> -\t\t\tlocal);\n> -\t\treturn 0;\n> -\t}\n> +\tif (!remotes->nr)\n> +\t\tBUG(\"must provide at least one remote for branch config\");\n> +\tif (rebasing && remotes->nr > 1)\n> +\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n> +\n> +\tif (!origin)\n> +\t\tfor_each_string_list_item(item, remotes)\n> +\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n> +\t\t\t    && !strcmp(local, shortname)) {\n> +\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n> +\t\t\t\t\tlocal);\n> +\t\t\t\treturn 0;\n> +\t\t\t}\n\nOK, if there is the current branch _among_ the remotes and we are\nsynching with the local repository, we reject to muck the\nconfiguration for 'local' branch with any of the refs in 'remotes'.\n\nMakes sense.\n\nJust FYI (because there is nothing actionable on your part), there\nis another topic in flight that wants to change the warning message\nto be phrased like so:\n\n\t\t_(\"not setting branch '%s' as its own upstream\")\n\ni.e. without capitalizing the first word, and without finishing the\nsentence with a full stop.  I'll make the conflict resolution to\nread as such.\n\n> -test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '\n> +test_expect_success '--set-upstream-to notices an error to set branch as own upstream' \"\n>  \tgit branch --set-upstream-to refs/heads/my13 my13 2>actual &&\n>  \tcat >expect <<-\\EOF &&\n> -\twarning: Not setting branch my13 as its own upstream.\n> +\twarning: not setting branch 'my13' as its own upstream.\n\nLikewise.\n\n>  \tEOF\n>  \ttest_expect_code 1 git config branch.my13.remote &&\n>  \ttest_expect_code 1 git config branch.my13.merge &&\n>  \ttest_cmp expect actual\n> -'\n> +\"\n\nThe above is currently correct but it often is an invitation for\nfuture bugs to use double-quotes around the executable part of\ntest_expect_success.  We can always write ' --> '\\'' to enclose the\nthing in a pair of single quotes when we start needing to refer to\n$shell_variables in the executable part, so let's take the above\npatch as-is, but something to keep in mind.\n\nThanks, queued.\n\n"},{"id":"444184","messageId":"xmqqsfutzaaq.fsf@gitster.g","threadId":"56468","inReplyTo":"cover.1639524556.git.steadmon@google.com","subject":"Re: [PATCH v6 0/3] branch: inherit tracking configs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-16T00:02:37Z","receivedAt":"2021-12-16T00:02:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Changes since V5:\n> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n>   so many conditionals.\n> * Note that rebasing is not compatible with inheriting multiple upstream\n>   branches.\n> * Moved the change to case-sensitivity for branch.autosetupmerge to its\n>   own commit.\n> * Improve advice on failed tracking setup when multiple branches are\n>   involved.\n> * Make better use of string_list API.\n> * Make better use of config API.\n> * More straight-forward use of the `struct tracking` API.\n> * Numerous style fixes.\n\nI've queued this, and rebased Glen's \"branch --recurse-submodules\"\non top, and parked both of them near the tip of 'seen'.  I do not\nhave much confidence in the conflict resolution needed during the\nrebasing or the other branch or merges into 'seen', and I would\nappreciate it if you two can take a look to sanity check the result.\n\nThanks.\n"},{"id":"444191","messageId":"kl6l35mtv0yz.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"xmqqsfutzaaq.fsf@gitster.g","subject":"Re: [PATCH v6 0/3] branch: inherit tracking configs","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T00:37:40Z","receivedAt":"2021-12-16T00:37:57Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Josh Steadmon <steadmon@google.com> writes:\n>\n>> Changes since V5:\n>> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n>>   so many conditionals.\n>> * Note that rebasing is not compatible with inheriting multiple upstream\n>>   branches.\n>> * Moved the change to case-sensitivity for branch.autosetupmerge to its\n>>   own commit.\n>> * Improve advice on failed tracking setup when multiple branches are\n>>   involved.\n>> * Make better use of string_list API.\n>> * Make better use of config API.\n>> * More straight-forward use of the `struct tracking` API.\n>> * Numerous style fixes.\n>\n> I've queued this, and rebased Glen's \"branch --recurse-submodules\"\n> on top, and parked both of them near the tip of 'seen'.  I do not\n> have much confidence in the conflict resolution needed during the\n> rebasing or the other branch or merges into 'seen', and I would\n> appreciate it if you two can take a look to sanity check the result.\n>\n> Thanks.\n\nI've just sent out a new version [1] which is rebased on top of Josh's\nv6. Please use that version instead :)\n\nI did not rebase this on top of 'seen' though; I'll take a look and see\nif there's anything of concern.\n\n[1] https://lore.kernel.org/git/20211216003213.99135-1-chooglen@google.com/,\n"},{"id":"444193","messageId":"xmqqlf0lz6os.fsf@gitster.g","threadId":"56468","inReplyTo":"kl6l35mtv0yz.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 0/3] branch: inherit tracking configs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-16T01:20:35Z","receivedAt":"2021-12-16T01:20:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Glen Choo <chooglen@google.com> writes:\n\n> I did not rebase this on top of 'seen' though; I'll take a look and see\n> if there's anything of concern.\n\nThat's OK; please do not ever rebase anything on top of 'seen' or\n'next'.  In this particular case, you and Josh agreed to base your\ntopic on top of Josh'es, if I understand it correctly, so find the\ntip of js/branch-track-inherit from 'seen' [*] and rebase your topic\nthere.\n\nBut making a trial merge of the combined topic to say 'next' and\ntesting the result out before sending the patches to the list would\nbe very much appreciated ;-)\n\n\n[Footnote]\n\n* One way to do so would be:\n\n $ git fetch\n $ git show 'remote/origin/seen^{/^Merge branch .js/branch-track-inherit.}'\n\n\n\n\n\n"},{"id":"444254","messageId":"kl6lo85g8grj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"43d6f83fedc022c44d6a3be249e7fd8cd2a25007.1639524556.git.steadmon@google.com","subject":"Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T19:57:20Z","receivedAt":"2021-12-16T19:57:36Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -49,25 +49,41 @@ static int should_setup_rebase(const char *origin)\n>  \treturn 0;\n>  }\n>  \n> -static const char tracking_advice[] =\n> -N_(\"\\n\"\n> -\"After fixing the error cause you may try to fix up\\n\"\n> -\"the remote tracking information by invoking\\n\"\n> -\"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n> -\n> -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n> +/**\n> + * Install upstream tracking configuration for a branch; specifically, add\n> + * `branch.<name>.remote` and `branch.<name>.merge` entries.\n> + *\n> + * `flag` contains integer flags for options; currently only\n> + * BRANCH_CONFIG_VERBOSE is checked.\n> + *\n> + * `local` is the name of the branch whose configuration we're installing.\n> + *\n> + * `origin` is the name of the remote owning the upstream branches. NULL means\n> + * the upstream branches are local to this repo.\n> + *\n> + * `remotes` is a list of refs that are upstream of local\n> + */\n> +static int install_branch_config_multiple_remotes(int flag, const char *local,\n> +\t\tconst char *origin, struct string_list *remotes)\n\nVery helpful description. I got slightly confused when I first reviewed\nthis, so having the comments will help future readers a lot.\n\n>  {\n>  \tconst char *shortname = NULL;\n>  \tstruct strbuf key = STRBUF_INIT;\n> +\tstruct string_list_item *item;\n>  \tint rebasing = should_setup_rebase(origin);\n>  \n> -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> -\t    && !strcmp(local, shortname)\n> -\t    && !origin) {\n> -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> -\t\t\tlocal);\n> -\t\treturn 0;\n> -\t}\n> +\tif (!remotes->nr)\n> +\t\tBUG(\"must provide at least one remote for branch config\");\n> +\tif (rebasing && remotes->nr > 1)\n> +\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n\nNit: if we're being pedantic, we cannot inherit upstream tracking\nconfiguration when rebasing is requested with multiple upstream\nbranches.\n\nBut this message is already very niche and loaded with specifics, so\nadding \"...with multiple upstream branches\" might just be more\nconfusing, so this is a nit.\n\n> @@ -75,8 +91,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config, then write our branches. If\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n> +\tif (git_config_set_gently(key.buf, NULL) < 0)\n>  \t\tgoto out_err;\n> +\tfor_each_string_list_item(item, remotes)\n> +\t\tif (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n\nWe get to use for_each_string_item() now, nice.\n\n> @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tstrbuf_release(&key);\n>  \n>  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> -\t\tif (shortname) {\n> +\t\tconst char *name;\n> +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> +\n> +\t\tfor_each_string_list_item(item, remotes) {\n> +\t\t\tname = item->string;\n> +\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n> +\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n> +\t\t}\n> +\n> +\t\tif (remotes->nr == 1) {\n> +\t\t\tstruct strbuf refname = STRBUF_INIT;\n> +\n>  \t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> -\t\t\t\t\t  local, shortname, origin);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> -\t\t\t\t\t  local, shortname);\n> +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n> +\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n> +\n> +\t\t\t/*\n> +\t\t\t * Rebasing is only allowed in the case of a single\n> +\t\t\t * upstream branch.\n> +\t\t\t */\n> +\t\t\tprintf_ln(rebasing ?\n> +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n> +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n> +\t\t\t\tlocal, refname.buf);\n> +\n> +\t\t\tstrbuf_release(&refname);\n> +\t\t} else if (origin) {\n> +\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n> +\t\t\t\tlocal, origin);\n> +\t\t\tprintf(\"%s\", ref_string.buf);\n\nIt's not clear to me why the hint contains the word 'from' when it is a\nremote ref...\n\n>  \t\t} else {\n> -\t\t\tif (origin)\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> -\t\t\telse\n> -\t\t\t\tprintf_ln(rebasing ?\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> -\t\t\t\t\t  local, remote);\n> +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n> +\t\t\tprintf(\"%s\", ref_string.buf);\n\nbut does not have the word 'from' when it is a local ref. As far as I\ncan tell, this is the only difference between remote and local refs, and\nadding the word 'from' does not seem like a good enough reason to add an\n'if' condition. Maybe I missed something here?\n\nThis motivates my answer to the question you asked in [1]:\n\n  I removed as many distinctions as possible, as most can still be\n  inferred from context. [...] Likewise, we don't need to specify whether\n  refs are remote or local: \"some-remote/some-branch\" vs.\n  \"a-local-branch\" should be understandable without us spelling it out.\n\nI agree that there is adequate context, so I would be ok with the\nsimplification if there was corresponding code simplification e.g.\ndropping \"if (origin)\". But in its current form, I don't think there is\ngood enough reason to simplify the message.\n\nOf course, IIUC, this is as simple as dropping 'from' in the \"if\n(origin)\" case.\n\n> @@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tstrbuf_release(&key);\n>  \terror(_(\"Unable to write upstream branch configuration\"));\n>  \n> -\tadvise(_(tracking_advice),\n> -\t       origin ? origin : \"\",\n> -\t       origin ? \"/\" : \"\",\n> -\t       shortname ? shortname : remote);\n> +\tadvise(_(\"\\nAfter fixing the error cause you may try to fix up\\n\"\n> +\t\t\"the remote tracking information by invoking:\"));\n> +\tif (remotes->nr == 1)\n> +\t\tadvise(\"  git branch --set-upstream-to=%s%s%s\",\n> +\t\t\torigin ? origin : \"\",\n> +\t\t\torigin ? \"/\" : \"\",\n> +\t\t\tremotes->items[0].string);\n> +\telse\n> +\t\tfor_each_string_list_item(item, remotes)\n> +\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n> +\t\t\t\tlocal, item->string);\n\nThe advice is now correct, nice! Does the user also need to run \"git\nconfig --add branch.%s.remote %s\" ?\n\n[1] https://lore.kernel.org/git/Ybk6QsMdeBl6IweW@google.com\n"},{"id":"444259","messageId":"kl6llf0k8ckj.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"57e57e6e6a0735341a86f7b5dc52d477d8789578.1639524556.git.steadmon@google.com","subject":"Re: [PATCH v6 2/3] branch: add flags and config to inherit tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-16T21:27:56Z","receivedAt":"2021-12-16T21:28:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> @@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\t\t   enum branch_track track, int quiet)\n>  {\n>  \tstruct tracking tracking;\n> +\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n>  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n>  \n>  \tmemset(&tracking, 0, sizeof(tracking));\n>  \ttracking.spec.dst = (char *)orig_ref;\n> -\tif (for_each_remote(find_tracked_branch, &tracking))\n> +\ttracking.srcs = &tracking_srcs;\n> +\tif (track != BRANCH_TRACK_INHERIT)\n> +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> +\telse if (inherit_tracking(&tracking, orig_ref))\n>  \t\treturn;\n>  \n>  \tif (!tracking.matches)\n> @@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\tcase BRANCH_TRACK_ALWAYS:\n>  \t\tcase BRANCH_TRACK_EXPLICIT:\n>  \t\tcase BRANCH_TRACK_OVERRIDE:\n> +\t\tcase BRANCH_TRACK_INHERIT:\n>  \t\t\tbreak;\n>  \t\tdefault:\n>  \t\t\treturn;\n> @@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n>  \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n>  \t\t    orig_ref);\n>  \n> -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n> -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n> +\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n> +\t\tstring_list_append(tracking.srcs, orig_ref);\n\nSo, in the BRANCH_TRACK_{ALWAYS,EXPLICIT,OVERRIDE} cases, we append\norig_ref because we expect orig_ref to be a local ref that the caller\nwants to track. This is not the case with BRANCH_TRACK_INHERIT, where we\nwant to inherit the configuration and we no longer care about orig_ref.\n\nThis is correct, though it's more unobvious than what I originally\nenvisioned when I commented on [1]. As a small nit, it might benefit\nfrom a clarifying comment, but this is fine as it is :)\n\n> diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\n> index 4453741b96..49c7def21c 100755\n> --- a/t/t2027-checkout-track.sh\n> +++ b/t/t2027-checkout-track.sh\n> @@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n>  \ttest_i18ngrep \"cannot be used with updating paths\" err\n>  '\n>  \n> +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n> +\t# Set up tracking config on main\n> +\ttest_config branch.main.remote origin &&\n> +\ttest_config branch.main.merge refs/heads/main &&\n> +\ttest_config branch.autoSetupMerge inherit &&\n> +\t# With --track=inherit, we copy the tracking config from main\n> +\tgit checkout --track=inherit -b b1 main &&\n> +\ttest_cmp_config origin branch.b1.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b1.merge &&\n> +\t# With branch.autoSetupMerge=inherit, we do the same\n> +\tgit checkout -b b2 main &&\n> +\ttest_cmp_config origin branch.b2.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b2.merge &&\n> +\t# But --track overrides this\n> +\tgit checkout --track -b b3 main &&\n> +\ttest_cmp_config . branch.b3.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b3.merge &&\n> +\t# And --track=direct does as well\n> +\tgit checkout --track=direct -b b4 main &&\n> +\ttest_cmp_config . branch.b4.remote &&\n> +\ttest_cmp_config refs/heads/main branch.b4.merge\n\nNit: in both cases, the expected result is that branch.b*.merge is\n\"refs/heads/main\". so the difference between --track=direct and\n--track=inherit would be more obvious if main tracked something other\nthan origin/main.\n\nAs an side, the comments in the tests make it really readable :)\n\nOverall this patch looks good.\n\n[1] https://lore.kernel.org/git/kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com\n"},{"id":"444325","messageId":"Ybwb4UkQwAVRcJp5@google.com","threadId":"56468","inReplyTo":"kl6lo85g8grj.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:10:57Z","receivedAt":"2021-12-17T05:11:06Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.16 11:57, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> >  {\n> >  \tconst char *shortname = NULL;\n> >  \tstruct strbuf key = STRBUF_INIT;\n> > +\tstruct string_list_item *item;\n> >  \tint rebasing = should_setup_rebase(origin);\n> >  \n> > -\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n> > -\t    && !strcmp(local, shortname)\n> > -\t    && !origin) {\n> > -\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n> > -\t\t\tlocal);\n> > -\t\treturn 0;\n> > -\t}\n> > +\tif (!remotes->nr)\n> > +\t\tBUG(\"must provide at least one remote for branch config\");\n> > +\tif (rebasing && remotes->nr > 1)\n> > +\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n> \n> Nit: if we're being pedantic, we cannot inherit upstream tracking\n> configuration when rebasing is requested with multiple upstream\n> branches.\n> \n> But this message is already very niche and loaded with specifics, so\n> adding \"...with multiple upstream branches\" might just be more\n> confusing, so this is a nit.\n\nI think it's worthwhile to be precise. Thanks for pointing this out.\nFixed in V7.\n\n\n> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tstrbuf_release(&key);\n> >  \n> >  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> > -\t\tif (shortname) {\n> > +\t\tconst char *name;\n> > +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> > +\n> > +\t\tfor_each_string_list_item(item, remotes) {\n> > +\t\t\tname = item->string;\n> > +\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n> > +\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n> > +\t\t}\n> > +\n> > +\t\tif (remotes->nr == 1) {\n> > +\t\t\tstruct strbuf refname = STRBUF_INIT;\n> > +\n> >  \t\t\tif (origin)\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> > -\t\t\t\t\t  local, shortname, origin);\n> > -\t\t\telse\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> > -\t\t\t\t\t  local, shortname);\n> > +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n> > +\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n> > +\n> > +\t\t\t/*\n> > +\t\t\t * Rebasing is only allowed in the case of a single\n> > +\t\t\t * upstream branch.\n> > +\t\t\t */\n> > +\t\t\tprintf_ln(rebasing ?\n> > +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n> > +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n> > +\t\t\t\tlocal, refname.buf);\n> > +\n> > +\t\t\tstrbuf_release(&refname);\n> > +\t\t} else if (origin) {\n> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n> > +\t\t\t\tlocal, origin);\n> > +\t\t\tprintf(\"%s\", ref_string.buf);\n> \n> It's not clear to me why the hint contains the word 'from' when it is a\n> remote ref...\n\nBecause in the multiple-branch case, we don't prepend the origin to each\nref, so we need to let users know which remote the refs are coming from.\n\n\n> >  \t\t} else {\n> > -\t\t\tif (origin)\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> > -\t\t\t\t\t  local, remote);\n> > -\t\t\telse\n> > -\t\t\t\tprintf_ln(rebasing ?\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> > -\t\t\t\t\t  local, remote);\n> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n> > +\t\t\tprintf(\"%s\", ref_string.buf);\n> \n> but does not have the word 'from' when it is a local ref. As far as I\n> can tell, this is the only difference between remote and local refs, and\n> adding the word 'from' does not seem like a good enough reason to add an\n> 'if' condition. Maybe I missed something here?\n> \n> This motivates my answer to the question you asked in [1]:\n> \n>   I removed as many distinctions as possible, as most can still be\n>   inferred from context. [...] Likewise, we don't need to specify whether\n>   refs are remote or local: \"some-remote/some-branch\" vs.\n>   \"a-local-branch\" should be understandable without us spelling it out.\n> \n> I agree that there is adequate context, so I would be ok with the\n> simplification if there was corresponding code simplification e.g.\n> dropping \"if (origin)\". But in its current form, I don't think there is\n> good enough reason to simplify the message.\n\nI think the proper point of comparison is not the original code, but the\ncode from V5 where we try to preserve the same level of detail in output\nas the original code. If we are committed to both having multiple\nremotes and keeping similar styles of output as the original\nimplementation, then something like the massive conditional in V5 is\nunavoidable.\n\n> Of course, IIUC, this is as simple as dropping 'from' in the \"if\n> (origin)\" case.\n> \n> > @@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >  \tstrbuf_release(&key);\n> >  \terror(_(\"Unable to write upstream branch configuration\"));\n> >  \n> > -\tadvise(_(tracking_advice),\n> > -\t       origin ? origin : \"\",\n> > -\t       origin ? \"/\" : \"\",\n> > -\t       shortname ? shortname : remote);\n> > +\tadvise(_(\"\\nAfter fixing the error cause you may try to fix up\\n\"\n> > +\t\t\"the remote tracking information by invoking:\"));\n> > +\tif (remotes->nr == 1)\n> > +\t\tadvise(\"  git branch --set-upstream-to=%s%s%s\",\n> > +\t\t\torigin ? origin : \"\",\n> > +\t\t\torigin ? \"/\" : \"\",\n> > +\t\t\tremotes->items[0].string);\n> > +\telse\n> > +\t\tfor_each_string_list_item(item, remotes)\n> > +\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n> > +\t\t\t\tlocal, item->string);\n> \n> The advice is now correct, nice! Does the user also need to run \"git\n> config --add branch.%s.remote %s\" ?\n> \n> [1] https://lore.kernel.org/git/Ybk6QsMdeBl6IweW@google.com\n\nYes, thank you for the catch. Fixed in V7.\n"},{"id":"444326","messageId":"Ybwb8O2iLERz4Bmu@google.com","threadId":"56468","inReplyTo":"kl6llf0k8ckj.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 2/3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:11:12Z","receivedAt":"2021-12-17T05:11:20Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.16 13:27, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> > @@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> >  \t\t\t   enum branch_track track, int quiet)\n> >  {\n> >  \tstruct tracking tracking;\n> > +\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n> >  \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n> >  \n> >  \tmemset(&tracking, 0, sizeof(tracking));\n> >  \ttracking.spec.dst = (char *)orig_ref;\n> > -\tif (for_each_remote(find_tracked_branch, &tracking))\n> > +\ttracking.srcs = &tracking_srcs;\n> > +\tif (track != BRANCH_TRACK_INHERIT)\n> > +\t\tfor_each_remote(find_tracked_branch, &tracking);\n> > +\telse if (inherit_tracking(&tracking, orig_ref))\n> >  \t\treturn;\n> >  \n> >  \tif (!tracking.matches)\n> > @@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> >  \t\tcase BRANCH_TRACK_ALWAYS:\n> >  \t\tcase BRANCH_TRACK_EXPLICIT:\n> >  \t\tcase BRANCH_TRACK_OVERRIDE:\n> > +\t\tcase BRANCH_TRACK_INHERIT:\n> >  \t\t\tbreak;\n> >  \t\tdefault:\n> >  \t\t\treturn;\n> > @@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n> >  \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n> >  \t\t    orig_ref);\n> >  \n> > -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n> > -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n> > +\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n> > +\t\tstring_list_append(tracking.srcs, orig_ref);\n> \n> So, in the BRANCH_TRACK_{ALWAYS,EXPLICIT,OVERRIDE} cases, we append\n> orig_ref because we expect orig_ref to be a local ref that the caller\n> wants to track. This is not the case with BRANCH_TRACK_INHERIT, where we\n> want to inherit the configuration and we no longer care about orig_ref.\n> \n> This is correct, though it's more unobvious than what I originally\n> envisioned when I commented on [1]. As a small nit, it might benefit\n> from a clarifying comment, but this is fine as it is :)\n\nActually, you're right, the `track != BRANCH_TRACK_INHERIT` condition is\nsuperfluous. The only way we could have BRANCH_TRACK_INHERIT and\ntracking.srcs->nr < 1 at the same time would be if there were no\n\"branch.*.merge\" entries in the config to inherit, but if that were true\nthen we would have had returned from this function earlier.\n\n> > diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\n> > index 4453741b96..49c7def21c 100755\n> > --- a/t/t2027-checkout-track.sh\n> > +++ b/t/t2027-checkout-track.sh\n> > @@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n> >  \ttest_i18ngrep \"cannot be used with updating paths\" err\n> >  '\n> >  \n> > +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n> > +\t# Set up tracking config on main\n> > +\ttest_config branch.main.remote origin &&\n> > +\ttest_config branch.main.merge refs/heads/main &&\n> > +\ttest_config branch.autoSetupMerge inherit &&\n> > +\t# With --track=inherit, we copy the tracking config from main\n> > +\tgit checkout --track=inherit -b b1 main &&\n> > +\ttest_cmp_config origin branch.b1.remote &&\n> > +\ttest_cmp_config refs/heads/main branch.b1.merge &&\n> > +\t# With branch.autoSetupMerge=inherit, we do the same\n> > +\tgit checkout -b b2 main &&\n> > +\ttest_cmp_config origin branch.b2.remote &&\n> > +\ttest_cmp_config refs/heads/main branch.b2.merge &&\n> > +\t# But --track overrides this\n> > +\tgit checkout --track -b b3 main &&\n> > +\ttest_cmp_config . branch.b3.remote &&\n> > +\ttest_cmp_config refs/heads/main branch.b3.merge &&\n> > +\t# And --track=direct does as well\n> > +\tgit checkout --track=direct -b b4 main &&\n> > +\ttest_cmp_config . branch.b4.remote &&\n> > +\ttest_cmp_config refs/heads/main branch.b4.merge\n> \n> Nit: in both cases, the expected result is that branch.b*.merge is\n> \"refs/heads/main\". so the difference between --track=direct and\n> --track=inherit would be more obvious if main tracked something other\n> than origin/main.\n\nFixed in V7.\n\n\n> As an side, the comments in the tests make it really readable :)\n> \n> Overall this patch looks good.\n> \n> [1] https://lore.kernel.org/git/kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com\n"},{"id":"444327","messageId":"cover.1639717481.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v7 0/3] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:12:05Z","receivedAt":"2021-12-17T05:12:12Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Changes since V6:\n* Strip the refs/heads/ prefix in the verbose output when we have only a\n  single upstream branch.\n* Improve the fatal error message to note that rebasing is only\n  incompatible with multiple upstream refs.\n* Also note that `branch.<name>.remote` should be set in the manual\n  recovery advice.\n* Simplify the logic in setup_tracking() when no tracking sources match.\n* Make the difference in test cases in t2027 more obvious.\n\nChanges since V5:\n* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n  so many conditionals.\n* Note that rebasing is not compatible with inheriting multiple upstream\n  branches.\n* Moved the change to case-sensitivity for branch.autosetupmerge to its\n  own commit.\n* Improve advice on failed tracking setup when multiple branches are\n  involved.\n* Make better use of string_list API.\n* Make better use of config API.\n* More straight-forward use of the `struct tracking` API.\n* Numerous style fixes.\n\nChanges since V4:\n* Add new patch (1/2) to refactor branch.c:install_branch_config() to\n  accept multiple upstream refs\n* When multiple upstream branches are set in the parent branch, inherit\n  them all, instead of just the first\n* Break out error string arguments for easier translation\n* Don't ignore case for values of branch.autosetupmerge\n* Move reference to git-pull out of usage string for --track into\n  git-branch.txt\n* Use test_config instead of `git config` in t2027\n* Style fixes: add single-quotes around warning string arguments, remove\n  unnecessary braces\n\nChanges since V3:\n* Use branch_get() instead of git_config_get_string() to look up branch\n  configuration.\n* Remove unnecessary string formatting in new error message in\n  parse-options-cb.c.\n\nJosh Steadmon (3):\n  branch: accept multiple upstream branches for tracking\n  branch: add flags and config to inherit tracking\n  config: require lowercase for branch.*.autosetupmerge\n\n Documentation/config/branch.txt |   3 +-\n Documentation/git-branch.txt    |  24 ++--\n Documentation/git-checkout.txt  |   2 +-\n Documentation/git-switch.txt    |   2 +-\n branch.c                        | 189 ++++++++++++++++++++++++--------\n branch.h                        |   3 +-\n builtin/branch.c                |   6 +-\n builtin/checkout.c              |   6 +-\n config.c                        |   5 +-\n parse-options-cb.c              |  16 +++\n parse-options.h                 |   2 +\n t/t2017-checkout-orphan.sh      |  11 +-\n t/t2027-checkout-track.sh       |  23 ++++\n t/t2060-switch.sh               |  28 +++++\n t/t3200-branch.sh               |  39 ++++++-\n t/t7201-co.sh                   |  17 +++\n 16 files changed, 310 insertions(+), 66 deletions(-)\n\nRange-diff against v6:\n1:  43d6f83fed ! 1:  9152367ba9 branch: accept multiple upstream branches for tracking\n    @@ branch.c: static int should_setup_rebase(const char *origin)\n     +\tif (!remotes->nr)\n     +\t\tBUG(\"must provide at least one remote for branch config\");\n     +\tif (rebasing && remotes->nr > 1)\n    -+\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n    ++\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n    ++\t\t      \"multiple refs when rebasing is requested\"));\n     +\n     +\tif (!origin)\n     +\t\tfor_each_string_list_item(item, remotes)\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n     -\t\t\t\t\t  local, shortname);\n     +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n    -+\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n    ++\t\t\tskip_prefix(remotes->items[0].string, \"refs/heads/\", &name);\n    ++\t\t\tstrbuf_addstr(&refname, name);\n     +\n     +\t\t\t/*\n     +\t\t\t * Rebasing is only allowed in the case of a single\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     +\t\t\torigin ? origin : \"\",\n     +\t\t\torigin ? \"/\" : \"\",\n     +\t\t\tremotes->items[0].string);\n    -+\telse\n    ++\telse {\n    ++\t\tadvise(\"  git config --add branch.\\\"%s\\\".remote %s\",\n    ++\t\t\tlocal, origin ? origin : \".\");\n     +\t\tfor_each_string_list_item(item, remotes)\n     +\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n     +\t\t\t\tlocal, item->string);\n    ++\t}\n      \n      \treturn -1;\n      }\n2:  57e57e6e6a ! 2:  afeb84539e branch: add flags and config to inherit tracking\n    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n      \n     -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n     -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n    -+\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n    ++\tif (tracking.srcs->nr < 1)\n     +\t\tstring_list_append(tracking.srcs, orig_ref);\n    -+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n    -+\t\t\t      tracking.srcs) < 0)\n    ++\tif (install_branch_config_multiple_remotes(config_flags, new_ref,\n    ++\t\t\t\ttracking.remote, tracking.srcs) < 0)\n      \t\texit(-1);\n      \n     -\tfree(tracking.src);\n    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e\n     +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n     +\t# Set up tracking config on main\n     +\ttest_config branch.main.remote origin &&\n    -+\ttest_config branch.main.merge refs/heads/main &&\n    ++\ttest_config branch.main.merge refs/heads/some-branch &&\n     +\ttest_config branch.autoSetupMerge inherit &&\n     +\t# With --track=inherit, we copy the tracking config from main\n     +\tgit checkout --track=inherit -b b1 main &&\n     +\ttest_cmp_config origin branch.b1.remote &&\n    -+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n    ++\ttest_cmp_config refs/heads/some-branch branch.b1.merge &&\n     +\t# With branch.autoSetupMerge=inherit, we do the same\n     +\tgit checkout -b b2 main &&\n     +\ttest_cmp_config origin branch.b2.remote &&\n    -+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n    ++\ttest_cmp_config refs/heads/some-branch branch.b2.merge &&\n     +\t# But --track overrides this\n     +\tgit checkout --track -b b3 main &&\n     +\ttest_cmp_config . branch.b3.remote &&\n3:  f79d27dc24 = 3:  a818a6561b config: require lowercase for branch.*.autosetupmerge\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444328","messageId":"9152367ba9e8e4285d691cd3b6ec6956b599f93c.1639717481.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639717481.git.steadmon@google.com","subject":"[PATCH v7 1/3] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:12:06Z","receivedAt":"2021-12-17T05:12:15Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Add a new static variant of install_branch_config() that accepts\nmultiple remote branch names for tracking. This will be used in an\nupcoming commit that enables inheriting the tracking configuration from\na parent branch.\n\nCurrently, all callers of install_branch_config() pass only a single\nremote. Make install_branch_config() a small wrapper around\ninstall_branch_config_multiple_remotes() so that existing callers do not\nneed to be changed.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n branch.c          | 140 +++++++++++++++++++++++++++++++++-------------\n t/t3200-branch.sh |   6 +-\n 2 files changed, 104 insertions(+), 42 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..9e57c54848 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -49,25 +49,42 @@ static int should_setup_rebase(const char *origin)\n \treturn 0;\n }\n \n-static const char tracking_advice[] =\n-N_(\"\\n\"\n-\"After fixing the error cause you may try to fix up\\n\"\n-\"the remote tracking information by invoking\\n\"\n-\"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n-\n-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n+/**\n+ * Install upstream tracking configuration for a branch; specifically, add\n+ * `branch.<name>.remote` and `branch.<name>.merge` entries.\n+ *\n+ * `flag` contains integer flags for options; currently only\n+ * BRANCH_CONFIG_VERBOSE is checked.\n+ *\n+ * `local` is the name of the branch whose configuration we're installing.\n+ *\n+ * `origin` is the name of the remote owning the upstream branches. NULL means\n+ * the upstream branches are local to this repo.\n+ *\n+ * `remotes` is a list of refs that are upstream of local\n+ */\n+static int install_branch_config_multiple_remotes(int flag, const char *local,\n+\t\tconst char *origin, struct string_list *remotes)\n {\n \tconst char *shortname = NULL;\n \tstruct strbuf key = STRBUF_INIT;\n+\tstruct string_list_item *item;\n \tint rebasing = should_setup_rebase(origin);\n \n-\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n-\t    && !strcmp(local, shortname)\n-\t    && !origin) {\n-\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n-\t\t\tlocal);\n-\t\treturn 0;\n-\t}\n+\tif (!remotes->nr)\n+\t\tBUG(\"must provide at least one remote for branch config\");\n+\tif (rebasing && remotes->nr > 1)\n+\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n+\t\t      \"multiple refs when rebasing is requested\"));\n+\n+\tif (!origin)\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n+\t\t\t    && !strcmp(local, shortname)) {\n+\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n+\t\t\t\t\tlocal);\n+\t\t\t\treturn 0;\n+\t\t\t}\n \n \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n@@ -75,8 +92,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \n \tstrbuf_reset(&key);\n \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n-\tif (git_config_set_gently(key.buf, remote) < 0)\n+\t/*\n+\t * We want to overwrite any existing config with all the branches in\n+\t * \"remotes\". Override any existing config, then write our branches. If\n+\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n+\t * we've written so far.\n+\t */\n+\tif (git_config_set_gently(key.buf, NULL) < 0)\n \t\tgoto out_err;\n+\tfor_each_string_list_item(item, remotes)\n+\t\tif (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)\n+\t\t\tgoto out_err;\n \n \tif (rebasing) {\n \t\tstrbuf_reset(&key);\n@@ -87,29 +113,43 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \n \tif (flag & BRANCH_CONFIG_VERBOSE) {\n-\t\tif (shortname) {\n+\t\tconst char *name;\n+\t\tstruct strbuf ref_string = STRBUF_INIT;\n+\n+\t\tfor_each_string_list_item(item, remotes) {\n+\t\t\tname = item->string;\n+\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n+\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n+\t\t}\n+\n+\t\tif (remotes->nr == 1) {\n+\t\t\tstruct strbuf refname = STRBUF_INIT;\n+\n \t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n-\t\t\t\t\t  local, shortname, origin);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n-\t\t\t\t\t  local, shortname);\n+\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n+\t\t\tskip_prefix(remotes->items[0].string, \"refs/heads/\", &name);\n+\t\t\tstrbuf_addstr(&refname, name);\n+\n+\t\t\t/*\n+\t\t\t * Rebasing is only allowed in the case of a single\n+\t\t\t * upstream branch.\n+\t\t\t */\n+\t\t\tprintf_ln(rebasing ?\n+\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n+\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n+\t\t\t\tlocal, refname.buf);\n+\n+\t\t\tstrbuf_release(&refname);\n+\t\t} else if (origin) {\n+\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n+\t\t\t\tlocal, origin);\n+\t\t\tprintf(\"%s\", ref_string.buf);\n \t\t} else {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n+\t\t\tprintf(\"%s\", ref_string.buf);\n \t\t}\n+\n+\t\tstrbuf_release(&ref_string);\n \t}\n \n \treturn 0;\n@@ -118,14 +158,36 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \terror(_(\"Unable to write upstream branch configuration\"));\n \n-\tadvise(_(tracking_advice),\n-\t       origin ? origin : \"\",\n-\t       origin ? \"/\" : \"\",\n-\t       shortname ? shortname : remote);\n+\tadvise(_(\"\\nAfter fixing the error cause you may try to fix up\\n\"\n+\t\t\"the remote tracking information by invoking:\"));\n+\tif (remotes->nr == 1)\n+\t\tadvise(\"  git branch --set-upstream-to=%s%s%s\",\n+\t\t\torigin ? origin : \"\",\n+\t\t\torigin ? \"/\" : \"\",\n+\t\t\tremotes->items[0].string);\n+\telse {\n+\t\tadvise(\"  git config --add branch.\\\"%s\\\".remote %s\",\n+\t\t\tlocal, origin ? origin : \".\");\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n+\t\t\t\tlocal, item->string);\n+\t}\n \n \treturn -1;\n }\n \n+int install_branch_config(int flag, const char *local, const char *origin,\n+\t\tconst char *remote)\n+{\n+\tint ret;\n+\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n+\n+\tstring_list_append(&remotes, remote);\n+\tret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n+\tstring_list_clear(&remotes, 0);\n+\treturn ret;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..4b0ef35913 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '\n \ttest_must_fail git branch --set-upstream origin/main\n '\n \n-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '\n+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' \"\n \tgit branch --set-upstream-to refs/heads/my13 my13 2>actual &&\n \tcat >expect <<-\\EOF &&\n-\twarning: Not setting branch my13 as its own upstream.\n+\twarning: not setting branch 'my13' as its own upstream.\n \tEOF\n \ttest_expect_code 1 git config branch.my13.remote &&\n \ttest_expect_code 1 git config branch.my13.merge &&\n \ttest_cmp expect actual\n-'\n+\"\n \n # Keep this test last, as it changes the current branch\n cat >expect <<EOF\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444330","messageId":"afeb84539e6fd4bab0f8a01f8a38f07cbcb85cbe.1639717481.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639717481.git.steadmon@google.com","subject":"[PATCH v7 2/3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:12:07Z","receivedAt":"2021-12-17T05:12:18Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Documentation/config/branch.txt |  3 +-\n Documentation/git-branch.txt    | 24 +++++++++++-----\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 49 ++++++++++++++++++++++++++++-----\n branch.h                        |  3 +-\n builtin/branch.c                |  6 ++--\n builtin/checkout.c              |  6 ++--\n config.c                        |  3 ++\n parse-options-cb.c              | 16 +++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      | 11 +++++++-\n t/t2027-checkout-track.sh       | 23 ++++++++++++++++\n t/t2060-switch.sh               | 28 +++++++++++++++++++\n t/t3200-branch.sh               | 33 ++++++++++++++++++++++\n t/t7201-co.sh                   | 17 ++++++++++++\n 16 files changed, 205 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..c8b393e51c 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n++\n+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on\n+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex 9e57c54848..5e712ad95b 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -11,7 +11,7 @@\n \n struct tracking {\n \tstruct refspec_item spec;\n-\tchar *src;\n+\tstruct string_list *srcs;\n \tconst char *remote;\n \tint matches;\n };\n@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tif (++tracking->matches == 1) {\n-\t\t\ttracking->src = tracking->spec.src;\n+\t\t\tstring_list_append(tracking->srcs, tracking->spec.src);\n \t\t\ttracking->remote = remote->name;\n \t\t} else {\n \t\t\tfree(tracking->spec.src);\n-\t\t\tFREE_AND_NULL(tracking->src);\n+\t\t\tstring_list_clear(tracking->srcs, 0);\n \t\t}\n \t\ttracking->spec.src = NULL;\n \t}\n@@ -188,6 +188,34 @@ int install_branch_config(int flag, const char *local, const char *origin,\n \treturn ret;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tconst char *bare_ref;\n+\tstruct branch *branch;\n+\tint i;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tbranch = branch_get(bare_ref);\n+\tif (!branch->remote_name) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = xstrdup(branch->remote_name);\n+\tfor (i = 0; i < branch->merge_nr; i++)\n+\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -197,11 +225,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n {\n \tstruct tracking tracking;\n+\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\ttracking.srcs = &tracking_srcs;\n+\tif (track != BRANCH_TRACK_INHERIT)\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\telse if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\n@@ -209,6 +241,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_ALWAYS:\n \t\tcase BRANCH_TRACK_EXPLICIT:\n \t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n \t\t\treturn;\n@@ -218,11 +251,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n \t\t    orig_ref);\n \n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n+\tif (tracking.srcs->nr < 1)\n+\t\tstring_list_append(tracking.srcs, orig_ref);\n+\tif (install_branch_config_multiple_remotes(config_flags, new_ref,\n+\t\t\t\ttracking.remote, tracking.srcs) < 0)\n \t\texit(-1);\n \n-\tfree(tracking.src);\n+\tstring_list_clear(tracking.srcs, 0);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..815dcd40c0 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT,\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..ebde5023c3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set branch tracking configuration\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..152c94f29d 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..d346dbe210 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `%s' expects \\\"%s\\\" or \\\"%s\\\"\"),\n+\t\t\t     \"--track\", \"direct\", \"inherit\");\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..4d689bd377 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout main &&\n \tgit config branch.autosetupmerge always &&\n \tgit checkout --orphan gamma &&\n-\ttest -z \"$(git config branch.gamma.merge)\" &&\n+\ttest_cmp_config \"\" --default \"\" branch.gamma.merge &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.merge &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.remote &&\n+\techo refs/heads/eta >expected &&\n+\tgit symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..dca35aa3e3 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\ttest_config branch.main.remote origin &&\n+\ttest_config branch.main.merge refs/heads/some-branch &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/some-branch branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/some-branch branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..ebb961be29 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 4b0ef35913..a049276439 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.merge\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..4fdf88ba46 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444329","messageId":"a818a6561b2cb30f7dff9f52629cbbcf21658740.1639717481.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1639717481.git.steadmon@google.com","subject":"[PATCH v7 3/3] config: require lowercase for branch.*.autosetupmerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-17T05:12:08Z","receivedAt":"2021-12-17T05:12:19Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Although we only documented that branch.*.autosetupmerge would accept\n\"always\" as a value, the actual implementation would accept any\ncombination of upper- or lower-case. Fix this to be consistent with\ndocumentation and with other values of this config variable.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 152c94f29d..54c0e7d98e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)\n static int git_default_branch_config(const char *var, const char *value)\n {\n \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n-\t\tif (value && !strcasecmp(value, \"always\")) {\n+\t\tif (value && !strcmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n \t\t} else if (value && !strcmp(value, \"inherit\")) {\n-- \n2.34.1.173.g76aa8bc2d0-goog\n\n"},{"id":"444513","messageId":"kl6lzgovyvt7.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"Ybwb4UkQwAVRcJp5@google.com","subject":"Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T18:29:08Z","receivedAt":"2021-12-20T18:29:16Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n>> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>> >  \tstrbuf_release(&key);\n>> >  \n>> >  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n>> > -\t\tif (shortname) {\n>> > +\t\tconst char *name;\n>> > +\t\tstruct strbuf ref_string = STRBUF_INIT;\n>> > +\n>> > +\t\tfor_each_string_list_item(item, remotes) {\n>> > +\t\t\tname = item->string;\n>> > +\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n>> > +\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n>> > +\t\t}\n>> > +\n>> > +\t\tif (remotes->nr == 1) {\n>> > +\t\t\tstruct strbuf refname = STRBUF_INIT;\n>> > +\n>> >  \t\t\tif (origin)\n>> > -\t\t\t\tprintf_ln(rebasing ?\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n>> > -\t\t\t\t\t  local, shortname, origin);\n>> > -\t\t\telse\n>> > -\t\t\t\tprintf_ln(rebasing ?\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n>> > -\t\t\t\t\t  local, shortname);\n>> > +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n>> > +\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n>> > +\n>> > +\t\t\t/*\n>> > +\t\t\t * Rebasing is only allowed in the case of a single\n>> > +\t\t\t * upstream branch.\n>> > +\t\t\t */\n>> > +\t\t\tprintf_ln(rebasing ?\n>> > +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n>> > +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n>> > +\t\t\t\tlocal, refname.buf);\n>> > +\n>> > +\t\t\tstrbuf_release(&refname);\n>> > +\t\t} else if (origin) {\n>> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n>> > +\t\t\t\tlocal, origin);\n>> > +\t\t\tprintf(\"%s\", ref_string.buf);\n>> \n>> It's not clear to me why the hint contains the word 'from' when it is a\n>> remote ref...\n>\n> Because in the multiple-branch case, we don't prepend the origin to each\n> ref, so we need to let users know which remote the refs are coming from.\n\nI see. So if I'm reading this correctly, the error message in the remote\ncase would read something like:\n\n  branch 'main' set up to track from 'origin':\n    main\n    topic1\n    topic2\n\nIs there any reason why we couldn't append the origin to the ref to make\nit consistent? I think this could be as simple as:\n\n\n\tfor_each_string_list_item(item, remotes) {\n\t\tname = item->string;\n\t\tskip_prefix(name, \"refs/heads/\", &name);\n\t\t\tif (origin)\n+         strbuf_addf(&ref_string, \"%s/\", origin);\n\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n\t}\n\nand the resulting list could look like:\n\n  branch 'main' set up to track from 'origin':\n    origin/main\n    origin/topic1\n    origin/topic2\n\nThis looks repetitive, but I suggest this because, as I understand it,\nwe are omitting the \"{local,remote} ref\" phrase based on conventions\naround ref names, like \"origin/main\" is probably a remote ref and not an\noddly named local ref. However, when we print the list like so,\n\n  branch 'main' set up to track from 'origin':\n    main\n    topic1\n    topic2\n\nwe now expect the user to understand that 'main', 'topic1' and 'topic2'\nto implicitly have 'origin/' prepended to them. This behavior seems\ninconsistent to me; I'd anticipate most users responding \"Wait, I was\nsupposed to be tracking 'origin' branches right? Why am I looking at\nlocal branches?\". Some users would be able to recover because they can\nfigure out what we mean, but others might just give up.\n\nPrepending 'origin/' would get rid of this problem altogether, and it\nwould let us drop the 'from'.\n\n>> >  \t\t} else {\n>> > -\t\t\tif (origin)\n>> > -\t\t\t\tprintf_ln(rebasing ?\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n>> > -\t\t\t\t\t  local, remote);\n>> > -\t\t\telse\n>> > -\t\t\t\tprintf_ln(rebasing ?\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n>> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n>> > -\t\t\t\t\t  local, remote);\n>> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n>> > +\t\t\tprintf(\"%s\", ref_string.buf);\n>> \n>> but does not have the word 'from' when it is a local ref. As far as I\n>> can tell, this is the only difference between remote and local refs, and\n>> adding the word 'from' does not seem like a good enough reason to add an\n>> 'if' condition. Maybe I missed something here?\n>> \n>> This motivates my answer to the question you asked in [1]:\n>> \n>>   I removed as many distinctions as possible, as most can still be\n>>   inferred from context. [...] Likewise, we don't need to specify whether\n>>   refs are remote or local: \"some-remote/some-branch\" vs.\n>>   \"a-local-branch\" should be understandable without us spelling it out.\n>> \n>> I agree that there is adequate context, so I would be ok with the\n>> simplification if there was corresponding code simplification e.g.\n>> dropping \"if (origin)\". But in its current form, I don't think there is\n>> good enough reason to simplify the message.\n>\n> I think the proper point of comparison is not the original code, but the\n> code from V5 where we try to preserve the same level of detail in output\n> as the original code. If we are committed to both having multiple\n> remotes and keeping similar styles of output as the original\n> implementation, then something like the massive conditional in V5 is\n> unavoidable.\n\nI see. So for instance, post-simplification you have:\n\n  printf_ln(rebasing ?\n    _(\"branch '%s' set up to track '%s' by rebasing.\") :\n    _(\"branch '%s' set up to track '%s'.\"),\n    local, refname.buf);\n\nif you preserve the same amount of detail as before, you'd have to\ndistinguish between local/remote, which doubles the number of cases to\n4, which is why the conditional v5 is so complicated.\n\nThat said, I think that it's already much simpler than v5 because you've\nsplit the singular and plural cases. I wonder if you have considered\nbuilding the final string purely from format strings, like:\n\n  char *message_format = _(\"branch %s set up to track %s%s%s%s\");\n  char *ref_type_clause = origin ? \" remote ref \" : \" local ref \";\n  char *rebasing_clause = rebasing ? \" by rebasing.\" : \".\";\n  char *branch_names = \"<branch names>\";\n  printf_ln(message_format, local, ref_type_clause, branch_names, rebasing_clause);\n\nThis sounds potentially unfriendly to i18n, but it would make the\nconditional simpler. What do you think?\n"},{"id":"444540","messageId":"kl6llf0fyolb.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"cover.1639717481.git.steadmon@google.com","subject":"Re: [PATCH v7 0/3] branch: inherit tracking configs","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-20T21:05:04Z","receivedAt":"2021-12-20T21:05:07Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Changes since V6:\n> * Strip the refs/heads/ prefix in the verbose output when we have only a\n>   single upstream branch.\n> * Improve the fatal error message to note that rebasing is only\n>   incompatible with multiple upstream refs.\n> * Also note that `branch.<name>.remote` should be set in the manual\n>   recovery advice.\n> * Simplify the logic in setup_tracking() when no tracking sources match.\n> * Make the difference in test cases in t2027 more obvious.\n>\n> Changes since V5:\n> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n>   so many conditionals.\n> * Note that rebasing is not compatible with inheriting multiple upstream\n>   branches.\n> * Moved the change to case-sensitivity for branch.autosetupmerge to its\n>   own commit.\n> * Improve advice on failed tracking setup when multiple branches are\n>   involved.\n> * Make better use of string_list API.\n> * Make better use of config API.\n> * More straight-forward use of the `struct tracking` API.\n> * Numerous style fixes.\n>\n> Changes since V4:\n> * Add new patch (1/2) to refactor branch.c:install_branch_config() to\n>   accept multiple upstream refs\n> * When multiple upstream branches are set in the parent branch, inherit\n>   them all, instead of just the first\n> * Break out error string arguments for easier translation\n> * Don't ignore case for values of branch.autosetupmerge\n> * Move reference to git-pull out of usage string for --track into\n>   git-branch.txt\n> * Use test_config instead of `git config` in t2027\n> * Style fixes: add single-quotes around warning string arguments, remove\n>   unnecessary braces\n>\n> Changes since V3:\n> * Use branch_get() instead of git_config_get_string() to look up branch\n>   configuration.\n> * Remove unnecessary string formatting in new error message in\n>   parse-options-cb.c.\n>\n> Josh Steadmon (3):\n>   branch: accept multiple upstream branches for tracking\n>   branch: add flags and config to inherit tracking\n>   config: require lowercase for branch.*.autosetupmerge\n>\n>  Documentation/config/branch.txt |   3 +-\n>  Documentation/git-branch.txt    |  24 ++--\n>  Documentation/git-checkout.txt  |   2 +-\n>  Documentation/git-switch.txt    |   2 +-\n>  branch.c                        | 189 ++++++++++++++++++++++++--------\n>  branch.h                        |   3 +-\n>  builtin/branch.c                |   6 +-\n>  builtin/checkout.c              |   6 +-\n>  config.c                        |   5 +-\n>  parse-options-cb.c              |  16 +++\n>  parse-options.h                 |   2 +\n>  t/t2017-checkout-orphan.sh      |  11 +-\n>  t/t2027-checkout-track.sh       |  23 ++++\n>  t/t2060-switch.sh               |  28 +++++\n>  t/t3200-branch.sh               |  39 ++++++-\n>  t/t7201-co.sh                   |  17 +++\n>  16 files changed, 310 insertions(+), 66 deletions(-)\n>\n> Range-diff against v6:\n> 1:  43d6f83fed ! 1:  9152367ba9 branch: accept multiple upstream branches for tracking\n>     @@ branch.c: static int should_setup_rebase(const char *origin)\n>      +\tif (!remotes->nr)\n>      +\t\tBUG(\"must provide at least one remote for branch config\");\n>      +\tif (rebasing && remotes->nr > 1)\n>     -+\t\tdie(_(\"cannot inherit upstream tracking configuration when rebasing is requested\"));\n>     ++\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n>     ++\t\t      \"multiple refs when rebasing is requested\"));\n>      +\n>      +\tif (!origin)\n>      +\t\tfor_each_string_list_item(item, remotes)\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n>      -\t\t\t\t\t  local, shortname);\n>      +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n>     -+\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n>     ++\t\t\tskip_prefix(remotes->items[0].string, \"refs/heads/\", &name);\n>     ++\t\t\tstrbuf_addstr(&refname, name);\n>      +\n>      +\t\t\t/*\n>      +\t\t\t * Rebasing is only allowed in the case of a single\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>      +\t\t\torigin ? origin : \"\",\n>      +\t\t\torigin ? \"/\" : \"\",\n>      +\t\t\tremotes->items[0].string);\n>     -+\telse\n>     ++\telse {\n>     ++\t\tadvise(\"  git config --add branch.\\\"%s\\\".remote %s\",\n>     ++\t\t\tlocal, origin ? origin : \".\");\n>      +\t\tfor_each_string_list_item(item, remotes)\n>      +\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n>      +\t\t\t\tlocal, item->string);\n>     ++\t}\n>       \n>       \treturn -1;\n>       }\n> 2:  57e57e6e6a ! 2:  afeb84539e branch: add flags and config to inherit tracking\n>     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,\n>       \n>      -\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n>      -\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n>     -+\tif (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)\n>     ++\tif (tracking.srcs->nr < 1)\n>      +\t\tstring_list_append(tracking.srcs, orig_ref);\n>     -+\tif (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,\n>     -+\t\t\t      tracking.srcs) < 0)\n>     ++\tif (install_branch_config_multiple_remotes(config_flags, new_ref,\n>     ++\t\t\t\ttracking.remote, tracking.srcs) < 0)\n>       \t\texit(-1);\n>       \n>      -\tfree(tracking.src);\n>     @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e\n>      +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n>      +\t# Set up tracking config on main\n>      +\ttest_config branch.main.remote origin &&\n>     -+\ttest_config branch.main.merge refs/heads/main &&\n>     ++\ttest_config branch.main.merge refs/heads/some-branch &&\n>      +\ttest_config branch.autoSetupMerge inherit &&\n>      +\t# With --track=inherit, we copy the tracking config from main\n>      +\tgit checkout --track=inherit -b b1 main &&\n>      +\ttest_cmp_config origin branch.b1.remote &&\n>     -+\ttest_cmp_config refs/heads/main branch.b1.merge &&\n>     ++\ttest_cmp_config refs/heads/some-branch branch.b1.merge &&\n>      +\t# With branch.autoSetupMerge=inherit, we do the same\n>      +\tgit checkout -b b2 main &&\n>      +\ttest_cmp_config origin branch.b2.remote &&\n>     -+\ttest_cmp_config refs/heads/main branch.b2.merge &&\n>     ++\ttest_cmp_config refs/heads/some-branch branch.b2.merge &&\n>      +\t# But --track overrides this\n>      +\tgit checkout --track -b b3 main &&\n>      +\ttest_cmp_config . branch.b3.remote &&\n> 3:  f79d27dc24 = 3:  a818a6561b config: require lowercase for branch.*.autosetupmerge\n>\n> base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n> -- \n> 2.34.1.173.g76aa8bc2d0-goog\n\nThanks! As noted in v6, I don't have strong opinions about omitting\n'local/remote' from the help message, but I suspect others might. It\nwould be nice to get more opinions here.\n\nv7 looks pretty good, it addresses all of the feedback on v6.\nUnfortunately, I spotted some things _after_ you had already sent out v7\n(my bad). That feedback is in [1].\n\nWe can ignore my feedback on 'simplifying' the conditional with a\nformat string. The more I think about it, the more impossible i18n\nseems.\n\nThis just leaves the feedback on the message when inheriting from\nmultiple remote-tracking branches, i.e. prepending 'origin/' to the refs\nin:\n\n  branch 'main' set up to track from 'origin':\n    main\n    topic1\n    topic2\n \n[1] https://lore.kernel.org/git/kl6lzgovyvt7.fsf@chooglen-macbookpro.roam.corp.google.com\n"},{"id":"444576","messageId":"YcFJrCBHeCNCyTMF@google.com","threadId":"56468","inReplyTo":"kl6lzgovyvt7.fsf@chooglen-macbookpro.roam.corp.google.com","subject":"Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-21T03:27:40Z","receivedAt":"2021-12-21T03:27:50Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"On 2021.12.20 10:29, Glen Choo wrote:\n> Josh Steadmon <steadmon@google.com> writes:\n> \n> >> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n> >> >  \tstrbuf_release(&key);\n> >> >  \n> >> >  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> >> > -\t\tif (shortname) {\n> >> > +\t\tconst char *name;\n> >> > +\t\tstruct strbuf ref_string = STRBUF_INIT;\n> >> > +\n> >> > +\t\tfor_each_string_list_item(item, remotes) {\n> >> > +\t\t\tname = item->string;\n> >> > +\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n> >> > +\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n> >> > +\t\t}\n> >> > +\n> >> > +\t\tif (remotes->nr == 1) {\n> >> > +\t\t\tstruct strbuf refname = STRBUF_INIT;\n> >> > +\n> >> >  \t\t\tif (origin)\n> >> > -\t\t\t\tprintf_ln(rebasing ?\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n> >> > -\t\t\t\t\t  local, shortname, origin);\n> >> > -\t\t\telse\n> >> > -\t\t\t\tprintf_ln(rebasing ?\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n> >> > -\t\t\t\t\t  local, shortname);\n> >> > +\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n> >> > +\t\t\tstrbuf_addstr(&refname, remotes->items[0].string);\n> >> > +\n> >> > +\t\t\t/*\n> >> > +\t\t\t * Rebasing is only allowed in the case of a single\n> >> > +\t\t\t * upstream branch.\n> >> > +\t\t\t */\n> >> > +\t\t\tprintf_ln(rebasing ?\n> >> > +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n> >> > +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n> >> > +\t\t\t\tlocal, refname.buf);\n> >> > +\n> >> > +\t\t\tstrbuf_release(&refname);\n> >> > +\t\t} else if (origin) {\n> >> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n> >> > +\t\t\t\tlocal, origin);\n> >> > +\t\t\tprintf(\"%s\", ref_string.buf);\n> >> \n> >> It's not clear to me why the hint contains the word 'from' when it is a\n> >> remote ref...\n> >\n> > Because in the multiple-branch case, we don't prepend the origin to each\n> > ref, so we need to let users know which remote the refs are coming from.\n> \n> I see. So if I'm reading this correctly, the error message in the remote\n> case would read something like:\n> \n>   branch 'main' set up to track from 'origin':\n>     main\n>     topic1\n>     topic2\n> \n> Is there any reason why we couldn't append the origin to the ref to make\n> it consistent? I think this could be as simple as:\n> \n> \n> \tfor_each_string_list_item(item, remotes) {\n> \t\tname = item->string;\n> \t\tskip_prefix(name, \"refs/heads/\", &name);\n> \t\t\tif (origin)\n> +         strbuf_addf(&ref_string, \"%s/\", origin);\n> \t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n> \t}\n> \n> and the resulting list could look like:\n> \n>   branch 'main' set up to track from 'origin':\n>     origin/main\n>     origin/topic1\n>     origin/topic2\n> \n> This looks repetitive, but I suggest this because, as I understand it,\n> we are omitting the \"{local,remote} ref\" phrase based on conventions\n> around ref names, like \"origin/main\" is probably a remote ref and not an\n> oddly named local ref. However, when we print the list like so,\n> \n>   branch 'main' set up to track from 'origin':\n>     main\n>     topic1\n>     topic2\n> \n> we now expect the user to understand that 'main', 'topic1' and 'topic2'\n> to implicitly have 'origin/' prepended to them. This behavior seems\n> inconsistent to me; I'd anticipate most users responding \"Wait, I was\n> supposed to be tracking 'origin' branches right? Why am I looking at\n> local branches?\". Some users would be able to recover because they can\n> figure out what we mean, but others might just give up.\n> \n> Prepending 'origin/' would get rid of this problem altogether, and it\n> would let us drop the 'from'.\n\nYeah, I think that's better. Fixed in V7, thanks.\n\n\n> >> >  \t\t} else {\n> >> > -\t\t\tif (origin)\n> >> > -\t\t\t\tprintf_ln(rebasing ?\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n> >> > -\t\t\t\t\t  local, remote);\n> >> > -\t\t\telse\n> >> > -\t\t\t\tprintf_ln(rebasing ?\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n> >> > -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n> >> > -\t\t\t\t\t  local, remote);\n> >> > +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n> >> > +\t\t\tprintf(\"%s\", ref_string.buf);\n> >> \n> >> but does not have the word 'from' when it is a local ref. As far as I\n> >> can tell, this is the only difference between remote and local refs, and\n> >> adding the word 'from' does not seem like a good enough reason to add an\n> >> 'if' condition. Maybe I missed something here?\n> >> \n> >> This motivates my answer to the question you asked in [1]:\n> >> \n> >>   I removed as many distinctions as possible, as most can still be\n> >>   inferred from context. [...] Likewise, we don't need to specify whether\n> >>   refs are remote or local: \"some-remote/some-branch\" vs.\n> >>   \"a-local-branch\" should be understandable without us spelling it out.\n> >> \n> >> I agree that there is adequate context, so I would be ok with the\n> >> simplification if there was corresponding code simplification e.g.\n> >> dropping \"if (origin)\". But in its current form, I don't think there is\n> >> good enough reason to simplify the message.\n> >\n> > I think the proper point of comparison is not the original code, but the\n> > code from V5 where we try to preserve the same level of detail in output\n> > as the original code. If we are committed to both having multiple\n> > remotes and keeping similar styles of output as the original\n> > implementation, then something like the massive conditional in V5 is\n> > unavoidable.\n> \n> I see. So for instance, post-simplification you have:\n> \n>   printf_ln(rebasing ?\n>     _(\"branch '%s' set up to track '%s' by rebasing.\") :\n>     _(\"branch '%s' set up to track '%s'.\"),\n>     local, refname.buf);\n> \n> if you preserve the same amount of detail as before, you'd have to\n> distinguish between local/remote, which doubles the number of cases to\n> 4, which is why the conditional v5 is so complicated.\n> \n> That said, I think that it's already much simpler than v5 because you've\n> split the singular and plural cases. I wonder if you have considered\n> building the final string purely from format strings, like:\n> \n>   char *message_format = _(\"branch %s set up to track %s%s%s%s\");\n>   char *ref_type_clause = origin ? \" remote ref \" : \" local ref \";\n>   char *rebasing_clause = rebasing ? \" by rebasing.\" : \".\";\n>   char *branch_names = \"<branch names>\";\n>   printf_ln(message_format, local, ref_type_clause, branch_names, rebasing_clause);\n> \n> This sounds potentially unfriendly to i18n, but it would make the\n> conditional simpler. What do you think?\n\nYeah, the translation-unfriendliness is why I avoided this approach.\n"},{"id":"444577","messageId":"cover.1640039978.git.steadmon@google.com","threadId":"56468","inReplyTo":"9628d145881cb875f8e284967e10f587b9f686f9.1631126999.git.steadmon@google.com","subject":"[PATCH v8 0/3] branch: inherit tracking configs","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-21T03:30:21Z","receivedAt":"2021-12-21T03:30:29Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Changes since V7:\n* Further simplify verbose output by adding an \"<origin>/\" prefix for\n  remote-tracking upstream refs.\n* Add a comment explaining the self-tracking check & early exit.\n\nChanges since V6:\n* Strip the refs/heads/ prefix in the verbose output when we have only a\n  single upstream branch.\n* Improve the fatal error message to note that rebasing is only\n  incompatible with multiple upstream refs.\n* Also note that `branch.<name>.remote` should be set in the manual\n  recovery advice.\n* Simplify the logic in setup_tracking() when no tracking sources match.\n* Make the difference in test cases in t2027 more obvious.\n\nChanges since V5:\n* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n  so many conditionals.\n* Note that rebasing is not compatible with inheriting multiple upstream\n  branches.\n* Moved the change to case-sensitivity for branch.autosetupmerge to its\n  own commit.\n* Improve advice on failed tracking setup when multiple branches are\n  involved.\n* Make better use of string_list API.\n* Make better use of config API.\n* More straight-forward use of the `struct tracking` API.\n* Numerous style fixes.\n\nChanges since V4:\n* Add new patch (1/2) to refactor branch.c:install_branch_config() to\n  accept multiple upstream refs\n* When multiple upstream branches are set in the parent branch, inherit\n  them all, instead of just the first\n* Break out error string arguments for easier translation\n* Don't ignore case for values of branch.autosetupmerge\n* Move reference to git-pull out of usage string for --track into\n  git-branch.txt\n* Use test_config instead of `git config` in t2027\n* Style fixes: add single-quotes around warning string arguments, remove\n  unnecessary braces\n\nChanges since V3:\n* Use branch_get() instead of git_config_get_string() to look up branch\n  configuration.\n* Remove unnecessary string formatting in new error message in\n  parse-options-cb.c.\n\nJosh Steadmon (3):\n  branch: accept multiple upstream branches for tracking\n  branch: add flags and config to inherit tracking\n  config: require lowercase for branch.*.autosetupmerge\n\n Documentation/config/branch.txt |   3 +-\n Documentation/git-branch.txt    |  24 ++--\n Documentation/git-checkout.txt  |   2 +-\n Documentation/git-switch.txt    |   2 +-\n branch.c                        | 192 ++++++++++++++++++++++++--------\n branch.h                        |   3 +-\n builtin/branch.c                |   6 +-\n builtin/checkout.c              |   6 +-\n config.c                        |   5 +-\n parse-options-cb.c              |  16 +++\n parse-options.h                 |   2 +\n t/t2017-checkout-orphan.sh      |  11 +-\n t/t2027-checkout-track.sh       |  23 ++++\n t/t2060-switch.sh               |  28 +++++\n t/t3200-branch.sh               |  39 ++++++-\n t/t7201-co.sh                   |  17 +++\n 16 files changed, 312 insertions(+), 67 deletions(-)\n\nRange-diff against v7:\n1:  9152367ba9 ! 1:  a5265e1c7f branch: accept multiple upstream branches for tracking\n    @@ branch.c: static int should_setup_rebase(const char *origin)\n     +\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n     +\t\t      \"multiple refs when rebasing is requested\"));\n     +\n    ++\t/*\n    ++\t * If the new branch is trying to track itself, something has gone\n    ++\t * wrong. Warn the user and don't proceed any further.\n    ++\t */\n     +\tif (!origin)\n     +\t\tfor_each_string_list_item(item, remotes)\n     +\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n      \n      \tif (flag & BRANCH_CONFIG_VERBOSE) {\n     -\t\tif (shortname) {\n    -+\t\tconst char *name;\n    -+\t\tstruct strbuf ref_string = STRBUF_INIT;\n    -+\n    -+\t\tfor_each_string_list_item(item, remotes) {\n    -+\t\t\tname = item->string;\n    -+\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n    -+\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n    -+\t\t}\n    -+\n    -+\t\tif (remotes->nr == 1) {\n    -+\t\t\tstruct strbuf refname = STRBUF_INIT;\n    -+\n    - \t\t\tif (origin)\n    +-\t\t\tif (origin)\n     -\t\t\t\tprintf_ln(rebasing ?\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n     -\t\t\t\t\t  local, shortname);\n    -+\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n    -+\t\t\tskip_prefix(remotes->items[0].string, \"refs/heads/\", &name);\n    -+\t\t\tstrbuf_addstr(&refname, name);\n    ++\t\tstruct strbuf tmp_ref_name = STRBUF_INIT;\n    ++\t\tstruct string_list friendly_ref_names = STRING_LIST_INIT_DUP;\n     +\n    ++\t\tfor_each_string_list_item(item, remotes) {\n    ++\t\t\tshortname = item->string;\n    ++\t\t\tskip_prefix(shortname, \"refs/heads/\", &shortname);\n    ++\t\t\tif (origin) {\n    ++\t\t\t\tstrbuf_addf(&tmp_ref_name, \"%s/%s\",\n    ++\t\t\t\t\t    origin, shortname);\n    ++\t\t\t\tstring_list_append_nodup(\n    ++\t\t\t\t\t&friendly_ref_names,\n    ++\t\t\t\t\tstrbuf_detach(&tmp_ref_name, NULL));\n    ++\t\t\t} else {\n    ++\t\t\t\tstring_list_append(\n    ++\t\t\t\t\t&friendly_ref_names, shortname);\n    ++\t\t\t}\n    ++\t\t}\n    ++\n    ++\t\tif (remotes->nr == 1) {\n     +\t\t\t/*\n     +\t\t\t * Rebasing is only allowed in the case of a single\n     +\t\t\t * upstream branch.\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     +\t\t\tprintf_ln(rebasing ?\n     +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n     +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n    -+\t\t\t\tlocal, refname.buf);\n    -+\n    -+\t\t\tstrbuf_release(&refname);\n    -+\t\t} else if (origin) {\n    -+\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n    -+\t\t\t\tlocal, origin);\n    -+\t\t\tprintf(\"%s\", ref_string.buf);\n    ++\t\t\t\tlocal, friendly_ref_names.items[0].string);\n      \t\t} else {\n     -\t\t\tif (origin)\n     -\t\t\t\tprintf_ln(rebasing ?\n    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n     -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n     -\t\t\t\t\t  local, remote);\n     +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n    -+\t\t\tprintf(\"%s\", ref_string.buf);\n    ++\t\t\tfor_each_string_list_item(item, &friendly_ref_names)\n    ++\t\t\t\tprintf_ln(\"  %s\", item->string);\n      \t\t}\n     +\n    -+\t\tstrbuf_release(&ref_string);\n    ++\t\tstring_list_clear(&friendly_ref_names, 0);\n      \t}\n      \n      \treturn 0;\n2:  afeb84539e = 2:  dcba40e2c4 branch: add flags and config to inherit tracking\n3:  a818a6561b = 3:  ae7d27b4be config: require lowercase for branch.*.autosetupmerge\n\nbase-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n-- \n2.34.1.307.g9b7440fafd-goog\n\n"},{"id":"444578","messageId":"a5265e1c7f4dc1a69e85511d2cc3f659cc46788f.1640039978.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1640039978.git.steadmon@google.com","subject":"[PATCH v8 1/3] branch: accept multiple upstream branches for tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-21T03:30:22Z","receivedAt":"2021-12-21T03:30:31Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Add a new static variant of install_branch_config() that accepts\nmultiple remote branch names for tracking. This will be used in an\nupcoming commit that enables inheriting the tracking configuration from\na parent branch.\n\nCurrently, all callers of install_branch_config() pass only a single\nremote. Make install_branch_config() a small wrapper around\ninstall_branch_config_multiple_remotes() so that existing callers do not\nneed to be changed.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n branch.c          | 143 +++++++++++++++++++++++++++++++++-------------\n t/t3200-branch.sh |   6 +-\n 2 files changed, 106 insertions(+), 43 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 7a88a4861e..a41a300f9c 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -49,25 +49,46 @@ static int should_setup_rebase(const char *origin)\n \treturn 0;\n }\n \n-static const char tracking_advice[] =\n-N_(\"\\n\"\n-\"After fixing the error cause you may try to fix up\\n\"\n-\"the remote tracking information by invoking\\n\"\n-\"\\\"git branch --set-upstream-to=%s%s%s\\\".\");\n-\n-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)\n+/**\n+ * Install upstream tracking configuration for a branch; specifically, add\n+ * `branch.<name>.remote` and `branch.<name>.merge` entries.\n+ *\n+ * `flag` contains integer flags for options; currently only\n+ * BRANCH_CONFIG_VERBOSE is checked.\n+ *\n+ * `local` is the name of the branch whose configuration we're installing.\n+ *\n+ * `origin` is the name of the remote owning the upstream branches. NULL means\n+ * the upstream branches are local to this repo.\n+ *\n+ * `remotes` is a list of refs that are upstream of local\n+ */\n+static int install_branch_config_multiple_remotes(int flag, const char *local,\n+\t\tconst char *origin, struct string_list *remotes)\n {\n \tconst char *shortname = NULL;\n \tstruct strbuf key = STRBUF_INIT;\n+\tstruct string_list_item *item;\n \tint rebasing = should_setup_rebase(origin);\n \n-\tif (skip_prefix(remote, \"refs/heads/\", &shortname)\n-\t    && !strcmp(local, shortname)\n-\t    && !origin) {\n-\t\twarning(_(\"Not setting branch %s as its own upstream.\"),\n-\t\t\tlocal);\n-\t\treturn 0;\n-\t}\n+\tif (!remotes->nr)\n+\t\tBUG(\"must provide at least one remote for branch config\");\n+\tif (rebasing && remotes->nr > 1)\n+\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n+\t\t      \"multiple refs when rebasing is requested\"));\n+\n+\t/*\n+\t * If the new branch is trying to track itself, something has gone\n+\t * wrong. Warn the user and don't proceed any further.\n+\t */\n+\tif (!origin)\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n+\t\t\t    && !strcmp(local, shortname)) {\n+\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n+\t\t\t\t\tlocal);\n+\t\t\t\treturn 0;\n+\t\t\t}\n \n \tstrbuf_addf(&key, \"branch.%s.remote\", local);\n \tif (git_config_set_gently(key.buf, origin ? origin : \".\") < 0)\n@@ -75,8 +96,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \n \tstrbuf_reset(&key);\n \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n-\tif (git_config_set_gently(key.buf, remote) < 0)\n+\t/*\n+\t * We want to overwrite any existing config with all the branches in\n+\t * \"remotes\". Override any existing config, then write our branches. If\n+\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n+\t * we've written so far.\n+\t */\n+\tif (git_config_set_gently(key.buf, NULL) < 0)\n \t\tgoto out_err;\n+\tfor_each_string_list_item(item, remotes)\n+\t\tif (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)\n+\t\t\tgoto out_err;\n \n \tif (rebasing) {\n \t\tstrbuf_reset(&key);\n@@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \n \tif (flag & BRANCH_CONFIG_VERBOSE) {\n-\t\tif (shortname) {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n-\t\t\t\t\t  local, shortname, origin);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n-\t\t\t\t\t  local, shortname);\n+\t\tstruct strbuf tmp_ref_name = STRBUF_INIT;\n+\t\tstruct string_list friendly_ref_names = STRING_LIST_INIT_DUP;\n+\n+\t\tfor_each_string_list_item(item, remotes) {\n+\t\t\tshortname = item->string;\n+\t\t\tskip_prefix(shortname, \"refs/heads/\", &shortname);\n+\t\t\tif (origin) {\n+\t\t\t\tstrbuf_addf(&tmp_ref_name, \"%s/%s\",\n+\t\t\t\t\t    origin, shortname);\n+\t\t\t\tstring_list_append_nodup(\n+\t\t\t\t\t&friendly_ref_names,\n+\t\t\t\t\tstrbuf_detach(&tmp_ref_name, NULL));\n+\t\t\t} else {\n+\t\t\t\tstring_list_append(\n+\t\t\t\t\t&friendly_ref_names, shortname);\n+\t\t\t}\n+\t\t}\n+\n+\t\tif (remotes->nr == 1) {\n+\t\t\t/*\n+\t\t\t * Rebasing is only allowed in the case of a single\n+\t\t\t * upstream branch.\n+\t\t\t */\n+\t\t\tprintf_ln(rebasing ?\n+\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n+\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n+\t\t\t\tlocal, friendly_ref_names.items[0].string);\n \t\t} else {\n-\t\t\tif (origin)\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track remote ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n-\t\t\telse\n-\t\t\t\tprintf_ln(rebasing ?\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s' by rebasing.\") :\n-\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n-\t\t\t\t\t  local, remote);\n+\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n+\t\t\tfor_each_string_list_item(item, &friendly_ref_names)\n+\t\t\t\tprintf_ln(\"  %s\", item->string);\n \t\t}\n+\n+\t\tstring_list_clear(&friendly_ref_names, 0);\n \t}\n \n \treturn 0;\n@@ -118,14 +159,36 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n \tstrbuf_release(&key);\n \terror(_(\"Unable to write upstream branch configuration\"));\n \n-\tadvise(_(tracking_advice),\n-\t       origin ? origin : \"\",\n-\t       origin ? \"/\" : \"\",\n-\t       shortname ? shortname : remote);\n+\tadvise(_(\"\\nAfter fixing the error cause you may try to fix up\\n\"\n+\t\t\"the remote tracking information by invoking:\"));\n+\tif (remotes->nr == 1)\n+\t\tadvise(\"  git branch --set-upstream-to=%s%s%s\",\n+\t\t\torigin ? origin : \"\",\n+\t\t\torigin ? \"/\" : \"\",\n+\t\t\tremotes->items[0].string);\n+\telse {\n+\t\tadvise(\"  git config --add branch.\\\"%s\\\".remote %s\",\n+\t\t\tlocal, origin ? origin : \".\");\n+\t\tfor_each_string_list_item(item, remotes)\n+\t\t\tadvise(\"  git config --add branch.\\\"%s\\\".merge %s\",\n+\t\t\t\tlocal, item->string);\n+\t}\n \n \treturn -1;\n }\n \n+int install_branch_config(int flag, const char *local, const char *origin,\n+\t\tconst char *remote)\n+{\n+\tint ret;\n+\tstruct string_list remotes = STRING_LIST_INIT_DUP;\n+\n+\tstring_list_append(&remotes, remote);\n+\tret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);\n+\tstring_list_clear(&remotes, 0);\n+\treturn ret;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex cc4b10236e..4b0ef35913 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '\n \ttest_must_fail git branch --set-upstream origin/main\n '\n \n-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '\n+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' \"\n \tgit branch --set-upstream-to refs/heads/my13 my13 2>actual &&\n \tcat >expect <<-\\EOF &&\n-\twarning: Not setting branch my13 as its own upstream.\n+\twarning: not setting branch 'my13' as its own upstream.\n \tEOF\n \ttest_expect_code 1 git config branch.my13.remote &&\n \ttest_expect_code 1 git config branch.my13.merge &&\n \ttest_cmp expect actual\n-'\n+\"\n \n # Keep this test last, as it changes the current branch\n cat >expect <<EOF\n-- \n2.34.1.307.g9b7440fafd-goog\n\n"},{"id":"444579","messageId":"dcba40e2c4001611f837f612cc5ee7890c653aff.1640039978.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1640039978.git.steadmon@google.com","subject":"[PATCH v8 2/3] branch: add flags and config to inherit tracking","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-21T03:30:23Z","receivedAt":"2021-12-21T03:30:34Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"It can be helpful when creating a new branch to use the existing\ntracking configuration from the branch point. However, there is\ncurrently not a method to automatically do so.\n\nTeach git-{branch,checkout,switch} an \"inherit\" argument to the\n\"--track\" option. When this is set, creating a new branch will cause the\ntracking configuration to default to the configuration of the branch\npoint, if set.\n\nFor example, if branch \"main\" tracks \"origin/main\", and we run\n`git checkout --track=inherit -b feature main`, then branch \"feature\"\nwill track \"origin/main\". Thus, `git status` will show us how far\nahead/behind we are from origin, and `git pull` will pull from origin.\n\nThis is particularly useful when creating branches across many\nsubmodules, such as with `git submodule foreach ...` (or if running with\na patch such as [1], which we use at $job), as it avoids having to\nmanually set tracking info for each submodule.\n\nSince we've added an argument to \"--track\", also add \"--track=direct\" as\nanother way to explicitly get the original \"--track\" behavior (\"--track\"\nwithout an argument still works as well).\n\nFinally, teach branch.autoSetupMerge a new \"inherit\" option. When this\nis set, \"--track=inherit\" becomes the default behavior.\n\n[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n Documentation/config/branch.txt |  3 +-\n Documentation/git-branch.txt    | 24 +++++++++++-----\n Documentation/git-checkout.txt  |  2 +-\n Documentation/git-switch.txt    |  2 +-\n branch.c                        | 49 ++++++++++++++++++++++++++++-----\n branch.h                        |  3 +-\n builtin/branch.c                |  6 ++--\n builtin/checkout.c              |  6 ++--\n config.c                        |  3 ++\n parse-options-cb.c              | 16 +++++++++++\n parse-options.h                 |  2 ++\n t/t2017-checkout-orphan.sh      | 11 +++++++-\n t/t2027-checkout-track.sh       | 23 ++++++++++++++++\n t/t2060-switch.sh               | 28 +++++++++++++++++++\n t/t3200-branch.sh               | 33 ++++++++++++++++++++++\n t/t7201-co.sh                   | 17 ++++++++++++\n 16 files changed, 205 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..55f7522e12 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -7,7 +7,8 @@ branch.autoSetupMerge::\n \tautomatic setup is done; `true` -- automatic setup is done when the\n \tstarting point is a remote-tracking branch; `always` --\n \tautomatic setup is done when the starting point is either a\n-\tlocal branch or remote-tracking\n+\tlocal branch or remote-tracking branch; `inherit` -- if the starting point\n+\thas a tracking configuration, it is copied to the new\n \tbranch. This option defaults to true.\n \n branch.autoSetupRebase::\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 94dc9a54f2..c8b393e51c 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n \n -t::\n---track::\n+--track [inherit|direct]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n-\t`branch.<name>.merge` configuration entries to mark the\n-\tstart-point branch as \"upstream\" from the new branch. This\n+\t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n+\tconfiguration for the new branch. This\n \tconfiguration will tell git to show the relationship between the\n \ttwo branches in `git status` and `git branch -v`. Furthermore,\n \tit directs `git pull` without arguments to pull from the\n \tupstream when the new branch is checked out.\n +\n-This behavior is the default when the start point is a remote-tracking branch.\n+The exact upstream branch is chosen depending on the optional argument:\n+`--track` or `--track direct` means to use the start-point branch itself as the\n+upstream; `--track inherit` means to copy the upstream configuration of the\n+start-point branch.\n++\n+`--track direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\n-start-point is either a local or remote-tracking branch.\n+start-point is either a local or remote-tracking branch. Set it to\n+`inherit` if you want to copy the tracking configuration from the\n+branch point.\n++\n+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on\n+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.\n \n --no-track::\n \tDo not set up \"upstream\" configuration, even if the\n-\tbranch.autoSetupMerge configuration variable is true.\n+\tbranch.autoSetupMerge configuration variable is set.\n \n --set-upstream::\n \tAs this option had confusing syntax, it is no longer supported.\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex b1a6fe4499..a48e1ab62f 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -155,7 +155,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt\nindex 5c438cd505..96dc036ea5 100644\n--- a/Documentation/git-switch.txt\n+++ b/Documentation/git-switch.txt\n@@ -152,7 +152,7 @@ should result in deletion of the path).\n \tattached to a terminal, regardless of `--quiet`.\n \n -t::\n---track::\n+--track [direct|inherit]::\n \tWhen creating a new branch, set up \"upstream\" configuration.\n \t`-c` is implied. See `--track` in linkgit:git-branch[1] for\n \tdetails.\ndiff --git a/branch.c b/branch.c\nindex a41a300f9c..e28c0c86e5 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -11,7 +11,7 @@\n \n struct tracking {\n \tstruct refspec_item spec;\n-\tchar *src;\n+\tstruct string_list *srcs;\n \tconst char *remote;\n \tint matches;\n };\n@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n \tif (!remote_find_tracking(remote, &tracking->spec)) {\n \t\tif (++tracking->matches == 1) {\n-\t\t\ttracking->src = tracking->spec.src;\n+\t\t\tstring_list_append(tracking->srcs, tracking->spec.src);\n \t\t\ttracking->remote = remote->name;\n \t\t} else {\n \t\t\tfree(tracking->spec.src);\n-\t\t\tFREE_AND_NULL(tracking->src);\n+\t\t\tstring_list_clear(tracking->srcs, 0);\n \t\t}\n \t\ttracking->spec.src = NULL;\n \t}\n@@ -189,6 +189,34 @@ int install_branch_config(int flag, const char *local, const char *origin,\n \treturn ret;\n }\n \n+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)\n+{\n+\tconst char *bare_ref;\n+\tstruct branch *branch;\n+\tint i;\n+\n+\tbare_ref = orig_ref;\n+\tskip_prefix(orig_ref, \"refs/heads/\", &bare_ref);\n+\n+\tbranch = branch_get(bare_ref);\n+\tif (!branch->remote_name) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no remote is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\tif (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {\n+\t\twarning(_(\"asked to inherit tracking from '%s', but no merge configuration is set\"),\n+\t\t\tbare_ref);\n+\t\treturn -1;\n+\t}\n+\n+\ttracking->remote = xstrdup(branch->remote_name);\n+\tfor (i = 0; i < branch->merge_nr; i++)\n+\t\tstring_list_append(tracking->srcs, branch->merge_name[i]);\n+\treturn 0;\n+}\n+\n /*\n  * This is called when new_ref is branched off of orig_ref, and tries\n  * to infer the settings for branch.<new_ref>.{remote,merge} from the\n@@ -198,11 +226,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\t\t   enum branch_track track, int quiet)\n {\n \tstruct tracking tracking;\n+\tstruct string_list tracking_srcs = STRING_LIST_INIT_DUP;\n \tint config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;\n \n \tmemset(&tracking, 0, sizeof(tracking));\n \ttracking.spec.dst = (char *)orig_ref;\n-\tif (for_each_remote(find_tracked_branch, &tracking))\n+\ttracking.srcs = &tracking_srcs;\n+\tif (track != BRANCH_TRACK_INHERIT)\n+\t\tfor_each_remote(find_tracked_branch, &tracking);\n+\telse if (inherit_tracking(&tracking, orig_ref))\n \t\treturn;\n \n \tif (!tracking.matches)\n@@ -210,6 +242,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tcase BRANCH_TRACK_ALWAYS:\n \t\tcase BRANCH_TRACK_EXPLICIT:\n \t\tcase BRANCH_TRACK_OVERRIDE:\n+\t\tcase BRANCH_TRACK_INHERIT:\n \t\t\tbreak;\n \t\tdefault:\n \t\t\treturn;\n@@ -219,11 +252,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,\n \t\tdie(_(\"Not tracking: ambiguous information for ref %s\"),\n \t\t    orig_ref);\n \n-\tif (install_branch_config(config_flags, new_ref, tracking.remote,\n-\t\t\t      tracking.src ? tracking.src : orig_ref) < 0)\n+\tif (tracking.srcs->nr < 1)\n+\t\tstring_list_append(tracking.srcs, orig_ref);\n+\tif (install_branch_config_multiple_remotes(config_flags, new_ref,\n+\t\t\t\ttracking.remote, tracking.srcs) < 0)\n \t\texit(-1);\n \n-\tfree(tracking.src);\n+\tstring_list_clear(tracking.srcs, 0);\n }\n \n int read_branch_desc(struct strbuf *buf, const char *branch_name)\ndiff --git a/branch.h b/branch.h\nindex df0be61506..815dcd40c0 100644\n--- a/branch.h\n+++ b/branch.h\n@@ -10,7 +10,8 @@ enum branch_track {\n \tBRANCH_TRACK_REMOTE,\n \tBRANCH_TRACK_ALWAYS,\n \tBRANCH_TRACK_EXPLICIT,\n-\tBRANCH_TRACK_OVERRIDE\n+\tBRANCH_TRACK_OVERRIDE,\n+\tBRANCH_TRACK_INHERIT,\n };\n \n extern enum branch_track git_branch_track;\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex b23b1d1752..ebde5023c3 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\t\tN_(\"set branch tracking configuration\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n \t\tOPT_STRING('u', \"set-upstream-to\", &new_upstream, N_(\"upstream\"), N_(\"change the upstream info\")),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex b5d477919a..45dab414ea 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n-\t\t\tBRANCH_TRACK_EXPLICIT),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n+\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n+\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n \t\tOPT_STRING(0, \"orphan\", &opts->new_orphan_branch, N_(\"new-branch\"), N_(\"new unparented branch\")),\ndiff --git a/config.c b/config.c\nindex cb4a8058bf..152c94f29d 100644\n--- a/config.c\n+++ b/config.c\n@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (value && !strcasecmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n+\t\t} else if (value && !strcmp(value, \"inherit\")) {\n+\t\t\tgit_branch_track = BRANCH_TRACK_INHERIT;\n+\t\t\treturn 0;\n \t\t}\n \t\tgit_branch_track = git_config_bool(var, value);\n \t\treturn 0;\ndiff --git a/parse-options-cb.c b/parse-options-cb.c\nindex 3c811e1e4a..d346dbe210 100644\n--- a/parse-options-cb.c\n+++ b/parse-options-cb.c\n@@ -1,5 +1,6 @@\n #include \"git-compat-util.h\"\n #include \"parse-options.h\"\n+#include \"branch.h\"\n #include \"cache.h\"\n #include \"commit.h\"\n #include \"color.h\"\n@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset\n \n \treturn 0;\n }\n+\n+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)\n+{\n+\tif (unset)\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;\n+\telse if (!arg || !strcmp(arg, \"direct\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;\n+\telse if (!strcmp(arg, \"inherit\"))\n+\t\t*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;\n+\telse\n+\t\treturn error(_(\"option `%s' expects \\\"%s\\\" or \\\"%s\\\"\"),\n+\t\t\t     \"--track\", \"direct\", \"inherit\");\n+\n+\treturn 0;\n+}\ndiff --git a/parse-options.h b/parse-options.h\nindex a845a9d952..f35dbfdd5a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,\n \t\t\t\t\t   const char *, int);\n int parse_opt_passthru(const struct option *, const char *, int);\n int parse_opt_passthru_argv(const struct option *, const char *, int);\n+/* value is enum branch_track* */\n+int parse_opt_tracking_mode(const struct option *, const char *, int);\n \n #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', \"verbose\", (var), (h))\n #define OPT__QUIET(var, h)    OPT_COUNTUP('q', \"quiet\",   (var), (h))\ndiff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh\nindex 88d6992a5e..4d689bd377 100755\n--- a/t/t2017-checkout-orphan.sh\n+++ b/t/t2017-checkout-orphan.sh\n@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '\n \tgit checkout main &&\n \tgit config branch.autosetupmerge always &&\n \tgit checkout --orphan gamma &&\n-\ttest -z \"$(git config branch.gamma.merge)\" &&\n+\ttest_cmp_config \"\" --default \"\" branch.gamma.merge &&\n \ttest refs/heads/gamma = \"$(git symbolic-ref HEAD)\" &&\n+\ttest_must_fail git rev-parse --verify HEAD^ &&\n+\tgit checkout main &&\n+\tgit config branch.autosetupmerge inherit &&\n+\tgit checkout --orphan eta &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.merge &&\n+\ttest_cmp_config \"\" --default \"\" branch.eta.remote &&\n+\techo refs/heads/eta >expected &&\n+\tgit symbolic-ref HEAD >actual &&\n+\ttest_cmp expected actual &&\n \ttest_must_fail git rev-parse --verify HEAD^\n '\n \ndiff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh\nindex 4453741b96..dca35aa3e3 100755\n--- a/t/t2027-checkout-track.sh\n+++ b/t/t2027-checkout-track.sh\n@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '\n \ttest_i18ngrep \"cannot be used with updating paths\" err\n '\n \n+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '\n+\t# Set up tracking config on main\n+\ttest_config branch.main.remote origin &&\n+\ttest_config branch.main.merge refs/heads/some-branch &&\n+\ttest_config branch.autoSetupMerge inherit &&\n+\t# With --track=inherit, we copy the tracking config from main\n+\tgit checkout --track=inherit -b b1 main &&\n+\ttest_cmp_config origin branch.b1.remote &&\n+\ttest_cmp_config refs/heads/some-branch branch.b1.merge &&\n+\t# With branch.autoSetupMerge=inherit, we do the same\n+\tgit checkout -b b2 main &&\n+\ttest_cmp_config origin branch.b2.remote &&\n+\ttest_cmp_config refs/heads/some-branch branch.b2.merge &&\n+\t# But --track overrides this\n+\tgit checkout --track -b b3 main &&\n+\ttest_cmp_config . branch.b3.remote &&\n+\ttest_cmp_config refs/heads/main branch.b3.merge &&\n+\t# And --track=direct does as well\n+\tgit checkout --track=direct -b b4 main &&\n+\ttest_cmp_config . branch.b4.remote &&\n+\ttest_cmp_config refs/heads/main branch.b4.merge\n+'\n+\n test_done\ndiff --git a/t/t2060-switch.sh b/t/t2060-switch.sh\nindex 9bc6a3aa5c..ebb961be29 100755\n--- a/t/t2060-switch.sh\n+++ b/t/t2060-switch.sh\n@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '\n \ttest_must_fail git switch -d @^\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\t# default config does not copy tracking info\n+\tgit switch -c foo-no-inherit foo &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with --track=inherit, we copy tracking info from foo\n+\tgit switch --track=inherit -c foo2 foo &&\n+\ttest_cmp_config origin branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo2.merge &&\n+\t# with autoSetupMerge=inherit, we do the same\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit switch -c foo3 foo &&\n+\ttest_cmp_config origin branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo3.merge &&\n+\t# with --track, we override autoSetupMerge\n+\tgit switch --track -c foo4 foo &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo4.merge &&\n+\t# and --track=direct does as well\n+\tgit switch --track=direct -c foo5 foo &&\n+\ttest_cmp_config . branch.foo5.remote &&\n+\ttest_cmp_config refs/heads/foo branch.foo5.merge &&\n+\t# no tracking info to inherit from main\n+\tgit switch -c main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex 4b0ef35913..a049276439 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '\n \t)\n '\n \n+test_expect_success 'tracking info copied with --track=inherit' '\n+\tgit branch --track=inherit foo2 my1 &&\n+\ttest_cmp_config local branch.foo2.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo2.merge\n+'\n+\n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\ttest_unconfig branch.autoSetupMerge &&\n+\t# default config does not copy tracking info\n+\tgit branch foo-no-inherit my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from my1\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch foo3 my1 &&\n+\ttest_cmp_config local branch.foo3.remote &&\n+\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n+\t# no tracking info to inherit from main\n+\tgit branch main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n+test_expect_success '--track overrides branch.autoSetupMerge' '\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit branch --track=direct foo4 my1 &&\n+\ttest_cmp_config . branch.foo4.remote &&\n+\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n+\tgit branch --no-track foo5 my1 &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo5.merge\n+'\n+\n test_done\ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 7f6e23a4bb..4fdf88ba46 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n \ttest_cmp expect arm\n '\n \n+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n+\tgit reset --hard main &&\n+\t# default config does not copy tracking info\n+\tgit checkout -b foo-no-inherit koala/bear &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n+\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n+\ttest_config branch.autoSetupMerge inherit &&\n+\tgit checkout -b foo koala/bear &&\n+\ttest_cmp_config origin branch.foo.remote &&\n+\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n+\t# no tracking info to inherit from main\n+\tgit checkout -b main2 main &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n+\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n+'\n+\n test_done\n-- \n2.34.1.307.g9b7440fafd-goog\n\n"},{"id":"444580","messageId":"ae7d27b4be1e9b70fc4b7c97524bd6bfbe328270.1640039978.git.steadmon@google.com","threadId":"56468","inReplyTo":"cover.1640039978.git.steadmon@google.com","subject":"[PATCH v8 3/3] config: require lowercase for branch.*.autosetupmerge","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2021-12-21T03:30:24Z","receivedAt":"2021-12-21T03:30:37Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"Although we only documented that branch.*.autosetupmerge would accept\n\"always\" as a value, the actual implementation would accept any\ncombination of upper- or lower-case. Fix this to be consistent with\ndocumentation and with other values of this config variable.\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n config.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/config.c b/config.c\nindex 152c94f29d..54c0e7d98e 100644\n--- a/config.c\n+++ b/config.c\n@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)\n static int git_default_branch_config(const char *var, const char *value)\n {\n \tif (!strcmp(var, \"branch.autosetupmerge\")) {\n-\t\tif (value && !strcasecmp(value, \"always\")) {\n+\t\tif (value && !strcmp(value, \"always\")) {\n \t\t\tgit_branch_track = BRANCH_TRACK_ALWAYS;\n \t\t\treturn 0;\n \t\t} else if (value && !strcmp(value, \"inherit\")) {\n-- \n2.34.1.307.g9b7440fafd-goog\n\n"},{"id":"444585","messageId":"xmqq1r26sazc.fsf@gitster.g","threadId":"56468","inReplyTo":"a5265e1c7f4dc1a69e85511d2cc3f659cc46788f.1640039978.git.steadmon@google.com","subject":"Re: [PATCH v8 1/3] branch: accept multiple upstream branches for tracking","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-12-21T06:55:35Z","receivedAt":"2021-12-21T06:55:41Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> +/**\n> + * Install upstream tracking configuration for a branch; specifically, add\n> + * `branch.<name>.remote` and `branch.<name>.merge` entries.\n> + *\n> + * `flag` contains integer flags for options; currently only\n> + * BRANCH_CONFIG_VERBOSE is checked.\n> + *\n> + * `local` is the name of the branch whose configuration we're installing.\n> + *\n> + * `origin` is the name of the remote owning the upstream branches. NULL means\n> + * the upstream branches are local to this repo.\n> + *\n> + * `remotes` is a list of refs that are upstream of local\n> + */\n> +static int install_branch_config_multiple_remotes(int flag, const char *local,\n> +\t\tconst char *origin, struct string_list *remotes)\n>  {\n>  \tconst char *shortname = NULL;\n>  \tstruct strbuf key = STRBUF_INIT;\n> +\tstruct string_list_item *item;\n>  \tint rebasing = should_setup_rebase(origin);\n>  \n> +\tif (!remotes->nr)\n> +\t\tBUG(\"must provide at least one remote for branch config\");\n> +\tif (rebasing && remotes->nr > 1)\n> +\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n> +\t\t      \"multiple refs when rebasing is requested\"));\n> +\n> +\t/*\n> +\t * If the new branch is trying to track itself, something has gone\n> +\t * wrong. Warn the user and don't proceed any further.\n> +\t */\n> +\tif (!origin)\n> +\t\tfor_each_string_list_item(item, remotes)\n> +\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n> +\t\t\t    && !strcmp(local, shortname)) {\n> +\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n> +\t\t\t\t\tlocal);\n> +\t\t\t\treturn 0;\n> +\t\t\t}\n\nAdded comments make it easier to follow what is going on and more\nimportantly why.  I very much like it ;-)\n\n> @@ -75,8 +96,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \n>  \tstrbuf_reset(&key);\n>  \tstrbuf_addf(&key, \"branch.%s.merge\", local);\n> -\tif (git_config_set_gently(key.buf, remote) < 0)\n> +\t/*\n> +\t * We want to overwrite any existing config with all the branches in\n> +\t * \"remotes\". Override any existing config, then write our branches. If\n> +\t * more than one is provided, use CONFIG_REGEX_NONE to preserve what\n> +\t * we've written so far.\n> +\t */\n\nSorry, but I do not quite get this comment in two ways.\n\n - It talks about \"if more than one\" but the code seems to use\n   regex-none even when there is only one.\n\n - I agree that use of set-multivar with regex-none is the right way\n   to append new values to multi valued configuration, but wouldn't\n   the sequence be (1) first clear everything and then (2) set new\n   ones one by one?\n\nAhh, OK, the \"config-set\" we do first is the \"clear everything\"\nstep.\n\n\tWe want to overwrite any existing config.  First clear any\n\texisting config, and then write our branches one-by-one.\n\tUse of CONFIG_REGEX_NONE ensures that the multiple values\n\tare appended to the same variable.\n\nperhaps?\n\n> +\tif (git_config_set_gently(key.buf, NULL) < 0)\n>  \t\tgoto out_err;\n> +\tfor_each_string_list_item(item, remotes)\n> +\t\tif (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)\n> +\t\t\tgoto out_err;\n\n> @@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>  \tstrbuf_release(&key);\n>  \n>  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n> +\t\tstruct strbuf tmp_ref_name = STRBUF_INIT;\n> +\t\tstruct string_list friendly_ref_names = STRING_LIST_INIT_DUP;\n> +\n> +\t\tfor_each_string_list_item(item, remotes) {\n> +\t\t\tshortname = item->string;\n> +\t\t\tskip_prefix(shortname, \"refs/heads/\", &shortname);\n> +\t\t\tif (origin) {\n> +\t\t\t\tstrbuf_addf(&tmp_ref_name, \"%s/%s\",\n> +\t\t\t\t\t    origin, shortname);\n> +\t\t\t\tstring_list_append_nodup(\n> +\t\t\t\t\t&friendly_ref_names,\n> +\t\t\t\t\tstrbuf_detach(&tmp_ref_name, NULL));\n> +\t\t\t} else {\n> +\t\t\t\tstring_list_append(\n> +\t\t\t\t\t&friendly_ref_names, shortname);\n> +\t\t\t}\n> +\t\t}\n> +\n> +\t\tif (remotes->nr == 1) {\n> +\t\t\t/*\n> +\t\t\t * Rebasing is only allowed in the case of a single\n> +\t\t\t * upstream branch.\n> +\t\t\t */\n\nThere does not seem to be any code to forbid \"rebasing\" when\nremotes->nr != 1, though.  Did I miss a call to die() earlier?\n\n> +\t\t\tprintf_ln(rebasing ?\n> +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n> +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n> +\t\t\t\tlocal, friendly_ref_names.items[0].string);\n>  \t\t} else {\n> +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n> +\t\t\tfor_each_string_list_item(item, &friendly_ref_names)\n> +\t\t\t\tprintf_ln(\"  %s\", item->string);\n\nIn other words, I would have expected something in this else clause.\n\n>  \t\t}\n\nThanks.\n"},{"id":"444646","messageId":"kl6l7dbxzuzh.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"cover.1640039978.git.steadmon@google.com","subject":"Re: [PATCH v8 0/3] branch: inherit tracking configs","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-21T18:13:54Z","receivedAt":"2021-12-21T18:13:57Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> Changes since V7:\n> * Further simplify verbose output by adding an \"<origin>/\" prefix for\n>   remote-tracking upstream refs.\n> * Add a comment explaining the self-tracking check & early exit.\n>\n> Changes since V6:\n> * Strip the refs/heads/ prefix in the verbose output when we have only a\n>   single upstream branch.\n> * Improve the fatal error message to note that rebasing is only\n>   incompatible with multiple upstream refs.\n> * Also note that `branch.<name>.remote` should be set in the manual\n>   recovery advice.\n> * Simplify the logic in setup_tracking() when no tracking sources match.\n> * Make the difference in test cases in t2027 more obvious.\n>\n> Changes since V5:\n> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly\n>   so many conditionals.\n> * Note that rebasing is not compatible with inheriting multiple upstream\n>   branches.\n> * Moved the change to case-sensitivity for branch.autosetupmerge to its\n>   own commit.\n> * Improve advice on failed tracking setup when multiple branches are\n>   involved.\n> * Make better use of string_list API.\n> * Make better use of config API.\n> * More straight-forward use of the `struct tracking` API.\n> * Numerous style fixes.\n>\n> Changes since V4:\n> * Add new patch (1/2) to refactor branch.c:install_branch_config() to\n>   accept multiple upstream refs\n> * When multiple upstream branches are set in the parent branch, inherit\n>   them all, instead of just the first\n> * Break out error string arguments for easier translation\n> * Don't ignore case for values of branch.autosetupmerge\n> * Move reference to git-pull out of usage string for --track into\n>   git-branch.txt\n> * Use test_config instead of `git config` in t2027\n> * Style fixes: add single-quotes around warning string arguments, remove\n>   unnecessary braces\n>\n> Changes since V3:\n> * Use branch_get() instead of git_config_get_string() to look up branch\n>   configuration.\n> * Remove unnecessary string formatting in new error message in\n>   parse-options-cb.c.\n>\n> Josh Steadmon (3):\n>   branch: accept multiple upstream branches for tracking\n>   branch: add flags and config to inherit tracking\n>   config: require lowercase for branch.*.autosetupmerge\n>\n>  Documentation/config/branch.txt |   3 +-\n>  Documentation/git-branch.txt    |  24 ++--\n>  Documentation/git-checkout.txt  |   2 +-\n>  Documentation/git-switch.txt    |   2 +-\n>  branch.c                        | 192 ++++++++++++++++++++++++--------\n>  branch.h                        |   3 +-\n>  builtin/branch.c                |   6 +-\n>  builtin/checkout.c              |   6 +-\n>  config.c                        |   5 +-\n>  parse-options-cb.c              |  16 +++\n>  parse-options.h                 |   2 +\n>  t/t2017-checkout-orphan.sh      |  11 +-\n>  t/t2027-checkout-track.sh       |  23 ++++\n>  t/t2060-switch.sh               |  28 +++++\n>  t/t3200-branch.sh               |  39 ++++++-\n>  t/t7201-co.sh                   |  17 +++\n>  16 files changed, 312 insertions(+), 67 deletions(-)\n>\n> Range-diff against v7:\n> 1:  9152367ba9 ! 1:  a5265e1c7f branch: accept multiple upstream branches for tracking\n>     @@ branch.c: static int should_setup_rebase(const char *origin)\n>      +\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n>      +\t\t      \"multiple refs when rebasing is requested\"));\n>      +\n>     ++\t/*\n>     ++\t * If the new branch is trying to track itself, something has gone\n>     ++\t * wrong. Warn the user and don't proceed any further.\n>     ++\t */\n>      +\tif (!origin)\n>      +\t\tfor_each_string_list_item(item, remotes)\n>      +\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>       \n>       \tif (flag & BRANCH_CONFIG_VERBOSE) {\n>      -\t\tif (shortname) {\n>     -+\t\tconst char *name;\n>     -+\t\tstruct strbuf ref_string = STRBUF_INIT;\n>     -+\n>     -+\t\tfor_each_string_list_item(item, remotes) {\n>     -+\t\t\tname = item->string;\n>     -+\t\t\tskip_prefix(name, \"refs/heads/\", &name);\n>     -+\t\t\tstrbuf_addf(&ref_string, \"  %s\\n\", name);\n>     -+\t\t}\n>     -+\n>     -+\t\tif (remotes->nr == 1) {\n>     -+\t\t\tstruct strbuf refname = STRBUF_INIT;\n>     -+\n>     - \t\t\tif (origin)\n>     +-\t\t\tif (origin)\n>      -\t\t\t\tprintf_ln(rebasing ?\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.\") :\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track remote branch '%s' from '%s'.\"),\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s' by rebasing.\") :\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track local branch '%s'.\"),\n>      -\t\t\t\t\t  local, shortname);\n>     -+\t\t\t\tstrbuf_addf(&refname, \"%s/\", origin);\n>     -+\t\t\tskip_prefix(remotes->items[0].string, \"refs/heads/\", &name);\n>     -+\t\t\tstrbuf_addstr(&refname, name);\n>     ++\t\tstruct strbuf tmp_ref_name = STRBUF_INIT;\n>     ++\t\tstruct string_list friendly_ref_names = STRING_LIST_INIT_DUP;\n>      +\n>     ++\t\tfor_each_string_list_item(item, remotes) {\n>     ++\t\t\tshortname = item->string;\n>     ++\t\t\tskip_prefix(shortname, \"refs/heads/\", &shortname);\n>     ++\t\t\tif (origin) {\n>     ++\t\t\t\tstrbuf_addf(&tmp_ref_name, \"%s/%s\",\n>     ++\t\t\t\t\t    origin, shortname);\n>     ++\t\t\t\tstring_list_append_nodup(\n>     ++\t\t\t\t\t&friendly_ref_names,\n>     ++\t\t\t\t\tstrbuf_detach(&tmp_ref_name, NULL));\n>     ++\t\t\t} else {\n>     ++\t\t\t\tstring_list_append(\n>     ++\t\t\t\t\t&friendly_ref_names, shortname);\n>     ++\t\t\t}\n>     ++\t\t}\n>     ++\n>     ++\t\tif (remotes->nr == 1) {\n>      +\t\t\t/*\n>      +\t\t\t * Rebasing is only allowed in the case of a single\n>      +\t\t\t * upstream branch.\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>      +\t\t\tprintf_ln(rebasing ?\n>      +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n>      +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n>     -+\t\t\t\tlocal, refname.buf);\n>     -+\n>     -+\t\t\tstrbuf_release(&refname);\n>     -+\t\t} else if (origin) {\n>     -+\t\t\tprintf_ln(_(\"branch '%s' set up to track from '%s':\"),\n>     -+\t\t\t\tlocal, origin);\n>     -+\t\t\tprintf(\"%s\", ref_string.buf);\n>     ++\t\t\t\tlocal, friendly_ref_names.items[0].string);\n>       \t\t} else {\n>      -\t\t\tif (origin)\n>      -\t\t\t\tprintf_ln(rebasing ?\n>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori\n>      -\t\t\t\t\t  _(\"Branch '%s' set up to track local ref '%s'.\"),\n>      -\t\t\t\t\t  local, remote);\n>      +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n>     -+\t\t\tprintf(\"%s\", ref_string.buf);\n>     ++\t\t\tfor_each_string_list_item(item, &friendly_ref_names)\n>     ++\t\t\t\tprintf_ln(\"  %s\", item->string);\n>       \t\t}\n>      +\n>     -+\t\tstrbuf_release(&ref_string);\n>     ++\t\tstring_list_clear(&friendly_ref_names, 0);\n>       \t}\n>       \n>       \treturn 0;\n> 2:  afeb84539e = 2:  dcba40e2c4 branch: add flags and config to inherit tracking\n> 3:  a818a6561b = 3:  ae7d27b4be config: require lowercase for branch.*.autosetupmerge\n>\n> base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee\n> -- \n> 2.34.1.307.g9b7440fafd-goog\n\nThese changes look good to me. I'll leave some suggestions on the\npatches, but those are optional.\n\nI still think it would be nice to get more thoughts on the help message\nchanges, but that's Junio's call to make :)\n\nReviewed-by: Glen Choo <chooglen@google.com>\n"},{"id":"444647","messageId":"kl6l5yrhzutp.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"dcba40e2c4001611f837f612cc5ee7890c653aff.1640039978.git.steadmon@google.com","subject":"Re: [PATCH v8 2/3] branch: add flags and config to inherit tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-21T18:17:22Z","receivedAt":"2021-12-21T18:17:26Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\n> index 4b0ef35913..a049276439 100755\n> --- a/t/t3200-branch.sh\n> +++ b/t/t3200-branch.sh\n>  \n> +test_expect_success 'tracking info copied with --track=inherit' '\n> +\tgit branch --track=inherit foo2 my1 &&\n> +\ttest_cmp_config local branch.foo2.remote &&\n> +\ttest_cmp_config refs/heads/main branch.foo2.merge\n> +'\n> +\n> +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n> +\ttest_unconfig branch.autoSetupMerge &&\n> +\t# default config does not copy tracking info\n> +\tgit branch foo-no-inherit my1 &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n> +\t# with autoSetupMerge=inherit, we copy tracking info from my1\n> +\ttest_config branch.autoSetupMerge inherit &&\n> +\tgit branch foo3 my1 &&\n> +\ttest_cmp_config local branch.foo3.remote &&\n> +\ttest_cmp_config refs/heads/main branch.foo3.merge &&\n> +\t# no tracking info to inherit from main\n> +\tgit branch main2 main &&\n> +\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n> +\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n> +'\n> +\n> +test_expect_success '--track overrides branch.autoSetupMerge' '\n> +\ttest_config branch.autoSetupMerge inherit &&\n> +\tgit branch --track=direct foo4 my1 &&\n> +\ttest_cmp_config . branch.foo4.remote &&\n> +\ttest_cmp_config refs/heads/my1 branch.foo4.merge &&\n> +\tgit branch --no-track foo5 my1 &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo5.remote &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo5.merge\n> +'\n> +\n>  test_done\n> diff --git a/t/t7201-co.sh b/t/t7201-co.sh\n> index 7f6e23a4bb..4fdf88ba46 100755\n> --- a/t/t7201-co.sh\n> +++ b/t/t7201-co.sh\n> @@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '\n>  \ttest_cmp expect arm\n>  '\n>  \n> +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '\n> +\tgit reset --hard main &&\n> +\t# default config does not copy tracking info\n> +\tgit checkout -b foo-no-inherit koala/bear &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.remote &&\n> +\ttest_cmp_config \"\" --default \"\" branch.foo-no-inherit.merge &&\n> +\t# with autoSetupMerge=inherit, we copy tracking info from koala/bear\n> +\ttest_config branch.autoSetupMerge inherit &&\n> +\tgit checkout -b foo koala/bear &&\n> +\ttest_cmp_config origin branch.foo.remote &&\n> +\ttest_cmp_config refs/heads/koala/bear branch.foo.merge &&\n> +\t# no tracking info to inherit from main\n> +\tgit checkout -b main2 main &&\n> +\ttest_cmp_config \"\" --default \"\" branch.main2.remote &&\n> +\ttest_cmp_config \"\" --default \"\" branch.main2.merge\n> +'\n> +\n>  test_done\n> -- \n> 2.34.1.307.g9b7440fafd-goog\n\nAs a suggestion, I don't think we have tests for multiple branch.*.merge\nentries and it would be nice to have some (though I don't think it's\nabsolutely essential).\n"},{"id":"444649","messageId":"kl6l35mlzug1.fsf@chooglen-macbookpro.roam.corp.google.com","threadId":"56468","inReplyTo":"xmqq1r26sazc.fsf@gitster.g","subject":"Re: [PATCH v8 1/3] branch: accept multiple upstream branches for tracking","fromName":"Glen Choo","fromEmail":"chooglen@google.com","sentAt":"2021-12-21T18:25:34Z","receivedAt":"2021-12-21T18:25:38Z","isPatch":true,"sender":{"key":"glencbz@gmail.com","avatar":"https://avatars.githubusercontent.com/u/58092771?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> +/**\n>> + * Install upstream tracking configuration for a branch; specifically, add\n>> + * `branch.<name>.remote` and `branch.<name>.merge` entries.\n>> + *\n>> + * `flag` contains integer flags for options; currently only\n>> + * BRANCH_CONFIG_VERBOSE is checked.\n>> + *\n>> + * `local` is the name of the branch whose configuration we're installing.\n>> + *\n>> + * `origin` is the name of the remote owning the upstream branches. NULL means\n>> + * the upstream branches are local to this repo.\n>> + *\n>> + * `remotes` is a list of refs that are upstream of local\n>> + */\n>> +static int install_branch_config_multiple_remotes(int flag, const char *local,\n>> +\t\tconst char *origin, struct string_list *remotes)\n>>  {\n>>  \tconst char *shortname = NULL;\n>>  \tstruct strbuf key = STRBUF_INIT;\n>> +\tstruct string_list_item *item;\n>>  \tint rebasing = should_setup_rebase(origin);\n>>  \n>> +\tif (!remotes->nr)\n>> +\t\tBUG(\"must provide at least one remote for branch config\");\n>> +\tif (rebasing && remotes->nr > 1)\n>> +\t\tdie(_(\"cannot inherit upstream tracking configuration of \"\n>> +\t\t      \"multiple refs when rebasing is requested\"));\n>> +\n>> +\t/*\n>> +\t * If the new branch is trying to track itself, something has gone\n>> +\t * wrong. Warn the user and don't proceed any further.\n>> +\t */\n>> +\tif (!origin)\n>> +\t\tfor_each_string_list_item(item, remotes)\n>> +\t\t\tif (skip_prefix(item->string, \"refs/heads/\", &shortname)\n>> +\t\t\t    && !strcmp(local, shortname)) {\n>> +\t\t\t\twarning(_(\"not setting branch '%s' as its own upstream.\"),\n>> +\t\t\t\t\tlocal);\n>> +\t\t\t\treturn 0;\n>> +\t\t\t}\n>\n> Added comments make it easier to follow what is going on and more\n> importantly why.  I very much like it ;-)\n\nAgreed! We made a lot of 'why' decisions and I think the comments do a\ngreat job of capturing that.\n\n>> @@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const\n>>  \tstrbuf_release(&key);\n>>  \n>>  \tif (flag & BRANCH_CONFIG_VERBOSE) {\n>> +\t\tstruct strbuf tmp_ref_name = STRBUF_INIT;\n>> +\t\tstruct string_list friendly_ref_names = STRING_LIST_INIT_DUP;\n>> +\n>> +\t\tfor_each_string_list_item(item, remotes) {\n>> +\t\t\tshortname = item->string;\n>> +\t\t\tskip_prefix(shortname, \"refs/heads/\", &shortname);\n>> +\t\t\tif (origin) {\n>> +\t\t\t\tstrbuf_addf(&tmp_ref_name, \"%s/%s\",\n>> +\t\t\t\t\t    origin, shortname);\n>> +\t\t\t\tstring_list_append_nodup(\n>> +\t\t\t\t\t&friendly_ref_names,\n>> +\t\t\t\t\tstrbuf_detach(&tmp_ref_name, NULL));\n>> +\t\t\t} else {\n>> +\t\t\t\tstring_list_append(\n>> +\t\t\t\t\t&friendly_ref_names, shortname);\n>> +\t\t\t}\n>> +\t\t}\n>> +\n>> +\t\tif (remotes->nr == 1) {\n>> +\t\t\t/*\n>> +\t\t\t * Rebasing is only allowed in the case of a single\n>> +\t\t\t * upstream branch.\n>> +\t\t\t */\n>\n> There does not seem to be any code to forbid \"rebasing\" when\n> remotes->nr != 1, though.  Did I miss a call to die() earlier?\n\nThe die() call happens in install_branch_config_multiple_remotes(),\nwhere it belongs.\n\nI think someone who reads this comment will eventually track down the\ndie() call, but it does look a little out of place. Purely as a matter\nof personal taste, I would have expected this comment to be in the\n'else' clause, and it might read something like:\n\n  install_branch_config_multiple_remotes() does not allow rebasing with\n  multiple upstream branches.\n\nbut that's just a suggestion :) This patch looks fine to me.\n\n>\n>> +\t\t\tprintf_ln(rebasing ?\n>> +\t\t\t\t_(\"branch '%s' set up to track '%s' by rebasing.\") :\n>> +\t\t\t\t_(\"branch '%s' set up to track '%s'.\"),\n>> +\t\t\t\tlocal, friendly_ref_names.items[0].string);\n>>  \t\t} else {\n>> +\t\t\tprintf_ln(_(\"branch '%s' set up to track:\"), local);\n>> +\t\t\tfor_each_string_list_item(item, &friendly_ref_names)\n>> +\t\t\t\tprintf_ln(\"  %s\", item->string);\n>\n> In other words, I would have expected something in this else clause.\n>\n>>  \t\t}\n>\n> Thanks.\n"},{"id":"445888","messageId":"220111.86a6g3yqf9.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"dcba40e2c4001611f837f612cc5ee7890c653aff.1640039978.git.steadmon@google.com","subject":"incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-11T01:57:28Z","receivedAt":"2022-01-11T02:09:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Dec 20 2021, Josh Steadmon wrote:\n\n> Since we've added an argument to \"--track\", also add \"--track=direct\" as\n> another way to explicitly get the original \"--track\" behavior (\"--track\"\n> without an argument still works as well).\n> [...]\n> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n\nThe usage info here is correct...\n\n> ---track::\n> +--track [inherit|direct]::\n\n...as is this...\n\n> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> +\t\t\tN_(\"set branch tracking configuration\"),\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n....\n\n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n> -\t\t\tBRANCH_TRACK_EXPLICIT),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n\n... but these are not. I.e. we'll emit:\n\n    -t, --track[=direct|inherit]\n                          set branch tracking configuration\n\nI.e. implying that the valid uses are --track, --track=direct, and\n--trackinherit.\n\nIt looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was\nnever meant for an enumeration of possible values, but for something\nlike N_(\"mode\"). It could be made to support that, but it would require\nsome light patching of the releant bits of parse-options.c.\n\nThe PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to\nuse a string like \"mode\".\n\n> +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n\nAside: In v5 the other reference was changed from using the\n\"git-pull(1)\" manpage syntax. See\nhttps://lore.kernel.org/git/cover.1638859949.git.steadmon@google.com/;\nIt looks like this one should be changed too, but was missed.\n\n> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tparse_opt_tracking_mode),\n\n\n\n"},{"id":"446449","messageId":"3de40324bea6a1dd9bca2654721471e3809e87d8.1642538935.git.steadmon@google.com","threadId":"56468","inReplyTo":"220111.86a6g3yqf9.gmgdl@evledraar.gmail.com","subject":"[PATCH] branch,checkout: fix --track usage strings","fromName":"Josh Steadmon","fromEmail":"steadmon@google.com","sentAt":"2022-01-18T20:49:46Z","receivedAt":"2022-01-18T20:49:51Z","isPatch":true,"sender":{"key":"steadmon@google.com","avatar":"https://avatars.githubusercontent.com/u/2654920?v=4"},"body":"As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a\nlist of allowed parameters is not recommended. Both git-branch and\ngit-checkout were changed in d311566 (branch: add flags and config to\ninherit tracking, 2021-12-20) to use this discouraged combination for\ntheir --track flags.\n\nFix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp\nto simply be \"mode\". Users may discover allowed values in the manual\npages.\n\n[1]: https://lore.kernel.org/git/220111.86a6g3yqf9.gmgdl@evledraar.gmail.com/\n\nSigned-off-by: Josh Steadmon <steadmon@google.com>\n---\n builtin/branch.c   | 4 ++--\n builtin/checkout.c | 6 +++---\n 2 files changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 2251e6a54f..0c8d8a8827 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -638,9 +638,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, N_(\"mode\"),\n \t\t\tN_(\"set branch tracking configuration\"),\n-\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t\tPARSE_OPT_OPTARG,\n \t\t\tparse_opt_tracking_mode),\n \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 94814c37b4..6a5dd2a2a2 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1549,9 +1549,9 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n-\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n-\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, N_(\"mode\"),\n+\t\t\tN_(\"set branch tracking configuration\"),\n+\t\t\tPARSE_OPT_OPTARG,\n \t\t\tparse_opt_tracking_mode),\n \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n \t\t\t   PARSE_OPT_NOCOMPLETE),\n\nbase-commit: b56bd95bbc8f716cb8cbb5fdc18b9b0f00323c6a\n-- \n2.34.1.703.g22d0c6ccf7-goog\n\n"},{"id":"446459","messageId":"xmqqh7a0r87q.fsf@gitster.g","threadId":"56468","inReplyTo":"3de40324bea6a1dd9bca2654721471e3809e87d8.1642538935.git.steadmon@google.com","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-18T22:26:49Z","receivedAt":"2022-01-18T22:26:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Josh Steadmon <steadmon@google.com> writes:\n\n> As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a\n> list of allowed parameters is not recommended. Both git-branch and\n> git-checkout were changed in d311566 (branch: add flags and config to\n> inherit tracking, 2021-12-20) to use this discouraged combination for\n> their --track flags.\n\n(tl;dr) I'll take this as-is and hopefully I can fast-track it in\ntime before tagging -rc2 tomorrow.\n\nHaving said that, here is what parse-options.h describes this flag\nbit:\n\n *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n *\t\t\t\t(i.e. '<argh>') in the help message.\n *\t\t\t\tUseful for options with multiple parameters.\n\nNotice that this bit actually is meant \"for options with multiple\nparameters\"?  The recommendation given to you might not be showing\nthe right way.\n\nLooking at \"git grep -C2 OPT_LITERAL_ARGHELP \\*.c\" output, I suspect\nthat a better solution may be to enclose \"direct|inherit\" in a pair\nof parentheses, i.e. \"(direct|inherit)\".  That mimics the way how\n\"git am --show-current-patch[=(diff|raw)]\" does it.\n\nThen we would show\n\n\t--track[=(direct|inherit)]\n\ninstead of\n\n\t--track[=<mode>]\n\nwhich means that ...\n\n> Fix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp\n> to simply be \"mode\". Users may discover allowed values in the manual\n> pages.\n\n... users won't have to visit the manual page only to find out what\nmodes we support.\n\nIn any case, the lesson should not be lost in the list archive.  To\nhelp future developers from the same trouble, we should leave a note\nto revisit the above description of the flag bit in parse-options.h\nlater, possibly after the 2.35 final, to see if we can improve it\n(both the description and/or the behaviour of the code when it sees\nthe flag bit).\n\nThanks.\n"},{"id":"446492","messageId":"55d5327a-9c6c-7fd8-b540-e710259c0694@web.de","threadId":"56468","inReplyTo":"220111.86a6g3yqf9.gmgdl@evledraar.gmail.com","subject":"Re: incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-19T10:20:08Z","receivedAt":"2022-01-19T10:20:17Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 11.01.22 um 02:57 schrieb Ævar Arnfjörð Bjarmason:\n>\n> On Mon, Dec 20 2021, Josh Steadmon wrote:\n>\n>> Since we've added an argument to \"--track\", also add \"--track=direct\" as\n>> another way to explicitly get the original \"--track\" behavior (\"--track\"\n>> without an argument still works as well).\n>> [...]\n>> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n>> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n>\n> The usage info here is correct...\n\nActually it isn't, because optional arguments need the equal sign.  I.e.\nthis works as expected:\n\n   git branch --track=direct branch\n\nBut this here interprets \"direct\" as a branch name (and branch as a\nstart point):\n\n   git branch --track direct branch\n\nThe usage string could start with:\n\n  'git branch' [--track | --track=direct | --track=inherit | --no-track]\n\n... or the less repetitive:\n\n  'git branch' [--track[=(direct|inherit)] | --no-track]\n\nOptions with required arguments accept either whitespace or an equal\nsign between option name and arg.  With PARSE_OPT_OPTARG we cannot\naccept whitespace because we cannot decide whether the next thing after\nthe option name is an argument or the next parameter.\n\n>\n>> ---track::\n>> +--track [inherit|direct]::\n>\n> ...as is this...\n\nSame here:\n\n   --track[=(direct|inherit)]\n\n>\n>> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n>> -\t\t\tBRANCH_TRACK_EXPLICIT),\n>> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n>> +\t\t\tN_(\"set branch tracking configuration\"),\n>> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n>> +\t\t\tparse_opt_tracking_mode),\n> ....\n>\n>>  \tstruct option options[] = {\n>>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n>> -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n>> -\t\t\tBRANCH_TRACK_EXPLICIT),\n>> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n>\n> ... but these are not. I.e. we'll emit:\n>\n>     -t, --track[=direct|inherit]\n>                           set branch tracking configuration\n>\n> I.e. implying that the valid uses are --track, --track=direct, and\n> --trackinherit.\n\nWell spotted.  It should be specified as \"(direct|inherit)\" (i.e. with\nparens).\n\n> It looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was\n> never meant for an enumeration of possible values, but for something\n> like N_(\"mode\"). It could be made to support that, but it would require\n> some light patching of the releant bits of parse-options.c.\n\nCould you please elaborate that point?  AFAIU PARSE_OPT_OPTARG just\nrequires arguments to be attached with equal signs and there is no\nlimitation regarding the number of possible argument values.\n\n> The PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to\n> use a string like \"mode\".\n\nThat's true.  And it's also enabled automatically if the argument help\nstring contains any of the following characters: ()<>[]|.  So basically\nit's never needed explicitly..\n\n>\n>> +\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n>\n> Aside: In v5 the other reference was changed from using the\n> \"git-pull(1)\" manpage syntax. See\n> https://lore.kernel.org/git/cover.1638859949.git.steadmon@google.com/;\n> It looks like this one should be changed too, but was missed.\n>\n>> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n>> +\t\t\tparse_opt_tracking_mode),\n>\n>\n>\n\n"},{"id":"446493","messageId":"ba624c59-5330-360e-dc4a-432752d22fc1@web.de","threadId":"56468","inReplyTo":"xmqqh7a0r87q.fsf@gitster.g","subject":"[PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-19T10:56:58Z","receivedAt":"2022-01-19T10:57:32Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"The help strings for arguments are enclosed in angle brackets\nautomatically.  E.g. if argh is specified as \"name\", \"--option <name>\"\nis printed, to indicate that users need to supply an actual name.  The\nflag PARSE_OPT_LITERAL_ARGHELP turns this off, so that \"--option name\"\nis printed instead, to indicate that the literal string needs to be\nsupplied -- a rare case.\n\nThis flag is enabled automatically if argh contains special characters\nlike brackets.  The developer is supposed to provide any required angle\nbrackets for more complicated cases.  E.g. if argh is \"<start>,<end>\"\nthen \"--option <start>,<end>\" is printed.\n\nAdd a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.\n\nAlso remove the flag from option definitions for which it's inferred\nautomatically.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\nSomehow I feel this is not enough, but I can't pin down what's\nmissing.\n\n builtin/am.c    | 2 +-\n builtin/push.c  | 2 +-\n parse-options.h | 2 ++\n 3 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex b6be1f1cb1..fa8d28794a 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -2402,7 +2402,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n \t\t{ OPTION_CALLBACK, 0, \"show-current-patch\", &resume.mode,\n \t\t  \"(diff|raw)\",\n \t\t  N_(\"show the patch being applied\"),\n-\t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n+\t\t  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n \t\t  parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n \t\tOPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n \t\t\tN_(\"record the empty patch as an empty commit\"),\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 359db90321..4fa6dfbd09 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -552,7 +552,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n \t\tOPT_BIT('f', \"force\", &flags, N_(\"force updates\"), TRANSPORT_PUSH_FORCE),\n \t\tOPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_(\"<refname>:<expect>\"),\n \t\t\t       N_(\"require old value of ref to be at this value\"),\n-\t\t\t       PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),\n+\t\t\t       PARSE_OPT_OPTARG, parseopt_push_cas_option),\n \t\tOPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,\n \t\t\tN_(\"require remote updates to be integrated locally\"),\n \t\t\tTRANSPORT_PUSH_FORCE_IF_INCLUDES),\ndiff --git a/parse-options.h b/parse-options.h\nindex e22846d3b7..8d089fb3ae 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -108,6 +108,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n  *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n  *\t\t\t\t(i.e. '<argh>') in the help message.\n  *\t\t\t\tUseful for options with multiple parameters.\n+ *\t\t\t\tAutomatically enabled if argh contains any\n+ *\t\t\t\tof the following characters: ()<>[]|\n  *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n  *\t\t\t   by git-completion.bash. This option suppresses that.\n  *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n--\n2.34.1\n"},{"id":"446495","messageId":"220119.867davokff.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"ba624c59-5330-360e-dc4a-432752d22fc1@web.de","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-19T14:41:48Z","receivedAt":"2022-01-19T14:43:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Jan 19 2022, René Scharfe wrote:\n\n> The help strings for arguments are enclosed in angle brackets\n> automatically.  E.g. if argh is specified as \"name\", \"--option <name>\"\n> is printed, to indicate that users need to supply an actual name.  The\n> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that \"--option name\"\n> is printed instead, to indicate that the literal string needs to be\n> supplied -- a rare case.\n>\n> This flag is enabled automatically if argh contains special characters\n> like brackets.  The developer is supposed to provide any required angle\n> brackets for more complicated cases.  E.g. if argh is \"<start>,<end>\"\n> then \"--option <start>,<end>\" is printed.\n>\n> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.\n>\n> Also remove the flag from option definitions for which it's inferred\n> automatically.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n> Somehow I feel this is not enough, but I can't pin down what's\n> missing.\n\nRather than just remove the flag from {am,push}.c and document this it\nwould be better to add it to the optbug() checks in\nparse_options_check().\n\nThat way we'll ensure that these flags won't be redunantly specified, if\nwe care enough...\n"},{"id":"446497","messageId":"d41c3a86-dfc9-b867-0094-632d5f3e07cf@web.de","threadId":"56468","inReplyTo":"CA++g3E-azP3wFTtNkbFdmT7VW3hvULL0WkkAdwfrMb6HDtcXdg@mail.gmail.com","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-19T15:30:30Z","receivedAt":"2022-01-19T15:30:56Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 19.01.22 um 16:15 schrieb Bryce Ekrem:\n> Can you clarify what argh is instead of argc and argv\n> sounds like you said arghelp\n\nargh is a member of struct option, defined in parse-options.h.  It is a\nstring that describes the argument of an option, i.e. it's a short\n\"argument help\" or \"argument hint\".\n\n> On Wed, Jan 19, 2022 at 2:57 AM René Scharfe <l.s.r@web.de <mailto:l.s.r@web.de>> wrote:\n>\n>     The help strings for arguments are enclosed in angle brackets\n>     automatically.  E.g. if argh is specified as \"name\", \"--option <name>\"\n>     is printed, to indicate that users need to supply an actual name.  The\n>     flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that \"--option name\"\n>     is printed instead, to indicate that the literal string needs to be\n>     supplied -- a rare case.\n>\n>     This flag is enabled automatically if argh contains special characters\n>     like brackets.  The developer is supposed to provide any required angle\n>     brackets for more complicated cases.  E.g. if argh is \"<start>,<end>\"\n>     then \"--option <start>,<end>\" is printed.\n>\n>     Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.\n>\n>     Also remove the flag from option definitions for which it's inferred\n>     automatically.\n>\n>     Signed-off-by: René Scharfe <l.s.r@web.de <mailto:l.s.r@web.de>>\n>     ---\n>     Somehow I feel this is not enough, but I can't pin down what's\n>     missing.\n>\n>      builtin/am.c    | 2 +-\n>      builtin/push.c  | 2 +-\n>      parse-options.h | 2 ++\n>      3 files changed, 4 insertions(+), 2 deletions(-)\n>\n>     diff --git a/builtin/am.c b/builtin/am.c\n>     index b6be1f1cb1..fa8d28794a 100644\n>     --- a/builtin/am.c\n>     +++ b/builtin/am.c\n>     @@ -2402,7 +2402,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)\n>                     { OPTION_CALLBACK, 0, \"show-current-patch\", &resume.mode,\n>                       \"(diff|raw)\",\n                        ^^^^^^^^^^^^\nThis is the argh for that particular option (--show-current-patch).\n\n>                       N_(\"show the patch being applied\"),\n>     -                 PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,\n>     +                 PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG,\n>                       parse_opt_show_current_patch, RESUME_SHOW_PATCH },\n>                     OPT_CMDMODE(0, \"allow-empty\", &resume.mode,\n>                             N_(\"record the empty patch as an empty commit\"),\n>     diff --git a/builtin/push.c b/builtin/push.c\n>     index 359db90321..4fa6dfbd09 100644\n>     --- a/builtin/push.c\n>     +++ b/builtin/push.c\n>     @@ -552,7 +552,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)\n>                     OPT_BIT('f', \"force\", &flags, N_(\"force updates\"), TRANSPORT_PUSH_FORCE),\n>                     OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_(\"<refname>:<expect>\"),\n                                                            ^^^^^^^^^^^^^^^^^^^^^^^^\nAnd here's the one for CAS_OPT_NAME, defined elsewhere, (resolves\nto --force-with-lease).\n\n>                                    N_(\"require old value of ref to be at this value\"),\n>     -                              PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),\n>     +                              PARSE_OPT_OPTARG, parseopt_push_cas_option),\n>                     OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,\n>                             N_(\"require remote updates to be integrated locally\"),\n>                             TRANSPORT_PUSH_FORCE_IF_INCLUDES),\n>     diff --git a/parse-options.h b/parse-options.h\n>     index e22846d3b7..8d089fb3ae 100644\n>     --- a/parse-options.h\n>     +++ b/parse-options.h\n\nHere's the comment about argh from parse-options.h:\n\n * `argh`::\n *   token to explain the kind of argument this option wants. Does not\n *   begin in capital letter, and does not end with a full stop.\n *   Should be wrapped by N_() for translation.\n\n\n>     @@ -108,6 +108,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n>       *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n>       *                             (i.e. '<argh>') in the help message.\n>       *                             Useful for options with multiple parameters.\n>     + *                             Automatically enabled if argh contains any\n>     + *                             of the following characters: ()<>[]|\n>       *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n>       *                        by git-completion.bash. This option suppresses that.\n>       *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n>     --\n>     2.34.1\n>\n"},{"id":"446513","messageId":"xmqqlezboakd.fsf@gitster.g","threadId":"56468","inReplyTo":"ba624c59-5330-360e-dc4a-432752d22fc1@web.de","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-19T18:16:34Z","receivedAt":"2022-01-19T18:16:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> The help strings for arguments are enclosed in angle brackets\n> automatically.  E.g. if argh is specified as \"name\", \"--option <name>\"\n> is printed, to indicate that users need to supply an actual name.  The\n> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that \"--option name\"\n> is printed instead, to indicate that the literal string needs to be\n> supplied -- a rare case.\n>\n> This flag is enabled automatically if argh contains special characters\n> like brackets.  The developer is supposed to provide any required angle\n> brackets for more complicated cases.  E.g. if argh is \"<start>,<end>\"\n> then \"--option <start>,<end>\" is printed.\n\nThe above does explain why we want to have this change very well,\nbut at least some of it would help those who are reading the comment\nwe touch.\n\n> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.\n\nBut instead, the addition is only about when the flag bit is turned\non automatically.  Without understanding your\n\n    E.g. if argh is specified as \"name\", \"--option <name>\" is\n    printed, to indicate that users need to supply an actual name.\n\nreaders would not quite get from the current description \"says that\nargh shouldn't be enclosed in brackets\" when/why it is a good idea\nto add the option, I am afraid.\n\n> Also remove the flag from option definitions for which it's inferred\n> automatically.\n\nI am not sure if this is a good move.\n\nBecause these places explicitly gave PARSE_OPT_LITERAL_ARGHELP, it\nwas easy to grep for them when I was trying to find an existing\npractice.\n\nImagine, after we remove these redundant flags, we see a patch that\nwants to change the set of characters that automatically flips the\nflag bit on.  It is clear from the patch text why it helps one\nparticular OPT_STRING() or whatever the same patch adds, but how\nwould you make sure it will not regress _existing_ OPT_WHATEVER()\nthat do not use PARSE_OPT_LITERAL_ARGHELP because their argh happens\nto use the character that wasn't special before?\n\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n> Somehow I feel this is not enough, but I can't pin down what's\n> missing.\n\nLet me try.\n\n>   *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n>   *\t\t\t\t(i.e. '<argh>') in the help message.\n>   *\t\t\t\tUseful for options with multiple parameters.\n> + *\t\t\t\tAutomatically enabled if argh contains any\n> + *\t\t\t\tof the following characters: ()<>[]|\n>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n>   *\t\t\t   by git-completion.bash. This option suppresses that.\n>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n\nPARSE_OPT_LITERAL_ARGHELP: in short-help given by \"git cmd -h\", the\n\t\t\t   \"argh\" member of the struct option is\n\t\t\t   shown in a pair of angle brackets (e.g.\n\t\t\t   \"--option=<argh>\"); this flag tells the\n\t\t\t   machinery not to add these brackets, to\n\t\t\t   give more control to the developer.  E.g.\n\t\t\t   \"<start>,<end>\" given to argh is shown as\n\t\t\t   \"--option=<start>,<end>\".\n\nThat may be a bit too much, but on the other hand, among PARSE_OPT_X\ndescriptions, this is the only one that needs to talk about help\ntext on the argument to the option.\n\nOr we can flip it the other way, perhaps?\n\n\tTell the machinery to give \"argh\" member literally in the\n\tshort-help in \"git cmd -h\" output for the option.  E.g. {\n\t.argh = \"(diff|raw)\", .long_name = \"show\" } would give\n\t\"--show=(diff|raw)\".  Without the flag, \"argh\" is enclosed\n\tin a pair of angle brackets.\n\nI dunno.\n\n\n"},{"id":"446544","messageId":"c6ab4408-1091-4d14-849e-afe5f3053e8b@web.de","threadId":"56468","inReplyTo":"xmqqlezboakd.fsf@gitster.g","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-20T10:30:15Z","receivedAt":"2022-01-20T10:30:24Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 19.01.22 um 19:16 schrieb Junio C Hamano:\n> René Scharfe <l.s.r@web.de> writes:\n>\n>> The help strings for arguments are enclosed in angle brackets\n>> automatically.  E.g. if argh is specified as \"name\", \"--option <name>\"\n>> is printed, to indicate that users need to supply an actual name.  The\n>> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that \"--option name\"\n>> is printed instead, to indicate that the literal string needs to be\n>> supplied -- a rare case.\n>>\n>> This flag is enabled automatically if argh contains special characters\n>> like brackets.  The developer is supposed to provide any required angle\n>> brackets for more complicated cases.  E.g. if argh is \"<start>,<end>\"\n>> then \"--option <start>,<end>\" is printed.\n>\n> The above does explain why we want to have this change very well,\n> but at least some of it would help those who are reading the comment\n> we touch.\n>\n>> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.\n>\n> But instead, the addition is only about when the flag bit is turned\n> on automatically.  Without understanding your\n>\n>     E.g. if argh is specified as \"name\", \"--option <name>\" is\n>     printed, to indicate that users need to supply an actual name.\n>\n> readers would not quite get from the current description \"says that\n> argh shouldn't be enclosed in brackets\" when/why it is a good idea\n> to add the option, I am afraid.\n>\n>> Also remove the flag from option definitions for which it's inferred\n>> automatically.\n>\n> I am not sure if this is a good move.\n>\n> Because these places explicitly gave PARSE_OPT_LITERAL_ARGHELP, it\n> was easy to grep for them when I was trying to find an existing\n> practice.\n>\n> Imagine, after we remove these redundant flags, we see a patch that\n> wants to change the set of characters that automatically flips the\n> flag bit on.  It is clear from the patch text why it helps one\n> particular OPT_STRING() or whatever the same patch adds, but how\n> would you make sure it will not regress _existing_ OPT_WHATEVER()\n> that do not use PARSE_OPT_LITERAL_ARGHELP because their argh happens\n> to use the character that wasn't special before?\n\nBuilding a multi-line regex or going through the output of --help-all of\nall commands or adding a throwaway internal option to just print argh\nwould certainly be much harder.\n\nReducing the set of special characters would be part of a change to the\nnotation for describing options.  Perhaps we'd want to no longer use\ngrouping and thus get rid of parentheses.  That would require updating\nall affected manpages and usage strings as well -- quite a big effort,\nregardless of PARSE_OPT_LITERAL_ARGHELP's grepability.\n\nExtending the set, e.g. to give special meaning to curly brackets, can't\nrely on the explicit flag; all argh strings need to be examined to check\nwhether they become special.\n\n>\n>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>> ---\n>> Somehow I feel this is not enough, but I can't pin down what's\n>> missing.\n>\n> Let me try.\n>\n>>   *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n>>   *\t\t\t\t(i.e. '<argh>') in the help message.\n>>   *\t\t\t\tUseful for options with multiple parameters.\n>> + *\t\t\t\tAutomatically enabled if argh contains any\n>> + *\t\t\t\tof the following characters: ()<>[]|\n>>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n>>   *\t\t\t   by git-completion.bash. This option suppresses that.\n>>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n>\n> PARSE_OPT_LITERAL_ARGHELP: in short-help given by \"git cmd -h\", the\n> \t\t\t   \"argh\" member of the struct option is\n> \t\t\t   shown in a pair of angle brackets (e.g.\n> \t\t\t   \"--option=<argh>\"); this flag tells the\n> \t\t\t   machinery not to add these brackets, to\n> \t\t\t   give more control to the developer.  E.g.\n> \t\t\t   \"<start>,<end>\" given to argh is shown as\n> \t\t\t   \"--option=<start>,<end>\".\n>\n> That may be a bit too much, but on the other hand, among PARSE_OPT_X\n> descriptions, this is the only one that needs to talk about help\n> text on the argument to the option.\n>\n> Or we can flip it the other way, perhaps?\n>\n> \tTell the machinery to give \"argh\" member literally in the\n> \tshort-help in \"git cmd -h\" output for the option.  E.g. {\n> \t.argh = \"(diff|raw)\", .long_name = \"show\" } would give\n> \t\"--show=(diff|raw)\".  Without the flag, \"argh\" is enclosed\n> \tin a pair of angle brackets.\n>\n> I dunno.\n\nNow that I read the whole comment, I think the right place to introduce\nthe automatic brackets is the description of argh some lines up.\n\n--- >8 ---\nSubject: [PATCH 5/5] parse-options: document bracketing of argh\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n parse-options.h | 5 +++++\n 1 file changed, 5 insertions(+)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex e22846d3b7..88d589d159 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n  *   token to explain the kind of argument this option wants. Does not\n  *   begin in capital letter, and does not end with a full stop.\n  *   Should be wrapped by N_() for translation.\n+ *   Is automatically enclosed in brackets when printed, unless it\n+ *   contains any of the following characters: ()<>[]|\n+ *   E.g. \"name\" is shown as \"<name>\" to indicate that a name value\n+ *   needs to be supplied, not the literal string \"name\", but\n+ *   \"<start>,<end>\" and \"(this|that)\" are printed verbatim.\n  *\n  * `help`::\n  *   the short help associated to what the option does.\n--\n2.34.1\n"},{"id":"446556","messageId":"220120.864k5ymx55.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"55d5327a-9c6c-7fd8-b540-e710259c0694@web.de","subject":"Re: incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-20T12:00:22Z","receivedAt":"2022-01-20T12:04:10Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Wed, Jan 19 2022, René Scharfe wrote:\n\n> Am 11.01.22 um 02:57 schrieb Ævar Arnfjörð Bjarmason:\n>>\n>> On Mon, Dec 20 2021, Josh Steadmon wrote:\n>>\n>>> Since we've added an argument to \"--track\", also add \"--track=direct\" as\n>>> another way to explicitly get the original \"--track\" behavior (\"--track\"\n>>> without an argument still works as well).\n>>> [...]\n>>> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]\n>>> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n>>\n>> The usage info here is correct...\n>\n> Actually it isn't, because optional arguments need the equal sign.  I.e.\n> this works as expected:\n>\n>    git branch --track=direct branch\n>\n> But this here interprets \"direct\" as a branch name (and branch as a\n> start point):\n>\n>    git branch --track direct branch\n>\n> The usage string could start with:\n>\n>   'git branch' [--track | --track=direct | --track=inherit | --no-track]\n>\n> ... or the less repetitive:\n>\n>   'git branch' [--track[=(direct|inherit)] | --no-track]\n>\n> Options with required arguments accept either whitespace or an equal\n> sign between option name and arg.  With PARSE_OPT_OPTARG we cannot\n> accept whitespace because we cannot decide whether the next thing after\n> the option name is an argument or the next parameter.\n\nWell spotted. Your downthread patch LGTM (with the small nit I noted\nthat having an optbug() check for this would be even better).\n\n>>\n>>> ---track::\n>>> +--track [inherit|direct]::\n>>\n>> ...as is this...\n>\n> Same here:\n>\n>    --track[=(direct|inherit)]\n>\n>>\n>>> -\t\tOPT_SET_INT('t', \"track\",  &track, N_(\"set up tracking mode (see git-pull(1))\"),\n>>> -\t\t\tBRANCH_TRACK_EXPLICIT),\n>>> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n>>> +\t\t\tN_(\"set branch tracking configuration\"),\n>>> +\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n>>> +\t\t\tparse_opt_tracking_mode),\n>> ....\n>>\n>>>  \tstruct option options[] = {\n>>>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n>>> -\t\tOPT_SET_INT('t', \"track\",  &opts->track, N_(\"set upstream info for new branch\"),\n>>> -\t\t\tBRANCH_TRACK_EXPLICIT),\n>>> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n>>\n>> ... but these are not. I.e. we'll emit:\n>>\n>>     -t, --track[=direct|inherit]\n>>                           set branch tracking configuration\n>>\n>> I.e. implying that the valid uses are --track, --track=direct, and\n>> --trackinherit.\n>\n> Well spotted.  It should be specified as \"(direct|inherit)\" (i.e. with\n> parens).\n\n*nod*\n\n>> It looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was\n>> never meant for an enumeration of possible values, but for something\n>> like N_(\"mode\"). It could be made to support that, but it would require\n>> some light patching of the releant bits of parse-options.c.\n>\n> Could you please elaborate that point?  AFAIU PARSE_OPT_OPTARG just\n> requires arguments to be attached with equal signs and there is no\n> limitation regarding the number of possible argument values.\n\nI'd skimmed the code & -h generation, but see on a second reading that I\nwas just wrong about this.\n\nI.e. I thought it would always misformat alternate args, but as your\ndownthread patch shows where we'll now for \"git am -h\" emit e.g.:\n\n    --show-current-patch[=(diff|raw)]\n\nThe output is now correct (and was before, we were just giving the flag\nrudendantly).\n\n>> The PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to\n>> use a string like \"mode\".\n>\n> That's true.  And it's also enabled automatically if the argument help\n> string contains any of the following characters: ()<>[]|.  So basically\n> it's never needed explicitly..\n"},{"id":"446557","messageId":"220120.86zgnqli9v.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"3de40324bea6a1dd9bca2654721471e3809e87d8.1642538935.git.steadmon@google.com","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-20T12:05:52Z","receivedAt":"2022-01-20T12:10:41Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 18 2022, Josh Steadmon wrote:\n\n> As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a\n> list of allowed parameters is not recommended. Both git-branch and\n> git-checkout were changed in d311566 (branch: add flags and config to\n> inherit tracking, 2021-12-20) to use this discouraged combination for\n> their --track flags.\n>\n> Fix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp\n> to simply be \"mode\". Users may discover allowed values in the manual\n> pages.\n>\n> [1]: https://lore.kernel.org/git/220111.86a6g3yqf9.gmgdl@evledraar.gmail.com/\n>\n> Signed-off-by: Josh Steadmon <steadmon@google.com>\n> ---\n>  builtin/branch.c   | 4 ++--\n>  builtin/checkout.c | 6 +++---\n>  2 files changed, 5 insertions(+), 5 deletions(-)\n>\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 2251e6a54f..0c8d8a8827 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -638,9 +638,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tOPT__VERBOSE(&filter.verbose,\n>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> -\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"direct|inherit\",\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, N_(\"mode\"),\n>  \t\t\tN_(\"set branch tracking configuration\"),\n> -\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\t\tPARSE_OPT_OPTARG,\n>  \t\t\tparse_opt_tracking_mode),\n>  \t\tOPT_SET_INT_F(0, \"set-upstream\", &track, N_(\"do not use\"),\n>  \t\t\tBRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 94814c37b4..6a5dd2a2a2 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1549,9 +1549,9 @@ static struct option *add_common_switch_branch_options(\n>  {\n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> -\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"direct|inherit\",\n> -\t\t\tN_(\"set up tracking mode (see git-pull(1))\"),\n> -\t\t\tPARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, N_(\"mode\"),\n> +\t\t\tN_(\"set branch tracking configuration\"),\n> +\t\t\tPARSE_OPT_OPTARG,\n>  \t\t\tparse_opt_tracking_mode),\n>  \t\tOPT__FORCE(&opts->force, N_(\"force checkout (throw away local modifications)\"),\n>  \t\t\t   PARSE_OPT_NOCOMPLETE),\n>\n> base-commit: b56bd95bbc8f716cb8cbb5fdc18b9b0f00323c6a\n\nAdditional comment: I think this change is correct, as noted in my\njust-sent\nhttps://lore.kernel.org/git/220120.864k5ymx55.gmgdl@evledraar.gmail.com/;\nit would be nice but not necessary to follow-up with an optbug() test as\nnoted in\nhttps://lore.kernel.org/git/220119.867davokff.gmgdl@evledraar.gmail.com/\nthough.\n\nBut to not merely repeat myself, I also saw that we're emitting the\nwrong output from usage_argh() in cases of !PARSE_OPT_NOARG. I.e. we\nneed this fix too:\n    \n    diff --git a/parse-options.c b/parse-options.c\n    index a8283037be9..2be1eabd84e 100644\n    --- a/parse-options.c\n    +++ b/parse-options.c\n    @@ -915,8 +915,10 @@ static int usage_argh(const struct option *opts, FILE *outfile)\n                            s = literal ? \"[=%s]\" : \"[=<%s>]\";\n                    else\n                            s = literal ? \"[%s]\" : \"[<%s>]\";\n    -       else\n    +       else if (opts->flags & PARSE_OPT_NOARG)\n                    s = literal ? \" %s\" : \" <%s>\";\n    +       else\n    +               s = literal ? \"[=]%s\" : \"[=]<%s>\";\n            return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _(\"...\"));\n     }\n\nWith that we'll now emit:\n    \n    $ ./git add -h 2>&1|grep chmod\n        --chmod[=](+|-)x      override the executable bit of the listed files\n\nWhich is correct, as we accept both of:\n\n    git add --chmod +x\n    git add --chmod=+x\n\nBut not:\n\n    $ git add --chmod\n    error: parse-options.c:58: option `chmod' requires a value\n\nBut the usage output stated that the \"=\" was mandatory before.\n"},{"id":"446558","messageId":"877dauy512.fsf@igel.home","threadId":"56468","inReplyTo":"220120.86zgnqli9v.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Andreas Schwab","fromEmail":"schwab@linux-m68k.org","sentAt":"2022-01-20T12:18:17Z","receivedAt":"2022-01-20T12:28:25Z","isPatch":true,"sender":{"key":"schwab@linux-m68k.org","avatar":"https://avatars.githubusercontent.com/u/2175493?v=4"},"body":"On Jan 20 2022, Ævar Arnfjörð Bjarmason wrote:\n\n> With that we'll now emit:\n>     \n>     $ ./git add -h 2>&1|grep chmod\n>         --chmod[=](+|-)x      override the executable bit of the listed files\n\nThat looks like --chmod+x is valid, which isn't.\n\n-- \nAndreas Schwab, schwab@linux-m68k.org\nGPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1\n\"And now for something completely different.\"\n"},{"id":"446559","messageId":"c3c26192-aee9-185a-e559-b8735139e49c@web.de","threadId":"56468","inReplyTo":"220120.864k5ymx55.gmgdl@evledraar.gmail.com","subject":"[PATCH] branch,checkout: fix --track documentation","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-20T12:35:54Z","receivedAt":"2022-01-20T12:36:14Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Document that the accepted variants of the --track option are --track,\n--track=direct, and --track=inherit.  The equal sign in the latter two\ncannot be replaced with whitespace; in general optional arguments need\nto be attached firmly to their option.\n\nPut \"direct\" consistently before \"inherit\", if only for the reasons\nthat the former is the default, explained first in the documentation,\nand comes before the latter alphabetically.\n\nMention both modes in the short help so that readers don't have to look\nthem up in the full documentation.  They are literal strings and thus\nuntranslatable.  PARSE_OPT_LITERAL_ARGHELP is inferred due to the pipe\nand parenthesis characters, so we don't have to provide that flag\nexplicitly.\n\nMention that -t has the same effect as --track and --track=direct.\nThere is no way to specify inherit mode using the short option, because\nshort options generally don't accept optional arguments.\n\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n Documentation/git-branch.txt   | 12 ++++++------\n Documentation/git-checkout.txt |  2 +-\n builtin/branch.c               |  2 +-\n builtin/checkout.c             |  2 +-\n 4 files changed, 9 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\nindex 2d52ae396b..731e340cbc 100644\n--- a/Documentation/git-branch.txt\n+++ b/Documentation/git-branch.txt\n@@ -16,7 +16,7 @@ SYNOPSIS\n \t[--points-at <object>] [--format=<format>]\n \t[(-r | --remotes) | (-a | --all)]\n \t[--list] [<pattern>...]\n-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n+'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]\n 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n 'git branch' --unset-upstream [<branchname>]\n 'git branch' (-m | -M) [<oldbranch>] <newbranch>\n@@ -206,7 +206,7 @@ This option is only applicable in non-verbose mode.\n \tDisplay the full sha1s in the output listing rather than abbreviating them.\n\n -t::\n---track [inherit|direct]::\n+--track[=(direct|inherit)]::\n \tWhen creating a new branch, set up `branch.<name>.remote` and\n \t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n \tconfiguration for the new branch. This\n@@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.\n \tupstream when the new branch is checked out.\n +\n The exact upstream branch is chosen depending on the optional argument:\n-`--track` or `--track direct` means to use the start-point branch itself as the\n-upstream; `--track inherit` means to copy the upstream configuration of the\n-start-point branch.\n+`-t`, `--track`, or `--track=direct` means to use the start-point branch\n+itself as the upstream; `--track=inherit` means to copy the upstream\n+configuration of the start-point branch.\n +\n-`--track direct` is the default when the start point is a remote-tracking branch.\n+`--track=direct` is the default when the start point is a remote-tracking branch.\n Set the branch.autoSetupMerge configuration variable to `false` if you\n want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n were given. Set it to `always` if you want this behavior when the\ndiff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\nindex 2a90ea6cd0..9f37e22e13 100644\n--- a/Documentation/git-checkout.txt\n+++ b/Documentation/git-checkout.txt\n@@ -156,7 +156,7 @@ of it\").\n \tlinkgit:git-branch[1] for details.\n\n -t::\n---track [direct|inherit]::\n+--track[=(direct|inherit)]::\n \tWhen creating a new branch, set up \"upstream\" configuration. See\n \t\"--track\" in linkgit:git-branch[1] for details.\n +\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex 0c8d8a8827..4ce2a24754 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -638,7 +638,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n \t\tOPT__VERBOSE(&filter.verbose,\n \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n-\t\tOPT_CALLBACK_F('t', \"track\",  &track, N_(\"mode\"),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"(direct|inherit)\",\n \t\t\tN_(\"set branch tracking configuration\"),\n \t\t\tPARSE_OPT_OPTARG,\n \t\t\tparse_opt_tracking_mode),\ndiff --git a/builtin/checkout.c b/builtin/checkout.c\nindex 6a5dd2a2a2..0bc2e63510 100644\n--- a/builtin/checkout.c\n+++ b/builtin/checkout.c\n@@ -1549,7 +1549,7 @@ static struct option *add_common_switch_branch_options(\n {\n \tstruct option options[] = {\n \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n-\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, N_(\"mode\"),\n+\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"(direct|inherit)\",\n \t\t\tN_(\"set branch tracking configuration\"),\n \t\t\tPARSE_OPT_OPTARG,\n \t\t\tparse_opt_tracking_mode),\n--\n2.34.1\n"},{"id":"446560","messageId":"220120.86v8yeld77.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"c3c26192-aee9-185a-e559-b8735139e49c@web.de","subject":"Re: [PATCH] branch,checkout: fix --track documentation","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-20T13:57:49Z","receivedAt":"2022-01-20T14:00:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 20 2022, René Scharfe wrote:\n\n> Document that the accepted variants of the --track option are --track,\n> --track=direct, and --track=inherit.  The equal sign in the latter two\n> cannot be replaced with whitespace; in general optional arguments need\n> to be attached firmly to their option.\n>\n> Put \"direct\" consistently before \"inherit\", if only for the reasons\n> that the former is the default, explained first in the documentation,\n> and comes before the latter alphabetically.\n>\n> Mention both modes in the short help so that readers don't have to look\n> them up in the full documentation.  They are literal strings and thus\n> untranslatable.  PARSE_OPT_LITERAL_ARGHELP is inferred due to the pipe\n> and parenthesis characters, so we don't have to provide that flag\n> explicitly.\n>\n> Mention that -t has the same effect as --track and --track=direct.\n> There is no way to specify inherit mode using the short option, because\n> short options generally don't accept optional arguments.\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  Documentation/git-branch.txt   | 12 ++++++------\n>  Documentation/git-checkout.txt |  2 +-\n>  builtin/branch.c               |  2 +-\n>  builtin/checkout.c             |  2 +-\n>  4 files changed, 9 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt\n> index 2d52ae396b..731e340cbc 100644\n> --- a/Documentation/git-branch.txt\n> +++ b/Documentation/git-branch.txt\n> @@ -16,7 +16,7 @@ SYNOPSIS\n>  \t[--points-at <object>] [--format=<format>]\n>  \t[(-r | --remotes) | (-a | --all)]\n>  \t[--list] [<pattern>...]\n> -'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n> +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]\n>  'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]\n>  'git branch' --unset-upstream [<branchname>]\n>  'git branch' (-m | -M) [<oldbranch>] <newbranch>\n> @@ -206,7 +206,7 @@ This option is only applicable in non-verbose mode.\n>  \tDisplay the full sha1s in the output listing rather than abbreviating them.\n>\n>  -t::\n> ---track [inherit|direct]::\n> +--track[=(direct|inherit)]::\n>  \tWhen creating a new branch, set up `branch.<name>.remote` and\n>  \t`branch.<name>.merge` configuration entries to set \"upstream\" tracking\n>  \tconfiguration for the new branch. This\n> @@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.\n>  \tupstream when the new branch is checked out.\n>  +\n>  The exact upstream branch is chosen depending on the optional argument:\n> -`--track` or `--track direct` means to use the start-point branch itself as the\n> -upstream; `--track inherit` means to copy the upstream configuration of the\n> -start-point branch.\n> +`-t`, `--track`, or `--track=direct` means to use the start-point branch\n> +itself as the upstream; `--track=inherit` means to copy the upstream\n> +configuration of the start-point branch.\n>  +\n> -`--track direct` is the default when the start point is a remote-tracking branch.\n> +`--track=direct` is the default when the start point is a remote-tracking branch.\n>  Set the branch.autoSetupMerge configuration variable to `false` if you\n>  want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`\n>  were given. Set it to `always` if you want this behavior when the\n> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt\n> index 2a90ea6cd0..9f37e22e13 100644\n> --- a/Documentation/git-checkout.txt\n> +++ b/Documentation/git-checkout.txt\n> @@ -156,7 +156,7 @@ of it\").\n>  \tlinkgit:git-branch[1] for details.\n>\n>  -t::\n> ---track [direct|inherit]::\n> +--track[=(direct|inherit)]::\n\nThese changes (and the below) all look good to me. Thanks for fixing\nthis.\n\n>  \tWhen creating a new branch, set up \"upstream\" configuration. See\n>  \t\"--track\" in linkgit:git-branch[1] for details.\n\nAs a side-note this \"--track\" reference is incorrect, and has been since\nd3115660b4c (branch: add flags and config to inherit tracking,\n2021-12-20), i.e. it should now mention \"--track[=(direct|inherit)]\".\n\nBut as we're not explicitly cross-linking anything here with the\nrelevant syntax I think leaving it as-is is fine, the user would also\nfind it with a substring search.\n"},{"id":"446561","messageId":"220120.86r192ld3u.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"877dauy512.fsf@igel.home","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-20T14:00:33Z","receivedAt":"2022-01-20T14:02:19Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 20 2022, Andreas Schwab wrote:\n\n> On Jan 20 2022, Ævar Arnfjörð Bjarmason wrote:\n>\n>> With that we'll now emit:\n>>     \n>>     $ ./git add -h 2>&1|grep chmod\n>>         --chmod[=](+|-)x      override the executable bit of the listed files\n>\n> That looks like --chmod+x is valid, which isn't.\n\nIndeed, it should be --chmod(=| )(+|-)x in that \"else\" case.\n\nBut I think the rest of what I pointed out still applies with that\namendmend. Thanks!\n"},{"id":"446589","messageId":"xmqqiluejmcp.fsf@gitster.g","threadId":"56468","inReplyTo":"c6ab4408-1091-4d14-849e-afe5f3053e8b@web.de","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-20T18:25:26Z","receivedAt":"2022-01-20T18:25:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> Now that I read the whole comment, I think the right place to introduce\n> the automatic brackets is the description of argh some lines up.\n>\n> --- >8 ---\n> Subject: [PATCH 5/5] parse-options: document bracketing of argh\n>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  parse-options.h | 5 +++++\n>  1 file changed, 5 insertions(+)\n>\n> diff --git a/parse-options.h b/parse-options.h\n> index e22846d3b7..88d589d159 100644\n> --- a/parse-options.h\n> +++ b/parse-options.h\n> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n>   *   token to explain the kind of argument this option wants. Does not\n>   *   begin in capital letter, and does not end with a full stop.\n>   *   Should be wrapped by N_() for translation.\n> + *   Is automatically enclosed in brackets when printed, unless it\n> + *   contains any of the following characters: ()<>[]|\n> + *   E.g. \"name\" is shown as \"<name>\" to indicate that a name value\n> + *   needs to be supplied, not the literal string \"name\", but\n> + *   \"<start>,<end>\" and \"(this|that)\" are printed verbatim.\n>   *\n>   * `help`::\n>   *   the short help associated to what the option does.\n\nVery nice.\n\nThis version gives the necessary information in (almost) one place.\n\nI said (almost) because \"it contains any of ...\" is not the only way\nto decline the <automatic angle brackets>, and am wondering if it is\nmore helpful to say something like\n\n\t... when printed, unless PARSE_OPT_LITERAL_ARGHELP is set in\n\tthe flags, or it contains any of the following characters: ...\n\nand then shrink the description of the flag bit to\n\n    PARSE_OPT_LITERAL_ARGHELP: controls if `argh` is enclosed in in\n    brackets when shown (see the description on `argh` above).\n\n"},{"id":"446590","messageId":"xmqqbl06jlr6.fsf@gitster.g","threadId":"56468","inReplyTo":"220120.86zgnqli9v.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-20T18:38:21Z","receivedAt":"2022-01-20T18:38:24Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> With that we'll now emit:\n>     \n>     $ ./git add -h 2>&1|grep chmod\n>         --chmod[=](+|-)x      override the executable bit of the listed files\n> ...\n> But the usage output stated that the \"=\" was mandatory before.\n\nI am not sure if it is healthy to be _that_ strict when interpreting\nthe boilerplate elements in the output.  Between \"git add -h\" that\ngives\n\n    (1) git add --chmod( |=)(+|-)x\n    (2) git add --chmod=(+|-)x\n\nI would prefer the latter 10x as much as the former.  The choice\n\"You can give either plus or minus\" is very much what the reader\nmust understand and it is worth reminding in the help.  Compared to\nthat, \"You can use the stuck form that is recommended by gitcli\ndocumentation when giving the argument to the --chmod option, or you\ncan give the argument to the option as a separate command line\nargument\", while technically correct, is not a choice that is worth\ncluttering the output and making it harder to read.\n\nTo put it differently, the choice (+|-) is something the user needs\nto pick correctly to make what they want to happen happen.  On the\nother hand, the choice ( |=) is not.  As this is a boilerplate\nchoice that is shared by any and all options that take an argument,\nonce you are aware that stuck form is recommended but that separate\nform is also accepted, you'd see \"git add --chmod=blah\" in the help\nand would not hesitate to type \"git add --chmod blah\".  And if you\nare not aware of the existence of the alternative, nothing is lost.\nYou can type '=' and see what you want to see happen happen.\n\nNot cluttering the help text with an extra choice that the user does\nnot have to make has a value.\n\n\n"},{"id":"446593","messageId":"xmqqk0eui5sa.fsf@gitster.g","threadId":"56468","inReplyTo":"c3c26192-aee9-185a-e559-b8735139e49c@web.de","subject":"Re: [PATCH] branch,checkout: fix --track documentation","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-20T19:08:37Z","receivedAt":"2022-01-20T19:08:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> Document that the accepted variants of the --track option are --track,\n> --track=direct, and --track=inherit.  The equal sign in the latter two\n> cannot be replaced with whitespace; in general optional arguments need\n> to be attached firmly to their option.\n>\n> Put \"direct\" consistently before \"inherit\", if only for the reasons\n> that the former is the default, explained first in the documentation,\n> and comes before the latter alphabetically.\n\n;-)  I see too many good reasons to modestly say \"if only for\" ;-)\n\n> -'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]\n> +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]\n\nGood.\n\n>  -t::\n> ---track [inherit|direct]::\n> +--track[=(direct|inherit)]::\n\nGood.\n\n> @@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.\n>  \tupstream when the new branch is checked out.\n>  +\n>  The exact upstream branch is chosen depending on the optional argument:\n> -`--track` or `--track direct` means to use the start-point branch itself as the\n> -upstream; `--track inherit` means to copy the upstream configuration of the\n> -start-point branch.\n> +`-t`, `--track`, or `--track=direct` means to use the start-point branch\n> +itself as the upstream; `--track=inherit` means to copy the upstream\n> +configuration of the start-point branch.\n\nWhen \"-x\" and \"--long-x\" both do the same thing, we list both in the\nheading but omit \"-x\" from the text, but in this case I fully agree\nwith the updated text as \"-t\" and \"--track[=...]\" work a bit\ndifferently and there is no way to say \"we want inherit\" with \"-t\".\n\n> -`--track direct` is the default when the start point is a remote-tracking branch.\n> +`--track=direct` is the default when the start point is a remote-tracking branch.\n\nGood.\n\n> diff --git a/builtin/branch.c b/builtin/branch.c\n> index 0c8d8a8827..4ce2a24754 100644\n> --- a/builtin/branch.c\n> +++ b/builtin/branch.c\n> @@ -638,7 +638,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)\n>  \t\tOPT__VERBOSE(&filter.verbose,\n>  \t\t\tN_(\"show hash and subject, give twice for upstream branch\")),\n>  \t\tOPT__QUIET(&quiet, N_(\"suppress informational messages\")),\n> -\t\tOPT_CALLBACK_F('t', \"track\",  &track, N_(\"mode\"),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &track, \"(direct|inherit)\",\n>  \t\t\tN_(\"set branch tracking configuration\"),\n>  \t\t\tPARSE_OPT_OPTARG,\n>  \t\t\tparse_opt_tracking_mode),\n\nGood.\n\n> diff --git a/builtin/checkout.c b/builtin/checkout.c\n> index 6a5dd2a2a2..0bc2e63510 100644\n> --- a/builtin/checkout.c\n> +++ b/builtin/checkout.c\n> @@ -1549,7 +1549,7 @@ static struct option *add_common_switch_branch_options(\n>  {\n>  \tstruct option options[] = {\n>  \t\tOPT_BOOL('d', \"detach\", &opts->force_detach, N_(\"detach HEAD at named commit\")),\n> -\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, N_(\"mode\"),\n> +\t\tOPT_CALLBACK_F('t', \"track\",  &opts->track, \"(direct|inherit)\",\n>  \t\t\tN_(\"set branch tracking configuration\"),\n>  \t\t\tPARSE_OPT_OPTARG,\n>  \t\t\tparse_opt_tracking_mode),\n\nGood.\n\nThanks.\n"},{"id":"446616","messageId":"f6cc8042-a455-6398-9f91-0e64921252e4@web.de","threadId":"56468","inReplyTo":"xmqqiluejmcp.fsf@gitster.g","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"René Scharfe","fromEmail":"l.s.r@web.de","sentAt":"2022-01-21T09:42:58Z","receivedAt":"2022-01-21T09:43:08Z","isPatch":true,"sender":{"key":"l.s.r@web.de","avatar":"https://avatars.githubusercontent.com/u/26122331?v=4"},"body":"Am 20.01.22 um 19:25 schrieb Junio C Hamano:\n> René Scharfe <l.s.r@web.de> writes:\n>\n>> Now that I read the whole comment, I think the right place to introduce\n>> the automatic brackets is the description of argh some lines up.\n>>\n>> --- >8 ---\n>> Subject: [PATCH 5/5] parse-options: document bracketing of argh\n>>\n>> Signed-off-by: René Scharfe <l.s.r@web.de>\n>> ---\n>>  parse-options.h | 5 +++++\n>>  1 file changed, 5 insertions(+)\n>>\n>> diff --git a/parse-options.h b/parse-options.h\n>> index e22846d3b7..88d589d159 100644\n>> --- a/parse-options.h\n>> +++ b/parse-options.h\n>> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n>>   *   token to explain the kind of argument this option wants. Does not\n>>   *   begin in capital letter, and does not end with a full stop.\n>>   *   Should be wrapped by N_() for translation.\n>> + *   Is automatically enclosed in brackets when printed, unless it\n>> + *   contains any of the following characters: ()<>[]|\n>> + *   E.g. \"name\" is shown as \"<name>\" to indicate that a name value\n>> + *   needs to be supplied, not the literal string \"name\", but\n>> + *   \"<start>,<end>\" and \"(this|that)\" are printed verbatim.\n>>   *\n>>   * `help`::\n>>   *   the short help associated to what the option does.\n>\n> Very nice.\n>\n> This version gives the necessary information in (almost) one place.\n>\n> I said (almost) because \"it contains any of ...\" is not the only way\n> to decline the <automatic angle brackets>, and am wondering if it is\n> more helpful to say something like\n>\n> \t... when printed, unless PARSE_OPT_LITERAL_ARGHELP is set in\n> \tthe flags, or it contains any of the following characters: ...\n>\n> and then shrink the description of the flag bit to\n>\n>     PARSE_OPT_LITERAL_ARGHELP: controls if `argh` is enclosed in in\n>     brackets when shown (see the description on `argh` above).\n>\n\nThe idea was to document the base behavior at the top and the effects of\nflags at the bottom.  Blurring this distinction and cross-referencing\ngives a better whole, though, I agree.  It would have helped me find the\nright place to put the list of special chars, for one thing.  So how\nabout this?\n\n--- >8 ---\nSubject: [PATCH v2] parse-options: document bracketing of argh\n\nHelped-by: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: René Scharfe <l.s.r@web.de>\n---\n parse-options.h | 10 +++++++---\n 1 file changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/parse-options.h b/parse-options.h\nindex e22846d3b7..2e801b3c9a 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n  *   token to explain the kind of argument this option wants. Does not\n  *   begin in capital letter, and does not end with a full stop.\n  *   Should be wrapped by N_() for translation.\n+ *   Is enclosed in brackets when printed, unless PARSE_OPT_LITERAL_ARGHELP\n+ *   is set in `flags` or it contains any of the following characters: ()<>[]|\n+ *   E.g. \"name\" is shown as \"<name>\" to indicate that a name value\n+ *   needs to be supplied, not the literal string \"name\", but\n+ *   \"<start>,<end>\" and \"(this|that)\" are printed verbatim.\n  *\n  * `help`::\n  *   the short help associated to what the option does.\n@@ -105,9 +110,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n  *\t\t\t\tnot last it will require an argument.\n  *\t\t\t\tShould not be used with PARSE_OPT_OPTARG.\n  *   PARSE_OPT_NODASH: this option doesn't start with a dash.\n- *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n- *\t\t\t\t(i.e. '<argh>') in the help message.\n- *\t\t\t\tUseful for options with multiple parameters.\n+ *   PARSE_OPT_LITERAL_ARGHELP: says that `argh` shouldn't be enclosed in\n+ *\t\t\t\tbrackets (see `argh` description above).\n  *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n  *\t\t\t   by git-completion.bash. This option suppresses that.\n  *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n--\n2.34.1\n"},{"id":"446620","messageId":"220121.86ee51l3jd.gmgdl@evledraar.gmail.com","threadId":"56468","inReplyTo":"xmqqbl06jlr6.fsf@gitster.g","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2022-01-21T11:27:33Z","receivedAt":"2022-01-21T11:41:17Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Jan 20 2022, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>\n>> With that we'll now emit:\n>>     \n>>     $ ./git add -h 2>&1|grep chmod\n>>         --chmod[=](+|-)x      override the executable bit of the listed files\n>> ...\n>> But the usage output stated that the \"=\" was mandatory before.\n>\n> I am not sure if it is healthy to be _that_ strict when interpreting\n> the boilerplate elements in the output.  Between \"git add -h\" that\n> gives\n>\n>     (1) git add --chmod( |=)(+|-)x\n>     (2) git add --chmod=(+|-)x\n>\n> I would prefer the latter 10x as much as the former.  The choice\n> \"You can give either plus or minus\" is very much what the reader\n> must understand and it is worth reminding in the help.  Compared to\n> that, \"You can use the stuck form that is recommended by gitcli\n> documentation when giving the argument to the --chmod option, or you\n> can give the argument to the option as a separate command line\n> argument\", while technically correct, is not a choice that is worth\n> cluttering the output and making it harder to read.\n>\n> To put it differently, the choice (+|-) is something the user needs\n> to pick correctly to make what they want to happen happen.  On the\n> other hand, the choice ( |=) is not.  As this is a boilerplate\n> choice that is shared by any and all options that take an argument,\n> once you are aware that stuck form is recommended but that separate\n> form is also accepted, you'd see \"git add --chmod=blah\" in the help\n> and would not hesitate to type \"git add --chmod blah\".  And if you\n> are not aware of the existence of the alternative, nothing is lost.\n> You can type '=' and see what you want to see happen happen.\n>\n> Not cluttering the help text with an extra choice that the user does\n> not have to make has a value.\n\nI.e. if we're not going for pedantic accuracy you'd prefer the below\ndiff instead?\n\nI.e. when an option doesn't take an optional argument we're going out of\nour way to say that you can omit the \"=\", but we can instead just\ninclude it and have the the explanation in gitcli(7) about when \"=\" is\noptional suffice.\n\nAlso, with the sh completion if you do \"git add --chm<TAB>\" it expands\nit to \"git add --chmod=\", i.e. the cursor is left after the \"=\" that's\nnot shown in the \"git add -h\". So always including it would be\nconsistent with that.\n    \n    diff --git a/parse-options.c b/parse-options.c\n    index a8283037be9..75c345bb738 100644\n    --- a/parse-options.c\n    +++ b/parse-options.c\n    @@ -916,7 +916,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)\n                    else\n                            s = literal ? \"[%s]\" : \"[<%s>]\";\n            else\n    -               s = literal ? \" %s\" : \" <%s>\";\n    +               s = literal ? \"=%s\" : \"=<%s>\";\n            return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _(\"...\"));\n     }\n     \nJust this patch will make getopts test fail, because we'll need\ne.g. this test_cmp adjusted:\n    \n    + diff -u expect output\n    --- expect      2022-01-21 11:31:17.395492260 +0000\n    +++ output      2022-01-21 11:31:17.395492260 +0000\n    @@ -5,7 +5,7 @@\n     \n         -h, --help            show the help\n         --foo                 some nifty option --foo\n    -    --bar ...             some cool option --bar with an argument\n    +    --bar=...             some cool option --bar with an argument\n         -b, --baz             a short and long option\n     \n     An option group Header\n    @@ -13,20 +13,20 @@\n         -d, --data[=...]      short and long option with an optional argument\n     \n     Argument hints\n    -    -B <arg>              short option required argument\n    -    --bar2 <arg>          long option required argument\n    -    -e, --fuz <with-space>\n    +    -B=<arg>              short option required argument\n    +    --bar2=<arg>          long option required argument\n    +    -e, --fuz=<with-space>\n                               short and long option required argument\n         -s[<some>]            short option optional argument\n         --long[=<data>]       long option optional argument\n         -g, --fluf[=<path>]   short and long option optional argument\n    -    --longest <very-long-argument-hint>\n    +    --longest=<very-long-argument-hint>\n                               a very long argument hint\n    -    --pair <key=value>    with an equals sign in the hint\n    +    --pair=<key=value>    with an equals sign in the hint\n         --aswitch             help te=t contains? fl*g characters!`\n    -    --bswitch <hint>      hint has trailing tab character\n    +    --bswitch=<hint>      hint has trailing tab character\n         --cswitch             switch has trailing tab character\n    -    --short-hint <a>      with a one symbol hint\n    +    --short-hint=<a>      with a one symbol hint\n\nIt's arguably a bit odd to see the \"=\" for those that have\n!opts->long_name, but on the other hand I can't think of a reason other\nthan it looking unusual to me for why we wouldn't include the \"=\" there\nfor consistency.\n\nIt's odd that we don't have the short options in --git-completion-helper\nat all, so \"git am -C<tab>\" isn't completed\", but looking at\nshow_gitcomp() we'd need some further adjusting to make that work.\n"},{"id":"446653","messageId":"xmqqmtjog5yq.fsf@gitster.g","threadId":"56468","inReplyTo":"f6cc8042-a455-6398-9f91-0e64921252e4@web.de","subject":"Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-21T20:59:57Z","receivedAt":"2022-01-21T21:00:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"René Scharfe <l.s.r@web.de> writes:\n\n> The idea was to document the base behavior at the top and the effects of\n> flags at the bottom.  Blurring this distinction and cross-referencing\n> gives a better whole, though, I agree.  It would have helped me find the\n> right place to put the list of special chars, for one thing.  So how\n> about this?\n>\n> --- >8 ---\n> Subject: [PATCH v2] parse-options: document bracketing of argh\n>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Signed-off-by: René Scharfe <l.s.r@web.de>\n> ---\n>  parse-options.h | 10 +++++++---\n>  1 file changed, 7 insertions(+), 3 deletions(-)\n>\n> diff --git a/parse-options.h b/parse-options.h\n> index e22846d3b7..2e801b3c9a 100644\n> --- a/parse-options.h\n> +++ b/parse-options.h\n> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n>   *   token to explain the kind of argument this option wants. Does not\n>   *   begin in capital letter, and does not end with a full stop.\n>   *   Should be wrapped by N_() for translation.\n> + *   Is enclosed in brackets when printed, unless PARSE_OPT_LITERAL_ARGHELP\n> + *   is set in `flags` or it contains any of the following characters: ()<>[]|\n> + *   E.g. \"name\" is shown as \"<name>\" to indicate that a name value\n> + *   needs to be supplied, not the literal string \"name\", but\n> + *   \"<start>,<end>\" and \"(this|that)\" are printed verbatim.\n\nSince \"does not begin\", \"does not end\", \"should be wrapped\" are\nabout how it appears in the initializer of the struct option, I had\nto read twice before realizing that \"Is enclosed\" is *not* about\nwhat the developer has to do.\n\nMaking it clear that this is not part of \"enumeration of sentences,\nwhich omit the same subject, that describe how the value appears in\nthe source\", e.g.\n\n\tWhen \"git cmd -h\" shows it, `argh` is enclosed in\n\t<brackets>, unless ...\n\nwould have avoided the confusion.\n\nOther than that, I think this version is the clearest and easiest to\nunderstand among the options in this thread we have seen so far.\n\nThanks.\n\n\n>   *\n>   * `help`::\n>   *   the short help associated to what the option does.\n> @@ -105,9 +110,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,\n>   *\t\t\t\tnot last it will require an argument.\n>   *\t\t\t\tShould not be used with PARSE_OPT_OPTARG.\n>   *   PARSE_OPT_NODASH: this option doesn't start with a dash.\n> - *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets\n> - *\t\t\t\t(i.e. '<argh>') in the help message.\n> - *\t\t\t\tUseful for options with multiple parameters.\n> + *   PARSE_OPT_LITERAL_ARGHELP: says that `argh` shouldn't be enclosed in\n> + *\t\t\t\tbrackets (see `argh` description above).\n>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable\n>   *\t\t\t   by git-completion.bash. This option suppresses that.\n>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to\n> --\n> 2.34.1\n"},{"id":"446654","messageId":"xmqqilucg5e8.fsf@gitster.g","threadId":"56468","inReplyTo":"220121.86ee51l3jd.gmgdl@evledraar.gmail.com","subject":"Re: [PATCH] branch,checkout: fix --track usage strings","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2022-01-21T21:12:15Z","receivedAt":"2022-01-21T21:12:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> Also, with the sh completion if you do \"git add --chm<TAB>\" it expands\n> it to \"git add --chmod=\", i.e. the cursor is left after the \"=\" that's\n> not shown in the \"git add -h\". So always including it would be\n> consistent with that.\n>     \n>     diff --git a/parse-options.c b/parse-options.c\n>     index a8283037be9..75c345bb738 100644\n>     --- a/parse-options.c\n>     +++ b/parse-options.c\n>     @@ -916,7 +916,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)\n>                     else\n>                             s = literal ? \"[%s]\" : \"[<%s>]\";\n>             else\n>     -               s = literal ? \" %s\" : \" <%s>\";\n>     +               s = literal ? \"=%s\" : \"=<%s>\";\n\nIf the option has a long name, I think it is a good change.  I do\nnot offhand know if it is a good change for a short option, though.\n\n    $ git diff -B=20/60\n    error: break-rewrites expects <n>/<m> form\n    $ git diff -B 20/60\n    fatal: ambiguous argument '20/60': unknown revision or path not in the working tree.\n    $ git diff -B20/60\n\ngitcli.txt has this (I didn't check with the parse-options\nimplementation, though):\n\n * when a command-line option takes an argument, use the 'stuck' form.  In\n   other words, write `git foo -oArg` instead of `git foo -o Arg` for short\n   options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`\n   for long options.  An option that takes optional option-argument must be\n   written in the 'stuck' form.\n\nSo probably you'd need the same \"show differently depending on which\nbetween short and long we will show\" on this side of the if/else.\n\n\telse {\n\t\tif (opts->long_name)\n\t\t\ts = literal ? \"=%s\" : \"=<%s>\";\n\t\telse\n\t\t\ts = literal ? \"%s\" : \"<%s>\";\n\t}\n\nperhaps?\n"}]}