Message ID | 20210728135041.501850-2-mathstuf@gmail.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Improve automatic setup of tracking for new branches | expand |
Ben Boeckel <mathstuf@gmail.com> writes: > The `branch.autoSetupMerge` works well for setting up tracking a local > branch, but there is no mechanism to automatically set up a remote > tracking situation. This description is probably insufficient to explain what's missing, probably because "set up a remote tracking situation" is a bit fuzzy. Without this patch, I can do this already: $ git checkout -t -b topic origin/topic And after the above, we have [branch "topic"] remote = origin merge = refs/heads/topic Of course, you can instead use a short-hand DWIM, e.g. $ git checkout topic ;# assuming origin/topic exists gives us the same thing. In either case, topic knows to integrate with the 'topic' branch from remote 'origin' and push back there. So instead of saying "there is no mechanism to ...", be a bit more specific what you can and cannot do in this first paragraph. Then we can describe the solution we'd propose in the second and subsequent paragraphs. Thanks. > +branch.defaultRemote:: > + When a new branch is created with 'git branch', 'git switch' or 'git > + checkout', this value will be used to initialize the > + "branch.<name>.remote" setting. You mean without "-t"? I offhand do not think of a reason why this is a good idea. How would one create local topic branches that you plan to merge locally into your own larger topic branch to be shared with others? Shouldn't there be an easy way to countermand the setting by this configuration? > +branch.defaultPushRemote:: > + When a new branch is created with 'git branch', 'git switch' or 'git > + checkout', this value will be used to initialize the > + "branch.<name>.pushRemote" setting. Ditto. > +branch.defaultMerge:: > + When a new branch is created with 'git branch', 'git switch' or 'git > + checkout', this value will be used to initialize the > + "branch.<name>.merge" setting. So the expected use case is to fork multiple local branches, to integrate with the same branch from the remote? I think we can already do $ git checkout -t -b xyzzy origin/master $ git checkout -t -b frotz origin/master and you can lose -t with branch.autosetupmerge setting. As the "git branch" command needs to get the name of your branch (e.g. 'xyzzy' or 'frotz') and which remote tracking branch you start from (e.g. 'origin/master'), even with branch.{defaultRemote,defaultMerge} configuration, you wouldn't lose that many keystrokes, I suspect. Or do you plan to make $ git branch xyzzy a short-hand for $ git branch -t xyzzy origin/master when defaultRemote and defaultMerge are set to 'origin' and 'refs/heads/master'? It would be way too confusing to start the new branch from origin/master in such a case, as everybody learned that "git branch xyzzy" that does not say where the branch initially points at uses HEAD.
On Wed, Jul 28, 2021 at 10:48:56 -0700, Junio C Hamano wrote: > Ben Boeckel <mathstuf@gmail.com> writes: > > > The `branch.autoSetupMerge` works well for setting up tracking a local > > branch, but there is no mechanism to automatically set up a remote > > tracking situation. > > This description is probably insufficient to explain what's missing, > probably because "set up a remote tracking situation" is a bit > fuzzy. Fair enough. I finally understood what was going on with "tracking" only recently. Usually I would track the remote's branch of the same name, but I found that "tracking" `origin/master` instead of `myfork/topic` makes `fugitive` render the following: - commits on my topic that aren't integrated (this makes it easy to tell it to "amend this commit" using its keybinding; - commits on `master` that aren't on my topic (so I can see if there's anything relevant to rebase on top of); and - diffs against my fork's branch (as last fetched) so that I can see the status. Maybe this is a tooling issue, but I saw this as a potential way to avoid having to specify this information on every topic I create. > Without this patch, I can do this already: > > $ git checkout -t -b topic origin/topic I should note that the *vast* majority of my development is done using the fork-based workflow. I have `remote.pushDefault` set to my fork and I use `push.default = simple` (there are only a handful of repositories where "my fork" is also `origin` and I have, on multiple occasions, wanted to make forks even there). My normal pattern is that I'm on the target branch already, so I have: $ git checkout -b topic which doesn't do any tracking. Just `-t` makes it track my *local* `master` when I really want it to track `origin/master`. AFAIK, there's no shortcut for this other than to give the full `-t origin/master` at branch creation time (and as something I do all the time). > And after the above, we have > > [branch "topic"] > remote = origin > merge = refs/heads/topic > > Of course, you can instead use a short-hand DWIM, e.g. > > $ git checkout topic ;# assuming origin/topic exists > > gives us the same thing. In either case, topic knows to integrate > with the 'topic' branch from remote 'origin' and push back there. This doesn't match with my usual experience in fork-based workflows. I want the topic to track `master` and then my `remote.pushDefault` makes sure it goes to "the right place" when pushing. > So instead of saying "there is no mechanism to ...", be a bit more > specific what you can and cannot do in this first paragraph. > > Then we can describe the solution we'd propose in the second and > subsequent paragraphs. > > Thanks. I'll work on an improved cover letter to give more background. > > +branch.defaultRemote:: > > + When a new branch is created with 'git branch', 'git switch' or 'git > > + checkout', this value will be used to initialize the > > + "branch.<name>.remote" setting. > > You mean without "-t"? I offhand do not think of a reason why this > is a good idea. How would one create local topic branches that you > plan to merge locally into your own larger topic branch to be shared > with others? Shouldn't there be an easy way to countermand the > setting by this configuration? Everything goes through merge requests for the projects I work on day-to-day (even contributions to "my own" projects due to CI workflows). I added a test that `--no-track` works for this case (which given its rarity for me is the right tradeoff at least). > > +branch.defaultPushRemote:: > > + When a new branch is created with 'git branch', 'git switch' or 'git > > + checkout', this value will be used to initialize the > > + "branch.<name>.pushRemote" setting. > > Ditto. > > > +branch.defaultMerge:: > > + When a new branch is created with 'git branch', 'git switch' or 'git > > + checkout', this value will be used to initialize the > > + "branch.<name>.merge" setting. > > So the expected use case is to fork multiple local branches, to > integrate with the same branch from the remote? I think we can > already do > > $ git checkout -t -b xyzzy origin/master > $ git checkout -t -b frotz origin/master > > and you can lose -t with branch.autosetupmerge setting. As the "git > branch" command needs to get the name of your branch (e.g. 'xyzzy' > or 'frotz') and which remote tracking branch you start from > (e.g. 'origin/master'), even with branch.{defaultRemote,defaultMerge} > configuration, you wouldn't lose that many keystrokes, I suspect. There are times that I want to branch from HEAD, but track `master` (for example, a branch destined for backporting to an older branch). The equivalent of: $ git checkout release $ git checkout -b backported-branch $ git branch --set-upstream-to=origin/master > Or do you plan to make > > $ git branch xyzzy > > a short-hand for > > $ git branch -t xyzzy origin/master > > when defaultRemote and defaultMerge are set to 'origin' and > 'refs/heads/master'? It would be way too confusing to start the > new branch from origin/master in such a case, as everybody learned > that "git branch xyzzy" that does not say where the branch initially > points at uses HEAD. No, it would just *track* `origin/master`, not branch from it. It should be shorthand for: $ git branch xyzzy $ git branch --set-upstream-to=origin/master xyzzy Though I personally use `git checkout -b` far more often to create branches. And since "every" branch I make would have `-t origin/master`, I wanted to have configuration to do this part for me. Hopefully this gives a clearer picture of where I'm coming from. Thanks, --Ben
Ben Boeckel <mathstuf@gmail.com> writes:
> Hopefully this gives a clearer picture of where I'm coming from.
I think you gave descriptions to a reasonable level of detail to
explain what your expected workflow is, to what extent the current
tools support the workflow already, and what are still missing (and
you want to extend the system to help).
The reason I asked these questions is to make sure that future
readers of "git log" after your contribution becomes part of our
history will not have to ask them (as you won't be as readily
available to answer their questions). So please don't be content
just because you answered and helped _me_ understand where you're
coming from. Make sure an updated documentation and proposed log
message explains them well to our future readers without your
assistance.
Thanks.
diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt index cc5f3249fc..6e9d446066 100644 --- a/Documentation/config/branch.txt +++ b/Documentation/config/branch.txt @@ -1,3 +1,18 @@ +branch.defaultRemote:: + When a new branch is created with 'git branch', 'git switch' or 'git + checkout', this value will be used to initialize the + "branch.<name>.remote" setting. + +branch.defaultPushRemote:: + When a new branch is created with 'git branch', 'git switch' or 'git + checkout', this value will be used to initialize the + "branch.<name>.pushRemote" setting. + +branch.defaultMerge:: + When a new branch is created with 'git branch', 'git switch' or 'git + checkout', this value will be used to initialize the + "branch.<name>.merge" setting. + branch.autoSetupMerge:: Tells 'git branch', 'git switch' and 'git checkout' to set up new branches so that linkgit:git-pull[1] will appropriately merge from the diff --git a/branch.c b/branch.c index 7a88a4861e..0e8ece8259 100644 --- a/branch.c +++ b/branch.c @@ -60,6 +60,9 @@ int install_branch_config(int flag, const char *local, const char *origin, const const char *shortname = NULL; struct strbuf key = STRBUF_INIT; int rebasing = should_setup_rebase(origin); + const char *actual_origin = origin ? origin : git_branch_remote; + const char *actual_push_origin = git_branch_push_remote; + const char *actual_remote = remote ? remote : git_branch_merge; if (skip_prefix(remote, "refs/heads/", &shortname) && !strcmp(local, shortname) @@ -70,12 +73,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const } strbuf_addf(&key, "branch.%s.remote", local); - if (git_config_set_gently(key.buf, origin ? origin : ".") < 0) + if (git_config_set_gently(key.buf, actual_origin ? actual_origin : ".") < 0) + goto out_err; + + strbuf_reset(&key); + strbuf_addf(&key, "branch.%s.pushremote", local); + if (git_config_set_gently(key.buf, actual_push_origin) < 0) goto out_err; strbuf_reset(&key); strbuf_addf(&key, "branch.%s.merge", local); - if (git_config_set_gently(key.buf, remote) < 0) + if (git_config_set_gently(key.buf, actual_remote) < 0) goto out_err; if (rebasing) { @@ -88,27 +96,27 @@ int install_branch_config(int flag, const char *local, const char *origin, const if (flag & BRANCH_CONFIG_VERBOSE) { if (shortname) { - if (origin) + if (actual_origin) printf_ln(rebasing ? _("Branch '%s' set up to track remote branch '%s' from '%s' by rebasing.") : _("Branch '%s' set up to track remote branch '%s' from '%s'."), - local, shortname, origin); + local, shortname, actual_origin); else printf_ln(rebasing ? _("Branch '%s' set up to track local branch '%s' by rebasing.") : _("Branch '%s' set up to track local branch '%s'."), local, shortname); } else { - if (origin) + if (actual_origin) printf_ln(rebasing ? _("Branch '%s' set up to track remote ref '%s' by rebasing.") : _("Branch '%s' set up to track remote ref '%s'."), - local, remote); + local, actual_remote); else printf_ln(rebasing ? _("Branch '%s' set up to track local ref '%s' by rebasing.") : _("Branch '%s' set up to track local ref '%s'."), - local, remote); + local, actual_remote); } } @@ -119,9 +127,9 @@ int install_branch_config(int flag, const char *local, const char *origin, const error(_("Unable to write upstream branch configuration")); advise(_(tracking_advice), - origin ? origin : "", - origin ? "/" : "", - shortname ? shortname : remote); + actual_origin ? actual_origin : "", + actual_origin ? "/" : "", + shortname ? shortname : actual_remote); return -1; } diff --git a/branch.h b/branch.h index df0be61506..6ce978731d 100644 --- a/branch.h +++ b/branch.h @@ -14,6 +14,9 @@ enum branch_track { }; extern enum branch_track git_branch_track; +extern const char* git_branch_remote; +extern const char* git_branch_push_remote; +extern const char* git_branch_merge; /* Functions for acting on the information about branches. */ diff --git a/config.c b/config.c index f33abeab85..42fc510a9f 100644 --- a/config.c +++ b/config.c @@ -1599,6 +1599,21 @@ static int git_default_branch_config(const char *var, const char *value) return error(_("malformed value for %s"), var); return 0; } + if (!strcmp(var, "branch.defaultremote")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&git_branch_remote, var, value); + } + if (!strcmp(var, "branch.defaultpushremote")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&git_branch_push_remote, var, value); + } + if (!strcmp(var, "branch.defaultmerge")) { + if (!value) + return config_error_nonbool(var); + return git_config_string(&git_branch_merge, var, value); + } /* Add other config variables here and to Documentation/config.txt. */ return 0; diff --git a/environment.c b/environment.c index 2f27008424..3b4d54e7dc 100644 --- a/environment.c +++ b/environment.c @@ -60,6 +60,9 @@ int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding = "SHIFT-JIS"; unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; enum branch_track git_branch_track = BRANCH_TRACK_REMOTE; +const char* git_branch_remote = NULL; +const char* git_branch_push_remote = NULL; +const char* git_branch_merge = NULL; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #ifndef OBJECT_CREATION_MODE diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index cc4b10236e..2edfb50872 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -797,6 +797,63 @@ test_expect_success 'test tracking setup via --track but deeper' ' test "$(git config branch.my7.merge)" = refs/heads/o/o ' +test_expect_success 'test tracking setup via branch.default* and --track' ' + git config branch.autosetupmerge always && + git config branch.defaultremote local && + git config branch.defaultmerge main && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/main || git fetch local) && + git branch --track other/foo my2 && + git config branch.autosetupmerge false && + test "$(git config branch.my2.remote)" = other && + ! test "$(git config branch.my2.pushremote)" = other && + test "$(git config branch.my2.merge)" = refs/heads/foo +' + +test_expect_success 'test tracking setup via branch.default* and --no-track' ' + git config branch.autosetupmerge always && + git config branch.defaultremote local && + git config branch.defaultmerge main && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/main || git fetch local) && + git branch --no-track my2 && + git config branch.autosetupmerge false && + ! test "$(git config branch.my2.remote)" = local && + ! test "$(git config branch.my2.pushremote)" = other && + ! test "$(git config branch.my2.merge)" = refs/heads/main +' + +test_expect_success 'test tracking setup via branch.default*' ' + git config branch.autosetupmerge always && + git config branch.defaultremote local && + git config branch.defaultmerge main && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/main || git fetch local) && + git branch my2 && + git config branch.autosetupmerge false && + test "$(git config branch.my2.remote)" = local && + ! test "$(git config branch.my2.pushremote)" = other && + test "$(git config branch.my2.merge)" = refs/heads/main +' + +test_expect_success 'test tracking setup via branch.default* with pushremote' ' + git config branch.autosetupmerge always && + git config branch.defaultremote local && + git config branch.defaultpushremote other && + git config branch.defaultmerge main && + git config remote.local.url . && + git config remote.local.fetch refs/heads/*:refs/remotes/local/* && + (git show-ref -q refs/remotes/local/main || git fetch local) && + git branch my2 && + git config branch.autosetupmerge false && + test "$(git config branch.my2.remote)" = local && + test "$(git config branch.my2.pushremote)" = other && + test "$(git config branch.my2.merge)" = refs/heads/main +' + test_expect_success 'test deleting branch deletes branch config' ' git branch -d my7 && test -z "$(git config branch.my7.remote)" &&
The `branch.autoSetupMerge` works well for setting up tracking a local branch, but there is no mechanism to automatically set up a remote tracking situation. This patch adds the following configuration values: - `branch.defaultRemote`: initializes `branch.<name>.remote` if not otherwise given; - `branch.defaultPushRemote`: initializes `branch.<name>.pushRemote` (currently falls back on `remote.pushDefault` when pushing); and - `branch.defaultMerge`: initializes `branch.<name>.merge` if not otherwise given. Signed-off-by: Ben Boeckel <mathstuf@gmail.com> --- Documentation/config/branch.txt | 15 +++++++++ branch.c | 28 ++++++++++------ branch.h | 3 ++ config.c | 15 +++++++++ environment.c | 3 ++ t/t3200-branch.sh | 57 +++++++++++++++++++++++++++++++++ 6 files changed, 111 insertions(+), 10 deletions(-)