All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge
@ 2021-09-08 20:15 Josh Steadmon
  2021-09-08 20:44 ` Josh Steadmon
                   ` (8 more replies)
  0 siblings, 9 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-09-08 20:15 UTC (permalink / raw)
  To: git

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach branch.autoSetupMerge a new "inherit" option. When this is set,
creating a new branch will cause the tracking configuration to default
to the configuration of the branch point, if set.

NEEDS WORK:
* this breaks `git checkout -b new-branch --recurse-submodules`
* add documentation
* add tests
* check corner cases, including whether this plays well with related
  cmd-line options (switch, checkout, branch)

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
I'll be looking into the --recurse-submodules breakage today, and then
I'll work on polishing the patch after that's fixed. But I thought it's
worth getting an idea of how the list feels about the feature in general
while I sort through the issues.

 branch.c | 36 +++++++++++++++++++++++++++++++++++-
 branch.h |  3 ++-
 config.c |  3 +++
 3 files changed, 40 insertions(+), 2 deletions(-)

diff --git a/branch.c b/branch.c
index 7a88a4861e..17d4cc5128 100644
--- a/branch.c
+++ b/branch.c
@@ -126,6 +126,38 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	return -1;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	struct strbuf key = STRBUF_INIT;
+	char *remote;
+	const char *bare_ref;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	strbuf_addf(&key, "branch.%s.remote", bare_ref);
+	if (git_config_get_string(key.buf, &remote)) {
+		warning("branch.autoSetupMerge=inherit, but could not find %s",
+			key.buf);
+		strbuf_release(&key);
+		return 1;
+	}
+	tracking->remote = remote;
+
+	strbuf_reset(&key);
+	strbuf_addf(&key, "branch.%s.merge", bare_ref);
+	if (git_config_get_string(key.buf, &tracking->src)) {
+		warning("branch.autoSetupMerge=inherit, but could not find %s",
+			key.buf);
+		strbuf_release(&key);
+		return 1;
+	}
+
+	tracking->matches = 1;
+	strbuf_release(&key);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -139,7 +171,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	if (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))
+		return;
+	else if (for_each_remote(find_tracked_branch, &tracking))
 		return;
 
 	if (!tracking.matches)
diff --git a/branch.h b/branch.h
index df0be61506..6484bda8a2 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT
 };
 
 extern enum branch_track git_branch_track;
diff --git a/config.c b/config.c
index cb4a8058bf..4bd5a18faf 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcasecmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
-- 
2.33.0.153.gba50c8fa24-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
@ 2021-09-08 20:44 ` Josh Steadmon
  2021-09-11  0:25 ` [PATCH v2] " Josh Steadmon
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-09-08 20:44 UTC (permalink / raw)
  To: git

Actually, this patch probably makes zero sense without [1], an old RFC
patch to use a more repo-like workflow that we've been using internally
at $job. Hopefully I can get a v2 that doesn't depend on this.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v2] branch: add "inherit" option for branch.autoSetupMerge
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
  2021-09-08 20:44 ` Josh Steadmon
@ 2021-09-11  0:25 ` Josh Steadmon
  2021-09-11  0:52   ` Junio C Hamano
  2021-10-17  4:45 ` [PATCH v3] branch: add flags and config to inherit tracking Josh Steadmon
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-09-11  0:25 UTC (permalink / raw)
  To: git

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach branch.autoSetupMerge a new "inherit" option. When this is set,
creating a new branch will cause the tracking configuration to default
to the configuration of the branch point, if set.

For example, if branch.autoSetupMerge=inherit, branch "main" tracks
"origin/main", and we run `git checkout -b feature main`, then branch
"feature" will track "origin/main". Thus, `git status` will show us how
far ahead/behind we are from origin, and `git pull` will pull from
origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
After a bit of testing, I've verified that this still works as intended
even without the extra patch [1] linked above. I've added documentation
and tests.

Range-diff against v1:
1:  9628d14588 ! 1:  0346f44754 branch: add "inherit" option for branch.autoSetupMerge
    @@ Commit message
         creating a new branch will cause the tracking configuration to default
         to the configuration of the branch point, if set.
     
    -    NEEDS WORK:
    -    * this breaks `git checkout -b new-branch --recurse-submodules`
    -    * add documentation
    -    * add tests
    -    * check corner cases, including whether this plays well with related
    -      cmd-line options (switch, checkout, branch)
    +    For example, if branch.autoSetupMerge=inherit, branch "main" tracks
    +    "origin/main", and we run `git checkout -b feature main`, then branch
    +    "feature" will track "origin/main". Thus, `git status` will show us how
    +    far ahead/behind we are from origin, and `git pull` will pull from
    +    origin.
    +
    +    This is particularly useful when creating branches across many
    +    submodules, such as with `git submodule foreach ...` (or if running with
    +    a patch such as [1], which we use at $job), as it avoids having to
    +    manually set tracking info for each submodule.
    +
    +    [1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/
     
     
    + ## Documentation/config/branch.txt ##
    +@@ Documentation/config/branch.txt: branch.autoSetupMerge::
    + 	automatic setup is done; `true` -- automatic setup is done when the
    + 	starting point is a remote-tracking branch; `always` --
    + 	automatic setup is done when the starting point is either a
    +-	local branch or remote-tracking
    ++	local branch or remote-tracking branch; `inherit` -- if the starting point
    ++	has a tracking configuration, it is copied to the new
    + 	branch. This option defaults to true.
    + 
    + branch.autoSetupRebase::
    +
    + ## Documentation/git-branch.txt ##
    +@@ Documentation/git-branch.txt: This behavior is the default when the start point is a remote-tracking branch.
    + Set the branch.autoSetupMerge configuration variable to `false` if you
    + want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
    + were given. Set it to `always` if you want this behavior when the
    +-start-point is either a local or remote-tracking branch.
    ++start-point is either a local or remote-tracking branch. Set it to
    ++`inherit` if you want to copy the tracking configuration from the
    ++start point.
    + 
    + --no-track::
    + 	Do not set up "upstream" configuration, even if the
    +
      ## branch.c ##
     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const
      	return -1;
    @@ config.c: static int git_default_branch_config(const char *var, const char *valu
      		}
      		git_branch_track = git_config_bool(var, value);
      		return 0;
    +
    + ## t/t2017-checkout-orphan.sh ##
    +@@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '
    + 	git checkout --orphan gamma &&
    + 	test -z "$(git config branch.gamma.merge)" &&
    + 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
    ++	test_must_fail git rev-parse --verify HEAD^ &&
    ++	git checkout main &&
    ++	git config branch.autosetupmerge inherit &&
    ++	git checkout --orphan eta &&
    ++	test -z "$(git config branch.eta.merge)" &&
    ++	test -z "$(git config branch.eta.remote)" &&
    ++	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
    + 	test_must_fail git rev-parse --verify HEAD^
    + '
    + 
    +
    + ## t/t2027-checkout-track.sh ##
    +@@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an extra path argument' '
    + 	test_i18ngrep "cannot be used with updating paths" err
    + '
    + 
    ++test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
    ++	# Set up tracking config on main
    ++	git config branch.main.remote origin &&
    ++	git config branch.main.merge refs/heads/main &&
    ++	test_config branch.autoSetupMerge inherit &&
    ++	# With branch.autoSetupMerge=inherit, we copy the tracking config
    ++	git checkout -b b1 main &&
    ++	test_cmp_config origin branch.b1.remote &&
    ++	test_cmp_config refs/heads/main branch.b1.merge &&
    ++	# But --track overrides this
    ++	git checkout --track -b b2 main &&
    ++	test_cmp_config . branch.b2.remote &&
    ++	test_cmp_config refs/heads/main branch.b2.merge
    ++'
    ++
    + test_done
    +
    + ## t/t2060-switch.sh ##
    +@@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progress' '
    + 	test_must_fail git switch -d @^
    + '
    + 
    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
    ++	# default config does not copy tracking info
    ++	git switch -c foo-no-inherit foo &&
    ++	test -z "$(git config branch.foo-no-inherit.remote)" &&
    ++	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	# with autoSetupMerge=inherit, we copy tracking info from foo
    ++	test_config branch.autoSetupMerge inherit &&
    ++	git switch -c foo2 foo &&
    ++	test_cmp_config origin branch.foo2.remote &&
    ++	test_cmp_config refs/heads/foo branch.foo2.merge &&
    ++	# no tracking info to inherit from main
    ++	git switch -c main2 main &&
    ++	test -z "$(git config branch.main2.remote)" &&
    ++	test -z "$(git config branch.main2.merge)"
    ++'
    ++
    + test_done
    +
    + ## t/t3200-branch.sh ##
    +@@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration' '
    + 	)
    + '
    + 
    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
    ++	test_unconfig branch.autoSetupMerge &&
    ++	# default config does not copy tracking info
    ++	git branch foo-no-inherit my1 &&
    ++	test -z "$(git config branch.foo-no-inherit.remote)" &&
    ++	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	# with autoSetupMerge=inherit, we copy tracking info from my1
    ++	test_config branch.autoSetupMerge inherit &&
    ++	git branch foo2 my1 &&
    ++	test_cmp_config local branch.foo2.remote &&
    ++	test_cmp_config refs/heads/main branch.foo2.merge &&
    ++	# no tracking info to inherit from main
    ++	git branch main2 main &&
    ++	test -z "$(git config branch.main2.remote)" &&
    ++	test -z "$(git config branch.main2.merge)"
    ++'
    ++
    + test_done
    +
    + ## t/t7201-co.sh ##
    +@@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '
    + 	test_cmp expect arm
    + '
    + 
    ++test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
    ++	git reset --hard main &&
    ++	# default config does not copy tracking info
    ++	git checkout -b foo-no-inherit koala/bear &&
    ++	test -z "$(git config branch.foo-no-inherit.remote)" &&
    ++	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
    ++	test_config branch.autoSetupMerge inherit &&
    ++	git checkout -b foo koala/bear &&
    ++	test_cmp_config origin branch.foo.remote &&
    ++	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
    ++	# no tracking info to inherit from main
    ++	git checkout -b main2 main &&
    ++	test -z "$(git config branch.main2.remote)" &&
    ++	test -z "$(git config branch.main2.merge)"
    ++'
    ++
    + test_done

 Documentation/config/branch.txt |  3 ++-
 Documentation/git-branch.txt    |  4 +++-
 branch.c                        | 36 ++++++++++++++++++++++++++++++++-
 branch.h                        |  3 ++-
 config.c                        |  3 +++
 t/t2017-checkout-orphan.sh      |  7 +++++++
 t/t2027-checkout-track.sh       | 15 ++++++++++++++
 t/t2060-switch.sh               | 16 +++++++++++++++
 t/t3200-branch.sh               | 17 ++++++++++++++++
 t/t7201-co.sh                   | 17 ++++++++++++++++
 10 files changed, 117 insertions(+), 4 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..81e901b8e8 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -218,7 +218,9 @@ This behavior is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+start point.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
diff --git a/branch.c b/branch.c
index 7a88a4861e..17d4cc5128 100644
--- a/branch.c
+++ b/branch.c
@@ -126,6 +126,38 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	return -1;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	struct strbuf key = STRBUF_INIT;
+	char *remote;
+	const char *bare_ref;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	strbuf_addf(&key, "branch.%s.remote", bare_ref);
+	if (git_config_get_string(key.buf, &remote)) {
+		warning("branch.autoSetupMerge=inherit, but could not find %s",
+			key.buf);
+		strbuf_release(&key);
+		return 1;
+	}
+	tracking->remote = remote;
+
+	strbuf_reset(&key);
+	strbuf_addf(&key, "branch.%s.merge", bare_ref);
+	if (git_config_get_string(key.buf, &tracking->src)) {
+		warning("branch.autoSetupMerge=inherit, but could not find %s",
+			key.buf);
+		strbuf_release(&key);
+		return 1;
+	}
+
+	tracking->matches = 1;
+	strbuf_release(&key);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -139,7 +171,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	if (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))
+		return;
+	else if (for_each_remote(find_tracked_branch, &tracking))
 		return;
 
 	if (!tracking.matches)
diff --git a/branch.h b/branch.h
index df0be61506..6484bda8a2 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT
 };
 
 extern enum branch_track git_branch_track;
diff --git a/config.c b/config.c
index cb4a8058bf..4bd5a18faf 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcasecmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..31fb64c5be 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout --orphan gamma &&
 	test -z "$(git config branch.gamma.merge)" &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test -z "$(git config branch.eta.merge)" &&
+	test -z "$(git config branch.eta.remote)" &&
+	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..4805965872 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,19 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	test_config branch.autoSetupMerge inherit &&
+	# With branch.autoSetupMerge=inherit, we copy the tracking config
+	git checkout -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/main branch.b1.merge &&
+	# But --track overrides this
+	git checkout --track -b b2 main &&
+	test_cmp_config . branch.b2.remote &&
+	test_cmp_config refs/heads/main branch.b2.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..f9972e2841 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,20 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from foo
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..8005a5ccc6 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,21 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..ae9f8d02c2 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
-- 
2.33.0.309.g3052b89438-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v2] branch: add "inherit" option for branch.autoSetupMerge
  2021-09-11  0:25 ` [PATCH v2] " Josh Steadmon
@ 2021-09-11  0:52   ` Junio C Hamano
  2021-10-17  4:35     ` Josh Steadmon
  0 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-09-11  0:52 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git

Josh Steadmon <steadmon@google.com> writes:

> It can be helpful when creating a new branch to use the existing
> tracking configuration from the branch point. However, there is
> currently not a method to automatically do so.
>
> Teach branch.autoSetupMerge a new "inherit" option. When this is set,
> creating a new branch will cause the tracking configuration to default
> to the configuration of the branch point, if set.

So, when a new branch N is forked from an existing branch A that
builds on branch B (which could be a local branch under refs/heads/
or a remote-tracking branch under refs/remotes/), a plain-vanilla
auto-setup-merge makes N build on A but with 'inherit', N is marked
to build on B instead?

I do not think it is wise to hide this useful feature behind a
configuration variable.  

Rather, this should be made available first to users who do not even
set the configuration and then as a convenience measure, made usable
via the configuration mechanism as well.

The current "git branch --track N A" makes N build on A, so perhaps
"git branch --track=inherit N A" should make N build on whatever A
builds on.  We may need to give a synonym --track=direct to the
traditional "build on the original branch that was used to specfy
the fork point" while doing so.

And then on top of that, we can add configuration variable handling.

Depending on the value of branch.autoSetupMerge, "git branch -b" and
"git checkout -b" would pretend as if "--track" or "--track=inherit"
were given, or something along that line.  The end result may be the
same for those who only use the configuration variables, but it
would give us some flexibility to countermand the configuration from
the command line.  Those who set branch.autoSetupMerge to 'inherit'
cannot say "git checkout -b N --track=direct A" to express "With
this single invocation alone I am making N build on A, even though I
know I usually make N build on whatever A builds on" if you give
only the configuration variable.

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v2] branch: add "inherit" option for branch.autoSetupMerge
  2021-09-11  0:52   ` Junio C Hamano
@ 2021-10-17  4:35     ` Josh Steadmon
  2021-10-17  5:50       ` Junio C Hamano
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-10-17  4:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On 2021.09.10 17:52, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > It can be helpful when creating a new branch to use the existing
> > tracking configuration from the branch point. However, there is
> > currently not a method to automatically do so.
> >
> > Teach branch.autoSetupMerge a new "inherit" option. When this is set,
> > creating a new branch will cause the tracking configuration to default
> > to the configuration of the branch point, if set.
> 
> So, when a new branch N is forked from an existing branch A that
> builds on branch B (which could be a local branch under refs/heads/
> or a remote-tracking branch under refs/remotes/), a plain-vanilla
> auto-setup-merge makes N build on A but with 'inherit', N is marked
> to build on B instead?
> 
> I do not think it is wise to hide this useful feature behind a
> configuration variable.  
> 
> Rather, this should be made available first to users who do not even
> set the configuration and then as a convenience measure, made usable
> via the configuration mechanism as well.
> 
> The current "git branch --track N A" makes N build on A, so perhaps
> "git branch --track=inherit N A" should make N build on whatever A
> builds on.  We may need to give a synonym --track=direct to the
> traditional "build on the original branch that was used to specfy
> the fork point" while doing so.
> 
> And then on top of that, we can add configuration variable handling.
> 
> Depending on the value of branch.autoSetupMerge, "git branch -b" and
> "git checkout -b" would pretend as if "--track" or "--track=inherit"
> were given, or something along that line.  The end result may be the
> same for those who only use the configuration variables, but it
> would give us some flexibility to countermand the configuration from
> the command line.  Those who set branch.autoSetupMerge to 'inherit'
> cannot say "git checkout -b N --track=direct A" to express "With
> this single invocation alone I am making N build on A, even though I
> know I usually make N build on whatever A builds on" if you give
> only the configuration variable.
> 
> Thanks.

Thanks for the feedback. I've added "--track=direct" and
"--track=inherit" flags in V3, which I'll send out shortly. I am a bit
skeptical of the value of having "--track=direct" when just "--track"
still works, but I'll leave it up to the list to decide.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v3] branch: add flags and config to inherit tracking
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
  2021-09-08 20:44 ` Josh Steadmon
  2021-09-11  0:25 ` [PATCH v2] " Josh Steadmon
@ 2021-10-17  4:45 ` Josh Steadmon
  2021-10-18 18:31   ` Ævar Arnfjörð Bjarmason
  2021-10-18 17:50 ` [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Glen Choo
                   ` (5 subsequent siblings)
  8 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-10-17  4:45 UTC (permalink / raw)
  To: git; +Cc: gitster, emilyshaffer

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
Range-diff against v2:
1:  0346f44754 ! 1:  b9356d9837 branch: add "inherit" option for branch.autoSetupMerge
    @@ Metadata
     Author: Josh Steadmon <steadmon@google.com>
     
      ## Commit message ##
    -    branch: add "inherit" option for branch.autoSetupMerge
    +    branch: add flags and config to inherit tracking
     
         It can be helpful when creating a new branch to use the existing
         tracking configuration from the branch point. However, there is
         currently not a method to automatically do so.
     
    -    Teach branch.autoSetupMerge a new "inherit" option. When this is set,
    -    creating a new branch will cause the tracking configuration to default
    -    to the configuration of the branch point, if set.
    +    Teach git-{branch,checkout,switch} an "inherit" argument to the
    +    "--track" option. When this is set, creating a new branch will cause the
    +    tracking configuration to default to the configuration of the branch
    +    point, if set.
     
    -    For example, if branch.autoSetupMerge=inherit, branch "main" tracks
    -    "origin/main", and we run `git checkout -b feature main`, then branch
    -    "feature" will track "origin/main". Thus, `git status` will show us how
    -    far ahead/behind we are from origin, and `git pull` will pull from
    -    origin.
    +    For example, if branch "main" tracks "origin/main", and we run
    +    `git checkout --track=inherit -b feature main`, then branch "feature"
    +    will track "origin/main". Thus, `git status` will show us how far
    +    ahead/behind we are from origin, and `git pull` will pull from origin.
     
         This is particularly useful when creating branches across many
         submodules, such as with `git submodule foreach ...` (or if running with
         a patch such as [1], which we use at $job), as it avoids having to
         manually set tracking info for each submodule.
     
    +    Since we've added an argument to "--track", also add "--track=direct" as
    +    another way to explicitly get the original "--track" behavior ("--track"
    +    without an argument still works as well).
    +
    +    Finally, teach branch.autoSetupMerge a new "inherit" option. When this
    +    is set, "--track=inherit" becomes the default behavior.
    +
         [1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/
     
    @@ Documentation/config/branch.txt: branch.autoSetupMerge::
      branch.autoSetupRebase::
     
      ## Documentation/git-branch.txt ##
    -@@ Documentation/git-branch.txt: This behavior is the default when the start point is a remote-tracking branch.
    +@@ Documentation/git-branch.txt: SYNOPSIS
    + 	[--points-at <object>] [--format=<format>]
    + 	[(-r | --remotes) | (-a | --all)]
    + 	[--list] [<pattern>...]
    +-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
    ++'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
    + 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
    + 'git branch' --unset-upstream [<branchname>]
    + 'git branch' (-m | -M) [<oldbranch>] <newbranch>
    +@@ Documentation/git-branch.txt: This option is only applicable in non-verbose mode.
    + 	Display the full sha1s in the output listing rather than abbreviating them.
    + 
    + -t::
    +---track::
    ++--track [inherit|direct]::
    + 	When creating a new branch, set up `branch.<name>.remote` and
    +-	`branch.<name>.merge` configuration entries to mark the
    +-	start-point branch as "upstream" from the new branch. This
    ++	`branch.<name>.merge` configuration entries to set "upstream" tracking
    ++	configuration for the new branch. This
    + 	configuration will tell git to show the relationship between the
    + 	two branches in `git status` and `git branch -v`. Furthermore,
    + 	it directs `git pull` without arguments to pull from the
    + 	upstream when the new branch is checked out.
    + +
    +-This behavior is the default when the start point is a remote-tracking branch.
    ++The exact upstream branch is chosen depending on the optional argument:
    ++`--track` or `--track direct` means to use the start-point branch itself as the
    ++upstream; `--track inherit` means to copy the upstream configuration of the
    ++start-point branch.
    +++
    ++`--track direct` is the default when the start point is a remote-tracking branch.
      Set the branch.autoSetupMerge configuration variable to `false` if you
      want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
      were given. Set it to `always` if you want this behavior when the
     -start-point is either a local or remote-tracking branch.
     +start-point is either a local or remote-tracking branch. Set it to
     +`inherit` if you want to copy the tracking configuration from the
    -+start point.
    ++branch point.
      
      --no-track::
      	Do not set up "upstream" configuration, even if the
    +-	branch.autoSetupMerge configuration variable is true.
    ++	branch.autoSetupMerge configuration variable is set.
    + 
    + --set-upstream::
    + 	As this option had confusing syntax, it is no longer supported.
    +
    + ## Documentation/git-checkout.txt ##
    +@@ Documentation/git-checkout.txt: of it").
    + 	linkgit:git-branch[1] for details.
    + 
    + -t::
    +---track::
    ++--track [direct|inherit]::
    + 	When creating a new branch, set up "upstream" configuration. See
    + 	"--track" in linkgit:git-branch[1] for details.
    + +
    +
    + ## Documentation/git-switch.txt ##
    +@@ Documentation/git-switch.txt: should result in deletion of the path).
    + 	attached to a terminal, regardless of `--quiet`.
    + 
    + -t::
    +---track::
    ++--track [direct|inherit]::
    + 	When creating a new branch, set up "upstream" configuration.
    + 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
    + 	details.
     
      ## branch.c ##
     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     +
     +	strbuf_addf(&key, "branch.%s.remote", bare_ref);
     +	if (git_config_get_string(key.buf, &remote)) {
    -+		warning("branch.autoSetupMerge=inherit, but could not find %s",
    -+			key.buf);
    ++		warning(_("asked to inherit tracking from %s, but could not find %s"),
    ++			bare_ref, key.buf);
     +		strbuf_release(&key);
    -+		return 1;
    ++		return -1;
     +	}
    -+	tracking->remote = remote;
     +
     +	strbuf_reset(&key);
     +	strbuf_addf(&key, "branch.%s.merge", bare_ref);
     +	if (git_config_get_string(key.buf, &tracking->src)) {
    -+		warning("branch.autoSetupMerge=inherit, but could not find %s",
    -+			key.buf);
    ++		warning(_("asked to inherit tracking from %s, but could not find %s"),
    ++			bare_ref, key.buf);
     +		strbuf_release(&key);
    -+		return 1;
    ++		free(remote);
    ++		return -1;
     +	}
     +
    ++	tracking->remote = remote;
     +	tracking->matches = 1;
     +	strbuf_release(&key);
     +	return 0;
    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      	memset(&tracking, 0, sizeof(tracking));
      	tracking.spec.dst = (char *)orig_ref;
     -	if (for_each_remote(find_tracked_branch, &tracking))
    -+	if (track == BRANCH_TRACK_INHERIT && inherit_tracking(&tracking, orig_ref))
    -+		return;
    -+	else if (for_each_remote(find_tracked_branch, &tracking))
    ++	if (track != BRANCH_TRACK_INHERIT) {
    ++		for_each_remote(find_tracked_branch, &tracking);
    ++	} else if (inherit_tracking(&tracking, orig_ref))
      		return;
      
      	if (!tracking.matches)
    @@ branch.h: enum branch_track {
      
      extern enum branch_track git_branch_track;
     
    + ## builtin/branch.c ##
    +@@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix)
    + 		OPT__VERBOSE(&filter.verbose,
    + 			N_("show hash and subject, give twice for upstream branch")),
    + 		OPT__QUIET(&quiet, N_("suppress informational messages")),
    +-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
    +-			BRANCH_TRACK_EXPLICIT),
    ++		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
    ++			N_("set up tracking mode (see git-pull(1))"),
    ++			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
    ++			parse_opt_tracking_mode),
    + 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
    + 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
    + 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
    +
    + ## builtin/checkout.c ##
    +@@ builtin/checkout.c: static struct option *add_common_switch_branch_options(
    + {
    + 	struct option options[] = {
    + 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
    +-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
    +-			BRANCH_TRACK_EXPLICIT),
    ++		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
    ++			N_("set up tracking mode (see git-pull(1))"),
    ++			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
    ++			parse_opt_tracking_mode),
    + 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
    + 			   PARSE_OPT_NOCOMPLETE),
    + 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
    +
      ## config.c ##
     @@ config.c: static int git_default_branch_config(const char *var, const char *value)
      		if (value && !strcasecmp(value, "always")) {
    @@ config.c: static int git_default_branch_config(const char *var, const char *valu
      		git_branch_track = git_config_bool(var, value);
      		return 0;
     
    + ## parse-options-cb.c ##
    +@@
    + #include "git-compat-util.h"
    + #include "parse-options.h"
    ++#include "branch.h"
    + #include "cache.h"
    + #include "commit.h"
    + #include "color.h"
    +@@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
    + 
    + 	return 0;
    + }
    ++
    ++int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
    ++	if (unset)
    ++		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
    ++	else if (!arg || !strcmp(arg, "direct"))
    ++		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
    ++	else if (!strcmp(arg, "inherit"))
    ++		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
    ++	else
    ++		return error(_("option `%s' expects \"direct\" or \"inherit\""),
    ++			     opt->long_name);
    ++
    ++	return 0;
    ++}
    +
    + ## parse-options.h ##
    +@@ parse-options.h: enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
    + 					   const char *, int);
    + int parse_opt_passthru(const struct option *, const char *, int);
    + int parse_opt_passthru_argv(const struct option *, const char *, int);
    ++/* value is enum branch_track* */
    ++int parse_opt_tracking_mode(const struct option *, const char *, int);
    + 
    + #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
    + #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
    +
      ## t/t2017-checkout-orphan.sh ##
     @@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '
      	git checkout --orphan gamma &&
    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e
     +	git config branch.main.remote origin &&
     +	git config branch.main.merge refs/heads/main &&
     +	test_config branch.autoSetupMerge inherit &&
    -+	# With branch.autoSetupMerge=inherit, we copy the tracking config
    -+	git checkout -b b1 main &&
    ++	# With --track=inherit, we copy the tracking config from main
    ++	git checkout --track=inherit -b b1 main &&
     +	test_cmp_config origin branch.b1.remote &&
     +	test_cmp_config refs/heads/main branch.b1.merge &&
    ++	# With branch.autoSetupMerge=inherit, we do the same
    ++	git checkout -b b2 main &&
    ++	test_cmp_config origin branch.b2.remote &&
    ++	test_cmp_config refs/heads/main branch.b2.merge &&
     +	# But --track overrides this
    -+	git checkout --track -b b2 main &&
    -+	test_cmp_config . branch.b2.remote &&
    -+	test_cmp_config refs/heads/main branch.b2.merge
    ++	git checkout --track -b b3 main &&
    ++	test_cmp_config . branch.b3.remote &&
    ++	test_cmp_config refs/heads/main branch.b3.merge &&
    ++	# And --track=direct does as well
    ++	git checkout --track=direct -b b4 main &&
    ++	test_cmp_config . branch.b4.remote &&
    ++	test_cmp_config refs/heads/main branch.b4.merge
     +'
     +
      test_done
    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr
     +	git switch -c foo-no-inherit foo &&
     +	test -z "$(git config branch.foo-no-inherit.remote)" &&
     +	test -z "$(git config branch.foo-no-inherit.merge)" &&
    -+	# with autoSetupMerge=inherit, we copy tracking info from foo
    -+	test_config branch.autoSetupMerge inherit &&
    -+	git switch -c foo2 foo &&
    ++	# with --track=inherit, we copy tracking info from foo
    ++	git switch --track=inherit -c foo2 foo &&
     +	test_cmp_config origin branch.foo2.remote &&
     +	test_cmp_config refs/heads/foo branch.foo2.merge &&
    ++	# with autoSetupMerge=inherit, we do the same
    ++	test_config branch.autoSetupMerge inherit &&
    ++	git switch -c foo3 foo &&
    ++	test_cmp_config origin branch.foo3.remote &&
    ++	test_cmp_config refs/heads/foo branch.foo3.merge &&
    ++	# with --track, we override autoSetupMerge
    ++	git switch --track -c foo4 foo &&
    ++	test_cmp_config . branch.foo4.remote &&
    ++	test_cmp_config refs/heads/foo branch.foo4.merge &&
    ++	# and --track=direct does as well
    ++	git switch --track=direct -c foo5 foo &&
    ++	test_cmp_config . branch.foo5.remote &&
    ++	test_cmp_config refs/heads/foo branch.foo5.merge &&
     +	# no tracking info to inherit from main
     +	git switch -c main2 main &&
     +	test -z "$(git config branch.main2.remote)" &&
    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'
      	)
      '
      
    ++test_expect_success 'tracking info copied with --track=inherit' '
    ++	git branch --track=inherit foo2 my1 &&
    ++	test_cmp_config local branch.foo2.remote &&
    ++	test_cmp_config refs/heads/main branch.foo2.merge
    ++'
    ++
     +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
     +	test_unconfig branch.autoSetupMerge &&
     +	# default config does not copy tracking info
    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'
     +	test -z "$(git config branch.foo-no-inherit.merge)" &&
     +	# with autoSetupMerge=inherit, we copy tracking info from my1
     +	test_config branch.autoSetupMerge inherit &&
    -+	git branch foo2 my1 &&
    -+	test_cmp_config local branch.foo2.remote &&
    -+	test_cmp_config refs/heads/main branch.foo2.merge &&
    ++	git branch foo3 my1 &&
    ++	test_cmp_config local branch.foo3.remote &&
    ++	test_cmp_config refs/heads/main branch.foo3.merge &&
     +	# no tracking info to inherit from main
     +	git branch main2 main &&
     +	test -z "$(git config branch.main2.remote)" &&
     +	test -z "$(git config branch.main2.merge)"
     +'
    ++
    ++test_expect_success '--track overrides branch.autoSetupMerge' '
    ++	test_config branch.autoSetupMerge inherit &&
    ++	git branch --track=direct foo4 my1 &&
    ++	test_cmp_config . branch.foo4.remote &&
    ++	test_cmp_config refs/heads/my1 branch.foo4.merge &&
    ++	git branch --no-track foo5 my1 &&
    ++	test -z "$(git config branch.foo5.remote)" &&
    ++	test -z "$(git config branch.foo5.merge)"
    ++'
     +
      test_done
     

 Documentation/config/branch.txt |  3 ++-
 Documentation/git-branch.txt    | 21 ++++++++++++-------
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 37 ++++++++++++++++++++++++++++++++-
 branch.h                        |  3 ++-
 builtin/branch.c                |  6 ++++--
 builtin/checkout.c              |  6 ++++--
 config.c                        |  3 +++
 parse-options-cb.c              | 15 +++++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      |  7 +++++++
 t/t2027-checkout-track.sh       | 23 ++++++++++++++++++++
 t/t2060-switch.sh               | 28 +++++++++++++++++++++++++
 t/t3200-branch.sh               | 33 +++++++++++++++++++++++++++++
 t/t7201-co.sh                   | 17 +++++++++++++++
 16 files changed, 192 insertions(+), 16 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..e484a3803a 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,31 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index 7a88a4861e..eef31f373d 100644
--- a/branch.c
+++ b/branch.c
@@ -126,6 +126,39 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	return -1;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	struct strbuf key = STRBUF_INIT;
+	char *remote;
+	const char *bare_ref;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	strbuf_addf(&key, "branch.%s.remote", bare_ref);
+	if (git_config_get_string(key.buf, &remote)) {
+		warning(_("asked to inherit tracking from %s, but could not find %s"),
+			bare_ref, key.buf);
+		strbuf_release(&key);
+		return -1;
+	}
+
+	strbuf_reset(&key);
+	strbuf_addf(&key, "branch.%s.merge", bare_ref);
+	if (git_config_get_string(key.buf, &tracking->src)) {
+		warning(_("asked to inherit tracking from %s, but could not find %s"),
+			bare_ref, key.buf);
+		strbuf_release(&key);
+		free(remote);
+		return -1;
+	}
+
+	tracking->remote = remote;
+	tracking->matches = 1;
+	strbuf_release(&key);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -139,7 +172,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	if (track != BRANCH_TRACK_INHERIT) {
+		for_each_remote(find_tracked_branch, &tracking);
+	} else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
diff --git a/branch.h b/branch.h
index df0be61506..6484bda8a2 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..15f70fe3fa 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..4bd5a18faf 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcasecmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..b1a1ab797e 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,17 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `%s' expects \"direct\" or \"inherit\""),
+			     opt->long_name);
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..31fb64c5be 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout --orphan gamma &&
 	test -z "$(git config branch.gamma.merge)" &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test -z "$(git config branch.eta.merge)" &&
+	test -z "$(git config branch.eta.remote)" &&
+	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..e87d77f629 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/main branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/main branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..76e9d12e36 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..bc547b08e1 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test -z "$(git config branch.foo5.remote)" &&
+	test -z "$(git config branch.foo5.merge)"
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..ae9f8d02c2 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.33.0.1079.g6e70778dc9-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v2] branch: add "inherit" option for branch.autoSetupMerge
  2021-10-17  4:35     ` Josh Steadmon
@ 2021-10-17  5:50       ` Junio C Hamano
  2021-11-15 21:57         ` Josh Steadmon
  0 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-10-17  5:50 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git

Josh Steadmon <steadmon@google.com> writes:

> Thanks for the feedback. I've added "--track=direct" and
> "--track=inherit" flags in V3, which I'll send out shortly. I am a bit
> skeptical of the value of having "--track=direct" when just "--track"
> still works, but I'll leave it up to the list to decide.

Ah, I assumed that with the configuration variable set to inherit,
you'd make a command line --track to use the upstream of the fork
point branch as the upstream of the new branch, but if your
intention is that (1) without --track or --no-track on the command
line, if configuration is set to inherit, the new branch will track
the upstream of the original, and (2) with --track on the command
line, the new branch will track the original without any "inherit"
magic, then I agree that there is no need for a way to explicitly
choose between --track={direct,inherit} from the command line.  The
choice would be, for those with remote.autosetupmerge=inherit, to
use --no-track (to not track anything), --track (to track directly
the original branch), or say nothing (to inherit tracking), and for
those without that, --no-track (to not track) and --track (to track
directly) are the same but there is no way (other than "git -c
var=val") to do "inherited tracking".

One advantage of allowing to be explicit is that you do not have to
remember (or know --- if you are visiting somebody else's repository
to help them out) how remote.autosetupmerge is configured (or
unconfigured).  From the command line, without having to worry about
configured values to interfere, you can control which tracking mode
is used.



^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (2 preceding siblings ...)
  2021-10-17  4:45 ` [PATCH v3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-10-18 17:50 ` Glen Choo
  2021-10-18 18:08   ` Glen Choo
  2021-11-15 21:44   ` Josh Steadmon
  2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
                   ` (4 subsequent siblings)
  8 siblings, 2 replies; 103+ messages in thread
From: Glen Choo @ 2021-10-18 17:50 UTC (permalink / raw)
  To: Josh Steadmon, git

Josh Steadmon <steadmon@google.com> writes:

> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
> +{
> +	struct strbuf key = STRBUF_INIT;
> +	char *remote;
> +	const char *bare_ref;
> +
> +	bare_ref = orig_ref;
> +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
> +
> +	strbuf_addf(&key, "branch.%s.remote", bare_ref);
> +	if (git_config_get_string(key.buf, &remote)) {
> +		warning("branch.autoSetupMerge=inherit, but could not find %s",
> +			key.buf);
> +		strbuf_release(&key);
> +		return 1;
> +	}
> +	tracking->remote = remote;
> +
> +	strbuf_reset(&key);
> +	strbuf_addf(&key, "branch.%s.merge", bare_ref);
> +	if (git_config_get_string(key.buf, &tracking->src)) {
> +		warning("branch.autoSetupMerge=inherit, but could not find %s",
> +			key.buf);
> +		strbuf_release(&key);
> +		return 1;
> +	}
> +
> +	tracking->matches = 1;
> +	strbuf_release(&key);
> +	return 0;
> +}

I believe that we can get the branch remote via struct branch. Instead
of reading the config, we could do something along the lines of:

   int *explicit;
   struct branch *branch = branch_get();
   char *remote = remote_for_branch(branch, explicit);
   /* Optionally check explicit if we don't want to fall back to
    * "origin" */

I'm not sure which is the idiomatic way to get the branch remote, feel
free to correct me.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge
  2021-10-18 17:50 ` [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Glen Choo
@ 2021-10-18 18:08   ` Glen Choo
  2021-11-15 21:44   ` Josh Steadmon
  1 sibling, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-10-18 18:08 UTC (permalink / raw)
  To: Josh Steadmon, git

Glen Choo <chooglen@google.com> writes:

> I believe that we can get the branch remote via struct branch. Instead
> of reading the config, we could do something along the lines of:
>
>    int *explicit;
>    struct branch *branch = branch_get();
>    char *remote = remote_for_branch(branch, explicit);
>    /* Optionally check explicit if we don't want to fall back to
>     * "origin" */
>
> I'm not sure which is the idiomatic way to get the branch remote, feel
> free to correct me.

Gah, I read and responded to v1 thinking it was v3. Reading v3, it looks
like the feedback is still relevant, so please pretend I responded to
the right email :P

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v3] branch: add flags and config to inherit tracking
  2021-10-17  4:45 ` [PATCH v3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-10-18 18:31   ` Ævar Arnfjörð Bjarmason
  2021-10-18 21:44     ` Junio C Hamano
  2021-11-15 22:22     ` Josh Steadmon
  0 siblings, 2 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-18 18:31 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, emilyshaffer


On Sat, Oct 16 2021, Josh Steadmon wrote:

> It can be helpful when creating a new branch to use the existing
> tracking configuration from the branch point. However, there is
> currently not a method to automatically do so.

There's no method to get *only* that config, but this use-case is why
the "-c" option (copy branch) was added.

I haven't looked at this in any detail, but the seeming lack of mention
of it in the commit message & docs makes me wonder if you missed that
that option could do what you wanted (but granted, it does a lot more,
which maybe you don't want).

But in terms of implementation can't this share more code with the copy
mode? I.e. I'd think that this would just be a limited mode of that,
where we pass some whitelist of specific config to copy over, instead
the current "all the config" with "copy".

And should these options be made to work together somehow? I.e. if you
want to copy branch A to B, but copy tracking info from C?

> [...]
>  -t::
> ---track::
> +--track [inherit|direct]::
>  	When creating a new branch, set up `branch.<name>.remote` and
> -	`branch.<name>.merge` configuration entries to mark the
> -	start-point branch as "upstream" from the new branch. This
> +	`branch.<name>.merge` configuration entries to set "upstream" tracking
> +	configuration for the new branch. This

Setting up ".remote" is what --tracke does, but doesn't it make sense
for such an option to copy over any other config related to that area,
e.g. also .pushRemote, as a user may have edited it since the creation
of the copied-from branch?

Maybe, maybe not. But this & the above comparison with copy makes me
wonder if we'd be better off with some mode similar to the matching
regexes "git config", i.e. you could do a "copy" but only on a list of
matching variables.

Then the --track mode could just be implemented in terms of that, no?

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v3] branch: add flags and config to inherit tracking
  2021-10-18 18:31   ` Ævar Arnfjörð Bjarmason
@ 2021-10-18 21:44     ` Junio C Hamano
  2021-10-18 22:11       ` Ævar Arnfjörð Bjarmason
  2021-11-15 22:22     ` Josh Steadmon
  1 sibling, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-10-18 21:44 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Josh Steadmon, git, emilyshaffer

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Sat, Oct 16 2021, Josh Steadmon wrote:
>
>> It can be helpful when creating a new branch to use the existing
>> tracking configuration from the branch point. However, there is
>> currently not a method to automatically do so.
>
> There's no method to get *only* that config, but this use-case is why
> the "-c" option (copy branch) was added.

Hmph, I doubt the claim about the original motivation behind "-c",
but it sure sounds like an interesting point of view.  The commit at
the tip, as well as configurations are copied, which is most of the
way there, but I suspect that the --track=inherit is mostly to be
used in the context of "git checkout -b" and the mention of "branch"
is merely for simplicity of the description of this topic, no?  And
you cannot say "git checkout --clone-branch original" (yet).

But it is a very interesting way to twist the point of view.



^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v3] branch: add flags and config to inherit tracking
  2021-10-18 21:44     ` Junio C Hamano
@ 2021-10-18 22:11       ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-10-18 22:11 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Josh Steadmon, git, emilyshaffer


On Mon, Oct 18 2021, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> On Sat, Oct 16 2021, Josh Steadmon wrote:
>>
>>> It can be helpful when creating a new branch to use the existing
>>> tracking configuration from the branch point. However, there is
>>> currently not a method to automatically do so.
>>
>> There's no method to get *only* that config, but this use-case is why
>> the "-c" option (copy branch) was added.
>
> Hmph, I doubt the claim about the original motivation behind "-c",
> but it sure sounds like an interesting point of view.

While I didn't implement --copy, a co-worker of mine at the time did (in
52d59cc6452 (branch: add a --copy (-c) option to go with --move (-m),
2017-06-18)).

He wanted to try to submit a patch series to git.git, and came to me
asking for ideas for things to work on. I suggested that he come up with
something that had personally irked him, was lacking from his POV etc.

After coming up blank there I suggested something to the effect of
"well, if you want to just hack on git.git, but can't come up with
anything, I've got a pretty long personal TODO list in that area,
so...".

The feature is the result of that discussion & my mentoring it. So yes,
it's useful for other things as a side-effect of scratching that
particular itch. But in terms of why we ended up with it that's the
exact use-case it's intended for.

> The commit at the tip, as well as configurations are copied, which is
> most of the way there, but I suspect that the --track=inherit is
> mostly to be used in the context of "git checkout -b" and the mention
> of "branch" is merely for simplicity of the description of this topic,
> no?  And you cannot say "git checkout --clone-branch original" (yet).

Yes, this has integration with "git checkout", but "git branch --copy"
doesn't.

The original iteration of "git branch --copy" that didn't make it into
git.git would switch you to the branch (so behave like checkout). So
there is a "git checkout --clone-branch" equivalent out there in the ML
archive, it was just invoked via "git branch".

I think at the time I advocated for having those "checkout" semantics,
but in retrospect I'm happy we didn't go with that, per the table I
later posted at [1]. That UX would be very confusing.

But we should have some way of doing "copy config and switch", and per
[1] we don't have that yet.

So now with this patch we'll have "copy remote config [and switch]"? I'm
not saying that's a bad thing as as incremental step, but it might be
worth thinking of the UX here more holistically. And Josh: I think some
extension/update of that ASCII table I posted in[1] would be very useful
to explain this change (and compare it to "branch -c").

1. https://lore.kernel.org/git/877dkdwgfe.fsf@evledraar.gmail.com/

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge
  2021-10-18 17:50 ` [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Glen Choo
  2021-10-18 18:08   ` Glen Choo
@ 2021-11-15 21:44   ` Josh Steadmon
  1 sibling, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-11-15 21:44 UTC (permalink / raw)
  To: Glen Choo; +Cc: git

On 2021.10.18 10:50, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
> > +{
> > +	struct strbuf key = STRBUF_INIT;
> > +	char *remote;
> > +	const char *bare_ref;
> > +
> > +	bare_ref = orig_ref;
> > +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
> > +
> > +	strbuf_addf(&key, "branch.%s.remote", bare_ref);
> > +	if (git_config_get_string(key.buf, &remote)) {
> > +		warning("branch.autoSetupMerge=inherit, but could not find %s",
> > +			key.buf);
> > +		strbuf_release(&key);
> > +		return 1;
> > +	}
> > +	tracking->remote = remote;
> > +
> > +	strbuf_reset(&key);
> > +	strbuf_addf(&key, "branch.%s.merge", bare_ref);
> > +	if (git_config_get_string(key.buf, &tracking->src)) {
> > +		warning("branch.autoSetupMerge=inherit, but could not find %s",
> > +			key.buf);
> > +		strbuf_release(&key);
> > +		return 1;
> > +	}
> > +
> > +	tracking->matches = 1;
> > +	strbuf_release(&key);
> > +	return 0;
> > +}
> 
> I believe that we can get the branch remote via struct branch. Instead
> of reading the config, we could do something along the lines of:
> 
>    int *explicit;
>    struct branch *branch = branch_get();
>    char *remote = remote_for_branch(branch, explicit);
>    /* Optionally check explicit if we don't want to fall back to
>     * "origin" */
> 
> I'm not sure which is the idiomatic way to get the branch remote, feel
> free to correct me.

Will fix in V4, thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v2] branch: add "inherit" option for branch.autoSetupMerge
  2021-10-17  5:50       ` Junio C Hamano
@ 2021-11-15 21:57         ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-11-15 21:57 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On 2021.10.16 22:50, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > Thanks for the feedback. I've added "--track=direct" and
> > "--track=inherit" flags in V3, which I'll send out shortly. I am a bit
> > skeptical of the value of having "--track=direct" when just "--track"
> > still works, but I'll leave it up to the list to decide.
> 
> Ah, I assumed that with the configuration variable set to inherit,
> you'd make a command line --track to use the upstream of the fork
> point branch as the upstream of the new branch, but if your
> intention is that (1) without --track or --no-track on the command
> line, if configuration is set to inherit, the new branch will track
> the upstream of the original, and (2) with --track on the command
> line, the new branch will track the original without any "inherit"
> magic, then I agree that there is no need for a way to explicitly
> choose between --track={direct,inherit} from the command line.

Yes, I intend for this to be similar to `branch.autoSetupmerge=always`
such that users who prefer this behavior can set it in their global
configs and then not need to remember to pass --track=inherit on the
command line.

> The
> choice would be, for those with remote.autosetupmerge=inherit, to
> use --no-track (to not track anything), --track (to track directly
> the original branch), or say nothing (to inherit tracking), and for
> those without that, --no-track (to not track) and --track (to track
> directly) are the same but there is no way (other than "git -c
> var=val") to do "inherited tracking".
> 
> One advantage of allowing to be explicit is that you do not have to
> remember (or know --- if you are visiting somebody else's repository
> to help them out) how remote.autosetupmerge is configured (or
> unconfigured).  From the command line, without having to worry about
> configured values to interfere, you can control which tracking mode
> is used.



^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v3] branch: add flags and config to inherit tracking
  2021-10-18 18:31   ` Ævar Arnfjörð Bjarmason
  2021-10-18 21:44     ` Junio C Hamano
@ 2021-11-15 22:22     ` Josh Steadmon
  1 sibling, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-11-15 22:22 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, gitster, emilyshaffer

On 2021.10.18 20:31, Ævar Arnfjörð Bjarmason wrote:
> 
> On Sat, Oct 16 2021, Josh Steadmon wrote:
> 
> > It can be helpful when creating a new branch to use the existing
> > tracking configuration from the branch point. However, there is
> > currently not a method to automatically do so.
> 
> There's no method to get *only* that config, but this use-case is why
> the "-c" option (copy branch) was added.
> 
> I haven't looked at this in any detail, but the seeming lack of mention
> of it in the commit message & docs makes me wonder if you missed that
> that option could do what you wanted (but granted, it does a lot more,
> which maybe you don't want).

Indeed, I did miss that option. Thank you for the pointer. I am
conflicted about whether or not we want to copy all the branch
configuration. Most of the options do seem useful to copy, but the
existing config values available for `branch.autoSetupMerge` are
strictly about setting up `branch.<name>.remote`, `branch.<name>.merge`,
and `branch.<name>.rebase`. Adding a new value here that additionally
pulls in all the rest of the config may be confusing. Alternatively we
could add an entirely new option, but then its interaction with
`branch.autoSetupMerge` would be confusing as well.

> But in terms of implementation can't this share more code with the copy
> mode? I.e. I'd think that this would just be a limited mode of that,
> where we pass some whitelist of specific config to copy over, instead
> the current "all the config" with "copy".

I will look into the copy machinery and see what can be reused in V4.

> And should these options be made to work together somehow? I.e. if you
> want to copy branch A to B, but copy tracking info from C?

I am skeptical of the benefit here, but I'm certainly willing to hear
arguments in favor.

The motivation for this series is for Git users (who are not necessarily
Git experts) to have a simple config they can tune to make reduce
friction for the use case of having large repositories with many
submodules (see Emily's discussion [1]). The idea is that we have many
people with a workflow where they'd have `submodule.recurse=true` and
`branch.autoSetupMerge=inherit`. When they checkout a new branch in the
superproject, branches would also be checked out in the submodules, and
appropriate tracking information would also be inherited so that they
can later `git push` without having to manually configure tracking for
every submodule.

This would be a very common operation for these users, and should
therefore require as little friction as possible. While I can see use
cases for your "copy A to B but copy tracking from C", it seems to me
that this would be a much less common situation, and is probably going
to be needed by Git experts who are capable of setting this manually
without relying on configs to make it the default behavior.


[1]: https://lore.kernel.org/git/YHofmWcIAidkvJiD@google.com/

> > [...]
> >  -t::
> > ---track::
> > +--track [inherit|direct]::
> >  	When creating a new branch, set up `branch.<name>.remote` and
> > -	`branch.<name>.merge` configuration entries to mark the
> > -	start-point branch as "upstream" from the new branch. This
> > +	`branch.<name>.merge` configuration entries to set "upstream" tracking
> > +	configuration for the new branch. This
> 
> Setting up ".remote" is what --tracke does, but doesn't it make sense
> for such an option to copy over any other config related to that area,
> e.g. also .pushRemote, as a user may have edited it since the creation
> of the copied-from branch?

Yes, .pushRemote and .mergeOptions both seem like they'd be useful to
copy here.

> Maybe, maybe not. But this & the above comparison with copy makes me
> wonder if we'd be better off with some mode similar to the matching
> regexes "git config", i.e. you could do a "copy" but only on a list of
> matching variables.
> 
> Then the --track mode could just be implemented in terms of that, no?

Using config matching to only copy portions of the branch config seems
overkill to me. IMO it would be better to get agreement for which of the
branch.<name>.* variables to copy, and then use that consistently for
all possible settings of `branch.autoSetupMerge` and
`branch.autoSetupRebase`. If that allows us to reuse the existing copy
machinery, then so much the better.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v4] branch: add flags and config to inherit tracking
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (3 preceding siblings ...)
  2021-10-18 17:50 ` [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Glen Choo
@ 2021-11-16 18:25 ` Josh Steadmon
  2021-11-17  0:33   ` Glen Choo
                     ` (2 more replies)
  2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
                   ` (3 subsequent siblings)
  8 siblings, 3 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-11-16 18:25 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
I've addressed Glen's feedback from V3. However, this brings up a new
issue that was not obvious before: "branch.<name>.merge" can be
specified more than once. On the other hand, the existing tracking setup
code supports only a single merge entry. For now I'm defaulting to use
the first merge entry listed in the branch struct, but I'm curious what
people think the best solution would be. This may be another point in
favor of Ævar's suggestion to reuse the copy-branch-config machinery.

Changes since V3:
* Use branch_get() instead of git_config_get_string() to look up branch
  configuration.
* Remove unnecessary string formatting in new error message in
  parse-options-cb.c.

Range-diff against v3:
1:  b9356d9837 ! 1:  7ad7507f18 branch: add flags and config to inherit tracking
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
      
     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
     +{
    -+	struct strbuf key = STRBUF_INIT;
    -+	char *remote;
     +	const char *bare_ref;
    ++	struct branch *branch;
     +
     +	bare_ref = orig_ref;
     +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
     +
    -+	strbuf_addf(&key, "branch.%s.remote", bare_ref);
    -+	if (git_config_get_string(key.buf, &remote)) {
    -+		warning(_("asked to inherit tracking from %s, but could not find %s"),
    -+			bare_ref, key.buf);
    -+		strbuf_release(&key);
    ++	branch = branch_get(bare_ref);
    ++	if (!branch->remote_name) {
    ++		warning(_("asked to inherit tracking from %s, but no remote is set"),
    ++			bare_ref);
     +		return -1;
     +	}
     +
    -+	strbuf_reset(&key);
    -+	strbuf_addf(&key, "branch.%s.merge", bare_ref);
    -+	if (git_config_get_string(key.buf, &tracking->src)) {
    -+		warning(_("asked to inherit tracking from %s, but could not find %s"),
    -+			bare_ref, key.buf);
    -+		strbuf_release(&key);
    -+		free(remote);
    ++	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
    ++		warning(_("asked to inherit tracking from %s, but no merge configuration is set"),
    ++			bare_ref);
     +		return -1;
     +	}
     +
    -+	tracking->remote = remote;
    ++	tracking->remote = xstrdup(branch->remote_name);
    ++	tracking->src = xstrdup(branch->merge_name[0]);
     +	tracking->matches = 1;
    -+	strbuf_release(&key);
     +	return 0;
     +}
     +
    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const
     +	else if (!strcmp(arg, "inherit"))
     +		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
     +	else
    -+		return error(_("option `%s' expects \"direct\" or \"inherit\""),
    -+			     opt->long_name);
    ++		return error(_("option `--track' expects \"direct\" or \"inherit\""));
     +
     +	return 0;
     +}

 Documentation/config/branch.txt |  3 ++-
 Documentation/git-branch.txt    | 21 ++++++++++++++-------
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 31 ++++++++++++++++++++++++++++++-
 branch.h                        |  3 ++-
 builtin/branch.c                |  6 ++++--
 builtin/checkout.c              |  6 ++++--
 config.c                        |  3 +++
 parse-options-cb.c              | 14 ++++++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      |  7 +++++++
 t/t2027-checkout-track.sh       | 23 +++++++++++++++++++++++
 t/t2060-switch.sh               | 28 ++++++++++++++++++++++++++++
 t/t3200-branch.sh               | 33 +++++++++++++++++++++++++++++++++
 t/t7201-co.sh                   | 17 +++++++++++++++++
 16 files changed, 185 insertions(+), 16 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..e484a3803a 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,31 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index 7a88a4861e..f018e440a6 100644
--- a/branch.c
+++ b/branch.c
@@ -126,6 +126,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	return -1;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	const char *bare_ref;
+	struct branch *branch;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	branch = branch_get(bare_ref);
+	if (!branch->remote_name) {
+		warning(_("asked to inherit tracking from %s, but no remote is set"),
+			bare_ref);
+		return -1;
+	}
+
+	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
+		warning(_("asked to inherit tracking from %s, but no merge configuration is set"),
+			bare_ref);
+		return -1;
+	}
+
+	tracking->remote = xstrdup(branch->remote_name);
+	tracking->src = xstrdup(branch->merge_name[0]);
+	tracking->matches = 1;
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	if (track != BRANCH_TRACK_INHERIT) {
+		for_each_remote(find_tracked_branch, &tracking);
+	} else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
diff --git a/branch.h b/branch.h
index df0be61506..6484bda8a2 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..15f70fe3fa 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..4bd5a18faf 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcasecmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..91261d8c41 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,16 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `--track' expects \"direct\" or \"inherit\""));
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..31fb64c5be 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout --orphan gamma &&
 	test -z "$(git config branch.gamma.merge)" &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test -z "$(git config branch.eta.merge)" &&
+	test -z "$(git config branch.eta.remote)" &&
+	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..e87d77f629 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	git config branch.main.remote origin &&
+	git config branch.main.merge refs/heads/main &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/main branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/main branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..76e9d12e36 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..bc547b08e1 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test -z "$(git config branch.foo5.remote)" &&
+	test -z "$(git config branch.foo5.merge)"
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..ae9f8d02c2 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.34.0.rc1.387.gb447b232ab-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-11-17  0:33   ` Glen Choo
  2021-11-18 22:29   ` Junio C Hamano
  2021-11-19  6:47   ` Ævar Arnfjörð Bjarmason
  2 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-11-17  0:33 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab

Josh Steadmon <steadmon@google.com> writes:

> I've addressed Glen's feedback from V3. However, this brings up a new
> issue that was not obvious before: "branch.<name>.merge" can be
> specified more than once. On the other hand, the existing tracking setup
> code supports only a single merge entry.

Yes, for istance, install_branch_config() uses git_config_set_gently(),
which will override duplicate values.

> For now I'm defaulting to use the first merge entry listed in the
> branch struct, but I'm curious what people think the best solution
> would be.

I can think of at least two possibilities:

The first would be to parse the information into our native data
structures. This is pretty much what you've done in v4, but insteaed of
defaulting to the first merge entry, we would iterate over all of the
possible merge entries and...

> @@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  
>  	memset(&tracking, 0, sizeof(tracking));
>  	tracking.spec.dst = (char *)orig_ref;
> -	if (for_each_remote(find_tracked_branch, &tracking))
> +	if (track != BRANCH_TRACK_INHERIT) {
> +		for_each_remote(find_tracked_branch, &tracking);
> +	} else if (inherit_tracking(&tracking, orig_ref))
>  		return;
>  
>  	if (!tracking.matches)

we get rid of the assumption that we can use a single 'struct tracking'.
when track=BRANCH_TRACK_INHERIT. Of course, this isn't as simple as
calling install_branch_config() repeatedly, because that would override
"branch.<name>.merge" over and over.

> This may be another point in favor of Ævar's suggestion to
> reuse the copy-branch-config machinery.

This is the second option, which is pretty simple. Inheriting the branch
tracking info is a matter of copying the config, which we already do
when we copy branches in builtin/branch.c:

	strbuf_addf(&oldsection, "branch.%s", interpreted_oldname);
	strbuf_release(&oldref);
	strbuf_addf(&newsection, "branch.%s", interpreted_newname);
	strbuf_release(&newref);
	if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
		die(_("Branch is renamed, but update of config-file failed"));

Between these two options, I think the first is a better long-term
solution because I think that parsing the config into our own data
structures is generally less error-prone than operating directly on a
file (e.g. using the data structures was what made this bug obvious to
us in the first place, using repo->config will handle multiple config
files correctly). I don't see '--track=inherit' as being _that_
conceptually similar to copying a branch; I see it as a different mode
of tracking that just so happens to be implementable by copying some
sections in the branch configuration.

But as a practical matter, I don't see any obviously terrible short-term
downsides to just copying the config. It's no less correct than our
branch copying logic and I'm afaid of introducing unintended
consequences by mucking around with install_branch_config().

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
  2021-11-17  0:33   ` Glen Choo
@ 2021-11-18 22:29   ` Junio C Hamano
  2021-11-30 22:05     ` Josh Steadmon
  2021-11-19  6:47   ` Ævar Arnfjörð Bjarmason
  2 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-11-18 22:29 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, chooglen, avarab

Josh Steadmon <steadmon@google.com> writes:

> I've addressed Glen's feedback from V3. However, this brings up a new
> issue that was not obvious before: "branch.<name>.merge" can be
> specified more than once. On the other hand, the existing tracking setup
> code supports only a single merge entry. For now I'm defaulting to use
> the first merge entry listed in the branch struct, but I'm curious what
> people think the best solution would be. This may be another point in
> favor of Ævar's suggestion to reuse the copy-branch-config machinery.

Or we can extend "existing tracking setup code" to support multiple
merge sources.

How does the "git pull" machinery react to them, by the way?  I
think the original intention is to support pulling multiple branches
from the (single) remote configured for the branch with a single
invocation of "git pull", creating an octopus merge, but does it
still work, or nobody uses such a crazy curiosity anymore and it was
once broken and left in non-working state ever since?  What I am
dreaming here is if we can safely ignore all but one of them, taking
the usual "last-one-wins" rule, after some transition period.

> +int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
> +	if (unset)
> +		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
> +	else if (!arg || !strcmp(arg, "direct"))
> +		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
> +	else if (!strcmp(arg, "inherit"))
> +		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
> +	else
> +		return error(_("option `--track' expects \"direct\" or \"inherit\""));

According to recent discussion in another thread,

	error(_("option '--%s` expects '%s' or '%s'"),
		"track", "direct", "inherit");

would be more translater friendly, as these three words are not
subject to translation?  I am not sure if it is really worth it,
though.


^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
  2021-11-17  0:33   ` Glen Choo
  2021-11-18 22:29   ` Junio C Hamano
@ 2021-11-19  6:47   ` Ævar Arnfjörð Bjarmason
  2021-11-30 21:34     ` Josh Steadmon
  2 siblings, 1 reply; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-11-19  6:47 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen


On Tue, Nov 16 2021, Josh Steadmon wrote:

> I've addressed Glen's feedback from V3. However, this brings up a new
> issue that was not obvious before: "branch.<name>.merge" can be
> specified more than once. On the other hand, the existing tracking setup
> code supports only a single merge entry. For now I'm defaulting to use
> the first merge entry listed in the branch struct, but I'm curious what
> people think the best solution would be. This may be another point in
> favor of Ævar's suggestion to reuse the copy-branch-config machinery.

I haven't looked in any detail now at the "should we copy the config?"
questions. Just some quick comments/nits below:


> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
> +{
> +	const char *bare_ref;
> +	struct branch *branch;
> +
> +	bare_ref = orig_ref;
> +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
> +
> +	branch = branch_get(bare_ref);
> +	if (!branch->remote_name) {
> +		warning(_("asked to inherit tracking from %s, but no remote is set"),
> +			bare_ref);
> +		return -1;
> +	}
> +
> +	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
> +		warning(_("asked to inherit tracking from %s, but no merge configuration is set"),
> +			bare_ref);

Should quote ('%s') the %s in both here.

> +		return -1;
> +	}
> +
> +	tracking->remote = xstrdup(branch->remote_name);
> +	tracking->src = xstrdup(branch->merge_name[0]);
> +	tracking->matches = 1;
> +	return 0;
> +}
> +
>  /*
>   * This is called when new_ref is branched off of orig_ref, and tries
>   * to infer the settings for branch.<new_ref>.{remote,merge} from the
> @@ -139,7 +166,9 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  
>  	memset(&tracking, 0, sizeof(tracking));
>  	tracking.spec.dst = (char *)orig_ref;
> -	if (for_each_remote(find_tracked_branch, &tracking))
> +	if (track != BRANCH_TRACK_INHERIT) {
> +		for_each_remote(find_tracked_branch, &tracking);
> +	} else if (inherit_tracking(&tracking, orig_ref))
>  		return;

Style: Dangling braces, can just skip the braces here.

> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>  		OPT__VERBOSE(&filter.verbose,
>  			N_("show hash and subject, give twice for upstream branch")),
>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> +			N_("set up tracking mode (see git-pull(1))"),

Hrm, should we say "git help pull" here, on just not reference it at all
and have a linkgit:git-pull[1]?

Or maybe git-branch.txt and git-pull.txt should be including a template?
As we do with Documentation/rev-list-options.txt, then this
cross-reference wouldn't be needed.

> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),
>  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
>  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
>  		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index b5d477919a..45dab414ea 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
>  {
>  	struct option options[] = {
>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
> +			N_("set up tracking mode (see git-pull(1))"),
> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),
>  		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
>  			   PARSE_OPT_NOCOMPLETE),
>  		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
> diff --git a/config.c b/config.c
> index cb4a8058bf..4bd5a18faf 100644
> --- a/config.c
> +++ b/config.c
> @@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
>  		if (value && !strcasecmp(value, "always")) {
>  			git_branch_track = BRANCH_TRACK_ALWAYS;
>  			return 0;
> +		} else if (value && !strcasecmp(value, "inherit")) {
> +			git_branch_track = BRANCH_TRACK_INHERIT;
> +			return 0;
>  		}

Looks like an existing issue, but we just document "inherit", not
"INHERIT", "iNhErIt" etc. I.e. should it being strcasecmp()
v.s. strcmp() be documented?

> +		return error(_("option `--track' expects \"direct\" or \"inherit\""));

Already commented-on by Junio.

> +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
> +	# Set up tracking config on main
> +	git config branch.main.remote origin &&
> +	git config branch.main.merge refs/heads/main &&
> +	test_config branch.autoSetupMerge inherit &&
> +	# With --track=inherit, we copy the tracking config from main
> +	git checkout --track=inherit -b b1 main &&
> +	test_cmp_config origin branch.b1.remote &&
> +	test_cmp_config refs/heads/main branch.b1.merge &&
> +	# With branch.autoSetupMerge=inherit, we do the same
> +	git checkout -b b2 main &&
> +	test_cmp_config origin branch.b2.remote &&
> +	test_cmp_config refs/heads/main branch.b2.merge &&
> +	# But --track overrides this
> +	git checkout --track -b b3 main &&
> +	test_cmp_config . branch.b3.remote &&
> +	test_cmp_config refs/heads/main branch.b3.merge &&
> +	# And --track=direct does as well
> +	git checkout --track=direct -b b4 main &&
> +	test_cmp_config . branch.b4.remote &&
> +	test_cmp_config refs/heads/main branch.b4.merge
> +'
> +

This is the last test, we can use test_config instead of "git config"
there I think, i.e. it's not setting up config for subseuent tests.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-19  6:47   ` Ævar Arnfjörð Bjarmason
@ 2021-11-30 21:34     ` Josh Steadmon
  2021-12-01  9:11       ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-11-30 21:34 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: git, gitster, chooglen

On 2021.11.19 07:47, Ævar Arnfjörð Bjarmason wrote:
> 
> On Tue, Nov 16 2021, Josh Steadmon wrote:
> 
> > I've addressed Glen's feedback from V3. However, this brings up a new
> > issue that was not obvious before: "branch.<name>.merge" can be
> > specified more than once. On the other hand, the existing tracking setup
> > code supports only a single merge entry. For now I'm defaulting to use
> > the first merge entry listed in the branch struct, but I'm curious what
> > people think the best solution would be. This may be another point in
> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.
> 
> I haven't looked in any detail now at the "should we copy the config?"
> questions. Just some quick comments/nits below:

Thanks for the comments. They're all fixed in V5, which I'll be sending
out soon.

[snip]

> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> >  		OPT__VERBOSE(&filter.verbose,
> >  			N_("show hash and subject, give twice for upstream branch")),
> >  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> > -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> > -			BRANCH_TRACK_EXPLICIT),
> > +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> > +			N_("set up tracking mode (see git-pull(1))"),
> 
> Hrm, should we say "git help pull" here, on just not reference it at all
> and have a linkgit:git-pull[1]?
> 
> Or maybe git-branch.txt and git-pull.txt should be including a template?
> As we do with Documentation/rev-list-options.txt, then this
> cross-reference wouldn't be needed.

Yeah, there's nothing really helpful in git-pull(1) about "--track"
that's easily searchable (i.e. without reading it all straight through),
so I just removed the pointer in the option help string, add added
linkgit:git-pull(1) and linkgit:git-config(1) to git-branch.txt.

I briefly looked at writing a common template for both git-branch.txt
and git-pull.txt but I feel like the git-pull discussion of tracking is
so spread out in that doc that it would require a significant rewrite to
make a common template work.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-18 22:29   ` Junio C Hamano
@ 2021-11-30 22:05     ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-11-30 22:05 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, chooglen, avarab

On 2021.11.18 14:29, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > I've addressed Glen's feedback from V3. However, this brings up a new
> > issue that was not obvious before: "branch.<name>.merge" can be
> > specified more than once. On the other hand, the existing tracking setup
> > code supports only a single merge entry. For now I'm defaulting to use
> > the first merge entry listed in the branch struct, but I'm curious what
> > people think the best solution would be. This may be another point in
> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.
> 
> Or we can extend "existing tracking setup code" to support multiple
> merge sources.
> 
> How does the "git pull" machinery react to them, by the way?  I
> think the original intention is to support pulling multiple branches
> from the (single) remote configured for the branch with a single
> invocation of "git pull", creating an octopus merge, but does it
> still work, or nobody uses such a crazy curiosity anymore and it was
> once broken and left in non-working state ever since?  What I am
> dreaming here is if we can safely ignore all but one of them, taking
> the usual "last-one-wins" rule, after some transition period.

It does still work and creates an octopus merge. Tested as follows:

$ git clone https://github.com/gitster/git
$ cd git
$ git config pull.rebase false
$ git checkout -b test-branch v2.30.0
$ cat >>.git/config <<EOF
[branch "test-branch"]
	remote = origin
	merge = refs/heads/master
	merge = refs/heads/ab/ambiguous-object-name
	merge = refs/heads/js/scalar
EOF
$ git pull
Fast-forwarding to: 4ef9e1ce4ac4ad79f99f5c5712146254b4cca530
Trying simple merge with 352f8e8fcba4726340c946200149e6285c514fc0
Trying simple merge with abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
Simple merge did not work, trying automatic merge.
Auto-merging Makefile
Auto-merging contrib/buildsystems/CMakeLists.txt
Merge made by the 'octopus' strategy.
[...]

$ git cat-file commit HEAD
tree 78bd62c62edfc6e51beb956e548b92b97210ddd4
parent 4ef9e1ce4ac4ad79f99f5c5712146254b4cca530
parent 352f8e8fcba4726340c946200149e6285c514fc0
parent abe6bb3905392d5eb6b01fa6e54d7e784e0522aa
author Josh Steadmon <josh@steadmon.net> 1638309707 -0800
committer Josh Steadmon <josh@steadmon.net> 1638309707 -0800

Merge branches 'ab/ambiguous-object-name', 'js/scalar' and 'master' of https://github.com/gitster/git into test-branch



> > +int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
> > +	if (unset)
> > +		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
> > +	else if (!arg || !strcmp(arg, "direct"))
> > +		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
> > +	else if (!strcmp(arg, "inherit"))
> > +		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
> > +	else
> > +		return error(_("option `--track' expects \"direct\" or \"inherit\""));
> 
> According to recent discussion in another thread,
> 
> 	error(_("option '--%s` expects '%s' or '%s'"),
> 		"track", "direct", "inherit");
> 
> would be more translater friendly, as these three words are not
> subject to translation?  I am not sure if it is really worth it,
> though.

I don't really feel strongly either way, so I switched to the more
translatable version. I was originally following some other examples in
the file, where it seemed that most strings that would require more than
one field expansion were just hardcoded instead.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v4] branch: add flags and config to inherit tracking
  2021-11-30 21:34     ` Josh Steadmon
@ 2021-12-01  9:11       ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-01  9:11 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen


On Tue, Nov 30 2021, Josh Steadmon wrote:

> On 2021.11.19 07:47, Ævar Arnfjörð Bjarmason wrote:
>> 
>> On Tue, Nov 16 2021, Josh Steadmon wrote:
>> 
>> > I've addressed Glen's feedback from V3. However, this brings up a new
>> > issue that was not obvious before: "branch.<name>.merge" can be
>> > specified more than once. On the other hand, the existing tracking setup
>> > code supports only a single merge entry. For now I'm defaulting to use
>> > the first merge entry listed in the branch struct, but I'm curious what
>> > people think the best solution would be. This may be another point in
>> > favor of Ævar's suggestion to reuse the copy-branch-config machinery.
>> 
>> I haven't looked in any detail now at the "should we copy the config?"
>> questions. Just some quick comments/nits below:
>
> Thanks for the comments. They're all fixed in V5, which I'll be sending
> out soon.
>
> [snip]

Thanks, happy that it helped.

>> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>> >  		OPT__VERBOSE(&filter.verbose,
>> >  			N_("show hash and subject, give twice for upstream branch")),
>> >  		OPT__QUIET(&quiet, N_("suppress informational messages")),
>> > -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
>> > -			BRANCH_TRACK_EXPLICIT),
>> > +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
>> > +			N_("set up tracking mode (see git-pull(1))"),
>> 
>> Hrm, should we say "git help pull" here, on just not reference it at all
>> and have a linkgit:git-pull[1]?
>> 
>> Or maybe git-branch.txt and git-pull.txt should be including a template?
>> As we do with Documentation/rev-list-options.txt, then this
>> cross-reference wouldn't be needed.
>
> Yeah, there's nothing really helpful in git-pull(1) about "--track"
> that's easily searchable (i.e. without reading it all straight through),
> so I just removed the pointer in the option help string, add added
> linkgit:git-pull(1) and linkgit:git-config(1) to git-branch.txt.
>
> I briefly looked at writing a common template for both git-branch.txt
> and git-pull.txt but I feel like the git-pull discussion of tracking is
> so spread out in that doc that it would require a significant rewrite to
> make a common template work.

*nod*. I didn't look into if it was easy/doable, just a hint in case
that direction was fruitful. Makes sense.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v5 0/2] branch: inherit tracking configs
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (4 preceding siblings ...)
  2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-07  7:12 ` Josh Steadmon
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
                     ` (2 more replies)
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
                   ` (2 subsequent siblings)
  8 siblings, 3 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-07  7:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, emilyshaffer, avarab

I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least
slightly) prefer handling multiple upstream branches in the existing
tracking setup, I've gone that direction rather than repurposing the
branch copy code. None of the other issues were controversial.

In this version, I'd appreciate feedback mainly on patch 1:
* Is the combination of `git_config_set_gently()` +
  `git_config_set_multivar_gently() the best way to write multiple
  config entries for the same key?
* Does the reorganization of the BRANCH_CONFIG_VERBOSE output make
  things more readable, or less? Should I try to simplify the output
  here so that we don't end up with so many translatable variants of the
  same message?

Also, a question specifically for Junio: this will conflict with
gc/branch-recurse-submodules; should I rebase on that, or wait till it
hits next, or just ignore it for now?

Changes since V4:
* Add new patch (1/2) to refactor branch.c:install_branch_config() to
  accept multiple upstream refs
* When multiple upstream branches are set in the parent branch, inherit
  them all, instead of just the first
* Break out error string arguments for easier translation
* Don't ignore case for values of branch.autosetupmerge
* Move reference to git-pull out of usage string for --track into
  git-branch.txt
* Use test_config instead of `git config` in t2027
* Style fixes: add single-quotes around warning string arguments, remove
  unnecessary braces

Changes since V3:
* Use branch_get() instead of git_config_get_string() to look up branch
  configuration.
* Remove unnecessary string formatting in new error message in
  parse-options-cb.c.

Josh Steadmon (2):
  branch: accept multiple upstream branches for tracking
  branch: add flags and config to inherit tracking

 Documentation/config/branch.txt |   3 +-
 Documentation/git-branch.txt    |  24 +++--
 Documentation/git-checkout.txt  |   2 +-
 Documentation/git-switch.txt    |   2 +-
 branch.c                        | 169 ++++++++++++++++++++++++--------
 branch.h                        |   3 +-
 builtin/branch.c                |   6 +-
 builtin/checkout.c              |   6 +-
 config.c                        |   5 +-
 parse-options-cb.c              |  15 +++
 parse-options.h                 |   2 +
 t/t2017-checkout-orphan.sh      |   7 ++
 t/t2027-checkout-track.sh       |  23 +++++
 t/t2060-switch.sh               |  28 ++++++
 t/t3200-branch.sh               |  33 +++++++
 t/t7201-co.sh                   |  17 ++++
 16 files changed, 289 insertions(+), 56 deletions(-)

Range-diff against v4:
-:  ---------- > 1:  ba7d557725 branch: accept multiple upstream branches for tracking
1:  7ad7507f18 ! 2:  c7e4af9a36 branch: add flags and config to inherit tracking
    @@ Documentation/git-branch.txt: This option is only applicable in non-verbose mode
     +start-point is either a local or remote-tracking branch. Set it to
     +`inherit` if you want to copy the tracking configuration from the
     +branch point.
    +++
    ++See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
    ++how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
      
      --no-track::
      	Do not set up "upstream" configuration, even if the
    @@ Documentation/git-switch.txt: should result in deletion of the path).
      	details.
     
      ## branch.c ##
    +@@
    + 
    + struct tracking {
    + 	struct refspec_item spec;
    +-	char *src;
    ++	struct string_list *srcs;
    + 	const char *remote;
    + 	int matches;
    + };
    +@@ branch.c: static int find_tracked_branch(struct remote *remote, void *priv)
    + 
    + 	if (!remote_find_tracking(remote, &tracking->spec)) {
    + 		if (++tracking->matches == 1) {
    +-			tracking->src = tracking->spec.src;
    ++			string_list_append(tracking->srcs, tracking->spec.src);
    + 			tracking->remote = remote->name;
    + 		} else {
    + 			free(tracking->spec.src);
    +-			FREE_AND_NULL(tracking->src);
    ++			string_list_clear(tracking->srcs, 0);
    + 		}
    + 		tracking->spec.src = NULL;
    + 	}
     @@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const
    - 	return -1;
    + 	string_list_clear(&remotes, 0);
      }
      
     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
     +{
     +	const char *bare_ref;
     +	struct branch *branch;
    ++	int i;
     +
     +	bare_ref = orig_ref;
     +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
     +
     +	branch = branch_get(bare_ref);
     +	if (!branch->remote_name) {
    -+		warning(_("asked to inherit tracking from %s, but no remote is set"),
    ++		warning(_("asked to inherit tracking from '%s', but no remote is set"),
     +			bare_ref);
     +		return -1;
     +	}
     +
     +	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
    -+		warning(_("asked to inherit tracking from %s, but no merge configuration is set"),
    ++		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
     +			bare_ref);
     +		return -1;
     +	}
     +
     +	tracking->remote = xstrdup(branch->remote_name);
    -+	tracking->src = xstrdup(branch->merge_name[0]);
    ++	for (i = 0; i < branch->merge_nr; i++)
    ++		string_list_append(tracking->srcs, branch->merge_name[i]);
     +	tracking->matches = 1;
     +	return 0;
     +}
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
       * This is called when new_ref is branched off of orig_ref, and tries
       * to infer the settings for branch.<new_ref>.{remote,merge} from the
     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
    + 			   enum branch_track track, int quiet)
    + {
    + 	struct tracking tracking;
    ++	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
    + 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
      
      	memset(&tracking, 0, sizeof(tracking));
      	tracking.spec.dst = (char *)orig_ref;
     -	if (for_each_remote(find_tracked_branch, &tracking))
    -+	if (track != BRANCH_TRACK_INHERIT) {
    ++	tracking.srcs = &tracking_srcs;
    ++	if (track != BRANCH_TRACK_INHERIT)
     +		for_each_remote(find_tracked_branch, &tracking);
    -+	} else if (inherit_tracking(&tracking, orig_ref))
    ++	else if (inherit_tracking(&tracking, orig_ref))
      		return;
      
      	if (!tracking.matches)
    +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
    + 		die(_("Not tracking: ambiguous information for ref %s"),
    + 		    orig_ref);
    + 
    +-	if (install_branch_config(config_flags, new_ref, tracking.remote,
    +-			      tracking.src ? tracking.src : orig_ref) < 0)
    ++	if (tracking.srcs->nr < 1)
    ++		string_list_append(tracking.srcs, orig_ref);
    ++	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
    ++			      tracking.srcs) < 0)
    + 		exit(-1);
    + 
    +-	free(tracking.src);
    ++	string_list_clear(tracking.srcs, 0);
    + }
    + 
    + int read_branch_desc(struct strbuf *buf, const char *branch_name)
     
      ## branch.h ##
     @@ branch.h: enum branch_track {
    @@ builtin/branch.c: int cmd_branch(int argc, const char **argv, const char *prefix
     -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
     -			BRANCH_TRACK_EXPLICIT),
     +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
    -+			N_("set up tracking mode (see git-pull(1))"),
    ++			N_("set branch tracking configuration"),
     +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
     +			parse_opt_tracking_mode),
      		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
    @@ builtin/checkout.c: static struct option *add_common_switch_branch_options(
      		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
     
      ## config.c ##
    -@@ config.c: static int git_default_branch_config(const char *var, const char *value)
    - 		if (value && !strcasecmp(value, "always")) {
    +@@ config.c: static int git_default_i18n_config(const char *var, const char *value)
    + static int git_default_branch_config(const char *var, const char *value)
    + {
    + 	if (!strcmp(var, "branch.autosetupmerge")) {
    +-		if (value && !strcasecmp(value, "always")) {
    ++		if (value && !strcmp(value, "always")) {
      			git_branch_track = BRANCH_TRACK_ALWAYS;
      			return 0;
    -+		} else if (value && !strcasecmp(value, "inherit")) {
    ++		} else if (value && !strcmp(value, "inherit")) {
     +			git_branch_track = BRANCH_TRACK_INHERIT;
     +			return 0;
      		}
    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const
     +	else if (!strcmp(arg, "inherit"))
     +		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
     +	else
    -+		return error(_("option `--track' expects \"direct\" or \"inherit\""));
    ++		return error(_("option `%s' expects \"%s\" or \"%s\""),
    ++			     "--track", "direct", "inherit");
     +
     +	return 0;
     +}
    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e
      
     +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
     +	# Set up tracking config on main
    -+	git config branch.main.remote origin &&
    -+	git config branch.main.merge refs/heads/main &&
    ++	test_config branch.main.remote origin &&
    ++	test_config branch.main.merge refs/heads/main &&
     +	test_config branch.autoSetupMerge inherit &&
     +	# With --track=inherit, we copy the tracking config from main
     +	git checkout --track=inherit -b b1 main &&

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.34.1.400.ga245620fadb-goog


^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
@ 2021-12-07  7:12   ` Josh Steadmon
  2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
                       ` (4 more replies)
  2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
  2021-12-07 18:52   ` [PATCH v5 0/2] branch: inherit tracking configs Junio C Hamano
  2 siblings, 5 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-07  7:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, emilyshaffer, avarab

Add a new static variant of install_branch_config() that accepts
multiple remote branch names for tracking. This will be used in an
upcoming commit that enables inheriting the tracking configuration from
a parent branch.

Currently, all callers of install_branch_config() pass only a single
remote. Make install_branch_config() a small wrapper around
install_branch_config_multiple_remotes() so that existing callers do not
need to be changed.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------
 1 file changed, 87 insertions(+), 33 deletions(-)

diff --git a/branch.c b/branch.c
index 7a88a4861e..1aabef4de0 100644
--- a/branch.c
+++ b/branch.c
@@ -55,19 +55,24 @@ N_("\n"
 "the remote tracking information by invoking\n"
 "\"git branch --set-upstream-to=%s%s%s\".");
 
-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,
+		struct string_list *remotes)
 {
 	const char *shortname = NULL;
 	struct strbuf key = STRBUF_INIT;
-	int rebasing = should_setup_rebase(origin);
-
-	if (skip_prefix(remote, "refs/heads/", &shortname)
-	    && !strcmp(local, shortname)
-	    && !origin) {
-		warning(_("Not setting branch %s as its own upstream."),
-			local);
-		return 0;
-	}
+	int i, rebasing = should_setup_rebase(origin);
+
+	if (remotes->nr < 1)
+		BUG("must provide at least one remote for branch config");
+
+	if (!origin)
+		for (i = 0; i < remotes->nr; i++)
+			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)
+			    && !strcmp(local, shortname)) {
+				warning(_("Not setting branch %s as its own upstream."),
+					local);
+				return 0;
+			}
 
 	strbuf_addf(&key, "branch.%s.remote", local);
 	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
@@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
 	strbuf_reset(&key);
 	strbuf_addf(&key, "branch.%s.merge", local);
-	if (git_config_set_gently(key.buf, remote) < 0)
+	/*
+	 * We want to overwrite any existing config with all the branches in
+	 * "remotes". Override any existing config with the first branch, but if
+	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
+	 * we've written so far.
+	 */
+	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
 		goto out_err;
+	for (i = 1; i < remotes->nr; i++)
+		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
+			goto out_err;
 
 	if (rebasing) {
 		strbuf_reset(&key);
@@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (shortname) {
-			if (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);
-			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);
+		int plural = remotes->nr > 1;
+		int all_shortnames = 1;
+		const char *msg_fmt;
+		struct strbuf ref_string = STRBUF_INIT;
+
+		for (i = 0; i < remotes->nr; i++)
+			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)) {
+				strbuf_addf(&ref_string, "'%s', ", shortname);
+			} else {
+				all_shortnames = 0;
+				strbuf_addf(&ref_string, "'%s', ", remotes->items[i].string);
+			}
+		/* The last two characters are an extraneous ", ", so trim those. */
+		strbuf_setlen(&ref_string, ref_string.len - 2);
+
+		if (all_shortnames && origin) {
+			if (rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";
+			else if (rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
+			else if (!rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
+			else if (!rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";
+
+			printf_ln(_(msg_fmt), local, ref_string, origin);
 		} else {
-			if (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);
-			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);
+			if (all_shortnames && !origin && rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track local branches %s by rebasing.";
+			if (all_shortnames && !origin && rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track local branch %s by rebasing.";
+			if (all_shortnames && !origin && !rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track local branches %s.";
+			if (all_shortnames && !origin && !rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track local branch %s.";
+			if (!all_shortnames && origin && rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track remote refs %s by rebasing.";
+			if (!all_shortnames && origin && rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track remote ref %s by rebasing.";
+			if (!all_shortnames && origin && !rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track remote refs %s.";
+			if (!all_shortnames && origin && !rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track remote ref %s.";
+			if (!all_shortnames && !origin && rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track local refs %s by rebasing.";
+			if (!all_shortnames && !origin && rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track local ref %s by rebasing.";
+			if (!all_shortnames && !origin && !rebasing && plural)
+				msg_fmt = "Branch '%s' set up to track local refs %s.";
+			if (!all_shortnames && !origin && !rebasing && !plural)
+				msg_fmt = "Branch '%s' set up to track local ref %s.";
+
+			printf_ln(_(msg_fmt), local, ref_string);
 		}
+
+		strbuf_release(&ref_string);
 	}
 
 	return 0;
@@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	advise(_(tracking_advice),
 	       origin ? origin : "",
 	       origin ? "/" : "",
-	       shortname ? shortname : remote);
+	       remotes->items[0].string);
 
 	return -1;
 }
 
+int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {
+	struct string_list remotes = STRING_LIST_INIT_DUP;
+	string_list_append(&remotes, remote);
+	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
+	string_list_clear(&remotes, 0);
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
-- 
2.34.1.400.ga245620fadb-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-07  7:12   ` Josh Steadmon
  2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
                       ` (2 more replies)
  2021-12-07 18:52   ` [PATCH v5 0/2] branch: inherit tracking configs Junio C Hamano
  2 siblings, 3 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-07  7:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, emilyshaffer, avarab

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 Documentation/config/branch.txt |  3 +-
 Documentation/git-branch.txt    | 24 +++++++++++-----
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 49 ++++++++++++++++++++++++++++-----
 branch.h                        |  3 +-
 builtin/branch.c                |  6 ++--
 builtin/checkout.c              |  6 ++--
 config.c                        |  5 +++-
 parse-options-cb.c              | 15 ++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      |  7 +++++
 t/t2027-checkout-track.sh       | 23 ++++++++++++++++
 t/t2060-switch.sh               | 28 +++++++++++++++++++
 t/t3200-branch.sh               | 33 ++++++++++++++++++++++
 t/t7201-co.sh                   | 17 ++++++++++++
 16 files changed, 202 insertions(+), 23 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..c8b393e51c 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
++
+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index 1aabef4de0..8b17dd5c05 100644
--- a/branch.c
+++ b/branch.c
@@ -11,7 +11,7 @@
 
 struct tracking {
 	struct refspec_item spec;
-	char *src;
+	struct string_list *srcs;
 	const char *remote;
 	int matches;
 };
@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		if (++tracking->matches == 1) {
-			tracking->src = tracking->spec.src;
+			string_list_append(tracking->srcs, tracking->spec.src);
 			tracking->remote = remote->name;
 		} else {
 			free(tracking->spec.src);
-			FREE_AND_NULL(tracking->src);
+			string_list_clear(tracking->srcs, 0);
 		}
 		tracking->spec.src = NULL;
 	}
@@ -180,6 +180,35 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	string_list_clear(&remotes, 0);
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	const char *bare_ref;
+	struct branch *branch;
+	int i;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	branch = branch_get(bare_ref);
+	if (!branch->remote_name) {
+		warning(_("asked to inherit tracking from '%s', but no remote is set"),
+			bare_ref);
+		return -1;
+	}
+
+	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
+		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
+			bare_ref);
+		return -1;
+	}
+
+	tracking->remote = xstrdup(branch->remote_name);
+	for (i = 0; i < branch->merge_nr; i++)
+		string_list_append(tracking->srcs, branch->merge_name[i]);
+	tracking->matches = 1;
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 			   enum branch_track track, int quiet)
 {
 	struct tracking tracking;
+	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	tracking.srcs = &tracking_srcs;
+	if (track != BRANCH_TRACK_INHERIT)
+		for_each_remote(find_tracked_branch, &tracking);
+	else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		die(_("Not tracking: ambiguous information for ref %s"),
 		    orig_ref);
 
-	if (install_branch_config(config_flags, new_ref, tracking.remote,
-			      tracking.src ? tracking.src : orig_ref) < 0)
+	if (tracking.srcs->nr < 1)
+		string_list_append(tracking.srcs, orig_ref);
+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
+			      tracking.srcs) < 0)
 		exit(-1);
 
-	free(tracking.src);
+	string_list_clear(tracking.srcs, 0);
 }
 
 int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index df0be61506..6484bda8a2 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..ebde5023c3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set branch tracking configuration"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..54c0e7d98e 100644
--- a/config.c
+++ b/config.c
@@ -1577,9 +1577,12 @@ static int git_default_i18n_config(const char *var, const char *value)
 static int git_default_branch_config(const char *var, const char *value)
 {
 	if (!strcmp(var, "branch.autosetupmerge")) {
-		if (value && !strcasecmp(value, "always")) {
+		if (value && !strcmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..306194206f 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,17 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `%s' expects \"%s\" or \"%s\""),
+			     "--track", "direct", "inherit");
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..31fb64c5be 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -64,6 +64,13 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout --orphan gamma &&
 	test -z "$(git config branch.gamma.merge)" &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test -z "$(git config branch.eta.merge)" &&
+	test -z "$(git config branch.eta.remote)" &&
+	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..49c7def21c 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	test_config branch.main.remote origin &&
+	test_config branch.main.merge refs/heads/main &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/main branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/main branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..76e9d12e36 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..bc547b08e1 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test -z "$(git config branch.foo5.remote)" &&
+	test -z "$(git config branch.foo5.merge)"
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..ae9f8d02c2 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test -z "$(git config branch.foo-no-inherit.remote)" &&
+	test -z "$(git config branch.foo-no-inherit.merge)" &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test -z "$(git config branch.main2.remote)" &&
+	test -z "$(git config branch.main2.merge)"
+'
+
 test_done
-- 
2.34.1.400.ga245620fadb-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
  2021-12-09 23:03       ` Josh Steadmon
  2021-12-07 19:28     ` Junio C Hamano
                       ` (3 subsequent siblings)
  4 siblings, 1 reply; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-07  8:57 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen, emilyshaffer


On Mon, Dec 06 2021, Josh Steadmon wrote:

> Add a new static variant of install_branch_config() that accepts
> multiple remote branch names for tracking. This will be used in an
> upcoming commit that enables inheriting the tracking configuration from
> a parent branch.
>
> Currently, all callers of install_branch_config() pass only a single
> remote. Make install_branch_config() a small wrapper around
> install_branch_config_multiple_remotes() so that existing callers do not
> need to be changed.
>
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
>  branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------
>  1 file changed, 87 insertions(+), 33 deletions(-)
>
> diff --git a/branch.c b/branch.c
> index 7a88a4861e..1aabef4de0 100644
> --- a/branch.c
> +++ b/branch.c
> @@ -55,19 +55,24 @@ N_("\n"
>  "the remote tracking information by invoking\n"
>  "\"git branch --set-upstream-to=%s%s%s\".");
>  
> -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
> +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,
> +		struct string_list *remotes)
>  {
>  	const char *shortname = NULL;
>  	struct strbuf key = STRBUF_INIT;
> -	int rebasing = should_setup_rebase(origin);
> -
> -	if (skip_prefix(remote, "refs/heads/", &shortname)
> -	    && !strcmp(local, shortname)
> -	    && !origin) {
> -		warning(_("Not setting branch %s as its own upstream."),
> -			local);
> -		return 0;
> -	}
> +	int i, rebasing = should_setup_rebase(origin);
> +
> +	if (remotes->nr < 1)
> +		BUG("must provide at least one remote for branch config");

Since it's unsigned IMO this would be clearer: if (!remotes->nr)

> +
> +	if (!origin)
> +		for (i = 0; i < remotes->nr; i++)
> +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)

For this and others, since you don't use the [i] for anything except
getting the current item using for_each_string_list_item() would be
better.

Partially a nit, partially that I've got another WIP
soon-to-be-submitted topic to fix overflow bugs in that API, and not
having "int i" etc. hardcoded in various places
helps. I.e. for_each_string_list_item() is future-proof.

> +			    && !strcmp(local, shortname)) {
> +				warning(_("Not setting branch %s as its own upstream."),

Better to quote '%s', also s/Not/not/ (lower-case) for all error/warning/die etc.

> +					local);
> +				return 0;
> +			}
>  
>  	strbuf_addf(&key, "branch.%s.remote", local);
>  	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config with the first branch, but if
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */
> +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
>  		goto out_err;
> +	for (i = 1; i < remotes->nr; i++)
> +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;
>  
>  	if (rebasing) {
>  		strbuf_reset(&key);
> @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	strbuf_release(&key);
>  
>  	if (flag & BRANCH_CONFIG_VERBOSE) {
> -		if (shortname) {
> -			if (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);
> -			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);
> +		int plural = remotes->nr > 1;

This....

> +		int all_shortnames = 1;
> +		const char *msg_fmt;
> +		struct strbuf ref_string = STRBUF_INIT;
> +
> +		for (i = 0; i < remotes->nr; i++)
> +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)) {
> +				strbuf_addf(&ref_string, "'%s', ", shortname);
> +			} else {
> +				all_shortnames = 0;
> +				strbuf_addf(&ref_string, "'%s', ", remotes->items[i].string);
> +			}
> +		/* The last two characters are an extraneous ", ", so trim those. */
> +		strbuf_setlen(&ref_string, ref_string.len - 2);

languages RTL in around way wrong the be to going is thing of sort This.

:)

We deal with this in most other places by just formatting a list of
branches. E.g. in the ambiguous object output:

    https://lore.kernel.org/git/patch-v5-4.6-36b6b440c37-20211125T215529Z-avarab@gmail.com/

> +
> +		if (all_shortnames && origin) {
> +			if (rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";
> +			else if (rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
> +			else if (!rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
> +			else if (!rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";

...and this is hardcoding plural rules used in English that don't apply
in a lot of other languages...

> +
> +			printf_ln(_(msg_fmt), local, ref_string, origin);
>  		} else {
> -			if (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);
> -			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);
> +			if (all_shortnames && !origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local branches %s by rebasing.";
> +			if (all_shortnames && !origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local branch %s by rebasing.";
> +			if (all_shortnames && !origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local branches %s.";
> +			if (all_shortnames && !origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local branch %s.";
> +			if (!all_shortnames && origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote refs %s by rebasing.";
> +			if (!all_shortnames && origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote ref %s by rebasing.";
> +			if (!all_shortnames && origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote refs %s.";
> +			if (!all_shortnames && origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote ref %s.";
> +			if (!all_shortnames && !origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local refs %s by rebasing.";
> +			if (!all_shortnames && !origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local ref %s by rebasing.";
> +			if (!all_shortnames && !origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local refs %s.";
> +			if (!all_shortnames && !origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local ref %s.";

...in English you've got one dog, then dogs, so == 1 and >1, but in
various other languages it's:

    git grep Plural-Forms -- po

Anyway, this is easily solved, and even with less verbosity, see:

    git grep -E -W '\bQ_\('

For examples of how to use the magic of libintl to do this for you.

> +
> +			printf_ln(_(msg_fmt), local, ref_string);

...also s/Branch/branch/ for all of them/.

>  		}
> +
> +		strbuf_release(&ref_string);
>  	}
>  
>  	return 0;
> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	advise(_(tracking_advice),
>  	       origin ? origin : "",
>  	       origin ? "/" : "",
> -	       shortname ? shortname : remote);
> +	       remotes->items[0].string);
>  
>  	return -1;
>  }
>  
> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {

nit: overly long line..
> +	struct string_list remotes = STRING_LIST_INIT_DUP;

nit: an extra \n after variable decls...

> +	string_list_append(&remotes, remote);
> +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
> +	string_list_clear(&remotes, 0);
> +}
> +
>  /*
>   * This is called when new_ref is branched off of orig_ref, and tries
>   * to infer the settings for branch.<new_ref>.{remote,merge} from the


^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
  2021-12-08  0:35       ` Glen Choo
  2021-12-14 22:27       ` Josh Steadmon
  2021-12-07 19:41     ` Junio C Hamano
  2021-12-08  1:02     ` Glen Choo
  2 siblings, 2 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-07  9:08 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen, emilyshaffer


On Mon, Dec 06 2021, Josh Steadmon wrote:

> It can be helpful when creating a new branch to use the existing
> tracking configuration from the branch point. However, there is
> currently not a method to automatically do so.
>
> Teach git-{branch,checkout,switch} an "inherit" argument to the
> "--track" option. When this is set, creating a new branch will cause the
> tracking configuration to default to the configuration of the branch
> point, if set.
>
> For example, if branch "main" tracks "origin/main", and we run
> `git checkout --track=inherit -b feature main`, then branch "feature"
> will track "origin/main". Thus, `git status` will show us how far
> ahead/behind we are from origin, and `git pull` will pull from origin.
>
> This is particularly useful when creating branches across many
> submodules, such as with `git submodule foreach ...` (or if running with
> a patch such as [1], which we use at $job), as it avoids having to
> manually set tracking info for each submodule.
>
> Since we've added an argument to "--track", also add "--track=direct" as
> another way to explicitly get the original "--track" behavior ("--track"
> without an argument still works as well).
> @@ -10,7 +10,8 @@ enum branch_track {
>  	BRANCH_TRACK_REMOTE,
>  	BRANCH_TRACK_ALWAYS,
>  	BRANCH_TRACK_EXPLICIT,
> -	BRANCH_TRACK_OVERRIDE
> +	BRANCH_TRACK_OVERRIDE,
> +	BRANCH_TRACK_INHERIT
>  };

So we've got 5 items in this enum...

>  
>  extern enum branch_track git_branch_track;
> diff --git a/builtin/branch.c b/builtin/branch.c
> index b23b1d1752..ebde5023c3 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>  		OPT__VERBOSE(&filter.verbose,
>  			N_("show hash and subject, give twice for upstream branch")),
>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> +			N_("set branch tracking configuration"),
> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),
>  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
>  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),

But map --track, --track=direct --track=inherit to 3/5 of them. Will it
ever make sense to do the oher 2/5 (I really haven't checked)....

>  		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index b5d477919a..45dab414ea 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
>  {
>  	struct option options[] = {
>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
> +			N_("set up tracking mode (see git-pull(1))"),
> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),
>  		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
>  			   PARSE_OPT_NOCOMPLETE),
>  		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),

I wonder if this interface wouldn't be a lot simpler as:

    --track
    --track-explicit --track-direct --track-inherit

Both because it'll work better for auto-complete, and we can (and
presumably some will want) just make --track mean whatever configured
--track-THING you want.

in any case, isn't there a NONEG missing here, or is --no-track-direct
etc. handled by OPT_CALLBACK_F() (I forget...).

>  	if (!strcmp(var, "branch.autosetupmerge")) {
> -		if (value && !strcasecmp(value, "always")) {
> +		if (value && !strcmp(value, "always")) {

...This probably makes sense, but it seems like the behavior change of
"let's not take this case-insensitive" should be split up into its own
change...

> +	test_must_fail git rev-parse --verify HEAD^ &&
> +	git checkout main &&
> +	git config branch.autosetupmerge inherit &&
> +	git checkout --orphan eta &&
> +	test -z "$(git config branch.eta.merge)" &&
> +	test -z "$(git config branch.eta.remote)" &&

Better with the test_must_be_empty etc. helpers.

> +	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&

and this with test_cmp.

(ditto occurances below)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 0/2] branch: inherit tracking configs
  2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-07 18:52   ` Junio C Hamano
  2021-12-08 17:06     ` Glen Choo
  2021-12-10 22:48     ` Johannes Schindelin
  2 siblings, 2 replies; 103+ messages in thread
From: Junio C Hamano @ 2021-12-07 18:52 UTC (permalink / raw)
  To: Josh Steadmon, Johannes Schindelin; +Cc: git, chooglen, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

> I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least
> slightly) prefer handling multiple upstream branches in the existing
> tracking setup, I've gone that direction rather than repurposing the
> branch copy code. None of the other issues were controversial.
>
> In this version, I'd appreciate feedback mainly on patch 1:
> * Is the combination of `git_config_set_gently()` +
>   `git_config_set_multivar_gently() the best way to write multiple
>   config entries for the same key?

IIRC git_config_set_*() is Dscho's brainchild.  If he is available
to comment, it may be a valuable input.

> * Does the reorganization of the BRANCH_CONFIG_VERBOSE output make
>   things more readable, or less? Should I try to simplify the output
>   here so that we don't end up with so many translatable variants of the
>   same message?

Let me find time to take a look.

> Also, a question specifically for Junio: this will conflict with
> gc/branch-recurse-submodules; should I rebase on that, or wait till it
> hits next, or just ignore it for now?

Can you two work out the preferred plan, taking relative importance,
priority, and difficulty between the topics into account, and report
to us how you want to proceed and why you chose the route once you
are done?

Unless the plan you two come up with is outrageously bad, such a
decision by stakeholders would be far more acceptable by the
community than going by my gut feeling.  In short, I'd prefer
decentralization ;-)

Having said that, I think this one is a simpler topic that is closer
to become stable enough than the other one, so it could be that the
rebases want to go the other direction.

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
@ 2021-12-07 19:28     ` Junio C Hamano
  2021-12-14 20:35       ` Josh Steadmon
  2021-12-08  0:16     ` Glen Choo
                       ` (2 subsequent siblings)
  4 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-12-07 19:28 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, chooglen, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

> +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,
> +		struct string_list *remotes)

The line got overly long so perhaps cut the line after "*local,",
as "origin" and "remotes" conceptually are closer together.

What is in the string list?  Names of refs at the remote "origin",
instead of a single ref there?

>  {
>  	const char *shortname = NULL;
>  	struct strbuf key = STRBUF_INIT;
> -	int rebasing = should_setup_rebase(origin);
> -
> -	if (skip_prefix(remote, "refs/heads/", &shortname)
> -	    && !strcmp(local, shortname)
> -	    && !origin) {
> -		warning(_("Not setting branch %s as its own upstream."),
> -			local);

When 'origin' is NULL in the original caller, it means a local
tracking, and making sure we do not say "my 'master' branch builds
on top of itself" makes sense.

> -		return 0;
> -	}
> +	int i, rebasing = should_setup_rebase(origin);
> +
> +	if (remotes->nr < 1)
> +		BUG("must provide at least one remote for branch config");
> +
> +	if (!origin)
> +		for (i = 0; i < remotes->nr; i++)
> +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)
> +			    && !strcmp(local, shortname)) {
> +				warning(_("Not setting branch %s as its own upstream."),
> +					local);
> +				return 0;

I am a bit surprised with this warning and early return before
inspecting the remainder of the list.  When 'origin' is NULL,
i.e. we are talking about the local building on top of another local
branch, if the function is called for the local branch 'main' with
'main' in the remotes list alone, we do want to issue the warning
and exit without doing anything (i.e. degenerating to the original
behaviour of taking a single string variable, when a string list
with a single element is given).  But if the remotes list has 'main'
and 'master', would we want to just "skip" the same one, but still
handle the other ones as if the "same" branch were not in the list?

> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config with the first branch, but if
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */
> +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
>  		goto out_err;
> +	for (i = 1; i < remotes->nr; i++)
> +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;
>  
>  	if (rebasing) {
>  		strbuf_reset(&key);
> @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	strbuf_release(&key);
>  
>  	if (flag & BRANCH_CONFIG_VERBOSE) {
> -		if (shortname) {
> -			if (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);
> -			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);
> +		int plural = remotes->nr > 1;
> +		int all_shortnames = 1;
> +		const char *msg_fmt;
> +		struct strbuf ref_string = STRBUF_INIT;
> +
> +		for (i = 0; i < remotes->nr; i++)
> +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)) {
> +				strbuf_addf(&ref_string, "'%s', ", shortname);
> +			} else {
> +				all_shortnames = 0;
> +				strbuf_addf(&ref_string, "'%s', ", remotes->items[i].string);

So, all_shortnames == true means everything was a local branch in
the 'origin' remote, and when it has a non-branch (like a tag),
all_shortnames becomes false?

> +			}
> +		/* The last two characters are an extraneous ", ", so trim those. */
> +		strbuf_setlen(&ref_string, ref_string.len - 2);

As you are starting from an empty ref_string, a more idiomatic way
to build concatenated string would be to prefix when you add a new
item, e.g.

	loop {
		if (ref_string already has items)
			ref_string.append(", ");
		ref_string.append(this_item);
	}

> +		if (all_shortnames && origin) {
> +			if (rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";

What does it mean to keep my 'topic' branch up-to-date by rebasing
on top of more than one remote sources?  By merging, I can sort-of
understand (i.e. creating an octopus), but would it make sense to
track more than one remote sources in general?  Is it common?

When the benefit is not clear, it might make more sense not to do
this when there are already multiple tracking sources defined for
the original; it might be a mistake that we may not want to spread
with the new option.

Of course, it is very possible that I am missing a perfectly valid
use case where having more than one makes good sense.  If so, please
do not take the above comments as an objection, but adding some
comments before the function to explain when having remote list with
more than one items makes sense and how such a setting can be used
to avoid future readers asking the same (stupid) question as I just
did.


> +			else if (rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
> +			else if (!rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
> +			else if (!rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";
> +
> +			printf_ln(_(msg_fmt), local, ref_string, origin);

I am not sure how well the "plural" thing works with i18n.  It may
suffice for the original in English to have only two choices between
one or more-than-one, but not all languages are English.  Counting
the actual number (I guess remotes->nr is it) and using Q_() to
choose between the possible variants.  I think Ævar knows about this
much better than I do.

But if we are not doing this "set multiple" and instead go the
"detect existing multiple and refrain from spreading the damage"
route, all of that is moot.

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
  2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
@ 2021-12-07 19:41     ` Junio C Hamano
  2021-12-14 20:37       ` Josh Steadmon
  2021-12-08  1:02     ` Glen Choo
  2 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-12-07 19:41 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, chooglen, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

>  --no-track::
>  	Do not set up "upstream" configuration, even if the
> -	branch.autoSetupMerge configuration variable is true.
> +	branch.autoSetupMerge configuration variable is set.

I guess "inherit" is different from "true".
Nice to see an attention to the details.  

> diff --git a/branch.h b/branch.h
> index df0be61506..6484bda8a2 100644
> --- a/branch.h
> +++ b/branch.h
> @@ -10,7 +10,8 @@ enum branch_track {
>  	BRANCH_TRACK_REMOTE,
>  	BRANCH_TRACK_ALWAYS,
>  	BRANCH_TRACK_EXPLICIT,
> -	BRANCH_TRACK_OVERRIDE
> +	BRANCH_TRACK_OVERRIDE,
> +	BRANCH_TRACK_INHERIT
>  };

Unless INHERIT must stay to be at the end of the enumeration even
when we add more of these, let's leave a common at the end to help
future developers.

> -		if (value && !strcasecmp(value, "always")) {
> +		if (value && !strcmp(value, "always")) {

This is belatedly correcting the mistake we made 5 years ago, which
can break existing users who used "[branch]autosetupmerge=Always" in
their configuration files.

I'm OK to fix it to accept only lowercase as written here, see if
anybody screams, and tell them that the all-lowercase is the only
documented way to spell this word.

>  			git_branch_track = BRANCH_TRACK_ALWAYS;
>  			return 0;
> +		} else if (value && !strcmp(value, "inherit")) {
> +			git_branch_track = BRANCH_TRACK_INHERIT;
> +			return 0;
>  		}
>  		git_branch_track = git_config_bool(var, value);
>  		return 0;

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
  2021-12-07 19:28     ` Junio C Hamano
@ 2021-12-08  0:16     ` Glen Choo
  2021-12-08  0:17     ` Glen Choo
  2021-12-08 23:53     ` Glen Choo
  4 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-08  0:16 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config with the first branch, but if
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */
> +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
>  		goto out_err;
> +	for (i = 1; i < remotes->nr; i++)
> +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;

I think that instead of overriding all config with the first value and
then appending every value after that, it'll be more obvious to readers
if we first unset all of the config, then write every value (then the
comment wouldn't have to justify why we make two calls and iteration
starts at 1).

I believe that unsetting all values for a key is supported by
git_config_set_multivar_gently() with value == NULL, i.e.

  /*
   * unset with value = NULL, not sure how this interacts with
   * CONFIG_REGEX_NONE
   */
  if (git_config_set_multivar_gently(key.buf, NULL,
    CONFIG_REGEX_NONE, 0))
    goto out_err;

  for_each_string_list_item(item, remotes) {
    git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);
  }

> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	advise(_(tracking_advice),
>  	       origin ? origin : "",
>  	       origin ? "/" : "",
> -	       shortname ? shortname : remote);
> +	       remotes->items[0].string);
>  
>  	return -1;
>  }

When there is more than one item in remotes->items, this advice is
_technically_ incorrect because --set-upstream-to only takes a single
upstream branch. I think that supporting multiple upstreams in
--set-upstream-to is a fairly niche use case and is out of scope of this
series, so let's not pursue that option.

Another option would be to replace the mention of --set-upstream-to with
"git config add", but that's unfriendly to the >90% of the user
population that doesn't want multiple merge entries.

If we leave the advice as-is, even though it is misleading, a user who
is sophisticated enough to set up multiple merge entries should also
know that --set-upstream-to won't solve their problems, and would
probably be able to fix their problems by mucking around with
.git/config or git config.

So I think it is ok to not change the advice and to only mention the
first merge item. However, it might be worth marking this as NEEDSWORK
so that subsequent readers of this file understand that this advice is
overly-simplistic and might be worth fixing.

>  
> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {
> +	struct string_list remotes = STRING_LIST_INIT_DUP;
> +	string_list_append(&remotes, remote);
> +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
> +	string_list_clear(&remotes, 0);
> +}

string_list_clear() is being called after `return`.

Ævar and Junio have commented on i18n, but I'm unfamiliar with that, so
I won't comment on that :)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
                       ` (2 preceding siblings ...)
  2021-12-08  0:16     ` Glen Choo
@ 2021-12-08  0:17     ` Glen Choo
  2021-12-09 22:45       ` Josh Steadmon
  2021-12-08 23:53     ` Glen Choo
  4 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-08  0:17 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

> @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config with the first branch, but if
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */
> +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
>  		goto out_err;
> +	for (i = 1; i < remotes->nr; i++)
> +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;

I think that instead of overriding all config with the first value and
then appending every value after that, it'll be more obvious to readers
if we first unset all of the config, then write every value (then the
comment wouldn't have to justify why we make two calls and iteration
starts at 1).

I believe that unsetting all values for a key is supported by
git_config_set_multivar_gently() with value == NULL, i.e.

  /*
   * unset with value = NULL, not sure how this interacts with
   * CONFIG_REGEX_NONE
   */
  if (git_config_set_multivar_gently(key.buf, NULL,
    CONFIG_REGEX_NONE, 0))
    goto out_err;

  for_each_string_list_item(item, remotes) {
    git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);
  }

> @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	advise(_(tracking_advice),
>  	       origin ? origin : "",
>  	       origin ? "/" : "",
> -	       shortname ? shortname : remote);
> +	       remotes->items[0].string);
>  
>  	return -1;
>  }

When there is more than one item in remotes->items, this advice is
_technically_ incorrect because --set-upstream-to only takes a single
upstream branch. I think that supporting multiple upstreams in
--set-upstream-to is a fairly niche use case and is out of scope of this
series, so let's not pursue that option.

Another option would be to replace the mention of --set-upstream-to with
"git config add", but that's unfriendly to the >90% of the user
population that doesn't want multiple merge entries.

If we leave the advice as-is, even though it is misleading, a user who
is sophisticated enough to set up multiple merge entries should also
know that --set-upstream-to won't solve their problems, and would
probably be able to fix their problems by mucking around with
.git/config or git config.

So I think it is ok to not change the advice and to only mention the
first merge item. However, it might be worth marking this as NEEDSWORK
so that subsequent readers of this file understand that this advice is
overly-simplistic and might be worth fixing.

>  
> +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {
> +	struct string_list remotes = STRING_LIST_INIT_DUP;
> +	string_list_append(&remotes, remote);
> +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
> +	string_list_clear(&remotes, 0);
> +}

string_list_clear() is being called after `return`.

Ævar and Junio have commented on i18n, but I'm unfamiliar with that, so
I won't comment on that :)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
@ 2021-12-08  0:35       ` Glen Choo
  2021-12-14 22:15         ` Josh Steadmon
  2021-12-14 22:27       ` Josh Steadmon
  1 sibling, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-08  0:35 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Josh Steadmon
  Cc: git, gitster, emilyshaffer

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

>> @@ -10,7 +10,8 @@ enum branch_track {
>>  	BRANCH_TRACK_REMOTE,
>>  	BRANCH_TRACK_ALWAYS,
>>  	BRANCH_TRACK_EXPLICIT,
>> -	BRANCH_TRACK_OVERRIDE
>> +	BRANCH_TRACK_OVERRIDE,
>> +	BRANCH_TRACK_INHERIT
>>  };
>
> So we've got 5 items in this enum...
>
>>  
>>  extern enum branch_track git_branch_track;
>> diff --git a/builtin/branch.c b/builtin/branch.c
>> index b23b1d1752..ebde5023c3 100644
>> --- a/builtin/branch.c
>> +++ b/builtin/branch.c
>> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>>  		OPT__VERBOSE(&filter.verbose,
>>  			N_("show hash and subject, give twice for upstream branch")),
>>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
>> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
>> -			BRANCH_TRACK_EXPLICIT),
>> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
>> +			N_("set branch tracking configuration"),
>> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
>> +			parse_opt_tracking_mode),
>>  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
>>  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
>
> But map --track, --track=direct --track=inherit to 3/5 of them. Will it
> ever make sense to do the oher 2/5 (I really haven't checked)....

Reasonable question, but I believe the answer is no, it doesn't make
sense to map all the values:

* BRANCH_TRACK_REMOTE is just a default value as far as I can tell (I
  don't think this does anything?)
* BRANCH_TRACK_ALWAYS behaves like BRANCH_TRACK_EXPLICIT but it's only
  meant to be set from config files, see 9ed36cfa35 (branch: optionally
  setup branch.*.merge from upstream local branches, 2008-02-19). We're
  more lenient with _ALWAYS than with _EXPLICIT; e.g. we don't die()
  when the upstream doesn't exist.

Even one of the other options doesn't really make that much sense...

* BRANCH_TRACK_OVERRIDE used to be used to implement --set-upstream, but
  that's not necessary any more. Now it's used to make create_branch()
  *not* create a branch sometimes, but that's going away if I get my
  refactor of create_branch()
  (https://lore.kernel.org/git/xmqq1r2pcnyw.fsf@gitster.g/T/#u) :)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
  2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
  2021-12-07 19:41     ` Junio C Hamano
@ 2021-12-08  1:02     ` Glen Choo
  2021-12-14 22:10       ` Josh Steadmon
  2 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-08  1:02 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

> +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
> +{
> +	const char *bare_ref;
> +	struct branch *branch;
> +	int i;
> +
> +	bare_ref = orig_ref;
> +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
> +
> +	branch = branch_get(bare_ref);
> +	if (!branch->remote_name) {
> +		warning(_("asked to inherit tracking from '%s', but no remote is set"),
> +			bare_ref);
> +		return -1;
> +	}
> +
> +	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
> +		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
> +			bare_ref);
> +		return -1;
> +	}
> +
> +	tracking->remote = xstrdup(branch->remote_name);
> +	for (i = 0; i < branch->merge_nr; i++)
> +		string_list_append(tracking->srcs, branch->merge_name[i]);
> +	tracking->matches = 1;
> +	return 0;
> +}

tracking->matches is used to keep track of the number of matched remote
refs. I believe we set tracking->matches = 1 to fulfill two specific
conditions in setup_tracking()...

> +
>  /*
>   * This is called when new_ref is branched off of orig_ref, and tries
>   * to infer the settings for branch.<new_ref>.{remote,merge} from the
> @@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  			   enum branch_track track, int quiet)
>  {
>  	struct tracking tracking;
> +	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
>  	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
>  
>  	memset(&tracking, 0, sizeof(tracking));
>  	tracking.spec.dst = (char *)orig_ref;
> -	if (for_each_remote(find_tracked_branch, &tracking))
> +	tracking.srcs = &tracking_srcs;
> +	if (track != BRANCH_TRACK_INHERIT)
> +		for_each_remote(find_tracked_branch, &tracking);
> +	else if (inherit_tracking(&tracking, orig_ref))
>  		return;
>  
>  	if (!tracking.matches)

*extra context*
	if (!tracking.matches)
		switch (track) {
		case BRANCH_TRACK_ALWAYS:
		case BRANCH_TRACK_EXPLICIT:
		case BRANCH_TRACK_OVERRIDE:
			break;
		default:
			return;
		}

First, tracking.matches > 0, because we want to do work if there are
branches to track.

Secondly,

*extra context*
	if (tracking.matches > 1)
		die(_("Not tracking: ambiguous information for ref %s"),
		    orig_ref);

tracking.matches <= 1, because we don't want to set up tracking if it's
not obvious what ref we want to track.

But as I understand it, BRANCH_TRACK_INHERIT should be unconditional, so
instead of fudging this behavior by setting the correct value for
tracking.matches (which is meant for matching remote refs), we can just
do what the other unconditional BRANCH_TRACK_* options do, which is to
to break instead of return, i.e.

	if (!tracking.matches)
		switch (track) {
		case BRANCH_TRACK_ALWAYS:
		case BRANCH_TRACK_EXPLICIT:
		case BRANCH_TRACK_OVERRIDE:
+   case BRANCH_TRACK_INHERIT:
			break;
		default:
			return;
		}

and BRANCH_TRACK_INHERIT won't have to pretend that tracking.matches is
meaningful to it.

>@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> 		die(_("Not tracking: ambiguous information for ref %s"),
> 		    orig_ref);
> 
>-	if (install_branch_config(config_flags, new_ref, tracking.remote,
>-			      tracking.src ? tracking.src : orig_ref) < 0)
>+	if (tracking.srcs->nr < 1)
>+		string_list_append(tracking.srcs, orig_ref);
>+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
>+			      tracking.srcs) < 0)
> 		exit(-1);
> 
>-	free(tracking.src);
>+	string_list_clear(tracking.srcs, 0);
> }

It looks like install_branch_config_multiple_remotes() can just replace
install_branch_config() in setup_tracking(), nice. This should make it
pretty easy for me to rebase gc/branch-recurse-submodules onto this.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 0/2] branch: inherit tracking configs
  2021-12-07 18:52   ` [PATCH v5 0/2] branch: inherit tracking configs Junio C Hamano
@ 2021-12-08 17:06     ` Glen Choo
  2021-12-10 22:48     ` Johannes Schindelin
  1 sibling, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-08 17:06 UTC (permalink / raw)
  To: Junio C Hamano, Josh Steadmon, Johannes Schindelin
  Cc: git, emilyshaffer, avarab

Junio C Hamano <gitster@pobox.com> writes:

> Can you two work out the preferred plan, taking relative importance,
> priority, and difficulty between the topics into account, and report
> to us how you want to proceed and why you chose the route once you
> are done?
>
> Unless the plan you two come up with is outrageously bad, such a
> decision by stakeholders would be far more acceptable by the
> community than going by my gut feeling.  In short, I'd prefer
> decentralization ;-)
>
> Having said that, I think this one is a simpler topic that is closer
> to become stable enough than the other one, so it could be that the
> rebases want to go the other direction.

Josh and I have discussed this, and yes, we agree with your assessment.

Rebasing my changes on top of this is also easier from a dependency
perspective because this series has a very obvious interface that I can
use.

I'll send a re-roll soon. Thanks!

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
                       ` (3 preceding siblings ...)
  2021-12-08  0:17     ` Glen Choo
@ 2021-12-08 23:53     ` Glen Choo
  2021-12-09  0:08       ` Glen Choo
  4 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-08 23:53 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, emilyshaffer, avarab


I noticed some test failures due to the printf_ln(msg_fmt) region. Since
you are reworking this, the problem might just go away, but I thought I
should mention it just in case.

> +		if (all_shortnames && origin) {
> +			if (rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";
> +			else if (rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
> +			else if (!rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
> +			else if (!rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";
> +
> +			printf_ln(_(msg_fmt), local, ref_string, origin);

Here 

>  		} else {
> -			if (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);
> -			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);
> +			if (all_shortnames && !origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local branches %s by rebasing.";
> +			if (all_shortnames && !origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local branch %s by rebasing.";
> +			if (all_shortnames && !origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local branches %s.";
> +			if (all_shortnames && !origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local branch %s.";
> +			if (!all_shortnames && origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote refs %s by rebasing.";
> +			if (!all_shortnames && origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote ref %s by rebasing.";
> +			if (!all_shortnames && origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track remote refs %s.";
> +			if (!all_shortnames && origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track remote ref %s.";
> +			if (!all_shortnames && !origin && rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local refs %s by rebasing.";
> +			if (!all_shortnames && !origin && rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local ref %s by rebasing.";
> +			if (!all_shortnames && !origin && !rebasing && plural)
> +				msg_fmt = "Branch '%s' set up to track local refs %s.";
> +			if (!all_shortnames && !origin && !rebasing && !plural)
> +				msg_fmt = "Branch '%s' set up to track local ref %s.";
> +
> +			printf_ln(_(msg_fmt), local, ref_string);

and here

>  		}
> +
> +		strbuf_release(&ref_string);
>  	}

We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to
segfault on my mac + clang, but inconsistently! With -O2, it doesn't
always segfault, but the wrong memory is read:

  Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.

With -O0, it always segfaults.

You can see this in the osx-clang run in [1], but it looks like the gcc
ones refuse to build.

s/ref_string/ref_string.buf should fix the problem.

[1] https://github.com/chooglen/git/runs/4464134763?check_suite_focus=true

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-08 23:53     ` Glen Choo
@ 2021-12-09  0:08       ` Glen Choo
  2021-12-09 22:49         ` Josh Steadmon
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-09  0:08 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, emilyshaffer, avarab

Glen Choo <chooglen@google.com> writes:

> We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to
> segfault on my mac + clang, but inconsistently! With -O2, it doesn't
> always segfault, but the wrong memory is read:
>
>   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.

I forgot to mention this earlier but in this example, the test *passes*
even though the stderr message is obviously wrong. I don't see any
coverage of the help message in t3200, which is a bit worrying to me.

After this series is done, is it worth adding test coverage of the help
message?

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-08  0:17     ` Glen Choo
@ 2021-12-09 22:45       ` Josh Steadmon
  2021-12-09 23:47         ` Glen Choo
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-09 22:45 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, emilyshaffer, avarab

On 2021.12.07 16:17, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  
> >  	strbuf_reset(&key);
> >  	strbuf_addf(&key, "branch.%s.merge", local);
> > -	if (git_config_set_gently(key.buf, remote) < 0)
> > +	/*
> > +	 * We want to overwrite any existing config with all the branches in
> > +	 * "remotes". Override any existing config with the first branch, but if
> > +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> > +	 * we've written so far.
> > +	 */
> > +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
> >  		goto out_err;
> > +	for (i = 1; i < remotes->nr; i++)
> > +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> > +			goto out_err;
> 
> I think that instead of overriding all config with the first value and
> then appending every value after that, it'll be more obvious to readers
> if we first unset all of the config, then write every value (then the
> comment wouldn't have to justify why we make two calls and iteration
> starts at 1).
> 
> I believe that unsetting all values for a key is supported by
> git_config_set_multivar_gently() with value == NULL, i.e.
> 
>   /*
>    * unset with value = NULL, not sure how this interacts with
>    * CONFIG_REGEX_NONE
>    */
>   if (git_config_set_multivar_gently(key.buf, NULL,
>     CONFIG_REGEX_NONE, 0))
>     goto out_err;
> 
>   for_each_string_list_item(item, remotes) {
>     git_config_set_multivar_gently(key.buf, item, CONFIG_REGEX_NONE, 0);
>   }

Fixed in V6, thanks for the suggestion.


> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	advise(_(tracking_advice),
> >  	       origin ? origin : "",
> >  	       origin ? "/" : "",
> > -	       shortname ? shortname : remote);
> > +	       remotes->items[0].string);
> >  
> >  	return -1;
> >  }
> 
> When there is more than one item in remotes->items, this advice is
> _technically_ incorrect because --set-upstream-to only takes a single
> upstream branch. I think that supporting multiple upstreams in
> --set-upstream-to is a fairly niche use case and is out of scope of this
> series, so let's not pursue that option.
> 
> Another option would be to replace the mention of --set-upstream-to with
> "git config add", but that's unfriendly to the >90% of the user
> population that doesn't want multiple merge entries.
> 
> If we leave the advice as-is, even though it is misleading, a user who
> is sophisticated enough to set up multiple merge entries should also
> know that --set-upstream-to won't solve their problems, and would
> probably be able to fix their problems by mucking around with
> .git/config or git config.
> 
> So I think it is ok to not change the advice and to only mention the
> first merge item. However, it might be worth marking this as NEEDSWORK
> so that subsequent readers of this file understand that this advice is
> overly-simplistic and might be worth fixing.

Sounds like we should just have separate advice strings for single vs.
multiple merge configs?


> >  
> > +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {
> > +	struct string_list remotes = STRING_LIST_INIT_DUP;
> > +	string_list_append(&remotes, remote);
> > +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
> > +	string_list_clear(&remotes, 0);
> > +}
> 
> string_list_clear() is being called after `return`.

That's an embarrassing bug, thank you for catching it.


> Ævar and Junio have commented on i18n, but I'm unfamiliar with that, so
> I won't comment on that :)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-09  0:08       ` Glen Choo
@ 2021-12-09 22:49         ` Josh Steadmon
  2021-12-09 23:43           ` Glen Choo
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-09 22:49 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, emilyshaffer, avarab

On 2021.12.08 16:08, Glen Choo wrote:
> Glen Choo <chooglen@google.com> writes:
> 
> > We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to
> > segfault on my mac + clang, but inconsistently! With -O2, it doesn't
> > always segfault, but the wrong memory is read:
> >
> >   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.
> 
> I forgot to mention this earlier but in this example, the test *passes*
> even though the stderr message is obviously wrong. I don't see any
> coverage of the help message in t3200, which is a bit worrying to me.
> 
> After this series is done, is it worth adding test coverage of the help
> message?

Yeah, I caught this earlier while reworking this section based on Ævar's
review, but thank you for pointing it out. I'm unsure about checking
formatting of message strings in tests; it would certainly have caught
this bug but it seems that more often they're just "change detectors"
rather than good tests. But I could be swayed if you or others feel it's
important.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
@ 2021-12-09 23:03       ` Josh Steadmon
  2021-12-10  1:00         ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-09 23:03 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, gitster, chooglen, emilyshaffer

On 2021.12.07 09:57, Ævar Arnfjörð Bjarmason wrote:
> 
> On Mon, Dec 06 2021, Josh Steadmon wrote:
> 
> > Add a new static variant of install_branch_config() that accepts
> > multiple remote branch names for tracking. This will be used in an
> > upcoming commit that enables inheriting the tracking configuration from
> > a parent branch.
> >
> > Currently, all callers of install_branch_config() pass only a single
> > remote. Make install_branch_config() a small wrapper around
> > install_branch_config_multiple_remotes() so that existing callers do not
> > need to be changed.
> >
> > Signed-off-by: Josh Steadmon <steadmon@google.com>
> > ---
> >  branch.c | 120 ++++++++++++++++++++++++++++++++++++++++---------------
> >  1 file changed, 87 insertions(+), 33 deletions(-)
> >
> > diff --git a/branch.c b/branch.c
> > index 7a88a4861e..1aabef4de0 100644
> > --- a/branch.c
> > +++ b/branch.c
> > @@ -55,19 +55,24 @@ N_("\n"
> >  "the remote tracking information by invoking\n"
> >  "\"git branch --set-upstream-to=%s%s%s\".");
> >  
> > -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
> > +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,
> > +		struct string_list *remotes)
> >  {
> >  	const char *shortname = NULL;
> >  	struct strbuf key = STRBUF_INIT;
> > -	int rebasing = should_setup_rebase(origin);
> > -
> > -	if (skip_prefix(remote, "refs/heads/", &shortname)
> > -	    && !strcmp(local, shortname)
> > -	    && !origin) {
> > -		warning(_("Not setting branch %s as its own upstream."),
> > -			local);
> > -		return 0;
> > -	}
> > +	int i, rebasing = should_setup_rebase(origin);
> > +
> > +	if (remotes->nr < 1)
> > +		BUG("must provide at least one remote for branch config");
> 
> Since it's unsigned IMO this would be clearer: if (!remotes->nr)

Fixed in v6.


> > +
> > +	if (!origin)
> > +		for (i = 0; i < remotes->nr; i++)
> > +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)
> 
> For this and others, since you don't use the [i] for anything except
> getting the current item using for_each_string_list_item() would be
> better.
> 
> Partially a nit, partially that I've got another WIP
> soon-to-be-submitted topic to fix overflow bugs in that API, and not
> having "int i" etc. hardcoded in various places
> helps. I.e. for_each_string_list_item() is future-proof.

Thanks. I somehow missed for_each_string_list_item() when I checked the
header file. Fixed in v6.

> > +			    && !strcmp(local, shortname)) {
> > +				warning(_("Not setting branch %s as its own upstream."),
> 
> Better to quote '%s', also s/Not/not/ (lower-case) for all error/warning/die etc.

Done (for now) in v6, but this might become moot as I address Junio's
review comments.


> > +					local);
> > +				return 0;
> > +			}
> >  
> >  	strbuf_addf(&key, "branch.%s.remote", local);
> >  	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  
> >  	strbuf_reset(&key);
> >  	strbuf_addf(&key, "branch.%s.merge", local);
> > -	if (git_config_set_gently(key.buf, remote) < 0)
> > +	/*
> > +	 * We want to overwrite any existing config with all the branches in
> > +	 * "remotes". Override any existing config with the first branch, but if
> > +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> > +	 * we've written so far.
> > +	 */
> > +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
> >  		goto out_err;
> > +	for (i = 1; i < remotes->nr; i++)
> > +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> > +			goto out_err;
> >  
> >  	if (rebasing) {
> >  		strbuf_reset(&key);
> > @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	strbuf_release(&key);
> >  
> >  	if (flag & BRANCH_CONFIG_VERBOSE) {
> > -		if (shortname) {
> > -			if (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);
> > -			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);
> > +		int plural = remotes->nr > 1;
> 
> This....
> 
> > +		int all_shortnames = 1;
> > +		const char *msg_fmt;
> > +		struct strbuf ref_string = STRBUF_INIT;
> > +
> > +		for (i = 0; i < remotes->nr; i++)
> > +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)) {
> > +				strbuf_addf(&ref_string, "'%s', ", shortname);
> > +			} else {
> > +				all_shortnames = 0;
> > +				strbuf_addf(&ref_string, "'%s', ", remotes->items[i].string);
> > +			}
> > +		/* The last two characters are an extraneous ", ", so trim those. */
> > +		strbuf_setlen(&ref_string, ref_string.len - 2);
> 
> languages RTL in around way wrong the be to going is thing of sort This.
> 
> :)
> 
> We deal with this in most other places by just formatting a list of
> branches. E.g. in the ambiguous object output:
> 
>     https://lore.kernel.org/git/patch-v5-4.6-36b6b440c37-20211125T215529Z-avarab@gmail.com/

Done, thanks for the suggestion.

> > +
> > +		if (all_shortnames && origin) {
> > +			if (rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";
> > +			else if (rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
> > +			else if (!rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
> > +			else if (!rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";
> 
> ...and this is hardcoding plural rules used in English that don't apply
> in a lot of other languages...
> 
> > +
> > +			printf_ln(_(msg_fmt), local, ref_string, origin);
> >  		} else {
> > -			if (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);
> > -			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);
> > +			if (all_shortnames && !origin && rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track local branches %s by rebasing.";
> > +			if (all_shortnames && !origin && rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track local branch %s by rebasing.";
> > +			if (all_shortnames && !origin && !rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track local branches %s.";
> > +			if (all_shortnames && !origin && !rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track local branch %s.";
> > +			if (!all_shortnames && origin && rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote refs %s by rebasing.";
> > +			if (!all_shortnames && origin && rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote ref %s by rebasing.";
> > +			if (!all_shortnames && origin && !rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote refs %s.";
> > +			if (!all_shortnames && origin && !rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote ref %s.";
> > +			if (!all_shortnames && !origin && rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track local refs %s by rebasing.";
> > +			if (!all_shortnames && !origin && rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track local ref %s by rebasing.";
> > +			if (!all_shortnames && !origin && !rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track local refs %s.";
> > +			if (!all_shortnames && !origin && !rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track local ref %s.";
> 
> ...in English you've got one dog, then dogs, so == 1 and >1, but in
> various other languages it's:
> 
>     git grep Plural-Forms -- po
> 
> Anyway, this is easily solved, and even with less verbosity, see:
> 
>     git grep -E -W '\bQ_\('
> 
> For examples of how to use the magic of libintl to do this for you.

Thank you for the pointer. I looked specifically for dealing with plural
forms in our docs, but the referenced "Preparing Strings" gettext docs
were not helpful for this. (Although I see now I should have read
further in po/README.md to find the relevant advice).  I may send a
separate change to po/README.md to make it easier to find in the future.

> > +
> > +			printf_ln(_(msg_fmt), local, ref_string);
> 
> ...also s/Branch/branch/ for all of them/.

Done.

> >  		}
> > +
> > +		strbuf_release(&ref_string);
> >  	}
> >  
> >  	return 0;
> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	advise(_(tracking_advice),
> >  	       origin ? origin : "",
> >  	       origin ? "/" : "",
> > -	       shortname ? shortname : remote);
> > +	       remotes->items[0].string);
> >  
> >  	return -1;
> >  }
> >  
> > +int install_branch_config(int flag, const char *local, const char *origin, const char *remote) {
> 
> nit: overly long line..

Fixed.


> > +	struct string_list remotes = STRING_LIST_INIT_DUP;
> 
> nit: an extra \n after variable decls...

Fixed.


> > +	string_list_append(&remotes, remote);
> > +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
> > +	string_list_clear(&remotes, 0);
> > +}
> > +
> >  /*
> >   * This is called when new_ref is branched off of orig_ref, and tries
> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the
> 


Thanks for the review!

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-09 22:49         ` Josh Steadmon
@ 2021-12-09 23:43           ` Glen Choo
  0 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-09 23:43 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

>> > We print ref_string, which is a strbuf. This causes t/t3200-branch.sh to
>> > segfault on my mac + clang, but inconsistently! With -O2, it doesn't
>> > always segfault, but the wrong memory is read:
>> >
>> >   Branch 'my3' set up to track remote branch local from 'Branch '%s' set up to track remote branch %s from '%s'.'.
>> 
>> I forgot to mention this earlier but in this example, the test *passes*
>> even though the stderr message is obviously wrong. I don't see any
>> coverage of the help message in t3200, which is a bit worrying to me.
>> 
>> After this series is done, is it worth adding test coverage of the help
>> message?
>
> Yeah, I caught this earlier while reworking this section based on Ævar's
> review, but thank you for pointing it out. I'm unsure about checking
> formatting of message strings in tests; it would certainly have caught
> this bug but it seems that more often they're just "change detectors"
> rather than good tests.

I agree, although such a test might still be beneficial on the whole if
the change detector is easy to update.

> But I could be swayed if you or others feel it's important.

Because this is rather "change detector"y, I don't think it's important
either, but I'm also open to being convinced.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-09 22:45       ` Josh Steadmon
@ 2021-12-09 23:47         ` Glen Choo
  2021-12-10  1:03           ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-09 23:47 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, emilyshaffer, avarab

Josh Steadmon <steadmon@google.com> writes:

>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>> >  	advise(_(tracking_advice),
>> >  	       origin ? origin : "",
>> >  	       origin ? "/" : "",
>> > -	       shortname ? shortname : remote);
>> > +	       remotes->items[0].string);
>> >  
>> >  	return -1;
>> >  }
>> 
>> When there is more than one item in remotes->items, this advice is
>> _technically_ incorrect because --set-upstream-to only takes a single
>> upstream branch. I think that supporting multiple upstreams in
>> --set-upstream-to is a fairly niche use case and is out of scope of this
>> series, so let's not pursue that option.
>> 
>> Another option would be to replace the mention of --set-upstream-to with
>> "git config add", but that's unfriendly to the >90% of the user
>> population that doesn't want multiple merge entries.
>> 
>> If we leave the advice as-is, even though it is misleading, a user who
>> is sophisticated enough to set up multiple merge entries should also
>> know that --set-upstream-to won't solve their problems, and would
>> probably be able to fix their problems by mucking around with
>> .git/config or git config.
>> 
>> So I think it is ok to not change the advice and to only mention the
>> first merge item. However, it might be worth marking this as NEEDSWORK
>> so that subsequent readers of this file understand that this advice is
>> overly-simplistic and might be worth fixing.
>
> Sounds like we should just have separate advice strings for single vs.
> multiple merge configs?

That sounds like a good idea if it's not too much work. Otherwise, a
NEEDSWORK is still acceptable to me (but that said, I'm not an authority
on this matter).

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-09 23:03       ` Josh Steadmon
@ 2021-12-10  1:00         ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-10  1:00 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen, emilyshaffer


On Thu, Dec 09 2021, Josh Steadmon wrote:

> On 2021.12.07 09:57, Ævar Arnfjörð Bjarmason wrote:
>> 
>> On Mon, Dec 06 2021, Josh Steadmon wrote:
>> ...in English you've got one dog, then dogs, so == 1 and >1, but in
>> various other languages it's:
>> 
>>     git grep Plural-Forms -- po
>> 
>> Anyway, this is easily solved, and even with less verbosity, see:
>> 
>>     git grep -E -W '\bQ_\('
>> 
>> For examples of how to use the magic of libintl to do this for you.
>
> Thank you for the pointer. I looked specifically for dealing with plural
> forms in our docs, but the referenced "Preparing Strings" gettext docs
> were not helpful for this. (Although I see now I should have read
> further in po/README.md to find the relevant advice).  I may send a
> separate change to po/README.md to make it easier to find in the future.

Thanks, that would be really helpful.

>> > +	string_list_append(&remotes, remote);
>> > +	return install_branch_config_multiple_remotes(flag, local, origin, &remotes);
>> > +	string_list_clear(&remotes, 0);
>> > +}
>> > +
>> >  /*
>> >   * This is called when new_ref is branched off of orig_ref, and tries
>> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the
>> 
>
>
> Thanks for the review!

Happy to help, cheers!

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-09 23:47         ` Glen Choo
@ 2021-12-10  1:03           ` Ævar Arnfjörð Bjarmason
  2021-12-10 17:32             ` Glen Choo
  0 siblings, 1 reply; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-10  1:03 UTC (permalink / raw)
  To: Glen Choo; +Cc: Josh Steadmon, git, gitster, emilyshaffer


On Thu, Dec 09 2021, Glen Choo wrote:

> Josh Steadmon <steadmon@google.com> writes:
>
>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>>> >  	advise(_(tracking_advice),
>>> >  	       origin ? origin : "",
>>> >  	       origin ? "/" : "",
>>> > -	       shortname ? shortname : remote);
>>> > +	       remotes->items[0].string);
>>> >  
>>> >  	return -1;
>>> >  }
>>> 
>>> When there is more than one item in remotes->items, this advice is
>>> _technically_ incorrect because --set-upstream-to only takes a single
>>> upstream branch. I think that supporting multiple upstreams in
>>> --set-upstream-to is a fairly niche use case and is out of scope of this
>>> series, so let's not pursue that option.
>>> 
>>> Another option would be to replace the mention of --set-upstream-to with
>>> "git config add", but that's unfriendly to the >90% of the user
>>> population that doesn't want multiple merge entries.
>>> 
>>> If we leave the advice as-is, even though it is misleading, a user who
>>> is sophisticated enough to set up multiple merge entries should also
>>> know that --set-upstream-to won't solve their problems, and would
>>> probably be able to fix their problems by mucking around with
>>> .git/config or git config.
>>> 
>>> So I think it is ok to not change the advice and to only mention the
>>> first merge item. However, it might be worth marking this as NEEDSWORK
>>> so that subsequent readers of this file understand that this advice is
>>> overly-simplistic and might be worth fixing.
>>
>> Sounds like we should just have separate advice strings for single vs.
>> multiple merge configs?
>
> That sounds like a good idea if it's not too much work. Otherwise, a
> NEEDSWORK is still acceptable to me (but that said, I'm not an authority
> on this matter).

We haven't used Q_() with advise() yet, but there's no reason not to:

	advise(Q_("fix your branch by doing xyz",
		  "fix your branches by doing xyz",
                  branches_nr));

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-10  1:03           ` Ævar Arnfjörð Bjarmason
@ 2021-12-10 17:32             ` Glen Choo
  2021-12-11  2:18               ` Ævar Arnfjörð Bjarmason
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-10 17:32 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: Josh Steadmon, git, gitster, emilyshaffer

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> On Thu, Dec 09 2021, Glen Choo wrote:
>
>> Josh Steadmon <steadmon@google.com> writes:
>>
>>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>>>> >  	advise(_(tracking_advice),
>>>> >  	       origin ? origin : "",
>>>> >  	       origin ? "/" : "",
>>>> > -	       shortname ? shortname : remote);
>>>> > +	       remotes->items[0].string);
>>>> >  
>>>> >  	return -1;
>>>> >  }
>>>> 
>>>> When there is more than one item in remotes->items, this advice is
>>>> _technically_ incorrect because --set-upstream-to only takes a single
>>>> upstream branch. I think that supporting multiple upstreams in
>>>> --set-upstream-to is a fairly niche use case and is out of scope of this
>>>> series, so let's not pursue that option.
>>>> 
>>>> Another option would be to replace the mention of --set-upstream-to with
>>>> "git config add", but that's unfriendly to the >90% of the user
>>>> population that doesn't want multiple merge entries.
>>>> 
>>>> If we leave the advice as-is, even though it is misleading, a user who
>>>> is sophisticated enough to set up multiple merge entries should also
>>>> know that --set-upstream-to won't solve their problems, and would
>>>> probably be able to fix their problems by mucking around with
>>>> .git/config or git config.
>>>> 
>>>> So I think it is ok to not change the advice and to only mention the
>>>> first merge item. However, it might be worth marking this as NEEDSWORK
>>>> so that subsequent readers of this file understand that this advice is
>>>> overly-simplistic and might be worth fixing.
>>>
>>> Sounds like we should just have separate advice strings for single vs.
>>> multiple merge configs?
>>
>> That sounds like a good idea if it's not too much work. Otherwise, a
>> NEEDSWORK is still acceptable to me (but that said, I'm not an authority
>> on this matter).
>
> We haven't used Q_() with advise() yet, but there's no reason not to:
>
> 	advise(Q_("fix your branch by doing xyz",
> 		  "fix your branches by doing xyz",
>                   branches_nr));

Neat, that should do it in most cases. I think this one is a little
trickier because the plural advice messages requires constructing a
list, e.g.

Singular: 
  "\n"
  "After fixing the error cause you may try to fix up\n"
  "the remote tracking information by invoking\n"
  "\"git branch --set-upstream-to=%s%s%s\"."

Plural:
  "\n"
  "After fixing the error cause you may try to fix up\n"
  "the remote tracking information by invoking\n"
  "\"git config --add my_new_remote remote_name\"
  "\"git config --add my_new_upstream1 upstream_name1\"
  "\"git config --add my_new_upstream2 upstream_name2\"

But perhaps this is not too hard since you've already included examples
of how to format lists of strings [1]

[1] https://lore.kernel.org/git/211207.86mtlcpyu4.gmgdl@evledraar.gmail.com

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 0/2] branch: inherit tracking configs
  2021-12-07 18:52   ` [PATCH v5 0/2] branch: inherit tracking configs Junio C Hamano
  2021-12-08 17:06     ` Glen Choo
@ 2021-12-10 22:48     ` Johannes Schindelin
  2021-12-14 22:11       ` Josh Steadmon
  1 sibling, 1 reply; 103+ messages in thread
From: Johannes Schindelin @ 2021-12-10 22:48 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Josh Steadmon, git, chooglen, emilyshaffer, avarab

Hi,

On Tue, 7 Dec 2021, Junio C Hamano wrote:

> Josh Steadmon <steadmon@google.com> writes:
>
> > I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least
> > slightly) prefer handling multiple upstream branches in the existing
> > tracking setup, I've gone that direction rather than repurposing the
> > branch copy code. None of the other issues were controversial.
> >
> > In this version, I'd appreciate feedback mainly on patch 1:
> > * Is the combination of `git_config_set_gently()` +
> >   `git_config_set_multivar_gently() the best way to write multiple
> >   config entries for the same key?
>
> IIRC git_config_set_*() is Dscho's brainchild.  If he is available
> to comment, it may be a valuable input.

The `git_config_set_multivar_gently()` function was really only intended
to add one key/value pair.

Currently, there is no function to add multiple key/value pairs, and while
it is slightly wasteful to lock the config multiple times to write a bunch
of key/value pairs, it is not the worst in the world for a small use case
like this one.

So yes, for the moment I would go with the suggested design.

One thing you might want to do is to avoid the extra
`git_config_set_gently()` before the `for` loop, simply by passing `i == 0
? 0 : CONFIG_FLAGS_MULTI_REPLACE` as `flags` parameter to the multivar
version of the function.

But that would optimize for code size rather than for readability, and I
would actually prefer the more verbose version.

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-10 17:32             ` Glen Choo
@ 2021-12-11  2:18               ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2021-12-11  2:18 UTC (permalink / raw)
  To: Glen Choo; +Cc: Josh Steadmon, git, gitster, emilyshaffer


On Fri, Dec 10 2021, Glen Choo wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> On Thu, Dec 09 2021, Glen Choo wrote:
>>
>>> Josh Steadmon <steadmon@google.com> writes:
>>>
>>>>> > @@ -121,11 +168,18 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>>>>> >  	advise(_(tracking_advice),
>>>>> >  	       origin ? origin : "",
>>>>> >  	       origin ? "/" : "",
>>>>> > -	       shortname ? shortname : remote);
>>>>> > +	       remotes->items[0].string);
>>>>> >  
>>>>> >  	return -1;
>>>>> >  }
>>>>> 
>>>>> When there is more than one item in remotes->items, this advice is
>>>>> _technically_ incorrect because --set-upstream-to only takes a single
>>>>> upstream branch. I think that supporting multiple upstreams in
>>>>> --set-upstream-to is a fairly niche use case and is out of scope of this
>>>>> series, so let's not pursue that option.
>>>>> 
>>>>> Another option would be to replace the mention of --set-upstream-to with
>>>>> "git config add", but that's unfriendly to the >90% of the user
>>>>> population that doesn't want multiple merge entries.
>>>>> 
>>>>> If we leave the advice as-is, even though it is misleading, a user who
>>>>> is sophisticated enough to set up multiple merge entries should also
>>>>> know that --set-upstream-to won't solve their problems, and would
>>>>> probably be able to fix their problems by mucking around with
>>>>> .git/config or git config.
>>>>> 
>>>>> So I think it is ok to not change the advice and to only mention the
>>>>> first merge item. However, it might be worth marking this as NEEDSWORK
>>>>> so that subsequent readers of this file understand that this advice is
>>>>> overly-simplistic and might be worth fixing.
>>>>
>>>> Sounds like we should just have separate advice strings for single vs.
>>>> multiple merge configs?
>>>
>>> That sounds like a good idea if it's not too much work. Otherwise, a
>>> NEEDSWORK is still acceptable to me (but that said, I'm not an authority
>>> on this matter).
>>
>> We haven't used Q_() with advise() yet, but there's no reason not to:
>>
>> 	advise(Q_("fix your branch by doing xyz",
>> 		  "fix your branches by doing xyz",
>>                   branches_nr));
>
> Neat, that should do it in most cases. I think this one is a little
> trickier because the plural advice messages requires constructing a
> list, e.g.
>
> Singular: 
>   "\n"
>   "After fixing the error cause you may try to fix up\n"
>   "the remote tracking information by invoking\n"
>   "\"git branch --set-upstream-to=%s%s%s\"."
>
> Plural:
>   "\n"
>   "After fixing the error cause you may try to fix up\n"
>   "the remote tracking information by invoking\n"
>   "\"git config --add my_new_remote remote_name\"
>   "\"git config --add my_new_upstream1 upstream_name1\"
>   "\"git config --add my_new_upstream2 upstream_name2\"
>
> But perhaps this is not too hard since you've already included examples
> of how to format lists of strings [1]
>
> [1] https://lore.kernel.org/git/211207.86mtlcpyu4.gmgdl@evledraar.gmail.com

You don't need to use the plural facility for that sort of
message.

Plurals in translated messages are specifically for the case where you
need to compose a sentence like:

    I had %d glasses of water with breakfast

But it's not needed for a message that can be rehrased as a heading
followed by a list of 1 or more items, e.g.:

    Things I've consumed in liquid form during breakfast this week:
    - Water
    - Tea
    - Coffe

If that list stopped at "Water" it would be OK. Every language has some
way of referring to items on a list in general terms, without knowing if
that list is composed of only one item, two etc.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 1/2] branch: accept multiple upstream branches for tracking
  2021-12-07 19:28     ` Junio C Hamano
@ 2021-12-14 20:35       ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 20:35 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, chooglen, emilyshaffer, avarab

On 2021.12.07 11:28, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > +static int install_branch_config_multiple_remotes(int flag, const char *local, const char *origin,
> > +		struct string_list *remotes)
> 
> The line got overly long so perhaps cut the line after "*local,",
> as "origin" and "remotes" conceptually are closer together.
> 
> What is in the string list?  Names of refs at the remote "origin",
> instead of a single ref there?

Added a comment explaining the purpose and arguments.


> >  {
> >  	const char *shortname = NULL;
> >  	struct strbuf key = STRBUF_INIT;
> > -	int rebasing = should_setup_rebase(origin);
> > -
> > -	if (skip_prefix(remote, "refs/heads/", &shortname)
> > -	    && !strcmp(local, shortname)
> > -	    && !origin) {
> > -		warning(_("Not setting branch %s as its own upstream."),
> > -			local);
> 
> When 'origin' is NULL in the original caller, it means a local
> tracking, and making sure we do not say "my 'master' branch builds
> on top of itself" makes sense.
> 
> > -		return 0;
> > -	}
> > +	int i, rebasing = should_setup_rebase(origin);
> > +
> > +	if (remotes->nr < 1)
> > +		BUG("must provide at least one remote for branch config");
> > +
> > +	if (!origin)
> > +		for (i = 0; i < remotes->nr; i++)
> > +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)
> > +			    && !strcmp(local, shortname)) {
> > +				warning(_("Not setting branch %s as its own upstream."),
> > +					local);
> > +				return 0;
> 
> I am a bit surprised with this warning and early return before
> inspecting the remainder of the list.  When 'origin' is NULL,
> i.e. we are talking about the local building on top of another local
> branch, if the function is called for the local branch 'main' with
> 'main' in the remotes list alone, we do want to issue the warning
> and exit without doing anything (i.e. degenerating to the original
> behaviour of taking a single string variable, when a string list
> with a single element is given).  But if the remotes list has 'main'
> and 'master', would we want to just "skip" the same one, but still
> handle the other ones as if the "same" branch were not in the list?

The inheritance case when creating a new branch is the only time we get
multiple branches, and I think it is a sign of a wider misconfiguration
if we have our own branch name listed as upstream in this case.

When inheriting, `local` should always be a new branch, and `remotes`
should contain the `branch.<name>.merge` entries of the branch we're
inheriting from. For this check to trigger would mean that the parent
branch has configured a local upstream branch that doesn't actually
exist. So it seems that something has gone wrong; perhaps we can assume
what the user wanted such as in your case above, but it seems to me that
it's safer to warn when this happens.

> > @@ -75,8 +80,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  
> >  	strbuf_reset(&key);
> >  	strbuf_addf(&key, "branch.%s.merge", local);
> > -	if (git_config_set_gently(key.buf, remote) < 0)
> > +	/*
> > +	 * We want to overwrite any existing config with all the branches in
> > +	 * "remotes". Override any existing config with the first branch, but if
> > +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> > +	 * we've written so far.
> > +	 */
> > +	if (git_config_set_gently(key.buf, remotes->items[0].string) < 0)
> >  		goto out_err;
> > +	for (i = 1; i < remotes->nr; i++)
> > +		if (git_config_set_multivar_gently(key.buf, remotes->items[i].string, CONFIG_REGEX_NONE, 0) < 0)
> > +			goto out_err;
> >  
> >  	if (rebasing) {
> >  		strbuf_reset(&key);
> > @@ -87,29 +101,62 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	strbuf_release(&key);
> >  
> >  	if (flag & BRANCH_CONFIG_VERBOSE) {
> > -		if (shortname) {
> > -			if (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);
> > -			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);
> > +		int plural = remotes->nr > 1;
> > +		int all_shortnames = 1;
> > +		const char *msg_fmt;
> > +		struct strbuf ref_string = STRBUF_INIT;
> > +
> > +		for (i = 0; i < remotes->nr; i++)
> > +			if (skip_prefix(remotes->items[i].string, "refs/heads/", &shortname)) {
> > +				strbuf_addf(&ref_string, "'%s', ", shortname);
> > +			} else {
> > +				all_shortnames = 0;
> > +				strbuf_addf(&ref_string, "'%s', ", remotes->items[i].string);
> 
> So, all_shortnames == true means everything was a local branch in
> the 'origin' remote, and when it has a non-branch (like a tag),
> all_shortnames becomes false?
> 
> > +			}
> > +		/* The last two characters are an extraneous ", ", so trim those. */
> > +		strbuf_setlen(&ref_string, ref_string.len - 2);
> 
> As you are starting from an empty ref_string, a more idiomatic way
> to build concatenated string would be to prefix when you add a new
> item, e.g.
> 
> 	loop {
> 		if (ref_string already has items)
> 			ref_string.append(", ");
> 		ref_string.append(this_item);
> 	}

Ack, although changes to address other review feedback has made this
point moot.


> > +		if (all_shortnames && origin) {
> > +			if (rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s' by rebasing.";
> 
> What does it mean to keep my 'topic' branch up-to-date by rebasing
> on top of more than one remote sources?  By merging, I can sort-of
> understand (i.e. creating an octopus), but would it make sense to
> track more than one remote sources in general?  Is it common?
> 
> When the benefit is not clear, it might make more sense not to do
> this when there are already multiple tracking sources defined for
> the original; it might be a mistake that we may not want to spread
> with the new option.
> 
> Of course, it is very possible that I am missing a perfectly valid
> use case where having more than one makes good sense.  If so, please
> do not take the above comments as an objection, but adding some
> comments before the function to explain when having remote list with
> more than one items makes sense and how such a setting can be used
> to avoid future readers asking the same (stupid) question as I just
> did.

No, that's an oversight on my part. This will now exit with an error if
we try to rebase onto multiple branches.


> > +			else if (rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s' by rebasing.";
> > +			else if (!rebasing && plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branches %s from '%s'.";
> > +			else if (!rebasing && !plural)
> > +				msg_fmt = "Branch '%s' set up to track remote branch %s from '%s'.";
> > +
> > +			printf_ln(_(msg_fmt), local, ref_string, origin);
> 
> I am not sure how well the "plural" thing works with i18n.  It may
> suffice for the original in English to have only two choices between
> one or more-than-one, but not all languages are English.  Counting
> the actual number (I guess remotes->nr is it) and using Q_() to
> choose between the possible variants.  I think Ævar knows about this
> much better than I do.
> 
> But if we are not doing this "set multiple" and instead go the
> "detect existing multiple and refrain from spreading the damage"
> route, all of that is moot.
> 
> Thanks.

Thanks for the feedback!

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07 19:41     ` Junio C Hamano
@ 2021-12-14 20:37       ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 20:37 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git, chooglen, emilyshaffer, avarab

On 2021.12.07 11:41, Junio C Hamano wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> >  --no-track::
> >  	Do not set up "upstream" configuration, even if the
> > -	branch.autoSetupMerge configuration variable is true.
> > +	branch.autoSetupMerge configuration variable is set.
> 
> I guess "inherit" is different from "true".
> Nice to see an attention to the details.  
> 
> > diff --git a/branch.h b/branch.h
> > index df0be61506..6484bda8a2 100644
> > --- a/branch.h
> > +++ b/branch.h
> > @@ -10,7 +10,8 @@ enum branch_track {
> >  	BRANCH_TRACK_REMOTE,
> >  	BRANCH_TRACK_ALWAYS,
> >  	BRANCH_TRACK_EXPLICIT,
> > -	BRANCH_TRACK_OVERRIDE
> > +	BRANCH_TRACK_OVERRIDE,
> > +	BRANCH_TRACK_INHERIT
> >  };
> 
> Unless INHERIT must stay to be at the end of the enumeration even
> when we add more of these, let's leave a common at the end to help
> future developers.

Fixed in V6.


> > -		if (value && !strcasecmp(value, "always")) {
> > +		if (value && !strcmp(value, "always")) {
> 
> This is belatedly correcting the mistake we made 5 years ago, which
> can break existing users who used "[branch]autosetupmerge=Always" in
> their configuration files.
> 
> I'm OK to fix it to accept only lowercase as written here, see if
> anybody screams, and tell them that the all-lowercase is the only
> documented way to spell this word.
> 
> >  			git_branch_track = BRANCH_TRACK_ALWAYS;
> >  			return 0;
> > +		} else if (value && !strcmp(value, "inherit")) {
> > +			git_branch_track = BRANCH_TRACK_INHERIT;
> > +			return 0;
> >  		}
> >  		git_branch_track = git_config_bool(var, value);
> >  		return 0;
> 
> Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-08  1:02     ` Glen Choo
@ 2021-12-14 22:10       ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 22:10 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, emilyshaffer, avarab

On 2021.12.07 17:02, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
> > +{
> > +	const char *bare_ref;
> > +	struct branch *branch;
> > +	int i;
> > +
> > +	bare_ref = orig_ref;
> > +	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
> > +
> > +	branch = branch_get(bare_ref);
> > +	if (!branch->remote_name) {
> > +		warning(_("asked to inherit tracking from '%s', but no remote is set"),
> > +			bare_ref);
> > +		return -1;
> > +	}
> > +
> > +	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
> > +		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
> > +			bare_ref);
> > +		return -1;
> > +	}
> > +
> > +	tracking->remote = xstrdup(branch->remote_name);
> > +	for (i = 0; i < branch->merge_nr; i++)
> > +		string_list_append(tracking->srcs, branch->merge_name[i]);
> > +	tracking->matches = 1;
> > +	return 0;
> > +}
> 
> tracking->matches is used to keep track of the number of matched remote
> refs. I believe we set tracking->matches = 1 to fulfill two specific
> conditions in setup_tracking()...
> 
> > +
> >  /*
> >   * This is called when new_ref is branched off of orig_ref, and tries
> >   * to infer the settings for branch.<new_ref>.{remote,merge} from the
> > @@ -189,11 +218,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> >  			   enum branch_track track, int quiet)
> >  {
> >  	struct tracking tracking;
> > +	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
> >  	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
> >  
> >  	memset(&tracking, 0, sizeof(tracking));
> >  	tracking.spec.dst = (char *)orig_ref;
> > -	if (for_each_remote(find_tracked_branch, &tracking))
> > +	tracking.srcs = &tracking_srcs;
> > +	if (track != BRANCH_TRACK_INHERIT)
> > +		for_each_remote(find_tracked_branch, &tracking);
> > +	else if (inherit_tracking(&tracking, orig_ref))
> >  		return;
> >  
> >  	if (!tracking.matches)
> 
> *extra context*
> 	if (!tracking.matches)
> 		switch (track) {
> 		case BRANCH_TRACK_ALWAYS:
> 		case BRANCH_TRACK_EXPLICIT:
> 		case BRANCH_TRACK_OVERRIDE:
> 			break;
> 		default:
> 			return;
> 		}
> 
> First, tracking.matches > 0, because we want to do work if there are
> branches to track.
> 
> Secondly,
> 
> *extra context*
> 	if (tracking.matches > 1)
> 		die(_("Not tracking: ambiguous information for ref %s"),
> 		    orig_ref);
> 
> tracking.matches <= 1, because we don't want to set up tracking if it's
> not obvious what ref we want to track.
> 
> But as I understand it, BRANCH_TRACK_INHERIT should be unconditional, so
> instead of fudging this behavior by setting the correct value for
> tracking.matches (which is meant for matching remote refs), we can just
> do what the other unconditional BRANCH_TRACK_* options do, which is to
> to break instead of return, i.e.
> 
> 	if (!tracking.matches)
> 		switch (track) {
> 		case BRANCH_TRACK_ALWAYS:
> 		case BRANCH_TRACK_EXPLICIT:
> 		case BRANCH_TRACK_OVERRIDE:
> +   case BRANCH_TRACK_INHERIT:
> 			break;
> 		default:
> 			return;
> 		}
> 
> and BRANCH_TRACK_INHERIT won't have to pretend that tracking.matches is
> meaningful to it.

Done in V6.

> >@@ -210,11 +243,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> > 		die(_("Not tracking: ambiguous information for ref %s"),
> > 		    orig_ref);
> > 
> >-	if (install_branch_config(config_flags, new_ref, tracking.remote,
> >-			      tracking.src ? tracking.src : orig_ref) < 0)
> >+	if (tracking.srcs->nr < 1)
> >+		string_list_append(tracking.srcs, orig_ref);
> >+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
> >+			      tracking.srcs) < 0)
> > 		exit(-1);
> > 
> >-	free(tracking.src);
> >+	string_list_clear(tracking.srcs, 0);
> > }
> 
> It looks like install_branch_config_multiple_remotes() can just replace
> install_branch_config() in setup_tracking(), nice. This should make it
> pretty easy for me to rebase gc/branch-recurse-submodules onto this.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 0/2] branch: inherit tracking configs
  2021-12-10 22:48     ` Johannes Schindelin
@ 2021-12-14 22:11       ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 22:11 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: Junio C Hamano, git, chooglen, emilyshaffer, avarab

On 2021.12.10 23:48, Johannes Schindelin wrote:
> Hi,
> 
> On Tue, 7 Dec 2021, Junio C Hamano wrote:
> 
> > Josh Steadmon <steadmon@google.com> writes:
> >
> > > I've addressed feedback from V4. Since 2/3 reviewers seemed to (at least
> > > slightly) prefer handling multiple upstream branches in the existing
> > > tracking setup, I've gone that direction rather than repurposing the
> > > branch copy code. None of the other issues were controversial.
> > >
> > > In this version, I'd appreciate feedback mainly on patch 1:
> > > * Is the combination of `git_config_set_gently()` +
> > >   `git_config_set_multivar_gently() the best way to write multiple
> > >   config entries for the same key?
> >
> > IIRC git_config_set_*() is Dscho's brainchild.  If he is available
> > to comment, it may be a valuable input.
> 
> The `git_config_set_multivar_gently()` function was really only intended
> to add one key/value pair.
> 
> Currently, there is no function to add multiple key/value pairs, and while
> it is slightly wasteful to lock the config multiple times to write a bunch
> of key/value pairs, it is not the worst in the world for a small use case
> like this one.
> 
> So yes, for the moment I would go with the suggested design.
> 
> One thing you might want to do is to avoid the extra
> `git_config_set_gently()` before the `for` loop, simply by passing `i == 0
> ? 0 : CONFIG_FLAGS_MULTI_REPLACE` as `flags` parameter to the multivar
> version of the function.
> 
> But that would optimize for code size rather than for readability, and I
> would actually prefer the more verbose version.

Sounds good, thanks for the advice!

> Ciao,
> Dscho

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-08  0:35       ` Glen Choo
@ 2021-12-14 22:15         ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 22:15 UTC (permalink / raw)
  To: Glen Choo
  Cc: Ævar Arnfjörð Bjarmason, git, gitster, emilyshaffer

On 2021.12.07 16:35, Glen Choo wrote:
> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
> 
> >> @@ -10,7 +10,8 @@ enum branch_track {
> >>  	BRANCH_TRACK_REMOTE,
> >>  	BRANCH_TRACK_ALWAYS,
> >>  	BRANCH_TRACK_EXPLICIT,
> >> -	BRANCH_TRACK_OVERRIDE
> >> +	BRANCH_TRACK_OVERRIDE,
> >> +	BRANCH_TRACK_INHERIT
> >>  };
> >
> > So we've got 5 items in this enum...
> >
> >>  
> >>  extern enum branch_track git_branch_track;
> >> diff --git a/builtin/branch.c b/builtin/branch.c
> >> index b23b1d1752..ebde5023c3 100644
> >> --- a/builtin/branch.c
> >> +++ b/builtin/branch.c
> >> @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> >>  		OPT__VERBOSE(&filter.verbose,
> >>  			N_("show hash and subject, give twice for upstream branch")),
> >>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> >> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> >> -			BRANCH_TRACK_EXPLICIT),
> >> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> >> +			N_("set branch tracking configuration"),
> >> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> >> +			parse_opt_tracking_mode),
> >>  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
> >>  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
> >
> > But map --track, --track=direct --track=inherit to 3/5 of them. Will it
> > ever make sense to do the oher 2/5 (I really haven't checked)....
> 
> Reasonable question, but I believe the answer is no, it doesn't make
> sense to map all the values:
> 
> * BRANCH_TRACK_REMOTE is just a default value as far as I can tell (I
>   don't think this does anything?)
> * BRANCH_TRACK_ALWAYS behaves like BRANCH_TRACK_EXPLICIT but it's only
>   meant to be set from config files, see 9ed36cfa35 (branch: optionally
>   setup branch.*.merge from upstream local branches, 2008-02-19). We're
>   more lenient with _ALWAYS than with _EXPLICIT; e.g. we don't die()
>   when the upstream doesn't exist.
> 
> Even one of the other options doesn't really make that much sense...
> 
> * BRANCH_TRACK_OVERRIDE used to be used to implement --set-upstream, but
>   that's not necessary any more. Now it's used to make create_branch()
>   *not* create a branch sometimes, but that's going away if I get my
>   refactor of create_branch()
>   (https://lore.kernel.org/git/xmqq1r2pcnyw.fsf@gitster.g/T/#u) :)

Agreed, thank you for stating things better than I could have :)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v5 2/2] branch: add flags and config to inherit tracking
  2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
  2021-12-08  0:35       ` Glen Choo
@ 2021-12-14 22:27       ` Josh Steadmon
  1 sibling, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 22:27 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason
  Cc: git, gitster, chooglen, emilyshaffer

On 2021.12.07 10:08, Ævar Arnfjörð Bjarmason wrote:
> 
> On Mon, Dec 06 2021, Josh Steadmon wrote:
> 
> > It can be helpful when creating a new branch to use the existing
> > tracking configuration from the branch point. However, there is
> > currently not a method to automatically do so.
> >
> > Teach git-{branch,checkout,switch} an "inherit" argument to the
> > "--track" option. When this is set, creating a new branch will cause the
> > tracking configuration to default to the configuration of the branch
> > point, if set.
> >
> > For example, if branch "main" tracks "origin/main", and we run
> > `git checkout --track=inherit -b feature main`, then branch "feature"
> > will track "origin/main". Thus, `git status` will show us how far
> > ahead/behind we are from origin, and `git pull` will pull from origin.
> >
> > This is particularly useful when creating branches across many
> > submodules, such as with `git submodule foreach ...` (or if running with
> > a patch such as [1], which we use at $job), as it avoids having to
> > manually set tracking info for each submodule.
> >
> > Since we've added an argument to "--track", also add "--track=direct" as
> > another way to explicitly get the original "--track" behavior ("--track"
> > without an argument still works as well).
> > @@ -10,7 +10,8 @@ enum branch_track {
> >  	BRANCH_TRACK_REMOTE,
> >  	BRANCH_TRACK_ALWAYS,
> >  	BRANCH_TRACK_EXPLICIT,
> > -	BRANCH_TRACK_OVERRIDE
> > +	BRANCH_TRACK_OVERRIDE,
> > +	BRANCH_TRACK_INHERIT
> >  };
> 
> So we've got 5 items in this enum...
> 
> >  
> >  extern enum branch_track git_branch_track;
> > diff --git a/builtin/branch.c b/builtin/branch.c
> > index b23b1d1752..ebde5023c3 100644
> > --- a/builtin/branch.c
> > +++ b/builtin/branch.c
> > @@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
> >  		OPT__VERBOSE(&filter.verbose,
> >  			N_("show hash and subject, give twice for upstream branch")),
> >  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> > -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> > -			BRANCH_TRACK_EXPLICIT),
> > +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> > +			N_("set branch tracking configuration"),
> > +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> > +			parse_opt_tracking_mode),
> >  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
> >  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
> 
> But map --track, --track=direct --track=inherit to 3/5 of them. Will it
> ever make sense to do the oher 2/5 (I really haven't checked)....
> 
> >  		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
> > diff --git a/builtin/checkout.c b/builtin/checkout.c
> > index b5d477919a..45dab414ea 100644
> > --- a/builtin/checkout.c
> > +++ b/builtin/checkout.c
> > @@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
> >  {
> >  	struct option options[] = {
> >  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> > -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
> > -			BRANCH_TRACK_EXPLICIT),
> > +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
> > +			N_("set up tracking mode (see git-pull(1))"),
> > +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> > +			parse_opt_tracking_mode),
> >  		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
> >  			   PARSE_OPT_NOCOMPLETE),
> >  		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
> 
> I wonder if this interface wouldn't be a lot simpler as:
> 
>     --track
>     --track-explicit --track-direct --track-inherit
> 
> Both because it'll work better for auto-complete, and we can (and
> presumably some will want) just make --track mean whatever configured
> --track-THING you want.

If I understand you correctly, I disagree here. I think you're saying
that if you always (or usually) want a specific tracking mode, you have
to both set the config appropriately and remember to pass `--track` on
the command line? Seems simpler to just let the config take precedence
in the absence of a flag. But I think I may have misunderstood you.

> in any case, isn't there a NONEG missing here, or is --no-track-direct
> etc. handled by OPT_CALLBACK_F() (I forget...).

Yeah, --no-track is correctly handled. It sets BRANCH_TRACK_NEVER, so
that you can override a branch.autosetupmerge=always config if
necessary.


> >  	if (!strcmp(var, "branch.autosetupmerge")) {
> > -		if (value && !strcasecmp(value, "always")) {
> > +		if (value && !strcmp(value, "always")) {
> 
> ...This probably makes sense, but it seems like the behavior change of
> "let's not take this case-insensitive" should be split up into its own
> change...

Done in V6.


> > +	test_must_fail git rev-parse --verify HEAD^ &&
> > +	git checkout main &&
> > +	git config branch.autosetupmerge inherit &&
> > +	git checkout --orphan eta &&
> > +	test -z "$(git config branch.eta.merge)" &&
> > +	test -z "$(git config branch.eta.remote)" &&
> 
> Better with the test_must_be_empty etc. helpers.
> 
> > +	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
> 
> and this with test_cmp.
> 
> (ditto occurances below)

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v6 0/3] branch: inherit tracking configs
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (5 preceding siblings ...)
  2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
@ 2021-12-14 23:44 ` Josh Steadmon
  2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
                     ` (4 more replies)
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
  8 siblings, 5 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 23:44 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Changes since V5:
* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
  so many conditionals.
* Note that rebasing is not compatible with inheriting multiple upstream
  branches.
* Moved the change to case-sensitivity for branch.autosetupmerge to its
  own commit.
* Improve advice on failed tracking setup when multiple branches are
  involved.
* Make better use of string_list API.
* Make better use of config API.
* More straight-forward use of the `struct tracking` API.
* Numerous style fixes.

Changes since V4:
* Add new patch (1/2) to refactor branch.c:install_branch_config() to
  accept multiple upstream refs
* When multiple upstream branches are set in the parent branch, inherit
  them all, instead of just the first
* Break out error string arguments for easier translation
* Don't ignore case for values of branch.autosetupmerge
* Move reference to git-pull out of usage string for --track into
  git-branch.txt
* Use test_config instead of `git config` in t2027
* Style fixes: add single-quotes around warning string arguments, remove
  unnecessary braces

Changes since V3:
* Use branch_get() instead of git_config_get_string() to look up branch
  configuration.
* Remove unnecessary string formatting in new error message in
  parse-options-cb.c.

Josh Steadmon (3):
  branch: accept multiple upstream branches for tracking
  branch: add flags and config to inherit tracking
  config: require lowercase for branch.*.autosetupmerge

 Documentation/config/branch.txt |   3 +-
 Documentation/git-branch.txt    |  24 +++--
 Documentation/git-checkout.txt  |   2 +-
 Documentation/git-switch.txt    |   2 +-
 branch.c                        | 184 ++++++++++++++++++++++++--------
 branch.h                        |   3 +-
 builtin/branch.c                |   6 +-
 builtin/checkout.c              |   6 +-
 config.c                        |   5 +-
 parse-options-cb.c              |  16 +++
 parse-options.h                 |   2 +
 t/t2017-checkout-orphan.sh      |  11 +-
 t/t2027-checkout-track.sh       |  23 ++++
 t/t2060-switch.sh               |  28 +++++
 t/t3200-branch.sh               |  39 ++++++-
 t/t7201-co.sh                   |  17 +++
 16 files changed, 305 insertions(+), 66 deletions(-)

Range-diff against v5:
1:  ba7d557725 < -:  ---------- branch: accept multiple upstream branches for tracking
-:  ---------- > 1:  43d6f83fed branch: accept multiple upstream branches for tracking
2:  c7e4af9a36 ! 2:  57e57e6e6a branch: add flags and config to inherit tracking
    @@ branch.c: static int find_tracked_branch(struct remote *remote, void *priv)
      		}
      		tracking->spec.src = NULL;
      	}
    -@@ branch.c: int install_branch_config(int flag, const char *local, const char *origin, const
    - 	string_list_clear(&remotes, 0);
    +@@ branch.c: int install_branch_config(int flag, const char *local, const char *origin,
    + 	return ret;
      }
      
     +static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     +	tracking->remote = xstrdup(branch->remote_name);
     +	for (i = 0; i < branch->merge_nr; i++)
     +		string_list_append(tracking->srcs, branch->merge_name[i]);
    -+	tracking->matches = 1;
     +	return 0;
     +}
     +
    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      		return;
      
      	if (!tracking.matches)
    +@@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
    + 		case BRANCH_TRACK_ALWAYS:
    + 		case BRANCH_TRACK_EXPLICIT:
    + 		case BRANCH_TRACK_OVERRIDE:
    ++		case BRANCH_TRACK_INHERIT:
    + 			break;
    + 		default:
    + 			return;
     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      		die(_("Not tracking: ambiguous information for ref %s"),
      		    orig_ref);
      
     -	if (install_branch_config(config_flags, new_ref, tracking.remote,
     -			      tracking.src ? tracking.src : orig_ref) < 0)
    -+	if (tracking.srcs->nr < 1)
    ++	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
     +		string_list_append(tracking.srcs, orig_ref);
     +	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
     +			      tracking.srcs) < 0)
    @@ branch.h: enum branch_track {
      	BRANCH_TRACK_EXPLICIT,
     -	BRANCH_TRACK_OVERRIDE
     +	BRANCH_TRACK_OVERRIDE,
    -+	BRANCH_TRACK_INHERIT
    ++	BRANCH_TRACK_INHERIT,
      };
      
      extern enum branch_track git_branch_track;
    @@ builtin/checkout.c: static struct option *add_common_switch_branch_options(
      		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
     
      ## config.c ##
    -@@ config.c: static int git_default_i18n_config(const char *var, const char *value)
    - static int git_default_branch_config(const char *var, const char *value)
    - {
    - 	if (!strcmp(var, "branch.autosetupmerge")) {
    --		if (value && !strcasecmp(value, "always")) {
    -+		if (value && !strcmp(value, "always")) {
    +@@ config.c: static int git_default_branch_config(const char *var, const char *value)
    + 		if (value && !strcasecmp(value, "always")) {
      			git_branch_track = BRANCH_TRACK_ALWAYS;
      			return 0;
     +		} else if (value && !strcmp(value, "inherit")) {
    @@ parse-options-cb.c: int parse_opt_passthru_argv(const struct option *opt, const
      	return 0;
      }
     +
    -+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset) {
    ++int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)
    ++{
     +	if (unset)
     +		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
     +	else if (!arg || !strcmp(arg, "direct"))
    @@ parse-options.h: enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx
     
      ## t/t2017-checkout-orphan.sh ##
     @@ t/t2017-checkout-orphan.sh: test_expect_success '--orphan ignores branch.autosetupmerge' '
    + 	git checkout main &&
    + 	git config branch.autosetupmerge always &&
      	git checkout --orphan gamma &&
    - 	test -z "$(git config branch.gamma.merge)" &&
    +-	test -z "$(git config branch.gamma.merge)" &&
    ++	test_cmp_config "" --default "" branch.gamma.merge &&
      	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
     +	test_must_fail git rev-parse --verify HEAD^ &&
     +	git checkout main &&
     +	git config branch.autosetupmerge inherit &&
     +	git checkout --orphan eta &&
    -+	test -z "$(git config branch.eta.merge)" &&
    -+	test -z "$(git config branch.eta.remote)" &&
    -+	test refs/heads/eta = "$(git symbolic-ref HEAD)" &&
    ++	test_cmp_config "" --default "" branch.eta.merge &&
    ++	test_cmp_config "" --default "" branch.eta.remote &&
    ++	echo refs/heads/eta >expected &&
    ++	git symbolic-ref HEAD >actual &&
    ++	test_cmp expected actual &&
      	test_must_fail git rev-parse --verify HEAD^
      '
      
    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr
     +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
     +	# default config does not copy tracking info
     +	git switch -c foo-no-inherit foo &&
    -+	test -z "$(git config branch.foo-no-inherit.remote)" &&
    -+	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
     +	# with --track=inherit, we copy tracking info from foo
     +	git switch --track=inherit -c foo2 foo &&
     +	test_cmp_config origin branch.foo2.remote &&
    @@ t/t2060-switch.sh: test_expect_success 'not switching when something is in progr
     +	test_cmp_config refs/heads/foo branch.foo5.merge &&
     +	# no tracking info to inherit from main
     +	git switch -c main2 main &&
    -+	test -z "$(git config branch.main2.remote)" &&
    -+	test -z "$(git config branch.main2.merge)"
    ++	test_cmp_config "" --default "" branch.main2.remote &&
    ++	test_cmp_config "" --default "" branch.main2.merge
     +'
     +
      test_done
    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'
     +	test_unconfig branch.autoSetupMerge &&
     +	# default config does not copy tracking info
     +	git branch foo-no-inherit my1 &&
    -+	test -z "$(git config branch.foo-no-inherit.remote)" &&
    -+	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
     +	# with autoSetupMerge=inherit, we copy tracking info from my1
     +	test_config branch.autoSetupMerge inherit &&
     +	git branch foo3 my1 &&
    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'
     +	test_cmp_config refs/heads/main branch.foo3.merge &&
     +	# no tracking info to inherit from main
     +	git branch main2 main &&
    -+	test -z "$(git config branch.main2.remote)" &&
    -+	test -z "$(git config branch.main2.merge)"
    ++	test_cmp_config "" --default "" branch.main2.remote &&
    ++	test_cmp_config "" --default "" branch.main2.merge
     +'
     +
     +test_expect_success '--track overrides branch.autoSetupMerge' '
    @@ t/t3200-branch.sh: test_expect_success 'invalid sort parameter in configuration'
     +	test_cmp_config . branch.foo4.remote &&
     +	test_cmp_config refs/heads/my1 branch.foo4.merge &&
     +	git branch --no-track foo5 my1 &&
    -+	test -z "$(git config branch.foo5.remote)" &&
    -+	test -z "$(git config branch.foo5.merge)"
    ++	test_cmp_config "" --default "" branch.foo5.remote &&
    ++	test_cmp_config "" --default "" branch.foo5.merge
     +'
     +
      test_done
    @@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '
     +	git reset --hard main &&
     +	# default config does not copy tracking info
     +	git checkout -b foo-no-inherit koala/bear &&
    -+	test -z "$(git config branch.foo-no-inherit.remote)" &&
    -+	test -z "$(git config branch.foo-no-inherit.merge)" &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
    ++	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
     +	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
     +	test_config branch.autoSetupMerge inherit &&
     +	git checkout -b foo koala/bear &&
    @@ t/t7201-co.sh: test_expect_success 'custom merge driver with checkout -m' '
     +	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
     +	# no tracking info to inherit from main
     +	git checkout -b main2 main &&
    -+	test -z "$(git config branch.main2.remote)" &&
    -+	test -z "$(git config branch.main2.merge)"
    ++	test_cmp_config "" --default "" branch.main2.remote &&
    ++	test_cmp_config "" --default "" branch.main2.merge
     +'
     +
      test_done
-:  ---------- > 3:  f79d27dc24 config: require lowercase for branch.*.autosetupmerge

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
@ 2021-12-14 23:44   ` Josh Steadmon
  2021-12-15 21:30     ` Junio C Hamano
  2021-12-16 19:57     ` Glen Choo
  2021-12-14 23:44   ` [PATCH v6 2/3] branch: add flags and config to inherit tracking Josh Steadmon
                     ` (3 subsequent siblings)
  4 siblings, 2 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 23:44 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Add a new static variant of install_branch_config() that accepts
multiple remote branch names for tracking. This will be used in an
upcoming commit that enables inheriting the tracking configuration from
a parent branch.

Currently, all callers of install_branch_config() pass only a single
remote. Make install_branch_config() a small wrapper around
install_branch_config_multiple_remotes() so that existing callers do not
need to be changed.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 branch.c          | 135 ++++++++++++++++++++++++++++++++--------------
 t/t3200-branch.sh |   6 +--
 2 files changed, 99 insertions(+), 42 deletions(-)

diff --git a/branch.c b/branch.c
index 7a88a4861e..fa165ebdab 100644
--- a/branch.c
+++ b/branch.c
@@ -49,25 +49,41 @@ static int should_setup_rebase(const char *origin)
 	return 0;
 }
 
-static const char tracking_advice[] =
-N_("\n"
-"After fixing the error cause you may try to fix up\n"
-"the remote tracking information by invoking\n"
-"\"git branch --set-upstream-to=%s%s%s\".");
-
-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+/**
+ * Install upstream tracking configuration for a branch; specifically, add
+ * `branch.<name>.remote` and `branch.<name>.merge` entries.
+ *
+ * `flag` contains integer flags for options; currently only
+ * BRANCH_CONFIG_VERBOSE is checked.
+ *
+ * `local` is the name of the branch whose configuration we're installing.
+ *
+ * `origin` is the name of the remote owning the upstream branches. NULL means
+ * the upstream branches are local to this repo.
+ *
+ * `remotes` is a list of refs that are upstream of local
+ */
+static int install_branch_config_multiple_remotes(int flag, const char *local,
+		const char *origin, struct string_list *remotes)
 {
 	const char *shortname = NULL;
 	struct strbuf key = STRBUF_INIT;
+	struct string_list_item *item;
 	int rebasing = should_setup_rebase(origin);
 
-	if (skip_prefix(remote, "refs/heads/", &shortname)
-	    && !strcmp(local, shortname)
-	    && !origin) {
-		warning(_("Not setting branch %s as its own upstream."),
-			local);
-		return 0;
-	}
+	if (!remotes->nr)
+		BUG("must provide at least one remote for branch config");
+	if (rebasing && remotes->nr > 1)
+		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));
+
+	if (!origin)
+		for_each_string_list_item(item, remotes)
+			if (skip_prefix(item->string, "refs/heads/", &shortname)
+			    && !strcmp(local, shortname)) {
+				warning(_("not setting branch '%s' as its own upstream."),
+					local);
+				return 0;
+			}
 
 	strbuf_addf(&key, "branch.%s.remote", local);
 	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
@@ -75,8 +91,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
 	strbuf_reset(&key);
 	strbuf_addf(&key, "branch.%s.merge", local);
-	if (git_config_set_gently(key.buf, remote) < 0)
+	/*
+	 * We want to overwrite any existing config with all the branches in
+	 * "remotes". Override any existing config, then write our branches. If
+	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
+	 * we've written so far.
+	 */
+	if (git_config_set_gently(key.buf, NULL) < 0)
 		goto out_err;
+	for_each_string_list_item(item, remotes)
+		if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
+			goto out_err;
 
 	if (rebasing) {
 		strbuf_reset(&key);
@@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (shortname) {
+		const char *name;
+		struct strbuf ref_string = STRBUF_INIT;
+
+		for_each_string_list_item(item, remotes) {
+			name = item->string;
+			skip_prefix(name, "refs/heads/", &name);
+			strbuf_addf(&ref_string, "  %s\n", name);
+		}
+
+		if (remotes->nr == 1) {
+			struct strbuf refname = STRBUF_INIT;
+
 			if (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);
-			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);
+				strbuf_addf(&refname, "%s/", origin);
+			strbuf_addstr(&refname, remotes->items[0].string);
+
+			/*
+			 * Rebasing is only allowed in the case of a single
+			 * upstream branch.
+			 */
+			printf_ln(rebasing ?
+				_("branch '%s' set up to track '%s' by rebasing.") :
+				_("branch '%s' set up to track '%s'."),
+				local, refname.buf);
+
+			strbuf_release(&refname);
+		} else if (origin) {
+			printf_ln(_("branch '%s' set up to track from '%s':"),
+				local, origin);
+			printf("%s", ref_string.buf);
 		} else {
-			if (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);
-			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);
+			printf_ln(_("branch '%s' set up to track:"), local);
+			printf("%s", ref_string.buf);
 		}
+
+		strbuf_release(&ref_string);
 	}
 
 	return 0;
@@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 	error(_("Unable to write upstream branch configuration"));
 
-	advise(_(tracking_advice),
-	       origin ? origin : "",
-	       origin ? "/" : "",
-	       shortname ? shortname : remote);
+	advise(_("\nAfter fixing the error cause you may try to fix up\n"
+		"the remote tracking information by invoking:"));
+	if (remotes->nr == 1)
+		advise("  git branch --set-upstream-to=%s%s%s",
+			origin ? origin : "",
+			origin ? "/" : "",
+			remotes->items[0].string);
+	else
+		for_each_string_list_item(item, remotes)
+			advise("  git config --add branch.\"%s\".merge %s",
+				local, item->string);
 
 	return -1;
 }
 
+int install_branch_config(int flag, const char *local, const char *origin,
+		const char *remote)
+{
+	int ret;
+	struct string_list remotes = STRING_LIST_INIT_DUP;
+
+	string_list_append(&remotes, remote);
+	ret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);
+	string_list_clear(&remotes, 0);
+	return ret;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..4b0ef35913 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '
 	test_must_fail git branch --set-upstream origin/main
 '
 
-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
 	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
 	cat >expect <<-\EOF &&
-	warning: Not setting branch my13 as its own upstream.
+	warning: not setting branch 'my13' as its own upstream.
 	EOF
 	test_expect_code 1 git config branch.my13.remote &&
 	test_expect_code 1 git config branch.my13.merge &&
 	test_cmp expect actual
-'
+"
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v6 2/3] branch: add flags and config to inherit tracking
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
  2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-14 23:44   ` Josh Steadmon
  2021-12-16 21:27     ` Glen Choo
  2021-12-14 23:44   ` [PATCH v6 3/3] config: require lowercase for branch.autosetupmerge Josh Steadmon
                     ` (2 subsequent siblings)
  4 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 23:44 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 Documentation/config/branch.txt |  3 +-
 Documentation/git-branch.txt    | 24 +++++++++++-----
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 49 ++++++++++++++++++++++++++++-----
 branch.h                        |  3 +-
 builtin/branch.c                |  6 ++--
 builtin/checkout.c              |  6 ++--
 config.c                        |  3 ++
 parse-options-cb.c              | 16 +++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      | 11 +++++++-
 t/t2027-checkout-track.sh       | 23 ++++++++++++++++
 t/t2060-switch.sh               | 28 +++++++++++++++++++
 t/t3200-branch.sh               | 33 ++++++++++++++++++++++
 t/t7201-co.sh                   | 17 ++++++++++++
 16 files changed, 205 insertions(+), 23 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..c8b393e51c 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
++
+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index fa165ebdab..7e0c29957c 100644
--- a/branch.c
+++ b/branch.c
@@ -11,7 +11,7 @@
 
 struct tracking {
 	struct refspec_item spec;
-	char *src;
+	struct string_list *srcs;
 	const char *remote;
 	int matches;
 };
@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		if (++tracking->matches == 1) {
-			tracking->src = tracking->spec.src;
+			string_list_append(tracking->srcs, tracking->spec.src);
 			tracking->remote = remote->name;
 		} else {
 			free(tracking->spec.src);
-			FREE_AND_NULL(tracking->src);
+			string_list_clear(tracking->srcs, 0);
 		}
 		tracking->spec.src = NULL;
 	}
@@ -183,6 +183,34 @@ int install_branch_config(int flag, const char *local, const char *origin,
 	return ret;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	const char *bare_ref;
+	struct branch *branch;
+	int i;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	branch = branch_get(bare_ref);
+	if (!branch->remote_name) {
+		warning(_("asked to inherit tracking from '%s', but no remote is set"),
+			bare_ref);
+		return -1;
+	}
+
+	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
+		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
+			bare_ref);
+		return -1;
+	}
+
+	tracking->remote = xstrdup(branch->remote_name);
+	for (i = 0; i < branch->merge_nr; i++)
+		string_list_append(tracking->srcs, branch->merge_name[i]);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 			   enum branch_track track, int quiet)
 {
 	struct tracking tracking;
+	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	tracking.srcs = &tracking_srcs;
+	if (track != BRANCH_TRACK_INHERIT)
+		for_each_remote(find_tracked_branch, &tracking);
+	else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
@@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		case BRANCH_TRACK_ALWAYS:
 		case BRANCH_TRACK_EXPLICIT:
 		case BRANCH_TRACK_OVERRIDE:
+		case BRANCH_TRACK_INHERIT:
 			break;
 		default:
 			return;
@@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		die(_("Not tracking: ambiguous information for ref %s"),
 		    orig_ref);
 
-	if (install_branch_config(config_flags, new_ref, tracking.remote,
-			      tracking.src ? tracking.src : orig_ref) < 0)
+	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
+		string_list_append(tracking.srcs, orig_ref);
+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
+			      tracking.srcs) < 0)
 		exit(-1);
 
-	free(tracking.src);
+	string_list_clear(tracking.srcs, 0);
 }
 
 int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index df0be61506..815dcd40c0 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT,
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..ebde5023c3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set branch tracking configuration"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..152c94f29d 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..d346dbe210 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `%s' expects \"%s\" or \"%s\""),
+			     "--track", "direct", "inherit");
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..4d689bd377 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout main &&
 	git config branch.autosetupmerge always &&
 	git checkout --orphan gamma &&
-	test -z "$(git config branch.gamma.merge)" &&
+	test_cmp_config "" --default "" branch.gamma.merge &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test_cmp_config "" --default "" branch.eta.merge &&
+	test_cmp_config "" --default "" branch.eta.remote &&
+	echo refs/heads/eta >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..49c7def21c 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	test_config branch.main.remote origin &&
+	test_config branch.main.merge refs/heads/main &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/main branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/main branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..ebb961be29 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 4b0ef35913..a049276439 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test_cmp_config "" --default "" branch.foo5.remote &&
+	test_cmp_config "" --default "" branch.foo5.merge
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..4fdf88ba46 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v6 3/3] config: require lowercase for branch.autosetupmerge
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
  2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-14 23:44   ` [PATCH v6 2/3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-14 23:44   ` Josh Steadmon
  2021-12-15  0:43   ` [PATCH v6 0/3] branch: inherit tracking configs Josh Steadmon
  2021-12-16  0:02   ` Junio C Hamano
  4 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-14 23:44 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Although we only documented that branch.autosetupmerge would accept
"always" as a value, the actual implementation would accept any
combination of upper- or lower-case. Fix this to be consistent with
documentation and with other values of this config variable.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 config.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 152c94f29d..54c0e7d98e 100644
--- a/config.c
+++ b/config.c
@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)
 static int git_default_branch_config(const char *var, const char *value)
 {
 	if (!strcmp(var, "branch.autosetupmerge")) {
-		if (value && !strcasecmp(value, "always")) {
+		if (value && !strcmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
 		} else if (value && !strcmp(value, "inherit")) {
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 0/3] branch: inherit tracking configs
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
                     ` (2 preceding siblings ...)
  2021-12-14 23:44   ` [PATCH v6 3/3] config: require lowercase for branch.autosetupmerge Josh Steadmon
@ 2021-12-15  0:43   ` Josh Steadmon
  2021-12-16  0:02   ` Junio C Hamano
  4 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-15  0:43 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

On 2021.12.14 15:44, Josh Steadmon wrote:
> Changes since V5:
> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
>   so many conditionals.

I meant to expand on this but forgot before sending the series. I
removed as many distinctions as possible, as most can still be inferred
from context. For example, previously the output specifically called out
branches vs. tags, but this is obvious in the name itself: "some-branch"
vs. "refs/tags/some-tag" for example. Likewise, we don't need to specify
whether refs are remote or local: "some-remote/some-branch" vs.
"a-local-branch" should be understandable without us spelling it out.

Of course, if people feel like I've over-simplified here, I'm happy to
revert this change.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-15 21:30     ` Junio C Hamano
  2021-12-16 19:57     ` Glen Choo
  1 sibling, 0 replies; 103+ messages in thread
From: Junio C Hamano @ 2021-12-15 21:30 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, chooglen, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

> + * `origin` is the name of the remote owning the upstream branches. NULL means
> + * the upstream branches are local to this repo.
> + *
> + * `remotes` is a list of refs that are upstream of local
> + */
> +static int install_branch_config_multiple_remotes(int flag, const char *local,
> +		const char *origin, struct string_list *remotes)
>  {
>  	const char *shortname = NULL;
>  	struct strbuf key = STRBUF_INIT;
> +	struct string_list_item *item;
>  	int rebasing = should_setup_rebase(origin);
>  
> -	if (skip_prefix(remote, "refs/heads/", &shortname)
> -	    && !strcmp(local, shortname)
> -	    && !origin) {
> -		warning(_("Not setting branch %s as its own upstream."),
> -			local);
> -		return 0;
> -	}
> +	if (!remotes->nr)
> +		BUG("must provide at least one remote for branch config");
> +	if (rebasing && remotes->nr > 1)
> +		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));
> +
> +	if (!origin)
> +		for_each_string_list_item(item, remotes)
> +			if (skip_prefix(item->string, "refs/heads/", &shortname)
> +			    && !strcmp(local, shortname)) {
> +				warning(_("not setting branch '%s' as its own upstream."),
> +					local);
> +				return 0;
> +			}

OK, if there is the current branch _among_ the remotes and we are
synching with the local repository, we reject to muck the
configuration for 'local' branch with any of the refs in 'remotes'.

Makes sense.

Just FYI (because there is nothing actionable on your part), there
is another topic in flight that wants to change the warning message
to be phrased like so:

		_("not setting branch '%s' as its own upstream")

i.e. without capitalizing the first word, and without finishing the
sentence with a full stop.  I'll make the conflict resolution to
read as such.

> -test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
> +test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
>  	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
>  	cat >expect <<-\EOF &&
> -	warning: Not setting branch my13 as its own upstream.
> +	warning: not setting branch 'my13' as its own upstream.

Likewise.

>  	EOF
>  	test_expect_code 1 git config branch.my13.remote &&
>  	test_expect_code 1 git config branch.my13.merge &&
>  	test_cmp expect actual
> -'
> +"

The above is currently correct but it often is an invitation for
future bugs to use double-quotes around the executable part of
test_expect_success.  We can always write ' --> '\'' to enclose the
thing in a pair of single quotes when we start needing to refer to
$shell_variables in the executable part, so let's take the above
patch as-is, but something to keep in mind.

Thanks, queued.


^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 0/3] branch: inherit tracking configs
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
                     ` (3 preceding siblings ...)
  2021-12-15  0:43   ` [PATCH v6 0/3] branch: inherit tracking configs Josh Steadmon
@ 2021-12-16  0:02   ` Junio C Hamano
  2021-12-16  0:37     ` Glen Choo
  4 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-12-16  0:02 UTC (permalink / raw)
  To: Josh Steadmon, chooglen; +Cc: git, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

> Changes since V5:
> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
>   so many conditionals.
> * Note that rebasing is not compatible with inheriting multiple upstream
>   branches.
> * Moved the change to case-sensitivity for branch.autosetupmerge to its
>   own commit.
> * Improve advice on failed tracking setup when multiple branches are
>   involved.
> * Make better use of string_list API.
> * Make better use of config API.
> * More straight-forward use of the `struct tracking` API.
> * Numerous style fixes.

I've queued this, and rebased Glen's "branch --recurse-submodules"
on top, and parked both of them near the tip of 'seen'.  I do not
have much confidence in the conflict resolution needed during the
rebasing or the other branch or merges into 'seen', and I would
appreciate it if you two can take a look to sanity check the result.

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 0/3] branch: inherit tracking configs
  2021-12-16  0:02   ` Junio C Hamano
@ 2021-12-16  0:37     ` Glen Choo
  2021-12-16  1:20       ` Junio C Hamano
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-16  0:37 UTC (permalink / raw)
  To: Junio C Hamano, Josh Steadmon; +Cc: git, avarab, Johannes.Schindelin

Junio C Hamano <gitster@pobox.com> writes:

> Josh Steadmon <steadmon@google.com> writes:
>
>> Changes since V5:
>> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
>>   so many conditionals.
>> * Note that rebasing is not compatible with inheriting multiple upstream
>>   branches.
>> * Moved the change to case-sensitivity for branch.autosetupmerge to its
>>   own commit.
>> * Improve advice on failed tracking setup when multiple branches are
>>   involved.
>> * Make better use of string_list API.
>> * Make better use of config API.
>> * More straight-forward use of the `struct tracking` API.
>> * Numerous style fixes.
>
> I've queued this, and rebased Glen's "branch --recurse-submodules"
> on top, and parked both of them near the tip of 'seen'.  I do not
> have much confidence in the conflict resolution needed during the
> rebasing or the other branch or merges into 'seen', and I would
> appreciate it if you two can take a look to sanity check the result.
>
> Thanks.

I've just sent out a new version [1] which is rebased on top of Josh's
v6. Please use that version instead :)

I did not rebase this on top of 'seen' though; I'll take a look and see
if there's anything of concern.

[1] https://lore.kernel.org/git/20211216003213.99135-1-chooglen@google.com/,

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 0/3] branch: inherit tracking configs
  2021-12-16  0:37     ` Glen Choo
@ 2021-12-16  1:20       ` Junio C Hamano
  0 siblings, 0 replies; 103+ messages in thread
From: Junio C Hamano @ 2021-12-16  1:20 UTC (permalink / raw)
  To: Glen Choo; +Cc: Josh Steadmon, git, avarab, Johannes.Schindelin

Glen Choo <chooglen@google.com> writes:

> I did not rebase this on top of 'seen' though; I'll take a look and see
> if there's anything of concern.

That's OK; please do not ever rebase anything on top of 'seen' or
'next'.  In this particular case, you and Josh agreed to base your
topic on top of Josh'es, if I understand it correctly, so find the
tip of js/branch-track-inherit from 'seen' [*] and rebase your topic
there.

But making a trial merge of the combined topic to say 'next' and
testing the result out before sending the patches to the list would
be very much appreciated ;-)


[Footnote]

* One way to do so would be:

 $ git fetch
 $ git show 'remote/origin/seen^{/^Merge branch .js/branch-track-inherit.}'






^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-15 21:30     ` Junio C Hamano
@ 2021-12-16 19:57     ` Glen Choo
  2021-12-17  5:10       ` Josh Steadmon
  1 sibling, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-16 19:57 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

> --- a/branch.c
> +++ b/branch.c
> @@ -49,25 +49,41 @@ static int should_setup_rebase(const char *origin)
>  	return 0;
>  }
>  
> -static const char tracking_advice[] =
> -N_("\n"
> -"After fixing the error cause you may try to fix up\n"
> -"the remote tracking information by invoking\n"
> -"\"git branch --set-upstream-to=%s%s%s\".");
> -
> -int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
> +/**
> + * Install upstream tracking configuration for a branch; specifically, add
> + * `branch.<name>.remote` and `branch.<name>.merge` entries.
> + *
> + * `flag` contains integer flags for options; currently only
> + * BRANCH_CONFIG_VERBOSE is checked.
> + *
> + * `local` is the name of the branch whose configuration we're installing.
> + *
> + * `origin` is the name of the remote owning the upstream branches. NULL means
> + * the upstream branches are local to this repo.
> + *
> + * `remotes` is a list of refs that are upstream of local
> + */
> +static int install_branch_config_multiple_remotes(int flag, const char *local,
> +		const char *origin, struct string_list *remotes)

Very helpful description. I got slightly confused when I first reviewed
this, so having the comments will help future readers a lot.

>  {
>  	const char *shortname = NULL;
>  	struct strbuf key = STRBUF_INIT;
> +	struct string_list_item *item;
>  	int rebasing = should_setup_rebase(origin);
>  
> -	if (skip_prefix(remote, "refs/heads/", &shortname)
> -	    && !strcmp(local, shortname)
> -	    && !origin) {
> -		warning(_("Not setting branch %s as its own upstream."),
> -			local);
> -		return 0;
> -	}
> +	if (!remotes->nr)
> +		BUG("must provide at least one remote for branch config");
> +	if (rebasing && remotes->nr > 1)
> +		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));

Nit: if we're being pedantic, we cannot inherit upstream tracking
configuration when rebasing is requested with multiple upstream
branches.

But this message is already very niche and loaded with specifics, so
adding "...with multiple upstream branches" might just be more
confusing, so this is a nit.

> @@ -75,8 +91,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config, then write our branches. If
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */
> +	if (git_config_set_gently(key.buf, NULL) < 0)
>  		goto out_err;
> +	for_each_string_list_item(item, remotes)
> +		if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;

We get to use for_each_string_item() now, nice.

> @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	strbuf_release(&key);
>  
>  	if (flag & BRANCH_CONFIG_VERBOSE) {
> -		if (shortname) {
> +		const char *name;
> +		struct strbuf ref_string = STRBUF_INIT;
> +
> +		for_each_string_list_item(item, remotes) {
> +			name = item->string;
> +			skip_prefix(name, "refs/heads/", &name);
> +			strbuf_addf(&ref_string, "  %s\n", name);
> +		}
> +
> +		if (remotes->nr == 1) {
> +			struct strbuf refname = STRBUF_INIT;
> +
>  			if (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);
> -			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);
> +				strbuf_addf(&refname, "%s/", origin);
> +			strbuf_addstr(&refname, remotes->items[0].string);
> +
> +			/*
> +			 * Rebasing is only allowed in the case of a single
> +			 * upstream branch.
> +			 */
> +			printf_ln(rebasing ?
> +				_("branch '%s' set up to track '%s' by rebasing.") :
> +				_("branch '%s' set up to track '%s'."),
> +				local, refname.buf);
> +
> +			strbuf_release(&refname);
> +		} else if (origin) {
> +			printf_ln(_("branch '%s' set up to track from '%s':"),
> +				local, origin);
> +			printf("%s", ref_string.buf);

It's not clear to me why the hint contains the word 'from' when it is a
remote ref...

>  		} else {
> -			if (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);
> -			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);
> +			printf_ln(_("branch '%s' set up to track:"), local);
> +			printf("%s", ref_string.buf);

but does not have the word 'from' when it is a local ref. As far as I
can tell, this is the only difference between remote and local refs, and
adding the word 'from' does not seem like a good enough reason to add an
'if' condition. Maybe I missed something here?

This motivates my answer to the question you asked in [1]:

  I removed as many distinctions as possible, as most can still be
  inferred from context. [...] Likewise, we don't need to specify whether
  refs are remote or local: "some-remote/some-branch" vs.
  "a-local-branch" should be understandable without us spelling it out.

I agree that there is adequate context, so I would be ok with the
simplification if there was corresponding code simplification e.g.
dropping "if (origin)". But in its current form, I don't think there is
good enough reason to simplify the message.

Of course, IIUC, this is as simple as dropping 'from' in the "if
(origin)" case.

> @@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	strbuf_release(&key);
>  	error(_("Unable to write upstream branch configuration"));
>  
> -	advise(_(tracking_advice),
> -	       origin ? origin : "",
> -	       origin ? "/" : "",
> -	       shortname ? shortname : remote);
> +	advise(_("\nAfter fixing the error cause you may try to fix up\n"
> +		"the remote tracking information by invoking:"));
> +	if (remotes->nr == 1)
> +		advise("  git branch --set-upstream-to=%s%s%s",
> +			origin ? origin : "",
> +			origin ? "/" : "",
> +			remotes->items[0].string);
> +	else
> +		for_each_string_list_item(item, remotes)
> +			advise("  git config --add branch.\"%s\".merge %s",
> +				local, item->string);

The advice is now correct, nice! Does the user also need to run "git
config --add branch.%s.remote %s" ?

[1] https://lore.kernel.org/git/Ybk6QsMdeBl6IweW@google.com

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 2/3] branch: add flags and config to inherit tracking
  2021-12-14 23:44   ` [PATCH v6 2/3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-16 21:27     ` Glen Choo
  2021-12-17  5:11       ` Josh Steadmon
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-16 21:27 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

> @@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  			   enum branch_track track, int quiet)
>  {
>  	struct tracking tracking;
> +	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
>  	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
>  
>  	memset(&tracking, 0, sizeof(tracking));
>  	tracking.spec.dst = (char *)orig_ref;
> -	if (for_each_remote(find_tracked_branch, &tracking))
> +	tracking.srcs = &tracking_srcs;
> +	if (track != BRANCH_TRACK_INHERIT)
> +		for_each_remote(find_tracked_branch, &tracking);
> +	else if (inherit_tracking(&tracking, orig_ref))
>  		return;
>  
>  	if (!tracking.matches)
> @@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  		case BRANCH_TRACK_ALWAYS:
>  		case BRANCH_TRACK_EXPLICIT:
>  		case BRANCH_TRACK_OVERRIDE:
> +		case BRANCH_TRACK_INHERIT:
>  			break;
>  		default:
>  			return;
> @@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
>  		die(_("Not tracking: ambiguous information for ref %s"),
>  		    orig_ref);
>  
> -	if (install_branch_config(config_flags, new_ref, tracking.remote,
> -			      tracking.src ? tracking.src : orig_ref) < 0)
> +	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
> +		string_list_append(tracking.srcs, orig_ref);

So, in the BRANCH_TRACK_{ALWAYS,EXPLICIT,OVERRIDE} cases, we append
orig_ref because we expect orig_ref to be a local ref that the caller
wants to track. This is not the case with BRANCH_TRACK_INHERIT, where we
want to inherit the configuration and we no longer care about orig_ref.

This is correct, though it's more unobvious than what I originally
envisioned when I commented on [1]. As a small nit, it might benefit
from a clarifying comment, but this is fine as it is :)

> diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
> index 4453741b96..49c7def21c 100755
> --- a/t/t2027-checkout-track.sh
> +++ b/t/t2027-checkout-track.sh
> @@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
>  	test_i18ngrep "cannot be used with updating paths" err
>  '
>  
> +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
> +	# Set up tracking config on main
> +	test_config branch.main.remote origin &&
> +	test_config branch.main.merge refs/heads/main &&
> +	test_config branch.autoSetupMerge inherit &&
> +	# With --track=inherit, we copy the tracking config from main
> +	git checkout --track=inherit -b b1 main &&
> +	test_cmp_config origin branch.b1.remote &&
> +	test_cmp_config refs/heads/main branch.b1.merge &&
> +	# With branch.autoSetupMerge=inherit, we do the same
> +	git checkout -b b2 main &&
> +	test_cmp_config origin branch.b2.remote &&
> +	test_cmp_config refs/heads/main branch.b2.merge &&
> +	# But --track overrides this
> +	git checkout --track -b b3 main &&
> +	test_cmp_config . branch.b3.remote &&
> +	test_cmp_config refs/heads/main branch.b3.merge &&
> +	# And --track=direct does as well
> +	git checkout --track=direct -b b4 main &&
> +	test_cmp_config . branch.b4.remote &&
> +	test_cmp_config refs/heads/main branch.b4.merge

Nit: in both cases, the expected result is that branch.b*.merge is
"refs/heads/main". so the difference between --track=direct and
--track=inherit would be more obvious if main tracked something other
than origin/main.

As an side, the comments in the tests make it really readable :)

Overall this patch looks good.

[1] https://lore.kernel.org/git/kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-16 19:57     ` Glen Choo
@ 2021-12-17  5:10       ` Josh Steadmon
  2021-12-20 18:29         ` Glen Choo
  0 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:10 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, avarab, Johannes.Schindelin

On 2021.12.16 11:57, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> >  {
> >  	const char *shortname = NULL;
> >  	struct strbuf key = STRBUF_INIT;
> > +	struct string_list_item *item;
> >  	int rebasing = should_setup_rebase(origin);
> >  
> > -	if (skip_prefix(remote, "refs/heads/", &shortname)
> > -	    && !strcmp(local, shortname)
> > -	    && !origin) {
> > -		warning(_("Not setting branch %s as its own upstream."),
> > -			local);
> > -		return 0;
> > -	}
> > +	if (!remotes->nr)
> > +		BUG("must provide at least one remote for branch config");
> > +	if (rebasing && remotes->nr > 1)
> > +		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));
> 
> Nit: if we're being pedantic, we cannot inherit upstream tracking
> configuration when rebasing is requested with multiple upstream
> branches.
> 
> But this message is already very niche and loaded with specifics, so
> adding "...with multiple upstream branches" might just be more
> confusing, so this is a nit.

I think it's worthwhile to be precise. Thanks for pointing this out.
Fixed in V7.


> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	strbuf_release(&key);
> >  
> >  	if (flag & BRANCH_CONFIG_VERBOSE) {
> > -		if (shortname) {
> > +		const char *name;
> > +		struct strbuf ref_string = STRBUF_INIT;
> > +
> > +		for_each_string_list_item(item, remotes) {
> > +			name = item->string;
> > +			skip_prefix(name, "refs/heads/", &name);
> > +			strbuf_addf(&ref_string, "  %s\n", name);
> > +		}
> > +
> > +		if (remotes->nr == 1) {
> > +			struct strbuf refname = STRBUF_INIT;
> > +
> >  			if (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);
> > -			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);
> > +				strbuf_addf(&refname, "%s/", origin);
> > +			strbuf_addstr(&refname, remotes->items[0].string);
> > +
> > +			/*
> > +			 * Rebasing is only allowed in the case of a single
> > +			 * upstream branch.
> > +			 */
> > +			printf_ln(rebasing ?
> > +				_("branch '%s' set up to track '%s' by rebasing.") :
> > +				_("branch '%s' set up to track '%s'."),
> > +				local, refname.buf);
> > +
> > +			strbuf_release(&refname);
> > +		} else if (origin) {
> > +			printf_ln(_("branch '%s' set up to track from '%s':"),
> > +				local, origin);
> > +			printf("%s", ref_string.buf);
> 
> It's not clear to me why the hint contains the word 'from' when it is a
> remote ref...

Because in the multiple-branch case, we don't prepend the origin to each
ref, so we need to let users know which remote the refs are coming from.


> >  		} else {
> > -			if (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);
> > -			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);
> > +			printf_ln(_("branch '%s' set up to track:"), local);
> > +			printf("%s", ref_string.buf);
> 
> but does not have the word 'from' when it is a local ref. As far as I
> can tell, this is the only difference between remote and local refs, and
> adding the word 'from' does not seem like a good enough reason to add an
> 'if' condition. Maybe I missed something here?
> 
> This motivates my answer to the question you asked in [1]:
> 
>   I removed as many distinctions as possible, as most can still be
>   inferred from context. [...] Likewise, we don't need to specify whether
>   refs are remote or local: "some-remote/some-branch" vs.
>   "a-local-branch" should be understandable without us spelling it out.
> 
> I agree that there is adequate context, so I would be ok with the
> simplification if there was corresponding code simplification e.g.
> dropping "if (origin)". But in its current form, I don't think there is
> good enough reason to simplify the message.

I think the proper point of comparison is not the original code, but the
code from V5 where we try to preserve the same level of detail in output
as the original code. If we are committed to both having multiple
remotes and keeping similar styles of output as the original
implementation, then something like the massive conditional in V5 is
unavoidable.

> Of course, IIUC, this is as simple as dropping 'from' in the "if
> (origin)" case.
> 
> > @@ -118,14 +156,33 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >  	strbuf_release(&key);
> >  	error(_("Unable to write upstream branch configuration"));
> >  
> > -	advise(_(tracking_advice),
> > -	       origin ? origin : "",
> > -	       origin ? "/" : "",
> > -	       shortname ? shortname : remote);
> > +	advise(_("\nAfter fixing the error cause you may try to fix up\n"
> > +		"the remote tracking information by invoking:"));
> > +	if (remotes->nr == 1)
> > +		advise("  git branch --set-upstream-to=%s%s%s",
> > +			origin ? origin : "",
> > +			origin ? "/" : "",
> > +			remotes->items[0].string);
> > +	else
> > +		for_each_string_list_item(item, remotes)
> > +			advise("  git config --add branch.\"%s\".merge %s",
> > +				local, item->string);
> 
> The advice is now correct, nice! Does the user also need to run "git
> config --add branch.%s.remote %s" ?
> 
> [1] https://lore.kernel.org/git/Ybk6QsMdeBl6IweW@google.com

Yes, thank you for the catch. Fixed in V7.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 2/3] branch: add flags and config to inherit tracking
  2021-12-16 21:27     ` Glen Choo
@ 2021-12-17  5:11       ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:11 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, avarab, Johannes.Schindelin

On 2021.12.16 13:27, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> > @@ -192,11 +220,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> >  			   enum branch_track track, int quiet)
> >  {
> >  	struct tracking tracking;
> > +	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
> >  	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
> >  
> >  	memset(&tracking, 0, sizeof(tracking));
> >  	tracking.spec.dst = (char *)orig_ref;
> > -	if (for_each_remote(find_tracked_branch, &tracking))
> > +	tracking.srcs = &tracking_srcs;
> > +	if (track != BRANCH_TRACK_INHERIT)
> > +		for_each_remote(find_tracked_branch, &tracking);
> > +	else if (inherit_tracking(&tracking, orig_ref))
> >  		return;
> >  
> >  	if (!tracking.matches)
> > @@ -204,6 +236,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> >  		case BRANCH_TRACK_ALWAYS:
> >  		case BRANCH_TRACK_EXPLICIT:
> >  		case BRANCH_TRACK_OVERRIDE:
> > +		case BRANCH_TRACK_INHERIT:
> >  			break;
> >  		default:
> >  			return;
> > @@ -213,11 +246,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
> >  		die(_("Not tracking: ambiguous information for ref %s"),
> >  		    orig_ref);
> >  
> > -	if (install_branch_config(config_flags, new_ref, tracking.remote,
> > -			      tracking.src ? tracking.src : orig_ref) < 0)
> > +	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
> > +		string_list_append(tracking.srcs, orig_ref);
> 
> So, in the BRANCH_TRACK_{ALWAYS,EXPLICIT,OVERRIDE} cases, we append
> orig_ref because we expect orig_ref to be a local ref that the caller
> wants to track. This is not the case with BRANCH_TRACK_INHERIT, where we
> want to inherit the configuration and we no longer care about orig_ref.
> 
> This is correct, though it's more unobvious than what I originally
> envisioned when I commented on [1]. As a small nit, it might benefit
> from a clarifying comment, but this is fine as it is :)

Actually, you're right, the `track != BRANCH_TRACK_INHERIT` condition is
superfluous. The only way we could have BRANCH_TRACK_INHERIT and
tracking.srcs->nr < 1 at the same time would be if there were no
"branch.*.merge" entries in the config to inherit, but if that were true
then we would have had returned from this function earlier.

> > diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
> > index 4453741b96..49c7def21c 100755
> > --- a/t/t2027-checkout-track.sh
> > +++ b/t/t2027-checkout-track.sh
> > @@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
> >  	test_i18ngrep "cannot be used with updating paths" err
> >  '
> >  
> > +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
> > +	# Set up tracking config on main
> > +	test_config branch.main.remote origin &&
> > +	test_config branch.main.merge refs/heads/main &&
> > +	test_config branch.autoSetupMerge inherit &&
> > +	# With --track=inherit, we copy the tracking config from main
> > +	git checkout --track=inherit -b b1 main &&
> > +	test_cmp_config origin branch.b1.remote &&
> > +	test_cmp_config refs/heads/main branch.b1.merge &&
> > +	# With branch.autoSetupMerge=inherit, we do the same
> > +	git checkout -b b2 main &&
> > +	test_cmp_config origin branch.b2.remote &&
> > +	test_cmp_config refs/heads/main branch.b2.merge &&
> > +	# But --track overrides this
> > +	git checkout --track -b b3 main &&
> > +	test_cmp_config . branch.b3.remote &&
> > +	test_cmp_config refs/heads/main branch.b3.merge &&
> > +	# And --track=direct does as well
> > +	git checkout --track=direct -b b4 main &&
> > +	test_cmp_config . branch.b4.remote &&
> > +	test_cmp_config refs/heads/main branch.b4.merge
> 
> Nit: in both cases, the expected result is that branch.b*.merge is
> "refs/heads/main". so the difference between --track=direct and
> --track=inherit would be more obvious if main tracked something other
> than origin/main.

Fixed in V7.


> As an side, the comments in the tests make it really readable :)
> 
> Overall this patch looks good.
> 
> [1] https://lore.kernel.org/git/kl6lfsr3c3j7.fsf@chooglen-macbookpro.roam.corp.google.com

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v7 0/3] branch: inherit tracking configs
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (6 preceding siblings ...)
  2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
@ 2021-12-17  5:12 ` Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
                     ` (3 more replies)
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
  8 siblings, 4 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Changes since V6:
* Strip the refs/heads/ prefix in the verbose output when we have only a
  single upstream branch.
* Improve the fatal error message to note that rebasing is only
  incompatible with multiple upstream refs.
* Also note that `branch.<name>.remote` should be set in the manual
  recovery advice.
* Simplify the logic in setup_tracking() when no tracking sources match.
* Make the difference in test cases in t2027 more obvious.

Changes since V5:
* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
  so many conditionals.
* Note that rebasing is not compatible with inheriting multiple upstream
  branches.
* Moved the change to case-sensitivity for branch.autosetupmerge to its
  own commit.
* Improve advice on failed tracking setup when multiple branches are
  involved.
* Make better use of string_list API.
* Make better use of config API.
* More straight-forward use of the `struct tracking` API.
* Numerous style fixes.

Changes since V4:
* Add new patch (1/2) to refactor branch.c:install_branch_config() to
  accept multiple upstream refs
* When multiple upstream branches are set in the parent branch, inherit
  them all, instead of just the first
* Break out error string arguments for easier translation
* Don't ignore case for values of branch.autosetupmerge
* Move reference to git-pull out of usage string for --track into
  git-branch.txt
* Use test_config instead of `git config` in t2027
* Style fixes: add single-quotes around warning string arguments, remove
  unnecessary braces

Changes since V3:
* Use branch_get() instead of git_config_get_string() to look up branch
  configuration.
* Remove unnecessary string formatting in new error message in
  parse-options-cb.c.

Josh Steadmon (3):
  branch: accept multiple upstream branches for tracking
  branch: add flags and config to inherit tracking
  config: require lowercase for branch.*.autosetupmerge

 Documentation/config/branch.txt |   3 +-
 Documentation/git-branch.txt    |  24 ++--
 Documentation/git-checkout.txt  |   2 +-
 Documentation/git-switch.txt    |   2 +-
 branch.c                        | 189 ++++++++++++++++++++++++--------
 branch.h                        |   3 +-
 builtin/branch.c                |   6 +-
 builtin/checkout.c              |   6 +-
 config.c                        |   5 +-
 parse-options-cb.c              |  16 +++
 parse-options.h                 |   2 +
 t/t2017-checkout-orphan.sh      |  11 +-
 t/t2027-checkout-track.sh       |  23 ++++
 t/t2060-switch.sh               |  28 +++++
 t/t3200-branch.sh               |  39 ++++++-
 t/t7201-co.sh                   |  17 +++
 16 files changed, 310 insertions(+), 66 deletions(-)

Range-diff against v6:
1:  43d6f83fed ! 1:  9152367ba9 branch: accept multiple upstream branches for tracking
    @@ branch.c: static int should_setup_rebase(const char *origin)
     +	if (!remotes->nr)
     +		BUG("must provide at least one remote for branch config");
     +	if (rebasing && remotes->nr > 1)
    -+		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));
    ++		die(_("cannot inherit upstream tracking configuration of "
    ++		      "multiple refs when rebasing is requested"));
     +
     +	if (!origin)
     +		for_each_string_list_item(item, remotes)
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     -					  _("Branch '%s' set up to track local branch '%s'."),
     -					  local, shortname);
     +				strbuf_addf(&refname, "%s/", origin);
    -+			strbuf_addstr(&refname, remotes->items[0].string);
    ++			skip_prefix(remotes->items[0].string, "refs/heads/", &name);
    ++			strbuf_addstr(&refname, name);
     +
     +			/*
     +			 * Rebasing is only allowed in the case of a single
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     +			origin ? origin : "",
     +			origin ? "/" : "",
     +			remotes->items[0].string);
    -+	else
    ++	else {
    ++		advise("  git config --add branch.\"%s\".remote %s",
    ++			local, origin ? origin : ".");
     +		for_each_string_list_item(item, remotes)
     +			advise("  git config --add branch.\"%s\".merge %s",
     +				local, item->string);
    ++	}
      
      	return -1;
      }
2:  57e57e6e6a ! 2:  afeb84539e branch: add flags and config to inherit tracking
    @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
      
     -	if (install_branch_config(config_flags, new_ref, tracking.remote,
     -			      tracking.src ? tracking.src : orig_ref) < 0)
    -+	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
    ++	if (tracking.srcs->nr < 1)
     +		string_list_append(tracking.srcs, orig_ref);
    -+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
    -+			      tracking.srcs) < 0)
    ++	if (install_branch_config_multiple_remotes(config_flags, new_ref,
    ++				tracking.remote, tracking.srcs) < 0)
      		exit(-1);
      
     -	free(tracking.src);
    @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e
     +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
     +	# Set up tracking config on main
     +	test_config branch.main.remote origin &&
    -+	test_config branch.main.merge refs/heads/main &&
    ++	test_config branch.main.merge refs/heads/some-branch &&
     +	test_config branch.autoSetupMerge inherit &&
     +	# With --track=inherit, we copy the tracking config from main
     +	git checkout --track=inherit -b b1 main &&
     +	test_cmp_config origin branch.b1.remote &&
    -+	test_cmp_config refs/heads/main branch.b1.merge &&
    ++	test_cmp_config refs/heads/some-branch branch.b1.merge &&
     +	# With branch.autoSetupMerge=inherit, we do the same
     +	git checkout -b b2 main &&
     +	test_cmp_config origin branch.b2.remote &&
    -+	test_cmp_config refs/heads/main branch.b2.merge &&
    ++	test_cmp_config refs/heads/some-branch branch.b2.merge &&
     +	# But --track overrides this
     +	git checkout --track -b b3 main &&
     +	test_cmp_config . branch.b3.remote &&
3:  f79d27dc24 = 3:  a818a6561b config: require lowercase for branch.*.autosetupmerge

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v7 1/3] branch: accept multiple upstream branches for tracking
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
@ 2021-12-17  5:12   ` Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 2/3] branch: add flags and config to inherit tracking Josh Steadmon
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Add a new static variant of install_branch_config() that accepts
multiple remote branch names for tracking. This will be used in an
upcoming commit that enables inheriting the tracking configuration from
a parent branch.

Currently, all callers of install_branch_config() pass only a single
remote. Make install_branch_config() a small wrapper around
install_branch_config_multiple_remotes() so that existing callers do not
need to be changed.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 branch.c          | 140 +++++++++++++++++++++++++++++++++-------------
 t/t3200-branch.sh |   6 +-
 2 files changed, 104 insertions(+), 42 deletions(-)

diff --git a/branch.c b/branch.c
index 7a88a4861e..9e57c54848 100644
--- a/branch.c
+++ b/branch.c
@@ -49,25 +49,42 @@ static int should_setup_rebase(const char *origin)
 	return 0;
 }
 
-static const char tracking_advice[] =
-N_("\n"
-"After fixing the error cause you may try to fix up\n"
-"the remote tracking information by invoking\n"
-"\"git branch --set-upstream-to=%s%s%s\".");
-
-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+/**
+ * Install upstream tracking configuration for a branch; specifically, add
+ * `branch.<name>.remote` and `branch.<name>.merge` entries.
+ *
+ * `flag` contains integer flags for options; currently only
+ * BRANCH_CONFIG_VERBOSE is checked.
+ *
+ * `local` is the name of the branch whose configuration we're installing.
+ *
+ * `origin` is the name of the remote owning the upstream branches. NULL means
+ * the upstream branches are local to this repo.
+ *
+ * `remotes` is a list of refs that are upstream of local
+ */
+static int install_branch_config_multiple_remotes(int flag, const char *local,
+		const char *origin, struct string_list *remotes)
 {
 	const char *shortname = NULL;
 	struct strbuf key = STRBUF_INIT;
+	struct string_list_item *item;
 	int rebasing = should_setup_rebase(origin);
 
-	if (skip_prefix(remote, "refs/heads/", &shortname)
-	    && !strcmp(local, shortname)
-	    && !origin) {
-		warning(_("Not setting branch %s as its own upstream."),
-			local);
-		return 0;
-	}
+	if (!remotes->nr)
+		BUG("must provide at least one remote for branch config");
+	if (rebasing && remotes->nr > 1)
+		die(_("cannot inherit upstream tracking configuration of "
+		      "multiple refs when rebasing is requested"));
+
+	if (!origin)
+		for_each_string_list_item(item, remotes)
+			if (skip_prefix(item->string, "refs/heads/", &shortname)
+			    && !strcmp(local, shortname)) {
+				warning(_("not setting branch '%s' as its own upstream."),
+					local);
+				return 0;
+			}
 
 	strbuf_addf(&key, "branch.%s.remote", local);
 	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
@@ -75,8 +92,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
 	strbuf_reset(&key);
 	strbuf_addf(&key, "branch.%s.merge", local);
-	if (git_config_set_gently(key.buf, remote) < 0)
+	/*
+	 * We want to overwrite any existing config with all the branches in
+	 * "remotes". Override any existing config, then write our branches. If
+	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
+	 * we've written so far.
+	 */
+	if (git_config_set_gently(key.buf, NULL) < 0)
 		goto out_err;
+	for_each_string_list_item(item, remotes)
+		if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
+			goto out_err;
 
 	if (rebasing) {
 		strbuf_reset(&key);
@@ -87,29 +113,43 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (shortname) {
+		const char *name;
+		struct strbuf ref_string = STRBUF_INIT;
+
+		for_each_string_list_item(item, remotes) {
+			name = item->string;
+			skip_prefix(name, "refs/heads/", &name);
+			strbuf_addf(&ref_string, "  %s\n", name);
+		}
+
+		if (remotes->nr == 1) {
+			struct strbuf refname = STRBUF_INIT;
+
 			if (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);
-			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);
+				strbuf_addf(&refname, "%s/", origin);
+			skip_prefix(remotes->items[0].string, "refs/heads/", &name);
+			strbuf_addstr(&refname, name);
+
+			/*
+			 * Rebasing is only allowed in the case of a single
+			 * upstream branch.
+			 */
+			printf_ln(rebasing ?
+				_("branch '%s' set up to track '%s' by rebasing.") :
+				_("branch '%s' set up to track '%s'."),
+				local, refname.buf);
+
+			strbuf_release(&refname);
+		} else if (origin) {
+			printf_ln(_("branch '%s' set up to track from '%s':"),
+				local, origin);
+			printf("%s", ref_string.buf);
 		} else {
-			if (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);
-			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);
+			printf_ln(_("branch '%s' set up to track:"), local);
+			printf("%s", ref_string.buf);
 		}
+
+		strbuf_release(&ref_string);
 	}
 
 	return 0;
@@ -118,14 +158,36 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 	error(_("Unable to write upstream branch configuration"));
 
-	advise(_(tracking_advice),
-	       origin ? origin : "",
-	       origin ? "/" : "",
-	       shortname ? shortname : remote);
+	advise(_("\nAfter fixing the error cause you may try to fix up\n"
+		"the remote tracking information by invoking:"));
+	if (remotes->nr == 1)
+		advise("  git branch --set-upstream-to=%s%s%s",
+			origin ? origin : "",
+			origin ? "/" : "",
+			remotes->items[0].string);
+	else {
+		advise("  git config --add branch.\"%s\".remote %s",
+			local, origin ? origin : ".");
+		for_each_string_list_item(item, remotes)
+			advise("  git config --add branch.\"%s\".merge %s",
+				local, item->string);
+	}
 
 	return -1;
 }
 
+int install_branch_config(int flag, const char *local, const char *origin,
+		const char *remote)
+{
+	int ret;
+	struct string_list remotes = STRING_LIST_INIT_DUP;
+
+	string_list_append(&remotes, remote);
+	ret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);
+	string_list_clear(&remotes, 0);
+	return ret;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..4b0ef35913 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '
 	test_must_fail git branch --set-upstream origin/main
 '
 
-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
 	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
 	cat >expect <<-\EOF &&
-	warning: Not setting branch my13 as its own upstream.
+	warning: not setting branch 'my13' as its own upstream.
 	EOF
 	test_expect_code 1 git config branch.my13.remote &&
 	test_expect_code 1 git config branch.my13.merge &&
 	test_cmp expect actual
-'
+"
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v7 2/3] branch: add flags and config to inherit tracking
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-17  5:12   ` Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
  2021-12-20 21:05   ` [PATCH v7 0/3] branch: inherit tracking configs Glen Choo
  3 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 Documentation/config/branch.txt |  3 +-
 Documentation/git-branch.txt    | 24 +++++++++++-----
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 49 ++++++++++++++++++++++++++++-----
 branch.h                        |  3 +-
 builtin/branch.c                |  6 ++--
 builtin/checkout.c              |  6 ++--
 config.c                        |  3 ++
 parse-options-cb.c              | 16 +++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      | 11 +++++++-
 t/t2027-checkout-track.sh       | 23 ++++++++++++++++
 t/t2060-switch.sh               | 28 +++++++++++++++++++
 t/t3200-branch.sh               | 33 ++++++++++++++++++++++
 t/t7201-co.sh                   | 17 ++++++++++++
 16 files changed, 205 insertions(+), 23 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..c8b393e51c 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
++
+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index 9e57c54848..5e712ad95b 100644
--- a/branch.c
+++ b/branch.c
@@ -11,7 +11,7 @@
 
 struct tracking {
 	struct refspec_item spec;
-	char *src;
+	struct string_list *srcs;
 	const char *remote;
 	int matches;
 };
@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		if (++tracking->matches == 1) {
-			tracking->src = tracking->spec.src;
+			string_list_append(tracking->srcs, tracking->spec.src);
 			tracking->remote = remote->name;
 		} else {
 			free(tracking->spec.src);
-			FREE_AND_NULL(tracking->src);
+			string_list_clear(tracking->srcs, 0);
 		}
 		tracking->spec.src = NULL;
 	}
@@ -188,6 +188,34 @@ int install_branch_config(int flag, const char *local, const char *origin,
 	return ret;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	const char *bare_ref;
+	struct branch *branch;
+	int i;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	branch = branch_get(bare_ref);
+	if (!branch->remote_name) {
+		warning(_("asked to inherit tracking from '%s', but no remote is set"),
+			bare_ref);
+		return -1;
+	}
+
+	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
+		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
+			bare_ref);
+		return -1;
+	}
+
+	tracking->remote = xstrdup(branch->remote_name);
+	for (i = 0; i < branch->merge_nr; i++)
+		string_list_append(tracking->srcs, branch->merge_name[i]);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -197,11 +225,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 			   enum branch_track track, int quiet)
 {
 	struct tracking tracking;
+	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	tracking.srcs = &tracking_srcs;
+	if (track != BRANCH_TRACK_INHERIT)
+		for_each_remote(find_tracked_branch, &tracking);
+	else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
@@ -209,6 +241,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		case BRANCH_TRACK_ALWAYS:
 		case BRANCH_TRACK_EXPLICIT:
 		case BRANCH_TRACK_OVERRIDE:
+		case BRANCH_TRACK_INHERIT:
 			break;
 		default:
 			return;
@@ -218,11 +251,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		die(_("Not tracking: ambiguous information for ref %s"),
 		    orig_ref);
 
-	if (install_branch_config(config_flags, new_ref, tracking.remote,
-			      tracking.src ? tracking.src : orig_ref) < 0)
+	if (tracking.srcs->nr < 1)
+		string_list_append(tracking.srcs, orig_ref);
+	if (install_branch_config_multiple_remotes(config_flags, new_ref,
+				tracking.remote, tracking.srcs) < 0)
 		exit(-1);
 
-	free(tracking.src);
+	string_list_clear(tracking.srcs, 0);
 }
 
 int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index df0be61506..815dcd40c0 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT,
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..ebde5023c3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set branch tracking configuration"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..152c94f29d 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..d346dbe210 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `%s' expects \"%s\" or \"%s\""),
+			     "--track", "direct", "inherit");
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..4d689bd377 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout main &&
 	git config branch.autosetupmerge always &&
 	git checkout --orphan gamma &&
-	test -z "$(git config branch.gamma.merge)" &&
+	test_cmp_config "" --default "" branch.gamma.merge &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test_cmp_config "" --default "" branch.eta.merge &&
+	test_cmp_config "" --default "" branch.eta.remote &&
+	echo refs/heads/eta >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..dca35aa3e3 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	test_config branch.main.remote origin &&
+	test_config branch.main.merge refs/heads/some-branch &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/some-branch branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/some-branch branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..ebb961be29 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 4b0ef35913..a049276439 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test_cmp_config "" --default "" branch.foo5.remote &&
+	test_cmp_config "" --default "" branch.foo5.merge
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..4fdf88ba46 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v7 3/3] config: require lowercase for branch.*.autosetupmerge
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-17  5:12   ` [PATCH v7 2/3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-17  5:12   ` Josh Steadmon
  2021-12-20 21:05   ` [PATCH v7 0/3] branch: inherit tracking configs Glen Choo
  3 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-17  5:12 UTC (permalink / raw)
  To: git; +Cc: gitster, chooglen, avarab, Johannes.Schindelin

Although we only documented that branch.*.autosetupmerge would accept
"always" as a value, the actual implementation would accept any
combination of upper- or lower-case. Fix this to be consistent with
documentation and with other values of this config variable.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 config.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 152c94f29d..54c0e7d98e 100644
--- a/config.c
+++ b/config.c
@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)
 static int git_default_branch_config(const char *var, const char *value)
 {
 	if (!strcmp(var, "branch.autosetupmerge")) {
-		if (value && !strcasecmp(value, "always")) {
+		if (value && !strcmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
 		} else if (value && !strcmp(value, "inherit")) {
-- 
2.34.1.173.g76aa8bc2d0-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-17  5:10       ` Josh Steadmon
@ 2021-12-20 18:29         ` Glen Choo
  2021-12-21  3:27           ` Josh Steadmon
  0 siblings, 1 reply; 103+ messages in thread
From: Glen Choo @ 2021-12-20 18:29 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

>> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>> >  	strbuf_release(&key);
>> >  
>> >  	if (flag & BRANCH_CONFIG_VERBOSE) {
>> > -		if (shortname) {
>> > +		const char *name;
>> > +		struct strbuf ref_string = STRBUF_INIT;
>> > +
>> > +		for_each_string_list_item(item, remotes) {
>> > +			name = item->string;
>> > +			skip_prefix(name, "refs/heads/", &name);
>> > +			strbuf_addf(&ref_string, "  %s\n", name);
>> > +		}
>> > +
>> > +		if (remotes->nr == 1) {
>> > +			struct strbuf refname = STRBUF_INIT;
>> > +
>> >  			if (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);
>> > -			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);
>> > +				strbuf_addf(&refname, "%s/", origin);
>> > +			strbuf_addstr(&refname, remotes->items[0].string);
>> > +
>> > +			/*
>> > +			 * Rebasing is only allowed in the case of a single
>> > +			 * upstream branch.
>> > +			 */
>> > +			printf_ln(rebasing ?
>> > +				_("branch '%s' set up to track '%s' by rebasing.") :
>> > +				_("branch '%s' set up to track '%s'."),
>> > +				local, refname.buf);
>> > +
>> > +			strbuf_release(&refname);
>> > +		} else if (origin) {
>> > +			printf_ln(_("branch '%s' set up to track from '%s':"),
>> > +				local, origin);
>> > +			printf("%s", ref_string.buf);
>> 
>> It's not clear to me why the hint contains the word 'from' when it is a
>> remote ref...
>
> Because in the multiple-branch case, we don't prepend the origin to each
> ref, so we need to let users know which remote the refs are coming from.

I see. So if I'm reading this correctly, the error message in the remote
case would read something like:

  branch 'main' set up to track from 'origin':
    main
    topic1
    topic2

Is there any reason why we couldn't append the origin to the ref to make
it consistent? I think this could be as simple as:


	for_each_string_list_item(item, remotes) {
		name = item->string;
		skip_prefix(name, "refs/heads/", &name);
			if (origin)
+         strbuf_addf(&ref_string, "%s/", origin);
		strbuf_addf(&ref_string, "  %s\n", name);
	}

and the resulting list could look like:

  branch 'main' set up to track from 'origin':
    origin/main
    origin/topic1
    origin/topic2

This looks repetitive, but I suggest this because, as I understand it,
we are omitting the "{local,remote} ref" phrase based on conventions
around ref names, like "origin/main" is probably a remote ref and not an
oddly named local ref. However, when we print the list like so,

  branch 'main' set up to track from 'origin':
    main
    topic1
    topic2

we now expect the user to understand that 'main', 'topic1' and 'topic2'
to implicitly have 'origin/' prepended to them. This behavior seems
inconsistent to me; I'd anticipate most users responding "Wait, I was
supposed to be tracking 'origin' branches right? Why am I looking at
local branches?". Some users would be able to recover because they can
figure out what we mean, but others might just give up.

Prepending 'origin/' would get rid of this problem altogether, and it
would let us drop the 'from'.

>> >  		} else {
>> > -			if (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);
>> > -			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);
>> > +			printf_ln(_("branch '%s' set up to track:"), local);
>> > +			printf("%s", ref_string.buf);
>> 
>> but does not have the word 'from' when it is a local ref. As far as I
>> can tell, this is the only difference between remote and local refs, and
>> adding the word 'from' does not seem like a good enough reason to add an
>> 'if' condition. Maybe I missed something here?
>> 
>> This motivates my answer to the question you asked in [1]:
>> 
>>   I removed as many distinctions as possible, as most can still be
>>   inferred from context. [...] Likewise, we don't need to specify whether
>>   refs are remote or local: "some-remote/some-branch" vs.
>>   "a-local-branch" should be understandable without us spelling it out.
>> 
>> I agree that there is adequate context, so I would be ok with the
>> simplification if there was corresponding code simplification e.g.
>> dropping "if (origin)". But in its current form, I don't think there is
>> good enough reason to simplify the message.
>
> I think the proper point of comparison is not the original code, but the
> code from V5 where we try to preserve the same level of detail in output
> as the original code. If we are committed to both having multiple
> remotes and keeping similar styles of output as the original
> implementation, then something like the massive conditional in V5 is
> unavoidable.

I see. So for instance, post-simplification you have:

  printf_ln(rebasing ?
    _("branch '%s' set up to track '%s' by rebasing.") :
    _("branch '%s' set up to track '%s'."),
    local, refname.buf);

if you preserve the same amount of detail as before, you'd have to
distinguish between local/remote, which doubles the number of cases to
4, which is why the conditional v5 is so complicated.

That said, I think that it's already much simpler than v5 because you've
split the singular and plural cases. I wonder if you have considered
building the final string purely from format strings, like:

  char *message_format = _("branch %s set up to track %s%s%s%s");
  char *ref_type_clause = origin ? " remote ref " : " local ref ";
  char *rebasing_clause = rebasing ? " by rebasing." : ".";
  char *branch_names = "<branch names>";
  printf_ln(message_format, local, ref_type_clause, branch_names, rebasing_clause);

This sounds potentially unfriendly to i18n, but it would make the
conditional simpler. What do you think?

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v7 0/3] branch: inherit tracking configs
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
                     ` (2 preceding siblings ...)
  2021-12-17  5:12   ` [PATCH v7 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
@ 2021-12-20 21:05   ` Glen Choo
  3 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-20 21:05 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab, Johannes.Schindelin

Josh Steadmon <steadmon@google.com> writes:

> Changes since V6:
> * Strip the refs/heads/ prefix in the verbose output when we have only a
>   single upstream branch.
> * Improve the fatal error message to note that rebasing is only
>   incompatible with multiple upstream refs.
> * Also note that `branch.<name>.remote` should be set in the manual
>   recovery advice.
> * Simplify the logic in setup_tracking() when no tracking sources match.
> * Make the difference in test cases in t2027 more obvious.
>
> Changes since V5:
> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
>   so many conditionals.
> * Note that rebasing is not compatible with inheriting multiple upstream
>   branches.
> * Moved the change to case-sensitivity for branch.autosetupmerge to its
>   own commit.
> * Improve advice on failed tracking setup when multiple branches are
>   involved.
> * Make better use of string_list API.
> * Make better use of config API.
> * More straight-forward use of the `struct tracking` API.
> * Numerous style fixes.
>
> Changes since V4:
> * Add new patch (1/2) to refactor branch.c:install_branch_config() to
>   accept multiple upstream refs
> * When multiple upstream branches are set in the parent branch, inherit
>   them all, instead of just the first
> * Break out error string arguments for easier translation
> * Don't ignore case for values of branch.autosetupmerge
> * Move reference to git-pull out of usage string for --track into
>   git-branch.txt
> * Use test_config instead of `git config` in t2027
> * Style fixes: add single-quotes around warning string arguments, remove
>   unnecessary braces
>
> Changes since V3:
> * Use branch_get() instead of git_config_get_string() to look up branch
>   configuration.
> * Remove unnecessary string formatting in new error message in
>   parse-options-cb.c.
>
> Josh Steadmon (3):
>   branch: accept multiple upstream branches for tracking
>   branch: add flags and config to inherit tracking
>   config: require lowercase for branch.*.autosetupmerge
>
>  Documentation/config/branch.txt |   3 +-
>  Documentation/git-branch.txt    |  24 ++--
>  Documentation/git-checkout.txt  |   2 +-
>  Documentation/git-switch.txt    |   2 +-
>  branch.c                        | 189 ++++++++++++++++++++++++--------
>  branch.h                        |   3 +-
>  builtin/branch.c                |   6 +-
>  builtin/checkout.c              |   6 +-
>  config.c                        |   5 +-
>  parse-options-cb.c              |  16 +++
>  parse-options.h                 |   2 +
>  t/t2017-checkout-orphan.sh      |  11 +-
>  t/t2027-checkout-track.sh       |  23 ++++
>  t/t2060-switch.sh               |  28 +++++
>  t/t3200-branch.sh               |  39 ++++++-
>  t/t7201-co.sh                   |  17 +++
>  16 files changed, 310 insertions(+), 66 deletions(-)
>
> Range-diff against v6:
> 1:  43d6f83fed ! 1:  9152367ba9 branch: accept multiple upstream branches for tracking
>     @@ branch.c: static int should_setup_rebase(const char *origin)
>      +	if (!remotes->nr)
>      +		BUG("must provide at least one remote for branch config");
>      +	if (rebasing && remotes->nr > 1)
>     -+		die(_("cannot inherit upstream tracking configuration when rebasing is requested"));
>     ++		die(_("cannot inherit upstream tracking configuration of "
>     ++		      "multiple refs when rebasing is requested"));
>      +
>      +	if (!origin)
>      +		for_each_string_list_item(item, remotes)
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>      -					  _("Branch '%s' set up to track local branch '%s'."),
>      -					  local, shortname);
>      +				strbuf_addf(&refname, "%s/", origin);
>     -+			strbuf_addstr(&refname, remotes->items[0].string);
>     ++			skip_prefix(remotes->items[0].string, "refs/heads/", &name);
>     ++			strbuf_addstr(&refname, name);
>      +
>      +			/*
>      +			 * Rebasing is only allowed in the case of a single
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>      +			origin ? origin : "",
>      +			origin ? "/" : "",
>      +			remotes->items[0].string);
>     -+	else
>     ++	else {
>     ++		advise("  git config --add branch.\"%s\".remote %s",
>     ++			local, origin ? origin : ".");
>      +		for_each_string_list_item(item, remotes)
>      +			advise("  git config --add branch.\"%s\".merge %s",
>      +				local, item->string);
>     ++	}
>       
>       	return -1;
>       }
> 2:  57e57e6e6a ! 2:  afeb84539e branch: add flags and config to inherit tracking
>     @@ branch.c: static void setup_tracking(const char *new_ref, const char *orig_ref,
>       
>      -	if (install_branch_config(config_flags, new_ref, tracking.remote,
>      -			      tracking.src ? tracking.src : orig_ref) < 0)
>     -+	if (tracking.srcs->nr < 1 && track != BRANCH_TRACK_INHERIT)
>     ++	if (tracking.srcs->nr < 1)
>      +		string_list_append(tracking.srcs, orig_ref);
>     -+	if (install_branch_config_multiple_remotes(config_flags, new_ref, tracking.remote,
>     -+			      tracking.srcs) < 0)
>     ++	if (install_branch_config_multiple_remotes(config_flags, new_ref,
>     ++				tracking.remote, tracking.srcs) < 0)
>       		exit(-1);
>       
>      -	free(tracking.src);
>     @@ t/t2027-checkout-track.sh: test_expect_success 'checkout --track -b rejects an e
>      +test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
>      +	# Set up tracking config on main
>      +	test_config branch.main.remote origin &&
>     -+	test_config branch.main.merge refs/heads/main &&
>     ++	test_config branch.main.merge refs/heads/some-branch &&
>      +	test_config branch.autoSetupMerge inherit &&
>      +	# With --track=inherit, we copy the tracking config from main
>      +	git checkout --track=inherit -b b1 main &&
>      +	test_cmp_config origin branch.b1.remote &&
>     -+	test_cmp_config refs/heads/main branch.b1.merge &&
>     ++	test_cmp_config refs/heads/some-branch branch.b1.merge &&
>      +	# With branch.autoSetupMerge=inherit, we do the same
>      +	git checkout -b b2 main &&
>      +	test_cmp_config origin branch.b2.remote &&
>     -+	test_cmp_config refs/heads/main branch.b2.merge &&
>     ++	test_cmp_config refs/heads/some-branch branch.b2.merge &&
>      +	# But --track overrides this
>      +	git checkout --track -b b3 main &&
>      +	test_cmp_config . branch.b3.remote &&
> 3:  f79d27dc24 = 3:  a818a6561b config: require lowercase for branch.*.autosetupmerge
>
> base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
> -- 
> 2.34.1.173.g76aa8bc2d0-goog

Thanks! As noted in v6, I don't have strong opinions about omitting
'local/remote' from the help message, but I suspect others might. It
would be nice to get more opinions here.

v7 looks pretty good, it addresses all of the feedback on v6.
Unfortunately, I spotted some things _after_ you had already sent out v7
(my bad). That feedback is in [1].

We can ignore my feedback on 'simplifying' the conditional with a
format string. The more I think about it, the more impossible i18n
seems.

This just leaves the feedback on the message when inheriting from
multiple remote-tracking branches, i.e. prepending 'origin/' to the refs
in:

  branch 'main' set up to track from 'origin':
    main
    topic1
    topic2
 
[1] https://lore.kernel.org/git/kl6lzgovyvt7.fsf@chooglen-macbookpro.roam.corp.google.com

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v6 1/3] branch: accept multiple upstream branches for tracking
  2021-12-20 18:29         ` Glen Choo
@ 2021-12-21  3:27           ` Josh Steadmon
  0 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-21  3:27 UTC (permalink / raw)
  To: Glen Choo; +Cc: git, gitster, avarab, Johannes.Schindelin

On 2021.12.20 10:29, Glen Choo wrote:
> Josh Steadmon <steadmon@google.com> writes:
> 
> >> > @@ -87,29 +112,42 @@ int install_branch_config(int flag, const char *local, const char *origin, const
> >> >  	strbuf_release(&key);
> >> >  
> >> >  	if (flag & BRANCH_CONFIG_VERBOSE) {
> >> > -		if (shortname) {
> >> > +		const char *name;
> >> > +		struct strbuf ref_string = STRBUF_INIT;
> >> > +
> >> > +		for_each_string_list_item(item, remotes) {
> >> > +			name = item->string;
> >> > +			skip_prefix(name, "refs/heads/", &name);
> >> > +			strbuf_addf(&ref_string, "  %s\n", name);
> >> > +		}
> >> > +
> >> > +		if (remotes->nr == 1) {
> >> > +			struct strbuf refname = STRBUF_INIT;
> >> > +
> >> >  			if (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);
> >> > -			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);
> >> > +				strbuf_addf(&refname, "%s/", origin);
> >> > +			strbuf_addstr(&refname, remotes->items[0].string);
> >> > +
> >> > +			/*
> >> > +			 * Rebasing is only allowed in the case of a single
> >> > +			 * upstream branch.
> >> > +			 */
> >> > +			printf_ln(rebasing ?
> >> > +				_("branch '%s' set up to track '%s' by rebasing.") :
> >> > +				_("branch '%s' set up to track '%s'."),
> >> > +				local, refname.buf);
> >> > +
> >> > +			strbuf_release(&refname);
> >> > +		} else if (origin) {
> >> > +			printf_ln(_("branch '%s' set up to track from '%s':"),
> >> > +				local, origin);
> >> > +			printf("%s", ref_string.buf);
> >> 
> >> It's not clear to me why the hint contains the word 'from' when it is a
> >> remote ref...
> >
> > Because in the multiple-branch case, we don't prepend the origin to each
> > ref, so we need to let users know which remote the refs are coming from.
> 
> I see. So if I'm reading this correctly, the error message in the remote
> case would read something like:
> 
>   branch 'main' set up to track from 'origin':
>     main
>     topic1
>     topic2
> 
> Is there any reason why we couldn't append the origin to the ref to make
> it consistent? I think this could be as simple as:
> 
> 
> 	for_each_string_list_item(item, remotes) {
> 		name = item->string;
> 		skip_prefix(name, "refs/heads/", &name);
> 			if (origin)
> +         strbuf_addf(&ref_string, "%s/", origin);
> 		strbuf_addf(&ref_string, "  %s\n", name);
> 	}
> 
> and the resulting list could look like:
> 
>   branch 'main' set up to track from 'origin':
>     origin/main
>     origin/topic1
>     origin/topic2
> 
> This looks repetitive, but I suggest this because, as I understand it,
> we are omitting the "{local,remote} ref" phrase based on conventions
> around ref names, like "origin/main" is probably a remote ref and not an
> oddly named local ref. However, when we print the list like so,
> 
>   branch 'main' set up to track from 'origin':
>     main
>     topic1
>     topic2
> 
> we now expect the user to understand that 'main', 'topic1' and 'topic2'
> to implicitly have 'origin/' prepended to them. This behavior seems
> inconsistent to me; I'd anticipate most users responding "Wait, I was
> supposed to be tracking 'origin' branches right? Why am I looking at
> local branches?". Some users would be able to recover because they can
> figure out what we mean, but others might just give up.
> 
> Prepending 'origin/' would get rid of this problem altogether, and it
> would let us drop the 'from'.

Yeah, I think that's better. Fixed in V7, thanks.


> >> >  		} else {
> >> > -			if (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);
> >> > -			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);
> >> > +			printf_ln(_("branch '%s' set up to track:"), local);
> >> > +			printf("%s", ref_string.buf);
> >> 
> >> but does not have the word 'from' when it is a local ref. As far as I
> >> can tell, this is the only difference between remote and local refs, and
> >> adding the word 'from' does not seem like a good enough reason to add an
> >> 'if' condition. Maybe I missed something here?
> >> 
> >> This motivates my answer to the question you asked in [1]:
> >> 
> >>   I removed as many distinctions as possible, as most can still be
> >>   inferred from context. [...] Likewise, we don't need to specify whether
> >>   refs are remote or local: "some-remote/some-branch" vs.
> >>   "a-local-branch" should be understandable without us spelling it out.
> >> 
> >> I agree that there is adequate context, so I would be ok with the
> >> simplification if there was corresponding code simplification e.g.
> >> dropping "if (origin)". But in its current form, I don't think there is
> >> good enough reason to simplify the message.
> >
> > I think the proper point of comparison is not the original code, but the
> > code from V5 where we try to preserve the same level of detail in output
> > as the original code. If we are committed to both having multiple
> > remotes and keeping similar styles of output as the original
> > implementation, then something like the massive conditional in V5 is
> > unavoidable.
> 
> I see. So for instance, post-simplification you have:
> 
>   printf_ln(rebasing ?
>     _("branch '%s' set up to track '%s' by rebasing.") :
>     _("branch '%s' set up to track '%s'."),
>     local, refname.buf);
> 
> if you preserve the same amount of detail as before, you'd have to
> distinguish between local/remote, which doubles the number of cases to
> 4, which is why the conditional v5 is so complicated.
> 
> That said, I think that it's already much simpler than v5 because you've
> split the singular and plural cases. I wonder if you have considered
> building the final string purely from format strings, like:
> 
>   char *message_format = _("branch %s set up to track %s%s%s%s");
>   char *ref_type_clause = origin ? " remote ref " : " local ref ";
>   char *rebasing_clause = rebasing ? " by rebasing." : ".";
>   char *branch_names = "<branch names>";
>   printf_ln(message_format, local, ref_type_clause, branch_names, rebasing_clause);
> 
> This sounds potentially unfriendly to i18n, but it would make the
> conditional simpler. What do you think?

Yeah, the translation-unfriendliness is why I avoided this approach.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v8 0/3] branch: inherit tracking configs
  2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
                   ` (7 preceding siblings ...)
  2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
@ 2021-12-21  3:30 ` Josh Steadmon
  2021-12-21  3:30   ` [PATCH v8 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
                     ` (3 more replies)
  8 siblings, 4 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-21  3:30 UTC (permalink / raw)
  To: git; +Cc: gitster, avarab, chooglen

Changes since V7:
* Further simplify verbose output by adding an "<origin>/" prefix for
  remote-tracking upstream refs.
* Add a comment explaining the self-tracking check & early exit.

Changes since V6:
* Strip the refs/heads/ prefix in the verbose output when we have only a
  single upstream branch.
* Improve the fatal error message to note that rebasing is only
  incompatible with multiple upstream refs.
* Also note that `branch.<name>.remote` should be set in the manual
  recovery advice.
* Simplify the logic in setup_tracking() when no tracking sources match.
* Make the difference in test cases in t2027 more obvious.

Changes since V5:
* Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
  so many conditionals.
* Note that rebasing is not compatible with inheriting multiple upstream
  branches.
* Moved the change to case-sensitivity for branch.autosetupmerge to its
  own commit.
* Improve advice on failed tracking setup when multiple branches are
  involved.
* Make better use of string_list API.
* Make better use of config API.
* More straight-forward use of the `struct tracking` API.
* Numerous style fixes.

Changes since V4:
* Add new patch (1/2) to refactor branch.c:install_branch_config() to
  accept multiple upstream refs
* When multiple upstream branches are set in the parent branch, inherit
  them all, instead of just the first
* Break out error string arguments for easier translation
* Don't ignore case for values of branch.autosetupmerge
* Move reference to git-pull out of usage string for --track into
  git-branch.txt
* Use test_config instead of `git config` in t2027
* Style fixes: add single-quotes around warning string arguments, remove
  unnecessary braces

Changes since V3:
* Use branch_get() instead of git_config_get_string() to look up branch
  configuration.
* Remove unnecessary string formatting in new error message in
  parse-options-cb.c.

Josh Steadmon (3):
  branch: accept multiple upstream branches for tracking
  branch: add flags and config to inherit tracking
  config: require lowercase for branch.*.autosetupmerge

 Documentation/config/branch.txt |   3 +-
 Documentation/git-branch.txt    |  24 ++--
 Documentation/git-checkout.txt  |   2 +-
 Documentation/git-switch.txt    |   2 +-
 branch.c                        | 192 ++++++++++++++++++++++++--------
 branch.h                        |   3 +-
 builtin/branch.c                |   6 +-
 builtin/checkout.c              |   6 +-
 config.c                        |   5 +-
 parse-options-cb.c              |  16 +++
 parse-options.h                 |   2 +
 t/t2017-checkout-orphan.sh      |  11 +-
 t/t2027-checkout-track.sh       |  23 ++++
 t/t2060-switch.sh               |  28 +++++
 t/t3200-branch.sh               |  39 ++++++-
 t/t7201-co.sh                   |  17 +++
 16 files changed, 312 insertions(+), 67 deletions(-)

Range-diff against v7:
1:  9152367ba9 ! 1:  a5265e1c7f branch: accept multiple upstream branches for tracking
    @@ branch.c: static int should_setup_rebase(const char *origin)
     +		die(_("cannot inherit upstream tracking configuration of "
     +		      "multiple refs when rebasing is requested"));
     +
    ++	/*
    ++	 * If the new branch is trying to track itself, something has gone
    ++	 * wrong. Warn the user and don't proceed any further.
    ++	 */
     +	if (!origin)
     +		for_each_string_list_item(item, remotes)
     +			if (skip_prefix(item->string, "refs/heads/", &shortname)
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
      
      	if (flag & BRANCH_CONFIG_VERBOSE) {
     -		if (shortname) {
    -+		const char *name;
    -+		struct strbuf ref_string = STRBUF_INIT;
    -+
    -+		for_each_string_list_item(item, remotes) {
    -+			name = item->string;
    -+			skip_prefix(name, "refs/heads/", &name);
    -+			strbuf_addf(&ref_string, "  %s\n", name);
    -+		}
    -+
    -+		if (remotes->nr == 1) {
    -+			struct strbuf refname = STRBUF_INIT;
    -+
    - 			if (origin)
    +-			if (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'."),
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     -					  _("Branch '%s' set up to track local branch '%s' by rebasing.") :
     -					  _("Branch '%s' set up to track local branch '%s'."),
     -					  local, shortname);
    -+				strbuf_addf(&refname, "%s/", origin);
    -+			skip_prefix(remotes->items[0].string, "refs/heads/", &name);
    -+			strbuf_addstr(&refname, name);
    ++		struct strbuf tmp_ref_name = STRBUF_INIT;
    ++		struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
     +
    ++		for_each_string_list_item(item, remotes) {
    ++			shortname = item->string;
    ++			skip_prefix(shortname, "refs/heads/", &shortname);
    ++			if (origin) {
    ++				strbuf_addf(&tmp_ref_name, "%s/%s",
    ++					    origin, shortname);
    ++				string_list_append_nodup(
    ++					&friendly_ref_names,
    ++					strbuf_detach(&tmp_ref_name, NULL));
    ++			} else {
    ++				string_list_append(
    ++					&friendly_ref_names, shortname);
    ++			}
    ++		}
    ++
    ++		if (remotes->nr == 1) {
     +			/*
     +			 * Rebasing is only allowed in the case of a single
     +			 * upstream branch.
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     +			printf_ln(rebasing ?
     +				_("branch '%s' set up to track '%s' by rebasing.") :
     +				_("branch '%s' set up to track '%s'."),
    -+				local, refname.buf);
    -+
    -+			strbuf_release(&refname);
    -+		} else if (origin) {
    -+			printf_ln(_("branch '%s' set up to track from '%s':"),
    -+				local, origin);
    -+			printf("%s", ref_string.buf);
    ++				local, friendly_ref_names.items[0].string);
      		} else {
     -			if (origin)
     -				printf_ln(rebasing ?
    @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
     -					  _("Branch '%s' set up to track local ref '%s'."),
     -					  local, remote);
     +			printf_ln(_("branch '%s' set up to track:"), local);
    -+			printf("%s", ref_string.buf);
    ++			for_each_string_list_item(item, &friendly_ref_names)
    ++				printf_ln("  %s", item->string);
      		}
     +
    -+		strbuf_release(&ref_string);
    ++		string_list_clear(&friendly_ref_names, 0);
      	}
      
      	return 0;
2:  afeb84539e = 2:  dcba40e2c4 branch: add flags and config to inherit tracking
3:  a818a6561b = 3:  ae7d27b4be config: require lowercase for branch.*.autosetupmerge

base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
-- 
2.34.1.307.g9b7440fafd-goog


^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH v8 1/3] branch: accept multiple upstream branches for tracking
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
@ 2021-12-21  3:30   ` Josh Steadmon
  2021-12-21  6:55     ` Junio C Hamano
  2021-12-21  3:30   ` [PATCH v8 2/3] branch: add flags and config to inherit tracking Josh Steadmon
                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 103+ messages in thread
From: Josh Steadmon @ 2021-12-21  3:30 UTC (permalink / raw)
  To: git; +Cc: gitster, avarab, chooglen

Add a new static variant of install_branch_config() that accepts
multiple remote branch names for tracking. This will be used in an
upcoming commit that enables inheriting the tracking configuration from
a parent branch.

Currently, all callers of install_branch_config() pass only a single
remote. Make install_branch_config() a small wrapper around
install_branch_config_multiple_remotes() so that existing callers do not
need to be changed.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 branch.c          | 143 +++++++++++++++++++++++++++++++++-------------
 t/t3200-branch.sh |   6 +-
 2 files changed, 106 insertions(+), 43 deletions(-)

diff --git a/branch.c b/branch.c
index 7a88a4861e..a41a300f9c 100644
--- a/branch.c
+++ b/branch.c
@@ -49,25 +49,46 @@ static int should_setup_rebase(const char *origin)
 	return 0;
 }
 
-static const char tracking_advice[] =
-N_("\n"
-"After fixing the error cause you may try to fix up\n"
-"the remote tracking information by invoking\n"
-"\"git branch --set-upstream-to=%s%s%s\".");
-
-int install_branch_config(int flag, const char *local, const char *origin, const char *remote)
+/**
+ * Install upstream tracking configuration for a branch; specifically, add
+ * `branch.<name>.remote` and `branch.<name>.merge` entries.
+ *
+ * `flag` contains integer flags for options; currently only
+ * BRANCH_CONFIG_VERBOSE is checked.
+ *
+ * `local` is the name of the branch whose configuration we're installing.
+ *
+ * `origin` is the name of the remote owning the upstream branches. NULL means
+ * the upstream branches are local to this repo.
+ *
+ * `remotes` is a list of refs that are upstream of local
+ */
+static int install_branch_config_multiple_remotes(int flag, const char *local,
+		const char *origin, struct string_list *remotes)
 {
 	const char *shortname = NULL;
 	struct strbuf key = STRBUF_INIT;
+	struct string_list_item *item;
 	int rebasing = should_setup_rebase(origin);
 
-	if (skip_prefix(remote, "refs/heads/", &shortname)
-	    && !strcmp(local, shortname)
-	    && !origin) {
-		warning(_("Not setting branch %s as its own upstream."),
-			local);
-		return 0;
-	}
+	if (!remotes->nr)
+		BUG("must provide at least one remote for branch config");
+	if (rebasing && remotes->nr > 1)
+		die(_("cannot inherit upstream tracking configuration of "
+		      "multiple refs when rebasing is requested"));
+
+	/*
+	 * If the new branch is trying to track itself, something has gone
+	 * wrong. Warn the user and don't proceed any further.
+	 */
+	if (!origin)
+		for_each_string_list_item(item, remotes)
+			if (skip_prefix(item->string, "refs/heads/", &shortname)
+			    && !strcmp(local, shortname)) {
+				warning(_("not setting branch '%s' as its own upstream."),
+					local);
+				return 0;
+			}
 
 	strbuf_addf(&key, "branch.%s.remote", local);
 	if (git_config_set_gently(key.buf, origin ? origin : ".") < 0)
@@ -75,8 +96,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 
 	strbuf_reset(&key);
 	strbuf_addf(&key, "branch.%s.merge", local);
-	if (git_config_set_gently(key.buf, remote) < 0)
+	/*
+	 * We want to overwrite any existing config with all the branches in
+	 * "remotes". Override any existing config, then write our branches. If
+	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
+	 * we've written so far.
+	 */
+	if (git_config_set_gently(key.buf, NULL) < 0)
 		goto out_err;
+	for_each_string_list_item(item, remotes)
+		if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
+			goto out_err;
 
 	if (rebasing) {
 		strbuf_reset(&key);
@@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 
 	if (flag & BRANCH_CONFIG_VERBOSE) {
-		if (shortname) {
-			if (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);
-			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);
+		struct strbuf tmp_ref_name = STRBUF_INIT;
+		struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
+
+		for_each_string_list_item(item, remotes) {
+			shortname = item->string;
+			skip_prefix(shortname, "refs/heads/", &shortname);
+			if (origin) {
+				strbuf_addf(&tmp_ref_name, "%s/%s",
+					    origin, shortname);
+				string_list_append_nodup(
+					&friendly_ref_names,
+					strbuf_detach(&tmp_ref_name, NULL));
+			} else {
+				string_list_append(
+					&friendly_ref_names, shortname);
+			}
+		}
+
+		if (remotes->nr == 1) {
+			/*
+			 * Rebasing is only allowed in the case of a single
+			 * upstream branch.
+			 */
+			printf_ln(rebasing ?
+				_("branch '%s' set up to track '%s' by rebasing.") :
+				_("branch '%s' set up to track '%s'."),
+				local, friendly_ref_names.items[0].string);
 		} else {
-			if (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);
-			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);
+			printf_ln(_("branch '%s' set up to track:"), local);
+			for_each_string_list_item(item, &friendly_ref_names)
+				printf_ln("  %s", item->string);
 		}
+
+		string_list_clear(&friendly_ref_names, 0);
 	}
 
 	return 0;
@@ -118,14 +159,36 @@ int install_branch_config(int flag, const char *local, const char *origin, const
 	strbuf_release(&key);
 	error(_("Unable to write upstream branch configuration"));
 
-	advise(_(tracking_advice),
-	       origin ? origin : "",
-	       origin ? "/" : "",
-	       shortname ? shortname : remote);
+	advise(_("\nAfter fixing the error cause you may try to fix up\n"
+		"the remote tracking information by invoking:"));
+	if (remotes->nr == 1)
+		advise("  git branch --set-upstream-to=%s%s%s",
+			origin ? origin : "",
+			origin ? "/" : "",
+			remotes->items[0].string);
+	else {
+		advise("  git config --add branch.\"%s\".remote %s",
+			local, origin ? origin : ".");
+		for_each_string_list_item(item, remotes)
+			advise("  git config --add branch.\"%s\".merge %s",
+				local, item->string);
+	}
 
 	return -1;
 }
 
+int install_branch_config(int flag, const char *local, const char *origin,
+		const char *remote)
+{
+	int ret;
+	struct string_list remotes = STRING_LIST_INIT_DUP;
+
+	string_list_append(&remotes, remote);
+	ret = install_branch_config_multiple_remotes(flag, local, origin, &remotes);
+	string_list_clear(&remotes, 0);
+	return ret;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index cc4b10236e..4b0ef35913 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -950,15 +950,15 @@ test_expect_success 'disabled option --set-upstream fails' '
 	test_must_fail git branch --set-upstream origin/main
 '
 
-test_expect_success '--set-upstream-to notices an error to set branch as own upstream' '
+test_expect_success '--set-upstream-to notices an error to set branch as own upstream' "
 	git branch --set-upstream-to refs/heads/my13 my13 2>actual &&
 	cat >expect <<-\EOF &&
-	warning: Not setting branch my13 as its own upstream.
+	warning: not setting branch 'my13' as its own upstream.
 	EOF
 	test_expect_code 1 git config branch.my13.remote &&
 	test_expect_code 1 git config branch.my13.merge &&
 	test_cmp expect actual
-'
+"
 
 # Keep this test last, as it changes the current branch
 cat >expect <<EOF
-- 
2.34.1.307.g9b7440fafd-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v8 2/3] branch: add flags and config to inherit tracking
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
  2021-12-21  3:30   ` [PATCH v8 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-21  3:30   ` Josh Steadmon
  2021-12-21 18:17     ` Glen Choo
  2022-01-11  1:57     ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) Ævar Arnfjörð Bjarmason
  2021-12-21  3:30   ` [PATCH v8 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
  2021-12-21 18:13   ` [PATCH v8 0/3] branch: inherit tracking configs Glen Choo
  3 siblings, 2 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-21  3:30 UTC (permalink / raw)
  To: git; +Cc: gitster, avarab, chooglen

It can be helpful when creating a new branch to use the existing
tracking configuration from the branch point. However, there is
currently not a method to automatically do so.

Teach git-{branch,checkout,switch} an "inherit" argument to the
"--track" option. When this is set, creating a new branch will cause the
tracking configuration to default to the configuration of the branch
point, if set.

For example, if branch "main" tracks "origin/main", and we run
`git checkout --track=inherit -b feature main`, then branch "feature"
will track "origin/main". Thus, `git status` will show us how far
ahead/behind we are from origin, and `git pull` will pull from origin.

This is particularly useful when creating branches across many
submodules, such as with `git submodule foreach ...` (or if running with
a patch such as [1], which we use at $job), as it avoids having to
manually set tracking info for each submodule.

Since we've added an argument to "--track", also add "--track=direct" as
another way to explicitly get the original "--track" behavior ("--track"
without an argument still works as well).

Finally, teach branch.autoSetupMerge a new "inherit" option. When this
is set, "--track=inherit" becomes the default behavior.

[1]: https://lore.kernel.org/git/20180927221603.148025-1-sbeller@google.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 Documentation/config/branch.txt |  3 +-
 Documentation/git-branch.txt    | 24 +++++++++++-----
 Documentation/git-checkout.txt  |  2 +-
 Documentation/git-switch.txt    |  2 +-
 branch.c                        | 49 ++++++++++++++++++++++++++++-----
 branch.h                        |  3 +-
 builtin/branch.c                |  6 ++--
 builtin/checkout.c              |  6 ++--
 config.c                        |  3 ++
 parse-options-cb.c              | 16 +++++++++++
 parse-options.h                 |  2 ++
 t/t2017-checkout-orphan.sh      | 11 +++++++-
 t/t2027-checkout-track.sh       | 23 ++++++++++++++++
 t/t2060-switch.sh               | 28 +++++++++++++++++++
 t/t3200-branch.sh               | 33 ++++++++++++++++++++++
 t/t7201-co.sh                   | 17 ++++++++++++
 16 files changed, 205 insertions(+), 23 deletions(-)

diff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt
index cc5f3249fc..55f7522e12 100644
--- a/Documentation/config/branch.txt
+++ b/Documentation/config/branch.txt
@@ -7,7 +7,8 @@ branch.autoSetupMerge::
 	automatic setup is done; `true` -- automatic setup is done when the
 	starting point is a remote-tracking branch; `always` --
 	automatic setup is done when the starting point is either a
-	local branch or remote-tracking
+	local branch or remote-tracking branch; `inherit` -- if the starting point
+	has a tracking configuration, it is copied to the new
 	branch. This option defaults to true.
 
 branch.autoSetupRebase::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 94dc9a54f2..c8b393e51c 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -205,24 +205,34 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.
 
 -t::
---track::
+--track [inherit|direct]::
 	When creating a new branch, set up `branch.<name>.remote` and
-	`branch.<name>.merge` configuration entries to mark the
-	start-point branch as "upstream" from the new branch. This
+	`branch.<name>.merge` configuration entries to set "upstream" tracking
+	configuration for the new branch. This
 	configuration will tell git to show the relationship between the
 	two branches in `git status` and `git branch -v`. Furthermore,
 	it directs `git pull` without arguments to pull from the
 	upstream when the new branch is checked out.
 +
-This behavior is the default when the start point is a remote-tracking branch.
+The exact upstream branch is chosen depending on the optional argument:
+`--track` or `--track direct` means to use the start-point branch itself as the
+upstream; `--track inherit` means to copy the upstream configuration of the
+start-point branch.
++
+`--track direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
-start-point is either a local or remote-tracking branch.
+start-point is either a local or remote-tracking branch. Set it to
+`inherit` if you want to copy the tracking configuration from the
+branch point.
++
+See linkgit:git-pull[1] and linkgit:git-config[1] for additional discussion on
+how the `branch.<name>.remote` and `branch.<name>.merge` options are used.
 
 --no-track::
 	Do not set up "upstream" configuration, even if the
-	branch.autoSetupMerge configuration variable is true.
+	branch.autoSetupMerge configuration variable is set.
 
 --set-upstream::
 	As this option had confusing syntax, it is no longer supported.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b1a6fe4499..a48e1ab62f 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -155,7 +155,7 @@ of it").
 	linkgit:git-branch[1] for details.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/Documentation/git-switch.txt b/Documentation/git-switch.txt
index 5c438cd505..96dc036ea5 100644
--- a/Documentation/git-switch.txt
+++ b/Documentation/git-switch.txt
@@ -152,7 +152,7 @@ should result in deletion of the path).
 	attached to a terminal, regardless of `--quiet`.
 
 -t::
---track::
+--track [direct|inherit]::
 	When creating a new branch, set up "upstream" configuration.
 	`-c` is implied. See `--track` in linkgit:git-branch[1] for
 	details.
diff --git a/branch.c b/branch.c
index a41a300f9c..e28c0c86e5 100644
--- a/branch.c
+++ b/branch.c
@@ -11,7 +11,7 @@
 
 struct tracking {
 	struct refspec_item spec;
-	char *src;
+	struct string_list *srcs;
 	const char *remote;
 	int matches;
 };
@@ -22,11 +22,11 @@ static int find_tracked_branch(struct remote *remote, void *priv)
 
 	if (!remote_find_tracking(remote, &tracking->spec)) {
 		if (++tracking->matches == 1) {
-			tracking->src = tracking->spec.src;
+			string_list_append(tracking->srcs, tracking->spec.src);
 			tracking->remote = remote->name;
 		} else {
 			free(tracking->spec.src);
-			FREE_AND_NULL(tracking->src);
+			string_list_clear(tracking->srcs, 0);
 		}
 		tracking->spec.src = NULL;
 	}
@@ -189,6 +189,34 @@ int install_branch_config(int flag, const char *local, const char *origin,
 	return ret;
 }
 
+static int inherit_tracking(struct tracking *tracking, const char *orig_ref)
+{
+	const char *bare_ref;
+	struct branch *branch;
+	int i;
+
+	bare_ref = orig_ref;
+	skip_prefix(orig_ref, "refs/heads/", &bare_ref);
+
+	branch = branch_get(bare_ref);
+	if (!branch->remote_name) {
+		warning(_("asked to inherit tracking from '%s', but no remote is set"),
+			bare_ref);
+		return -1;
+	}
+
+	if (branch->merge_nr < 1 || !branch->merge_name || !branch->merge_name[0]) {
+		warning(_("asked to inherit tracking from '%s', but no merge configuration is set"),
+			bare_ref);
+		return -1;
+	}
+
+	tracking->remote = xstrdup(branch->remote_name);
+	for (i = 0; i < branch->merge_nr; i++)
+		string_list_append(tracking->srcs, branch->merge_name[i]);
+	return 0;
+}
+
 /*
  * This is called when new_ref is branched off of orig_ref, and tries
  * to infer the settings for branch.<new_ref>.{remote,merge} from the
@@ -198,11 +226,15 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 			   enum branch_track track, int quiet)
 {
 	struct tracking tracking;
+	struct string_list tracking_srcs = STRING_LIST_INIT_DUP;
 	int config_flags = quiet ? 0 : BRANCH_CONFIG_VERBOSE;
 
 	memset(&tracking, 0, sizeof(tracking));
 	tracking.spec.dst = (char *)orig_ref;
-	if (for_each_remote(find_tracked_branch, &tracking))
+	tracking.srcs = &tracking_srcs;
+	if (track != BRANCH_TRACK_INHERIT)
+		for_each_remote(find_tracked_branch, &tracking);
+	else if (inherit_tracking(&tracking, orig_ref))
 		return;
 
 	if (!tracking.matches)
@@ -210,6 +242,7 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		case BRANCH_TRACK_ALWAYS:
 		case BRANCH_TRACK_EXPLICIT:
 		case BRANCH_TRACK_OVERRIDE:
+		case BRANCH_TRACK_INHERIT:
 			break;
 		default:
 			return;
@@ -219,11 +252,13 @@ static void setup_tracking(const char *new_ref, const char *orig_ref,
 		die(_("Not tracking: ambiguous information for ref %s"),
 		    orig_ref);
 
-	if (install_branch_config(config_flags, new_ref, tracking.remote,
-			      tracking.src ? tracking.src : orig_ref) < 0)
+	if (tracking.srcs->nr < 1)
+		string_list_append(tracking.srcs, orig_ref);
+	if (install_branch_config_multiple_remotes(config_flags, new_ref,
+				tracking.remote, tracking.srcs) < 0)
 		exit(-1);
 
-	free(tracking.src);
+	string_list_clear(tracking.srcs, 0);
 }
 
 int read_branch_desc(struct strbuf *buf, const char *branch_name)
diff --git a/branch.h b/branch.h
index df0be61506..815dcd40c0 100644
--- a/branch.h
+++ b/branch.h
@@ -10,7 +10,8 @@ enum branch_track {
 	BRANCH_TRACK_REMOTE,
 	BRANCH_TRACK_ALWAYS,
 	BRANCH_TRACK_EXPLICIT,
-	BRANCH_TRACK_OVERRIDE
+	BRANCH_TRACK_OVERRIDE,
+	BRANCH_TRACK_INHERIT,
 };
 
 extern enum branch_track git_branch_track;
diff --git a/builtin/branch.c b/builtin/branch.c
index b23b1d1752..ebde5023c3 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -632,8 +632,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+			N_("set branch tracking configuration"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
 		OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index b5d477919a..45dab414ea 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1532,8 +1532,10 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
-			BRANCH_TRACK_EXPLICIT),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
+			N_("set up tracking mode (see git-pull(1))"),
+			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),
 		OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")),
diff --git a/config.c b/config.c
index cb4a8058bf..152c94f29d 100644
--- a/config.c
+++ b/config.c
@@ -1580,6 +1580,9 @@ static int git_default_branch_config(const char *var, const char *value)
 		if (value && !strcasecmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
+		} else if (value && !strcmp(value, "inherit")) {
+			git_branch_track = BRANCH_TRACK_INHERIT;
+			return 0;
 		}
 		git_branch_track = git_config_bool(var, value);
 		return 0;
diff --git a/parse-options-cb.c b/parse-options-cb.c
index 3c811e1e4a..d346dbe210 100644
--- a/parse-options-cb.c
+++ b/parse-options-cb.c
@@ -1,5 +1,6 @@
 #include "git-compat-util.h"
 #include "parse-options.h"
+#include "branch.h"
 #include "cache.h"
 #include "commit.h"
 #include "color.h"
@@ -293,3 +294,18 @@ int parse_opt_passthru_argv(const struct option *opt, const char *arg, int unset
 
 	return 0;
 }
+
+int parse_opt_tracking_mode(const struct option *opt, const char *arg, int unset)
+{
+	if (unset)
+		*(enum branch_track *)opt->value = BRANCH_TRACK_NEVER;
+	else if (!arg || !strcmp(arg, "direct"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_EXPLICIT;
+	else if (!strcmp(arg, "inherit"))
+		*(enum branch_track *)opt->value = BRANCH_TRACK_INHERIT;
+	else
+		return error(_("option `%s' expects \"%s\" or \"%s\""),
+			     "--track", "direct", "inherit");
+
+	return 0;
+}
diff --git a/parse-options.h b/parse-options.h
index a845a9d952..f35dbfdd5a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -303,6 +303,8 @@ enum parse_opt_result parse_opt_unknown_cb(struct parse_opt_ctx_t *ctx,
 					   const char *, int);
 int parse_opt_passthru(const struct option *, const char *, int);
 int parse_opt_passthru_argv(const struct option *, const char *, int);
+/* value is enum branch_track* */
+int parse_opt_tracking_mode(const struct option *, const char *, int);
 
 #define OPT__VERBOSE(var, h)  OPT_COUNTUP('v', "verbose", (var), (h))
 #define OPT__QUIET(var, h)    OPT_COUNTUP('q', "quiet",   (var), (h))
diff --git a/t/t2017-checkout-orphan.sh b/t/t2017-checkout-orphan.sh
index 88d6992a5e..4d689bd377 100755
--- a/t/t2017-checkout-orphan.sh
+++ b/t/t2017-checkout-orphan.sh
@@ -62,8 +62,17 @@ test_expect_success '--orphan ignores branch.autosetupmerge' '
 	git checkout main &&
 	git config branch.autosetupmerge always &&
 	git checkout --orphan gamma &&
-	test -z "$(git config branch.gamma.merge)" &&
+	test_cmp_config "" --default "" branch.gamma.merge &&
 	test refs/heads/gamma = "$(git symbolic-ref HEAD)" &&
+	test_must_fail git rev-parse --verify HEAD^ &&
+	git checkout main &&
+	git config branch.autosetupmerge inherit &&
+	git checkout --orphan eta &&
+	test_cmp_config "" --default "" branch.eta.merge &&
+	test_cmp_config "" --default "" branch.eta.remote &&
+	echo refs/heads/eta >expected &&
+	git symbolic-ref HEAD >actual &&
+	test_cmp expected actual &&
 	test_must_fail git rev-parse --verify HEAD^
 '
 
diff --git a/t/t2027-checkout-track.sh b/t/t2027-checkout-track.sh
index 4453741b96..dca35aa3e3 100755
--- a/t/t2027-checkout-track.sh
+++ b/t/t2027-checkout-track.sh
@@ -24,4 +24,27 @@ test_expect_success 'checkout --track -b rejects an extra path argument' '
 	test_i18ngrep "cannot be used with updating paths" err
 '
 
+test_expect_success 'checkout --track -b overrides autoSetupMerge=inherit' '
+	# Set up tracking config on main
+	test_config branch.main.remote origin &&
+	test_config branch.main.merge refs/heads/some-branch &&
+	test_config branch.autoSetupMerge inherit &&
+	# With --track=inherit, we copy the tracking config from main
+	git checkout --track=inherit -b b1 main &&
+	test_cmp_config origin branch.b1.remote &&
+	test_cmp_config refs/heads/some-branch branch.b1.merge &&
+	# With branch.autoSetupMerge=inherit, we do the same
+	git checkout -b b2 main &&
+	test_cmp_config origin branch.b2.remote &&
+	test_cmp_config refs/heads/some-branch branch.b2.merge &&
+	# But --track overrides this
+	git checkout --track -b b3 main &&
+	test_cmp_config . branch.b3.remote &&
+	test_cmp_config refs/heads/main branch.b3.merge &&
+	# And --track=direct does as well
+	git checkout --track=direct -b b4 main &&
+	test_cmp_config . branch.b4.remote &&
+	test_cmp_config refs/heads/main branch.b4.merge
+'
+
 test_done
diff --git a/t/t2060-switch.sh b/t/t2060-switch.sh
index 9bc6a3aa5c..ebb961be29 100755
--- a/t/t2060-switch.sh
+++ b/t/t2060-switch.sh
@@ -107,4 +107,32 @@ test_expect_success 'not switching when something is in progress' '
 	test_must_fail git switch -d @^
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	# default config does not copy tracking info
+	git switch -c foo-no-inherit foo &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with --track=inherit, we copy tracking info from foo
+	git switch --track=inherit -c foo2 foo &&
+	test_cmp_config origin branch.foo2.remote &&
+	test_cmp_config refs/heads/foo branch.foo2.merge &&
+	# with autoSetupMerge=inherit, we do the same
+	test_config branch.autoSetupMerge inherit &&
+	git switch -c foo3 foo &&
+	test_cmp_config origin branch.foo3.remote &&
+	test_cmp_config refs/heads/foo branch.foo3.merge &&
+	# with --track, we override autoSetupMerge
+	git switch --track -c foo4 foo &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/foo branch.foo4.merge &&
+	# and --track=direct does as well
+	git switch --track=direct -c foo5 foo &&
+	test_cmp_config . branch.foo5.remote &&
+	test_cmp_config refs/heads/foo branch.foo5.merge &&
+	# no tracking info to inherit from main
+	git switch -c main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index 4b0ef35913..a049276439 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -1409,4 +1409,37 @@ test_expect_success 'invalid sort parameter in configuration' '
 	)
 '
 
+test_expect_success 'tracking info copied with --track=inherit' '
+	git branch --track=inherit foo2 my1 &&
+	test_cmp_config local branch.foo2.remote &&
+	test_cmp_config refs/heads/main branch.foo2.merge
+'
+
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	test_unconfig branch.autoSetupMerge &&
+	# default config does not copy tracking info
+	git branch foo-no-inherit my1 &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from my1
+	test_config branch.autoSetupMerge inherit &&
+	git branch foo3 my1 &&
+	test_cmp_config local branch.foo3.remote &&
+	test_cmp_config refs/heads/main branch.foo3.merge &&
+	# no tracking info to inherit from main
+	git branch main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
+test_expect_success '--track overrides branch.autoSetupMerge' '
+	test_config branch.autoSetupMerge inherit &&
+	git branch --track=direct foo4 my1 &&
+	test_cmp_config . branch.foo4.remote &&
+	test_cmp_config refs/heads/my1 branch.foo4.merge &&
+	git branch --no-track foo5 my1 &&
+	test_cmp_config "" --default "" branch.foo5.remote &&
+	test_cmp_config "" --default "" branch.foo5.merge
+'
+
 test_done
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 7f6e23a4bb..4fdf88ba46 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
 	test_cmp expect arm
 '
 
+test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
+	git reset --hard main &&
+	# default config does not copy tracking info
+	git checkout -b foo-no-inherit koala/bear &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
+	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
+	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
+	test_config branch.autoSetupMerge inherit &&
+	git checkout -b foo koala/bear &&
+	test_cmp_config origin branch.foo.remote &&
+	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
+	# no tracking info to inherit from main
+	git checkout -b main2 main &&
+	test_cmp_config "" --default "" branch.main2.remote &&
+	test_cmp_config "" --default "" branch.main2.merge
+'
+
 test_done
-- 
2.34.1.307.g9b7440fafd-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* [PATCH v8 3/3] config: require lowercase for branch.*.autosetupmerge
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
  2021-12-21  3:30   ` [PATCH v8 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
  2021-12-21  3:30   ` [PATCH v8 2/3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-21  3:30   ` Josh Steadmon
  2021-12-21 18:13   ` [PATCH v8 0/3] branch: inherit tracking configs Glen Choo
  3 siblings, 0 replies; 103+ messages in thread
From: Josh Steadmon @ 2021-12-21  3:30 UTC (permalink / raw)
  To: git; +Cc: gitster, avarab, chooglen

Although we only documented that branch.*.autosetupmerge would accept
"always" as a value, the actual implementation would accept any
combination of upper- or lower-case. Fix this to be consistent with
documentation and with other values of this config variable.

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 config.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config.c b/config.c
index 152c94f29d..54c0e7d98e 100644
--- a/config.c
+++ b/config.c
@@ -1577,7 +1577,7 @@ static int git_default_i18n_config(const char *var, const char *value)
 static int git_default_branch_config(const char *var, const char *value)
 {
 	if (!strcmp(var, "branch.autosetupmerge")) {
-		if (value && !strcasecmp(value, "always")) {
+		if (value && !strcmp(value, "always")) {
 			git_branch_track = BRANCH_TRACK_ALWAYS;
 			return 0;
 		} else if (value && !strcmp(value, "inherit")) {
-- 
2.34.1.307.g9b7440fafd-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH v8 1/3] branch: accept multiple upstream branches for tracking
  2021-12-21  3:30   ` [PATCH v8 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
@ 2021-12-21  6:55     ` Junio C Hamano
  2021-12-21 18:25       ` Glen Choo
  0 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2021-12-21  6:55 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, avarab, chooglen

Josh Steadmon <steadmon@google.com> writes:

> +/**
> + * Install upstream tracking configuration for a branch; specifically, add
> + * `branch.<name>.remote` and `branch.<name>.merge` entries.
> + *
> + * `flag` contains integer flags for options; currently only
> + * BRANCH_CONFIG_VERBOSE is checked.
> + *
> + * `local` is the name of the branch whose configuration we're installing.
> + *
> + * `origin` is the name of the remote owning the upstream branches. NULL means
> + * the upstream branches are local to this repo.
> + *
> + * `remotes` is a list of refs that are upstream of local
> + */
> +static int install_branch_config_multiple_remotes(int flag, const char *local,
> +		const char *origin, struct string_list *remotes)
>  {
>  	const char *shortname = NULL;
>  	struct strbuf key = STRBUF_INIT;
> +	struct string_list_item *item;
>  	int rebasing = should_setup_rebase(origin);
>  
> +	if (!remotes->nr)
> +		BUG("must provide at least one remote for branch config");
> +	if (rebasing && remotes->nr > 1)
> +		die(_("cannot inherit upstream tracking configuration of "
> +		      "multiple refs when rebasing is requested"));
> +
> +	/*
> +	 * If the new branch is trying to track itself, something has gone
> +	 * wrong. Warn the user and don't proceed any further.
> +	 */
> +	if (!origin)
> +		for_each_string_list_item(item, remotes)
> +			if (skip_prefix(item->string, "refs/heads/", &shortname)
> +			    && !strcmp(local, shortname)) {
> +				warning(_("not setting branch '%s' as its own upstream."),
> +					local);
> +				return 0;
> +			}

Added comments make it easier to follow what is going on and more
importantly why.  I very much like it ;-)

> @@ -75,8 +96,17 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  
>  	strbuf_reset(&key);
>  	strbuf_addf(&key, "branch.%s.merge", local);
> -	if (git_config_set_gently(key.buf, remote) < 0)
> +	/*
> +	 * We want to overwrite any existing config with all the branches in
> +	 * "remotes". Override any existing config, then write our branches. If
> +	 * more than one is provided, use CONFIG_REGEX_NONE to preserve what
> +	 * we've written so far.
> +	 */

Sorry, but I do not quite get this comment in two ways.

 - It talks about "if more than one" but the code seems to use
   regex-none even when there is only one.

 - I agree that use of set-multivar with regex-none is the right way
   to append new values to multi valued configuration, but wouldn't
   the sequence be (1) first clear everything and then (2) set new
   ones one by one?

Ahh, OK, the "config-set" we do first is the "clear everything"
step.

	We want to overwrite any existing config.  First clear any
	existing config, and then write our branches one-by-one.
	Use of CONFIG_REGEX_NONE ensures that the multiple values
	are appended to the same variable.

perhaps?

> +	if (git_config_set_gently(key.buf, NULL) < 0)
>  		goto out_err;
> +	for_each_string_list_item(item, remotes)
> +		if (git_config_set_multivar_gently(key.buf, item->string, CONFIG_REGEX_NONE, 0) < 0)
> +			goto out_err;

> @@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>  	strbuf_release(&key);
>  
>  	if (flag & BRANCH_CONFIG_VERBOSE) {
> +		struct strbuf tmp_ref_name = STRBUF_INIT;
> +		struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
> +
> +		for_each_string_list_item(item, remotes) {
> +			shortname = item->string;
> +			skip_prefix(shortname, "refs/heads/", &shortname);
> +			if (origin) {
> +				strbuf_addf(&tmp_ref_name, "%s/%s",
> +					    origin, shortname);
> +				string_list_append_nodup(
> +					&friendly_ref_names,
> +					strbuf_detach(&tmp_ref_name, NULL));
> +			} else {
> +				string_list_append(
> +					&friendly_ref_names, shortname);
> +			}
> +		}
> +
> +		if (remotes->nr == 1) {
> +			/*
> +			 * Rebasing is only allowed in the case of a single
> +			 * upstream branch.
> +			 */

There does not seem to be any code to forbid "rebasing" when
remotes->nr != 1, though.  Did I miss a call to die() earlier?

> +			printf_ln(rebasing ?
> +				_("branch '%s' set up to track '%s' by rebasing.") :
> +				_("branch '%s' set up to track '%s'."),
> +				local, friendly_ref_names.items[0].string);
>  		} else {
> +			printf_ln(_("branch '%s' set up to track:"), local);
> +			for_each_string_list_item(item, &friendly_ref_names)
> +				printf_ln("  %s", item->string);

In other words, I would have expected something in this else clause.

>  		}

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v8 0/3] branch: inherit tracking configs
  2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
                     ` (2 preceding siblings ...)
  2021-12-21  3:30   ` [PATCH v8 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
@ 2021-12-21 18:13   ` Glen Choo
  3 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-21 18:13 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab

Josh Steadmon <steadmon@google.com> writes:

> Changes since V7:
> * Further simplify verbose output by adding an "<origin>/" prefix for
>   remote-tracking upstream refs.
> * Add a comment explaining the self-tracking check & early exit.
>
> Changes since V6:
> * Strip the refs/heads/ prefix in the verbose output when we have only a
>   single upstream branch.
> * Improve the fatal error message to note that rebasing is only
>   incompatible with multiple upstream refs.
> * Also note that `branch.<name>.remote` should be set in the manual
>   recovery advice.
> * Simplify the logic in setup_tracking() when no tracking sources match.
> * Make the difference in test cases in t2027 more obvious.
>
> Changes since V5:
> * Greatly simplified BRANCH_CONFIG_VERBOSE output to not require nearly
>   so many conditionals.
> * Note that rebasing is not compatible with inheriting multiple upstream
>   branches.
> * Moved the change to case-sensitivity for branch.autosetupmerge to its
>   own commit.
> * Improve advice on failed tracking setup when multiple branches are
>   involved.
> * Make better use of string_list API.
> * Make better use of config API.
> * More straight-forward use of the `struct tracking` API.
> * Numerous style fixes.
>
> Changes since V4:
> * Add new patch (1/2) to refactor branch.c:install_branch_config() to
>   accept multiple upstream refs
> * When multiple upstream branches are set in the parent branch, inherit
>   them all, instead of just the first
> * Break out error string arguments for easier translation
> * Don't ignore case for values of branch.autosetupmerge
> * Move reference to git-pull out of usage string for --track into
>   git-branch.txt
> * Use test_config instead of `git config` in t2027
> * Style fixes: add single-quotes around warning string arguments, remove
>   unnecessary braces
>
> Changes since V3:
> * Use branch_get() instead of git_config_get_string() to look up branch
>   configuration.
> * Remove unnecessary string formatting in new error message in
>   parse-options-cb.c.
>
> Josh Steadmon (3):
>   branch: accept multiple upstream branches for tracking
>   branch: add flags and config to inherit tracking
>   config: require lowercase for branch.*.autosetupmerge
>
>  Documentation/config/branch.txt |   3 +-
>  Documentation/git-branch.txt    |  24 ++--
>  Documentation/git-checkout.txt  |   2 +-
>  Documentation/git-switch.txt    |   2 +-
>  branch.c                        | 192 ++++++++++++++++++++++++--------
>  branch.h                        |   3 +-
>  builtin/branch.c                |   6 +-
>  builtin/checkout.c              |   6 +-
>  config.c                        |   5 +-
>  parse-options-cb.c              |  16 +++
>  parse-options.h                 |   2 +
>  t/t2017-checkout-orphan.sh      |  11 +-
>  t/t2027-checkout-track.sh       |  23 ++++
>  t/t2060-switch.sh               |  28 +++++
>  t/t3200-branch.sh               |  39 ++++++-
>  t/t7201-co.sh                   |  17 +++
>  16 files changed, 312 insertions(+), 67 deletions(-)
>
> Range-diff against v7:
> 1:  9152367ba9 ! 1:  a5265e1c7f branch: accept multiple upstream branches for tracking
>     @@ branch.c: static int should_setup_rebase(const char *origin)
>      +		die(_("cannot inherit upstream tracking configuration of "
>      +		      "multiple refs when rebasing is requested"));
>      +
>     ++	/*
>     ++	 * If the new branch is trying to track itself, something has gone
>     ++	 * wrong. Warn the user and don't proceed any further.
>     ++	 */
>      +	if (!origin)
>      +		for_each_string_list_item(item, remotes)
>      +			if (skip_prefix(item->string, "refs/heads/", &shortname)
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>       
>       	if (flag & BRANCH_CONFIG_VERBOSE) {
>      -		if (shortname) {
>     -+		const char *name;
>     -+		struct strbuf ref_string = STRBUF_INIT;
>     -+
>     -+		for_each_string_list_item(item, remotes) {
>     -+			name = item->string;
>     -+			skip_prefix(name, "refs/heads/", &name);
>     -+			strbuf_addf(&ref_string, "  %s\n", name);
>     -+		}
>     -+
>     -+		if (remotes->nr == 1) {
>     -+			struct strbuf refname = STRBUF_INIT;
>     -+
>     - 			if (origin)
>     +-			if (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'."),
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>      -					  _("Branch '%s' set up to track local branch '%s' by rebasing.") :
>      -					  _("Branch '%s' set up to track local branch '%s'."),
>      -					  local, shortname);
>     -+				strbuf_addf(&refname, "%s/", origin);
>     -+			skip_prefix(remotes->items[0].string, "refs/heads/", &name);
>     -+			strbuf_addstr(&refname, name);
>     ++		struct strbuf tmp_ref_name = STRBUF_INIT;
>     ++		struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
>      +
>     ++		for_each_string_list_item(item, remotes) {
>     ++			shortname = item->string;
>     ++			skip_prefix(shortname, "refs/heads/", &shortname);
>     ++			if (origin) {
>     ++				strbuf_addf(&tmp_ref_name, "%s/%s",
>     ++					    origin, shortname);
>     ++				string_list_append_nodup(
>     ++					&friendly_ref_names,
>     ++					strbuf_detach(&tmp_ref_name, NULL));
>     ++			} else {
>     ++				string_list_append(
>     ++					&friendly_ref_names, shortname);
>     ++			}
>     ++		}
>     ++
>     ++		if (remotes->nr == 1) {
>      +			/*
>      +			 * Rebasing is only allowed in the case of a single
>      +			 * upstream branch.
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>      +			printf_ln(rebasing ?
>      +				_("branch '%s' set up to track '%s' by rebasing.") :
>      +				_("branch '%s' set up to track '%s'."),
>     -+				local, refname.buf);
>     -+
>     -+			strbuf_release(&refname);
>     -+		} else if (origin) {
>     -+			printf_ln(_("branch '%s' set up to track from '%s':"),
>     -+				local, origin);
>     -+			printf("%s", ref_string.buf);
>     ++				local, friendly_ref_names.items[0].string);
>       		} else {
>      -			if (origin)
>      -				printf_ln(rebasing ?
>     @@ branch.c: int install_branch_config(int flag, const char *local, const char *ori
>      -					  _("Branch '%s' set up to track local ref '%s'."),
>      -					  local, remote);
>      +			printf_ln(_("branch '%s' set up to track:"), local);
>     -+			printf("%s", ref_string.buf);
>     ++			for_each_string_list_item(item, &friendly_ref_names)
>     ++				printf_ln("  %s", item->string);
>       		}
>      +
>     -+		strbuf_release(&ref_string);
>     ++		string_list_clear(&friendly_ref_names, 0);
>       	}
>       
>       	return 0;
> 2:  afeb84539e = 2:  dcba40e2c4 branch: add flags and config to inherit tracking
> 3:  a818a6561b = 3:  ae7d27b4be config: require lowercase for branch.*.autosetupmerge
>
> base-commit: 6c40894d2466d4e7fddc047a05116aa9d14712ee
> -- 
> 2.34.1.307.g9b7440fafd-goog

These changes look good to me. I'll leave some suggestions on the
patches, but those are optional.

I still think it would be nice to get more thoughts on the help message
changes, but that's Junio's call to make :)

Reviewed-by: Glen Choo <chooglen@google.com>

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v8 2/3] branch: add flags and config to inherit tracking
  2021-12-21  3:30   ` [PATCH v8 2/3] branch: add flags and config to inherit tracking Josh Steadmon
@ 2021-12-21 18:17     ` Glen Choo
  2022-01-11  1:57     ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) Ævar Arnfjörð Bjarmason
  1 sibling, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-21 18:17 UTC (permalink / raw)
  To: Josh Steadmon, git; +Cc: gitster, avarab

Josh Steadmon <steadmon@google.com> writes:

> diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
> index 4b0ef35913..a049276439 100755
> --- a/t/t3200-branch.sh
> +++ b/t/t3200-branch.sh
>  
> +test_expect_success 'tracking info copied with --track=inherit' '
> +	git branch --track=inherit foo2 my1 &&
> +	test_cmp_config local branch.foo2.remote &&
> +	test_cmp_config refs/heads/main branch.foo2.merge
> +'
> +
> +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
> +	test_unconfig branch.autoSetupMerge &&
> +	# default config does not copy tracking info
> +	git branch foo-no-inherit my1 &&
> +	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
> +	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
> +	# with autoSetupMerge=inherit, we copy tracking info from my1
> +	test_config branch.autoSetupMerge inherit &&
> +	git branch foo3 my1 &&
> +	test_cmp_config local branch.foo3.remote &&
> +	test_cmp_config refs/heads/main branch.foo3.merge &&
> +	# no tracking info to inherit from main
> +	git branch main2 main &&
> +	test_cmp_config "" --default "" branch.main2.remote &&
> +	test_cmp_config "" --default "" branch.main2.merge
> +'
> +
> +test_expect_success '--track overrides branch.autoSetupMerge' '
> +	test_config branch.autoSetupMerge inherit &&
> +	git branch --track=direct foo4 my1 &&
> +	test_cmp_config . branch.foo4.remote &&
> +	test_cmp_config refs/heads/my1 branch.foo4.merge &&
> +	git branch --no-track foo5 my1 &&
> +	test_cmp_config "" --default "" branch.foo5.remote &&
> +	test_cmp_config "" --default "" branch.foo5.merge
> +'
> +
>  test_done
> diff --git a/t/t7201-co.sh b/t/t7201-co.sh
> index 7f6e23a4bb..4fdf88ba46 100755
> --- a/t/t7201-co.sh
> +++ b/t/t7201-co.sh
> @@ -657,4 +657,21 @@ test_expect_success 'custom merge driver with checkout -m' '
>  	test_cmp expect arm
>  '
>  
> +test_expect_success 'tracking info copied with autoSetupMerge=inherit' '
> +	git reset --hard main &&
> +	# default config does not copy tracking info
> +	git checkout -b foo-no-inherit koala/bear &&
> +	test_cmp_config "" --default "" branch.foo-no-inherit.remote &&
> +	test_cmp_config "" --default "" branch.foo-no-inherit.merge &&
> +	# with autoSetupMerge=inherit, we copy tracking info from koala/bear
> +	test_config branch.autoSetupMerge inherit &&
> +	git checkout -b foo koala/bear &&
> +	test_cmp_config origin branch.foo.remote &&
> +	test_cmp_config refs/heads/koala/bear branch.foo.merge &&
> +	# no tracking info to inherit from main
> +	git checkout -b main2 main &&
> +	test_cmp_config "" --default "" branch.main2.remote &&
> +	test_cmp_config "" --default "" branch.main2.merge
> +'
> +
>  test_done
> -- 
> 2.34.1.307.g9b7440fafd-goog

As a suggestion, I don't think we have tests for multiple branch.*.merge
entries and it would be nice to have some (though I don't think it's
absolutely essential).

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH v8 1/3] branch: accept multiple upstream branches for tracking
  2021-12-21  6:55     ` Junio C Hamano
@ 2021-12-21 18:25       ` Glen Choo
  0 siblings, 0 replies; 103+ messages in thread
From: Glen Choo @ 2021-12-21 18:25 UTC (permalink / raw)
  To: Junio C Hamano, Josh Steadmon; +Cc: git, avarab

Junio C Hamano <gitster@pobox.com> writes:

>> +/**
>> + * Install upstream tracking configuration for a branch; specifically, add
>> + * `branch.<name>.remote` and `branch.<name>.merge` entries.
>> + *
>> + * `flag` contains integer flags for options; currently only
>> + * BRANCH_CONFIG_VERBOSE is checked.
>> + *
>> + * `local` is the name of the branch whose configuration we're installing.
>> + *
>> + * `origin` is the name of the remote owning the upstream branches. NULL means
>> + * the upstream branches are local to this repo.
>> + *
>> + * `remotes` is a list of refs that are upstream of local
>> + */
>> +static int install_branch_config_multiple_remotes(int flag, const char *local,
>> +		const char *origin, struct string_list *remotes)
>>  {
>>  	const char *shortname = NULL;
>>  	struct strbuf key = STRBUF_INIT;
>> +	struct string_list_item *item;
>>  	int rebasing = should_setup_rebase(origin);
>>  
>> +	if (!remotes->nr)
>> +		BUG("must provide at least one remote for branch config");
>> +	if (rebasing && remotes->nr > 1)
>> +		die(_("cannot inherit upstream tracking configuration of "
>> +		      "multiple refs when rebasing is requested"));
>> +
>> +	/*
>> +	 * If the new branch is trying to track itself, something has gone
>> +	 * wrong. Warn the user and don't proceed any further.
>> +	 */
>> +	if (!origin)
>> +		for_each_string_list_item(item, remotes)
>> +			if (skip_prefix(item->string, "refs/heads/", &shortname)
>> +			    && !strcmp(local, shortname)) {
>> +				warning(_("not setting branch '%s' as its own upstream."),
>> +					local);
>> +				return 0;
>> +			}
>
> Added comments make it easier to follow what is going on and more
> importantly why.  I very much like it ;-)

Agreed! We made a lot of 'why' decisions and I think the comments do a
great job of capturing that.

>> @@ -87,29 +117,40 @@ int install_branch_config(int flag, const char *local, const char *origin, const
>>  	strbuf_release(&key);
>>  
>>  	if (flag & BRANCH_CONFIG_VERBOSE) {
>> +		struct strbuf tmp_ref_name = STRBUF_INIT;
>> +		struct string_list friendly_ref_names = STRING_LIST_INIT_DUP;
>> +
>> +		for_each_string_list_item(item, remotes) {
>> +			shortname = item->string;
>> +			skip_prefix(shortname, "refs/heads/", &shortname);
>> +			if (origin) {
>> +				strbuf_addf(&tmp_ref_name, "%s/%s",
>> +					    origin, shortname);
>> +				string_list_append_nodup(
>> +					&friendly_ref_names,
>> +					strbuf_detach(&tmp_ref_name, NULL));
>> +			} else {
>> +				string_list_append(
>> +					&friendly_ref_names, shortname);
>> +			}
>> +		}
>> +
>> +		if (remotes->nr == 1) {
>> +			/*
>> +			 * Rebasing is only allowed in the case of a single
>> +			 * upstream branch.
>> +			 */
>
> There does not seem to be any code to forbid "rebasing" when
> remotes->nr != 1, though.  Did I miss a call to die() earlier?

The die() call happens in install_branch_config_multiple_remotes(),
where it belongs.

I think someone who reads this comment will eventually track down the
die() call, but it does look a little out of place. Purely as a matter
of personal taste, I would have expected this comment to be in the
'else' clause, and it might read something like:

  install_branch_config_multiple_remotes() does not allow rebasing with
  multiple upstream branches.

but that's just a suggestion :) This patch looks fine to me.

>
>> +			printf_ln(rebasing ?
>> +				_("branch '%s' set up to track '%s' by rebasing.") :
>> +				_("branch '%s' set up to track '%s'."),
>> +				local, friendly_ref_names.items[0].string);
>>  		} else {
>> +			printf_ln(_("branch '%s' set up to track:"), local);
>> +			for_each_string_list_item(item, &friendly_ref_names)
>> +				printf_ln("  %s", item->string);
>
> In other words, I would have expected something in this else clause.
>
>>  		}
>
> Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)
  2021-12-21  3:30   ` [PATCH v8 2/3] branch: add flags and config to inherit tracking Josh Steadmon
  2021-12-21 18:17     ` Glen Choo
@ 2022-01-11  1:57     ` Ævar Arnfjörð Bjarmason
  2022-01-18 20:49       ` [PATCH] branch,checkout: fix --track usage strings Josh Steadmon
  2022-01-19 10:20       ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) René Scharfe
  1 sibling, 2 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-11  1:57 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, gitster, chooglen


On Mon, Dec 20 2021, Josh Steadmon wrote:

> Since we've added an argument to "--track", also add "--track=direct" as
> another way to explicitly get the original "--track" behavior ("--track"
> without an argument still works as well).
> [...]
> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]

The usage info here is correct...

> ---track::
> +--track [inherit|direct]::

...as is this...

> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> +			N_("set branch tracking configuration"),
> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),
....

>  	struct option options[] = {
>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
> -			BRANCH_TRACK_EXPLICIT),
> +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",

... but these are not. I.e. we'll emit:

    -t, --track[=direct|inherit]
                          set branch tracking configuration

I.e. implying that the valid uses are --track, --track=direct, and
--trackinherit.

It looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was
never meant for an enumeration of possible values, but for something
like N_("mode"). It could be made to support that, but it would require
some light patching of the releant bits of parse-options.c.

The PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to
use a string like "mode".

> +			N_("set up tracking mode (see git-pull(1))"),

Aside: In v5 the other reference was changed from using the
"git-pull(1)" manpage syntax. See
https://lore.kernel.org/git/cover.1638859949.git.steadmon@google.com/;
It looks like this one should be changed too, but was missed.

> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			parse_opt_tracking_mode),




^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH] branch,checkout: fix --track usage strings
  2022-01-11  1:57     ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) Ævar Arnfjörð Bjarmason
@ 2022-01-18 20:49       ` Josh Steadmon
  2022-01-18 22:26         ` Junio C Hamano
  2022-01-20 12:05         ` [PATCH] branch,checkout: fix --track usage strings Ævar Arnfjörð Bjarmason
  2022-01-19 10:20       ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) René Scharfe
  1 sibling, 2 replies; 103+ messages in thread
From: Josh Steadmon @ 2022-01-18 20:49 UTC (permalink / raw)
  To: git; +Cc: avarab

As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a
list of allowed parameters is not recommended. Both git-branch and
git-checkout were changed in d311566 (branch: add flags and config to
inherit tracking, 2021-12-20) to use this discouraged combination for
their --track flags.

Fix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp
to simply be "mode". Users may discover allowed values in the manual
pages.

[1]: https://lore.kernel.org/git/220111.86a6g3yqf9.gmgdl@evledraar.gmail.com/

Signed-off-by: Josh Steadmon <steadmon@google.com>
---
 builtin/branch.c   | 4 ++--
 builtin/checkout.c | 6 +++---
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/builtin/branch.c b/builtin/branch.c
index 2251e6a54f..0c8d8a8827 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -638,9 +638,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
+		OPT_CALLBACK_F('t', "track",  &track, N_("mode"),
 			N_("set branch tracking configuration"),
-			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+			PARSE_OPT_OPTARG,
 			parse_opt_tracking_mode),
 		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
 			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 94814c37b4..6a5dd2a2a2 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1549,9 +1549,9 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
-			N_("set up tracking mode (see git-pull(1))"),
-			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
+		OPT_CALLBACK_F('t', "track",  &opts->track, N_("mode"),
+			N_("set branch tracking configuration"),
+			PARSE_OPT_OPTARG,
 			parse_opt_tracking_mode),
 		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
 			   PARSE_OPT_NOCOMPLETE),

base-commit: b56bd95bbc8f716cb8cbb5fdc18b9b0f00323c6a
-- 
2.34.1.703.g22d0c6ccf7-goog


^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-18 20:49       ` [PATCH] branch,checkout: fix --track usage strings Josh Steadmon
@ 2022-01-18 22:26         ` Junio C Hamano
  2022-01-19 10:56           ` [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP René Scharfe
  2022-01-20 12:05         ` [PATCH] branch,checkout: fix --track usage strings Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2022-01-18 22:26 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git, avarab

Josh Steadmon <steadmon@google.com> writes:

> As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a
> list of allowed parameters is not recommended. Both git-branch and
> git-checkout were changed in d311566 (branch: add flags and config to
> inherit tracking, 2021-12-20) to use this discouraged combination for
> their --track flags.

(tl;dr) I'll take this as-is and hopefully I can fast-track it in
time before tagging -rc2 tomorrow.

Having said that, here is what parse-options.h describes this flag
bit:

 *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
 *				(i.e. '<argh>') in the help message.
 *				Useful for options with multiple parameters.

Notice that this bit actually is meant "for options with multiple
parameters"?  The recommendation given to you might not be showing
the right way.

Looking at "git grep -C2 OPT_LITERAL_ARGHELP \*.c" output, I suspect
that a better solution may be to enclose "direct|inherit" in a pair
of parentheses, i.e. "(direct|inherit)".  That mimics the way how
"git am --show-current-patch[=(diff|raw)]" does it.

Then we would show

	--track[=(direct|inherit)]

instead of

	--track[=<mode>]

which means that ...

> Fix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp
> to simply be "mode". Users may discover allowed values in the manual
> pages.

... users won't have to visit the manual page only to find out what
modes we support.

In any case, the lesson should not be lost in the list archive.  To
help future developers from the same trouble, we should leave a note
to revisit the above description of the flag bit in parse-options.h
later, possibly after the 2.35 final, to see if we can improve it
(both the description and/or the behaviour of the code when it sees
the flag bit).

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)
  2022-01-11  1:57     ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) Ævar Arnfjörð Bjarmason
  2022-01-18 20:49       ` [PATCH] branch,checkout: fix --track usage strings Josh Steadmon
@ 2022-01-19 10:20       ` René Scharfe
  2022-01-20 12:00         ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 103+ messages in thread
From: René Scharfe @ 2022-01-19 10:20 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Josh Steadmon
  Cc: git, gitster, chooglen, Junio C Hamano

Am 11.01.22 um 02:57 schrieb Ævar Arnfjörð Bjarmason:
>
> On Mon, Dec 20 2021, Josh Steadmon wrote:
>
>> Since we've added an argument to "--track", also add "--track=direct" as
>> another way to explicitly get the original "--track" behavior ("--track"
>> without an argument still works as well).
>> [...]
>> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
>> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
>
> The usage info here is correct...

Actually it isn't, because optional arguments need the equal sign.  I.e.
this works as expected:

   git branch --track=direct branch

But this here interprets "direct" as a branch name (and branch as a
start point):

   git branch --track direct branch

The usage string could start with:

  'git branch' [--track | --track=direct | --track=inherit | --no-track]

... or the less repetitive:

  'git branch' [--track[=(direct|inherit)] | --no-track]

Options with required arguments accept either whitespace or an equal
sign between option name and arg.  With PARSE_OPT_OPTARG we cannot
accept whitespace because we cannot decide whether the next thing after
the option name is an argument or the next parameter.

>
>> ---track::
>> +--track [inherit|direct]::
>
> ...as is this...

Same here:

   --track[=(direct|inherit)]

>
>> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
>> -			BRANCH_TRACK_EXPLICIT),
>> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
>> +			N_("set branch tracking configuration"),
>> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
>> +			parse_opt_tracking_mode),
> ....
>
>>  	struct option options[] = {
>>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
>> -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
>> -			BRANCH_TRACK_EXPLICIT),
>> +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
>
> ... but these are not. I.e. we'll emit:
>
>     -t, --track[=direct|inherit]
>                           set branch tracking configuration
>
> I.e. implying that the valid uses are --track, --track=direct, and
> --trackinherit.

Well spotted.  It should be specified as "(direct|inherit)" (i.e. with
parens).

> It looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was
> never meant for an enumeration of possible values, but for something
> like N_("mode"). It could be made to support that, but it would require
> some light patching of the releant bits of parse-options.c.

Could you please elaborate that point?  AFAIU PARSE_OPT_OPTARG just
requires arguments to be attached with equal signs and there is no
limitation regarding the number of possible argument values.

> The PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to
> use a string like "mode".

That's true.  And it's also enabled automatically if the argument help
string contains any of the following characters: ()<>[]|.  So basically
it's never needed explicitly..

>
>> +			N_("set up tracking mode (see git-pull(1))"),
>
> Aside: In v5 the other reference was changed from using the
> "git-pull(1)" manpage syntax. See
> https://lore.kernel.org/git/cover.1638859949.git.steadmon@google.com/;
> It looks like this one should be changed too, but was missed.
>
>> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
>> +			parse_opt_tracking_mode),
>
>
>


^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-18 22:26         ` Junio C Hamano
@ 2022-01-19 10:56           ` René Scharfe
  2022-01-19 14:41             ` Ævar Arnfjörð Bjarmason
                               ` (2 more replies)
  0 siblings, 3 replies; 103+ messages in thread
From: René Scharfe @ 2022-01-19 10:56 UTC (permalink / raw)
  To: Junio C Hamano, Josh Steadmon; +Cc: git, avarab

The help strings for arguments are enclosed in angle brackets
automatically.  E.g. if argh is specified as "name", "--option <name>"
is printed, to indicate that users need to supply an actual name.  The
flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that "--option name"
is printed instead, to indicate that the literal string needs to be
supplied -- a rare case.

This flag is enabled automatically if argh contains special characters
like brackets.  The developer is supposed to provide any required angle
brackets for more complicated cases.  E.g. if argh is "<start>,<end>"
then "--option <start>,<end>" is printed.

Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.

Also remove the flag from option definitions for which it's inferred
automatically.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
Somehow I feel this is not enough, but I can't pin down what's
missing.

 builtin/am.c    | 2 +-
 builtin/push.c  | 2 +-
 parse-options.h | 2 ++
 3 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/builtin/am.c b/builtin/am.c
index b6be1f1cb1..fa8d28794a 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -2402,7 +2402,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
 		{ OPTION_CALLBACK, 0, "show-current-patch", &resume.mode,
 		  "(diff|raw)",
 		  N_("show the patch being applied"),
-		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
+		  PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
 		  parse_opt_show_current_patch, RESUME_SHOW_PATCH },
 		OPT_CMDMODE(0, "allow-empty", &resume.mode,
 			N_("record the empty patch as an empty commit"),
diff --git a/builtin/push.c b/builtin/push.c
index 359db90321..4fa6dfbd09 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -552,7 +552,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
 		OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
 		OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
 			       N_("require old value of ref to be at this value"),
-			       PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),
+			       PARSE_OPT_OPTARG, parseopt_push_cas_option),
 		OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,
 			N_("require remote updates to be integrated locally"),
 			TRANSPORT_PUSH_FORCE_IF_INCLUDES),
diff --git a/parse-options.h b/parse-options.h
index e22846d3b7..8d089fb3ae 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -108,6 +108,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
  *				(i.e. '<argh>') in the help message.
  *				Useful for options with multiple parameters.
+ *				Automatically enabled if argh contains any
+ *				of the following characters: ()<>[]|
  *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
  *			   by git-completion.bash. This option suppresses that.
  *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to
--
2.34.1

^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-19 10:56           ` [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP René Scharfe
@ 2022-01-19 14:41             ` Ævar Arnfjörð Bjarmason
       [not found]             ` <CA++g3E-azP3wFTtNkbFdmT7VW3hvULL0WkkAdwfrMb6HDtcXdg@mail.gmail.com>
  2022-01-19 18:16             ` Junio C Hamano
  2 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-19 14:41 UTC (permalink / raw)
  To: René Scharfe; +Cc: Junio C Hamano, Josh Steadmon, git


On Wed, Jan 19 2022, René Scharfe wrote:

> The help strings for arguments are enclosed in angle brackets
> automatically.  E.g. if argh is specified as "name", "--option <name>"
> is printed, to indicate that users need to supply an actual name.  The
> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that "--option name"
> is printed instead, to indicate that the literal string needs to be
> supplied -- a rare case.
>
> This flag is enabled automatically if argh contains special characters
> like brackets.  The developer is supposed to provide any required angle
> brackets for more complicated cases.  E.g. if argh is "<start>,<end>"
> then "--option <start>,<end>" is printed.
>
> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.
>
> Also remove the flag from option definitions for which it's inferred
> automatically.
>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
> Somehow I feel this is not enough, but I can't pin down what's
> missing.

Rather than just remove the flag from {am,push}.c and document this it
would be better to add it to the optbug() checks in
parse_options_check().

That way we'll ensure that these flags won't be redunantly specified, if
we care enough...

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
       [not found]             ` <CA++g3E-azP3wFTtNkbFdmT7VW3hvULL0WkkAdwfrMb6HDtcXdg@mail.gmail.com>
@ 2022-01-19 15:30               ` René Scharfe
  0 siblings, 0 replies; 103+ messages in thread
From: René Scharfe @ 2022-01-19 15:30 UTC (permalink / raw)
  To: Bryce Ekrem; +Cc: Junio C Hamano, Josh Steadmon, git, avarab

Am 19.01.22 um 16:15 schrieb Bryce Ekrem:
> Can you clarify what argh is instead of argc and argv
> sounds like you said arghelp

argh is a member of struct option, defined in parse-options.h.  It is a
string that describes the argument of an option, i.e. it's a short
"argument help" or "argument hint".

> On Wed, Jan 19, 2022 at 2:57 AM René Scharfe <l.s.r@web.de <mailto:l.s.r@web.de>> wrote:
>
>     The help strings for arguments are enclosed in angle brackets
>     automatically.  E.g. if argh is specified as "name", "--option <name>"
>     is printed, to indicate that users need to supply an actual name.  The
>     flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that "--option name"
>     is printed instead, to indicate that the literal string needs to be
>     supplied -- a rare case.
>
>     This flag is enabled automatically if argh contains special characters
>     like brackets.  The developer is supposed to provide any required angle
>     brackets for more complicated cases.  E.g. if argh is "<start>,<end>"
>     then "--option <start>,<end>" is printed.
>
>     Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.
>
>     Also remove the flag from option definitions for which it's inferred
>     automatically.
>
>     Signed-off-by: René Scharfe <l.s.r@web.de <mailto:l.s.r@web.de>>
>     ---
>     Somehow I feel this is not enough, but I can't pin down what's
>     missing.
>
>      builtin/am.c    | 2 +-
>      builtin/push.c  | 2 +-
>      parse-options.h | 2 ++
>      3 files changed, 4 insertions(+), 2 deletions(-)
>
>     diff --git a/builtin/am.c b/builtin/am.c
>     index b6be1f1cb1..fa8d28794a 100644
>     --- a/builtin/am.c
>     +++ b/builtin/am.c
>     @@ -2402,7 +2402,7 @@ int cmd_am(int argc, const char **argv, const char *prefix)
>                     { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode,
>                       "(diff|raw)",
                        ^^^^^^^^^^^^
This is the argh for that particular option (--show-current-patch).

>                       N_("show the patch being applied"),
>     -                 PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP,
>     +                 PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG,
>                       parse_opt_show_current_patch, RESUME_SHOW_PATCH },
>                     OPT_CMDMODE(0, "allow-empty", &resume.mode,
>                             N_("record the empty patch as an empty commit"),
>     diff --git a/builtin/push.c b/builtin/push.c
>     index 359db90321..4fa6dfbd09 100644
>     --- a/builtin/push.c
>     +++ b/builtin/push.c
>     @@ -552,7 +552,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
>                     OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE),
>                     OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"),
                                                            ^^^^^^^^^^^^^^^^^^^^^^^^
And here's the one for CAS_OPT_NAME, defined elsewhere, (resolves
to --force-with-lease).

>                                    N_("require old value of ref to be at this value"),
>     -                              PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option),
>     +                              PARSE_OPT_OPTARG, parseopt_push_cas_option),
>                     OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags,
>                             N_("require remote updates to be integrated locally"),
>                             TRANSPORT_PUSH_FORCE_IF_INCLUDES),
>     diff --git a/parse-options.h b/parse-options.h
>     index e22846d3b7..8d089fb3ae 100644
>     --- a/parse-options.h
>     +++ b/parse-options.h

Here's the comment about argh from parse-options.h:

 * `argh`::
 *   token to explain the kind of argument this option wants. Does not
 *   begin in capital letter, and does not end with a full stop.
 *   Should be wrapped by N_() for translation.


>     @@ -108,6 +108,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
>       *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
>       *                             (i.e. '<argh>') in the help message.
>       *                             Useful for options with multiple parameters.
>     + *                             Automatically enabled if argh contains any
>     + *                             of the following characters: ()<>[]|
>       *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
>       *                        by git-completion.bash. This option suppresses that.
>       *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to
>     --
>     2.34.1
>

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-19 10:56           ` [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP René Scharfe
  2022-01-19 14:41             ` Ævar Arnfjörð Bjarmason
       [not found]             ` <CA++g3E-azP3wFTtNkbFdmT7VW3hvULL0WkkAdwfrMb6HDtcXdg@mail.gmail.com>
@ 2022-01-19 18:16             ` Junio C Hamano
  2022-01-20 10:30               ` René Scharfe
  2 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2022-01-19 18:16 UTC (permalink / raw)
  To: René Scharfe; +Cc: Josh Steadmon, git, avarab

René Scharfe <l.s.r@web.de> writes:

> The help strings for arguments are enclosed in angle brackets
> automatically.  E.g. if argh is specified as "name", "--option <name>"
> is printed, to indicate that users need to supply an actual name.  The
> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that "--option name"
> is printed instead, to indicate that the literal string needs to be
> supplied -- a rare case.
>
> This flag is enabled automatically if argh contains special characters
> like brackets.  The developer is supposed to provide any required angle
> brackets for more complicated cases.  E.g. if argh is "<start>,<end>"
> then "--option <start>,<end>" is printed.

The above does explain why we want to have this change very well,
but at least some of it would help those who are reading the comment
we touch.

> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.

But instead, the addition is only about when the flag bit is turned
on automatically.  Without understanding your

    E.g. if argh is specified as "name", "--option <name>" is
    printed, to indicate that users need to supply an actual name.

readers would not quite get from the current description "says that
argh shouldn't be enclosed in brackets" when/why it is a good idea
to add the option, I am afraid.

> Also remove the flag from option definitions for which it's inferred
> automatically.

I am not sure if this is a good move.

Because these places explicitly gave PARSE_OPT_LITERAL_ARGHELP, it
was easy to grep for them when I was trying to find an existing
practice.

Imagine, after we remove these redundant flags, we see a patch that
wants to change the set of characters that automatically flips the
flag bit on.  It is clear from the patch text why it helps one
particular OPT_STRING() or whatever the same patch adds, but how
would you make sure it will not regress _existing_ OPT_WHATEVER()
that do not use PARSE_OPT_LITERAL_ARGHELP because their argh happens
to use the character that wasn't special before?

> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
> Somehow I feel this is not enough, but I can't pin down what's
> missing.

Let me try.

>   *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
>   *				(i.e. '<argh>') in the help message.
>   *				Useful for options with multiple parameters.
> + *				Automatically enabled if argh contains any
> + *				of the following characters: ()<>[]|
>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
>   *			   by git-completion.bash. This option suppresses that.
>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to

PARSE_OPT_LITERAL_ARGHELP: in short-help given by "git cmd -h", the
			   "argh" member of the struct option is
			   shown in a pair of angle brackets (e.g.
			   "--option=<argh>"); this flag tells the
			   machinery not to add these brackets, to
			   give more control to the developer.  E.g.
			   "<start>,<end>" given to argh is shown as
			   "--option=<start>,<end>".

That may be a bit too much, but on the other hand, among PARSE_OPT_X
descriptions, this is the only one that needs to talk about help
text on the argument to the option.

Or we can flip it the other way, perhaps?

	Tell the machinery to give "argh" member literally in the
	short-help in "git cmd -h" output for the option.  E.g. {
	.argh = "(diff|raw)", .long_name = "show" } would give
	"--show=(diff|raw)".  Without the flag, "argh" is enclosed
	in a pair of angle brackets.

I dunno.



^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-19 18:16             ` Junio C Hamano
@ 2022-01-20 10:30               ` René Scharfe
  2022-01-20 18:25                 ` Junio C Hamano
  0 siblings, 1 reply; 103+ messages in thread
From: René Scharfe @ 2022-01-20 10:30 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Josh Steadmon, git, avarab

Am 19.01.22 um 19:16 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> The help strings for arguments are enclosed in angle brackets
>> automatically.  E.g. if argh is specified as "name", "--option <name>"
>> is printed, to indicate that users need to supply an actual name.  The
>> flag PARSE_OPT_LITERAL_ARGHELP turns this off, so that "--option name"
>> is printed instead, to indicate that the literal string needs to be
>> supplied -- a rare case.
>>
>> This flag is enabled automatically if argh contains special characters
>> like brackets.  The developer is supposed to provide any required angle
>> brackets for more complicated cases.  E.g. if argh is "<start>,<end>"
>> then "--option <start>,<end>" is printed.
>
> The above does explain why we want to have this change very well,
> but at least some of it would help those who are reading the comment
> we touch.
>
>> Add a comment to mention this PARSE_OPT_LITERAL_ARGHELP behavior.
>
> But instead, the addition is only about when the flag bit is turned
> on automatically.  Without understanding your
>
>     E.g. if argh is specified as "name", "--option <name>" is
>     printed, to indicate that users need to supply an actual name.
>
> readers would not quite get from the current description "says that
> argh shouldn't be enclosed in brackets" when/why it is a good idea
> to add the option, I am afraid.
>
>> Also remove the flag from option definitions for which it's inferred
>> automatically.
>
> I am not sure if this is a good move.
>
> Because these places explicitly gave PARSE_OPT_LITERAL_ARGHELP, it
> was easy to grep for them when I was trying to find an existing
> practice.
>
> Imagine, after we remove these redundant flags, we see a patch that
> wants to change the set of characters that automatically flips the
> flag bit on.  It is clear from the patch text why it helps one
> particular OPT_STRING() or whatever the same patch adds, but how
> would you make sure it will not regress _existing_ OPT_WHATEVER()
> that do not use PARSE_OPT_LITERAL_ARGHELP because their argh happens
> to use the character that wasn't special before?

Building a multi-line regex or going through the output of --help-all of
all commands or adding a throwaway internal option to just print argh
would certainly be much harder.

Reducing the set of special characters would be part of a change to the
notation for describing options.  Perhaps we'd want to no longer use
grouping and thus get rid of parentheses.  That would require updating
all affected manpages and usage strings as well -- quite a big effort,
regardless of PARSE_OPT_LITERAL_ARGHELP's grepability.

Extending the set, e.g. to give special meaning to curly brackets, can't
rely on the explicit flag; all argh strings need to be examined to check
whether they become special.

>
>> Signed-off-by: René Scharfe <l.s.r@web.de>
>> ---
>> Somehow I feel this is not enough, but I can't pin down what's
>> missing.
>
> Let me try.
>
>>   *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
>>   *				(i.e. '<argh>') in the help message.
>>   *				Useful for options with multiple parameters.
>> + *				Automatically enabled if argh contains any
>> + *				of the following characters: ()<>[]|
>>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
>>   *			   by git-completion.bash. This option suppresses that.
>>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to
>
> PARSE_OPT_LITERAL_ARGHELP: in short-help given by "git cmd -h", the
> 			   "argh" member of the struct option is
> 			   shown in a pair of angle brackets (e.g.
> 			   "--option=<argh>"); this flag tells the
> 			   machinery not to add these brackets, to
> 			   give more control to the developer.  E.g.
> 			   "<start>,<end>" given to argh is shown as
> 			   "--option=<start>,<end>".
>
> That may be a bit too much, but on the other hand, among PARSE_OPT_X
> descriptions, this is the only one that needs to talk about help
> text on the argument to the option.
>
> Or we can flip it the other way, perhaps?
>
> 	Tell the machinery to give "argh" member literally in the
> 	short-help in "git cmd -h" output for the option.  E.g. {
> 	.argh = "(diff|raw)", .long_name = "show" } would give
> 	"--show=(diff|raw)".  Without the flag, "argh" is enclosed
> 	in a pair of angle brackets.
>
> I dunno.

Now that I read the whole comment, I think the right place to introduce
the automatic brackets is the description of argh some lines up.

--- >8 ---
Subject: [PATCH 5/5] parse-options: document bracketing of argh

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 parse-options.h | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/parse-options.h b/parse-options.h
index e22846d3b7..88d589d159 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *   token to explain the kind of argument this option wants. Does not
  *   begin in capital letter, and does not end with a full stop.
  *   Should be wrapped by N_() for translation.
+ *   Is automatically enclosed in brackets when printed, unless it
+ *   contains any of the following characters: ()<>[]|
+ *   E.g. "name" is shown as "<name>" to indicate that a name value
+ *   needs to be supplied, not the literal string "name", but
+ *   "<start>,<end>" and "(this|that)" are printed verbatim.
  *
  * `help`::
  *   the short help associated to what the option does.
--
2.34.1

^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking)
  2022-01-19 10:20       ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) René Scharfe
@ 2022-01-20 12:00         ` Ævar Arnfjörð Bjarmason
  2022-01-20 12:35           ` [PATCH] branch,checkout: fix --track documentation René Scharfe
  0 siblings, 1 reply; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-20 12:00 UTC (permalink / raw)
  To: René Scharfe; +Cc: Josh Steadmon, git, chooglen, Junio C Hamano


On Wed, Jan 19 2022, René Scharfe wrote:

> Am 11.01.22 um 02:57 schrieb Ævar Arnfjörð Bjarmason:
>>
>> On Mon, Dec 20 2021, Josh Steadmon wrote:
>>
>>> Since we've added an argument to "--track", also add "--track=direct" as
>>> another way to explicitly get the original "--track" behavior ("--track"
>>> without an argument still works as well).
>>> [...]
>>> -'git branch' [--track | --no-track] [-f] <branchname> [<start-point>]
>>> +'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
>>
>> The usage info here is correct...
>
> Actually it isn't, because optional arguments need the equal sign.  I.e.
> this works as expected:
>
>    git branch --track=direct branch
>
> But this here interprets "direct" as a branch name (and branch as a
> start point):
>
>    git branch --track direct branch
>
> The usage string could start with:
>
>   'git branch' [--track | --track=direct | --track=inherit | --no-track]
>
> ... or the less repetitive:
>
>   'git branch' [--track[=(direct|inherit)] | --no-track]
>
> Options with required arguments accept either whitespace or an equal
> sign between option name and arg.  With PARSE_OPT_OPTARG we cannot
> accept whitespace because we cannot decide whether the next thing after
> the option name is an argument or the next parameter.

Well spotted. Your downthread patch LGTM (with the small nit I noted
that having an optbug() check for this would be even better).

>>
>>> ---track::
>>> +--track [inherit|direct]::
>>
>> ...as is this...
>
> Same here:
>
>    --track[=(direct|inherit)]
>
>>
>>> -		OPT_SET_INT('t', "track",  &track, N_("set up tracking mode (see git-pull(1))"),
>>> -			BRANCH_TRACK_EXPLICIT),
>>> +		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
>>> +			N_("set branch tracking configuration"),
>>> +			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
>>> +			parse_opt_tracking_mode),
>> ....
>>
>>>  	struct option options[] = {
>>>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
>>> -		OPT_SET_INT('t', "track",  &opts->track, N_("set upstream info for new branch"),
>>> -			BRANCH_TRACK_EXPLICIT),
>>> +		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
>>
>> ... but these are not. I.e. we'll emit:
>>
>>     -t, --track[=direct|inherit]
>>                           set branch tracking configuration
>>
>> I.e. implying that the valid uses are --track, --track=direct, and
>> --trackinherit.
>
> Well spotted.  It should be specified as "(direct|inherit)" (i.e. with
> parens).

*nod*

>> It looks like the problem is (ab)use of PARSE_OPT_OPTARG, i.e. it was
>> never meant for an enumeration of possible values, but for something
>> like N_("mode"). It could be made to support that, but it would require
>> some light patching of the releant bits of parse-options.c.
>
> Could you please elaborate that point?  AFAIU PARSE_OPT_OPTARG just
> requires arguments to be attached with equal signs and there is no
> limitation regarding the number of possible argument values.

I'd skimmed the code & -h generation, but see on a second reading that I
was just wrong about this.

I.e. I thought it would always misformat alternate args, but as your
downthread patch shows where we'll now for "git am -h" emit e.g.:

    --show-current-patch[=(diff|raw)]

The output is now correct (and was before, we were just giving the flag
rudendantly).

>> The PARSE_OPT_LITERAL_ARGHELP should also be dropped if it's fixed to
>> use a string like "mode".
>
> That's true.  And it's also enabled automatically if the argument help
> string contains any of the following characters: ()<>[]|.  So basically
> it's never needed explicitly..

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-18 20:49       ` [PATCH] branch,checkout: fix --track usage strings Josh Steadmon
  2022-01-18 22:26         ` Junio C Hamano
@ 2022-01-20 12:05         ` Ævar Arnfjörð Bjarmason
  2022-01-20 12:18           ` Andreas Schwab
  2022-01-20 18:38           ` Junio C Hamano
  1 sibling, 2 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-20 12:05 UTC (permalink / raw)
  To: Josh Steadmon; +Cc: git


On Tue, Jan 18 2022, Josh Steadmon wrote:

> As Ævar pointed out in [1], the use of PARSE_OPT_LITERAL_ARGHELP with a
> list of allowed parameters is not recommended. Both git-branch and
> git-checkout were changed in d311566 (branch: add flags and config to
> inherit tracking, 2021-12-20) to use this discouraged combination for
> their --track flags.
>
> Fix this by removing PARSE_OPT_LITERAL_ARGHELP, and changing the arghelp
> to simply be "mode". Users may discover allowed values in the manual
> pages.
>
> [1]: https://lore.kernel.org/git/220111.86a6g3yqf9.gmgdl@evledraar.gmail.com/
>
> Signed-off-by: Josh Steadmon <steadmon@google.com>
> ---
>  builtin/branch.c   | 4 ++--
>  builtin/checkout.c | 6 +++---
>  2 files changed, 5 insertions(+), 5 deletions(-)
>
> diff --git a/builtin/branch.c b/builtin/branch.c
> index 2251e6a54f..0c8d8a8827 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -638,9 +638,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>  		OPT__VERBOSE(&filter.verbose,
>  			N_("show hash and subject, give twice for upstream branch")),
>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> -		OPT_CALLBACK_F('t', "track",  &track, "direct|inherit",
> +		OPT_CALLBACK_F('t', "track",  &track, N_("mode"),
>  			N_("set branch tracking configuration"),
> -			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +			PARSE_OPT_OPTARG,
>  			parse_opt_tracking_mode),
>  		OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"),
>  			BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN),
> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 94814c37b4..6a5dd2a2a2 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1549,9 +1549,9 @@ static struct option *add_common_switch_branch_options(
>  {
>  	struct option options[] = {
>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_CALLBACK_F('t', "track",  &opts->track, "direct|inherit",
> -			N_("set up tracking mode (see git-pull(1))"),
> -			PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP,
> +		OPT_CALLBACK_F('t', "track",  &opts->track, N_("mode"),
> +			N_("set branch tracking configuration"),
> +			PARSE_OPT_OPTARG,
>  			parse_opt_tracking_mode),
>  		OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"),
>  			   PARSE_OPT_NOCOMPLETE),
>
> base-commit: b56bd95bbc8f716cb8cbb5fdc18b9b0f00323c6a

Additional comment: I think this change is correct, as noted in my
just-sent
https://lore.kernel.org/git/220120.864k5ymx55.gmgdl@evledraar.gmail.com/;
it would be nice but not necessary to follow-up with an optbug() test as
noted in
https://lore.kernel.org/git/220119.867davokff.gmgdl@evledraar.gmail.com/
though.

But to not merely repeat myself, I also saw that we're emitting the
wrong output from usage_argh() in cases of !PARSE_OPT_NOARG. I.e. we
need this fix too:
    
    diff --git a/parse-options.c b/parse-options.c
    index a8283037be9..2be1eabd84e 100644
    --- a/parse-options.c
    +++ b/parse-options.c
    @@ -915,8 +915,10 @@ static int usage_argh(const struct option *opts, FILE *outfile)
                            s = literal ? "[=%s]" : "[=<%s>]";
                    else
                            s = literal ? "[%s]" : "[<%s>]";
    -       else
    +       else if (opts->flags & PARSE_OPT_NOARG)
                    s = literal ? " %s" : " <%s>";
    +       else
    +               s = literal ? "[=]%s" : "[=]<%s>";
            return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
     }

With that we'll now emit:
    
    $ ./git add -h 2>&1|grep chmod
        --chmod[=](+|-)x      override the executable bit of the listed files

Which is correct, as we accept both of:

    git add --chmod +x
    git add --chmod=+x

But not:

    $ git add --chmod
    error: parse-options.c:58: option `chmod' requires a value

But the usage output stated that the "=" was mandatory before.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-20 12:05         ` [PATCH] branch,checkout: fix --track usage strings Ævar Arnfjörð Bjarmason
@ 2022-01-20 12:18           ` Andreas Schwab
  2022-01-20 14:00             ` Ævar Arnfjörð Bjarmason
  2022-01-20 18:38           ` Junio C Hamano
  1 sibling, 1 reply; 103+ messages in thread
From: Andreas Schwab @ 2022-01-20 12:18 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Josh Steadmon, git

On Jan 20 2022, Ævar Arnfjörð Bjarmason wrote:

> With that we'll now emit:
>     
>     $ ./git add -h 2>&1|grep chmod
>         --chmod[=](+|-)x      override the executable bit of the listed files

That looks like --chmod+x is valid, which isn't.

-- 
Andreas Schwab, schwab@linux-m68k.org
GPG Key fingerprint = 7578 EB47 D4E5 4D69 2510  2552 DF73 E780 A9DA AEC1
"And now for something completely different."

^ permalink raw reply	[flat|nested] 103+ messages in thread

* [PATCH] branch,checkout: fix --track documentation
  2022-01-20 12:00         ` Ævar Arnfjörð Bjarmason
@ 2022-01-20 12:35           ` René Scharfe
  2022-01-20 13:57             ` Ævar Arnfjörð Bjarmason
  2022-01-20 19:08             ` Junio C Hamano
  0 siblings, 2 replies; 103+ messages in thread
From: René Scharfe @ 2022-01-20 12:35 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason, Josh Steadmon
  Cc: git, chooglen, Junio C Hamano

Document that the accepted variants of the --track option are --track,
--track=direct, and --track=inherit.  The equal sign in the latter two
cannot be replaced with whitespace; in general optional arguments need
to be attached firmly to their option.

Put "direct" consistently before "inherit", if only for the reasons
that the former is the default, explained first in the documentation,
and comes before the latter alphabetically.

Mention both modes in the short help so that readers don't have to look
them up in the full documentation.  They are literal strings and thus
untranslatable.  PARSE_OPT_LITERAL_ARGHELP is inferred due to the pipe
and parenthesis characters, so we don't have to provide that flag
explicitly.

Mention that -t has the same effect as --track and --track=direct.
There is no way to specify inherit mode using the short option, because
short options generally don't accept optional arguments.

Signed-off-by: René Scharfe <l.s.r@web.de>
---
 Documentation/git-branch.txt   | 12 ++++++------
 Documentation/git-checkout.txt |  2 +-
 builtin/branch.c               |  2 +-
 builtin/checkout.c             |  2 +-
 4 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 2d52ae396b..731e340cbc 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -16,7 +16,7 @@ SYNOPSIS
 	[--points-at <object>] [--format=<format>]
 	[(-r | --remotes) | (-a | --all)]
 	[--list] [<pattern>...]
-'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
+'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]
 'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
 'git branch' --unset-upstream [<branchname>]
 'git branch' (-m | -M) [<oldbranch>] <newbranch>
@@ -206,7 +206,7 @@ This option is only applicable in non-verbose mode.
 	Display the full sha1s in the output listing rather than abbreviating them.

 -t::
---track [inherit|direct]::
+--track[=(direct|inherit)]::
 	When creating a new branch, set up `branch.<name>.remote` and
 	`branch.<name>.merge` configuration entries to set "upstream" tracking
 	configuration for the new branch. This
@@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.
 	upstream when the new branch is checked out.
 +
 The exact upstream branch is chosen depending on the optional argument:
-`--track` or `--track direct` means to use the start-point branch itself as the
-upstream; `--track inherit` means to copy the upstream configuration of the
-start-point branch.
+`-t`, `--track`, or `--track=direct` means to use the start-point branch
+itself as the upstream; `--track=inherit` means to copy the upstream
+configuration of the start-point branch.
 +
-`--track direct` is the default when the start point is a remote-tracking branch.
+`--track=direct` is the default when the start point is a remote-tracking branch.
 Set the branch.autoSetupMerge configuration variable to `false` if you
 want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
 were given. Set it to `always` if you want this behavior when the
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 2a90ea6cd0..9f37e22e13 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -156,7 +156,7 @@ of it").
 	linkgit:git-branch[1] for details.

 -t::
---track [direct|inherit]::
+--track[=(direct|inherit)]::
 	When creating a new branch, set up "upstream" configuration. See
 	"--track" in linkgit:git-branch[1] for details.
 +
diff --git a/builtin/branch.c b/builtin/branch.c
index 0c8d8a8827..4ce2a24754 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -638,7 +638,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
 		OPT__VERBOSE(&filter.verbose,
 			N_("show hash and subject, give twice for upstream branch")),
 		OPT__QUIET(&quiet, N_("suppress informational messages")),
-		OPT_CALLBACK_F('t', "track",  &track, N_("mode"),
+		OPT_CALLBACK_F('t', "track",  &track, "(direct|inherit)",
 			N_("set branch tracking configuration"),
 			PARSE_OPT_OPTARG,
 			parse_opt_tracking_mode),
diff --git a/builtin/checkout.c b/builtin/checkout.c
index 6a5dd2a2a2..0bc2e63510 100644
--- a/builtin/checkout.c
+++ b/builtin/checkout.c
@@ -1549,7 +1549,7 @@ static struct option *add_common_switch_branch_options(
 {
 	struct option options[] = {
 		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
-		OPT_CALLBACK_F('t', "track",  &opts->track, N_("mode"),
+		OPT_CALLBACK_F('t', "track",  &opts->track, "(direct|inherit)",
 			N_("set branch tracking configuration"),
 			PARSE_OPT_OPTARG,
 			parse_opt_tracking_mode),
--
2.34.1

^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track documentation
  2022-01-20 12:35           ` [PATCH] branch,checkout: fix --track documentation René Scharfe
@ 2022-01-20 13:57             ` Ævar Arnfjörð Bjarmason
  2022-01-20 19:08             ` Junio C Hamano
  1 sibling, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-20 13:57 UTC (permalink / raw)
  To: René Scharfe; +Cc: Josh Steadmon, git, chooglen, Junio C Hamano


On Thu, Jan 20 2022, René Scharfe wrote:

> Document that the accepted variants of the --track option are --track,
> --track=direct, and --track=inherit.  The equal sign in the latter two
> cannot be replaced with whitespace; in general optional arguments need
> to be attached firmly to their option.
>
> Put "direct" consistently before "inherit", if only for the reasons
> that the former is the default, explained first in the documentation,
> and comes before the latter alphabetically.
>
> Mention both modes in the short help so that readers don't have to look
> them up in the full documentation.  They are literal strings and thus
> untranslatable.  PARSE_OPT_LITERAL_ARGHELP is inferred due to the pipe
> and parenthesis characters, so we don't have to provide that flag
> explicitly.
>
> Mention that -t has the same effect as --track and --track=direct.
> There is no way to specify inherit mode using the short option, because
> short options generally don't accept optional arguments.
>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
>  Documentation/git-branch.txt   | 12 ++++++------
>  Documentation/git-checkout.txt |  2 +-
>  builtin/branch.c               |  2 +-
>  builtin/checkout.c             |  2 +-
>  4 files changed, 9 insertions(+), 9 deletions(-)
>
> diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
> index 2d52ae396b..731e340cbc 100644
> --- a/Documentation/git-branch.txt
> +++ b/Documentation/git-branch.txt
> @@ -16,7 +16,7 @@ SYNOPSIS
>  	[--points-at <object>] [--format=<format>]
>  	[(-r | --remotes) | (-a | --all)]
>  	[--list] [<pattern>...]
> -'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
> +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]
>  'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
>  'git branch' --unset-upstream [<branchname>]
>  'git branch' (-m | -M) [<oldbranch>] <newbranch>
> @@ -206,7 +206,7 @@ This option is only applicable in non-verbose mode.
>  	Display the full sha1s in the output listing rather than abbreviating them.
>
>  -t::
> ---track [inherit|direct]::
> +--track[=(direct|inherit)]::
>  	When creating a new branch, set up `branch.<name>.remote` and
>  	`branch.<name>.merge` configuration entries to set "upstream" tracking
>  	configuration for the new branch. This
> @@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.
>  	upstream when the new branch is checked out.
>  +
>  The exact upstream branch is chosen depending on the optional argument:
> -`--track` or `--track direct` means to use the start-point branch itself as the
> -upstream; `--track inherit` means to copy the upstream configuration of the
> -start-point branch.
> +`-t`, `--track`, or `--track=direct` means to use the start-point branch
> +itself as the upstream; `--track=inherit` means to copy the upstream
> +configuration of the start-point branch.
>  +
> -`--track direct` is the default when the start point is a remote-tracking branch.
> +`--track=direct` is the default when the start point is a remote-tracking branch.
>  Set the branch.autoSetupMerge configuration variable to `false` if you
>  want `git switch`, `git checkout` and `git branch` to always behave as if `--no-track`
>  were given. Set it to `always` if you want this behavior when the
> diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
> index 2a90ea6cd0..9f37e22e13 100644
> --- a/Documentation/git-checkout.txt
> +++ b/Documentation/git-checkout.txt
> @@ -156,7 +156,7 @@ of it").
>  	linkgit:git-branch[1] for details.
>
>  -t::
> ---track [direct|inherit]::
> +--track[=(direct|inherit)]::

These changes (and the below) all look good to me. Thanks for fixing
this.

>  	When creating a new branch, set up "upstream" configuration. See
>  	"--track" in linkgit:git-branch[1] for details.

As a side-note this "--track" reference is incorrect, and has been since
d3115660b4c (branch: add flags and config to inherit tracking,
2021-12-20), i.e. it should now mention "--track[=(direct|inherit)]".

But as we're not explicitly cross-linking anything here with the
relevant syntax I think leaving it as-is is fine, the user would also
find it with a substring search.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-20 12:18           ` Andreas Schwab
@ 2022-01-20 14:00             ` Ævar Arnfjörð Bjarmason
  0 siblings, 0 replies; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-20 14:00 UTC (permalink / raw)
  To: Andreas Schwab; +Cc: Josh Steadmon, git


On Thu, Jan 20 2022, Andreas Schwab wrote:

> On Jan 20 2022, Ævar Arnfjörð Bjarmason wrote:
>
>> With that we'll now emit:
>>     
>>     $ ./git add -h 2>&1|grep chmod
>>         --chmod[=](+|-)x      override the executable bit of the listed files
>
> That looks like --chmod+x is valid, which isn't.

Indeed, it should be --chmod(=| )(+|-)x in that "else" case.

But I think the rest of what I pointed out still applies with that
amendmend. Thanks!

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-20 10:30               ` René Scharfe
@ 2022-01-20 18:25                 ` Junio C Hamano
  2022-01-21  9:42                   ` René Scharfe
  0 siblings, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2022-01-20 18:25 UTC (permalink / raw)
  To: René Scharfe; +Cc: Josh Steadmon, git, avarab

René Scharfe <l.s.r@web.de> writes:

> Now that I read the whole comment, I think the right place to introduce
> the automatic brackets is the description of argh some lines up.
>
> --- >8 ---
> Subject: [PATCH 5/5] parse-options: document bracketing of argh
>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
>  parse-options.h | 5 +++++
>  1 file changed, 5 insertions(+)
>
> diff --git a/parse-options.h b/parse-options.h
> index e22846d3b7..88d589d159 100644
> --- a/parse-options.h
> +++ b/parse-options.h
> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
>   *   token to explain the kind of argument this option wants. Does not
>   *   begin in capital letter, and does not end with a full stop.
>   *   Should be wrapped by N_() for translation.
> + *   Is automatically enclosed in brackets when printed, unless it
> + *   contains any of the following characters: ()<>[]|
> + *   E.g. "name" is shown as "<name>" to indicate that a name value
> + *   needs to be supplied, not the literal string "name", but
> + *   "<start>,<end>" and "(this|that)" are printed verbatim.
>   *
>   * `help`::
>   *   the short help associated to what the option does.

Very nice.

This version gives the necessary information in (almost) one place.

I said (almost) because "it contains any of ..." is not the only way
to decline the <automatic angle brackets>, and am wondering if it is
more helpful to say something like

	... when printed, unless PARSE_OPT_LITERAL_ARGHELP is set in
	the flags, or it contains any of the following characters: ...

and then shrink the description of the flag bit to

    PARSE_OPT_LITERAL_ARGHELP: controls if `argh` is enclosed in in
    brackets when shown (see the description on `argh` above).


^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-20 12:05         ` [PATCH] branch,checkout: fix --track usage strings Ævar Arnfjörð Bjarmason
  2022-01-20 12:18           ` Andreas Schwab
@ 2022-01-20 18:38           ` Junio C Hamano
  2022-01-21 11:27             ` Ævar Arnfjörð Bjarmason
  1 sibling, 1 reply; 103+ messages in thread
From: Junio C Hamano @ 2022-01-20 18:38 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Josh Steadmon, git

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> With that we'll now emit:
>     
>     $ ./git add -h 2>&1|grep chmod
>         --chmod[=](+|-)x      override the executable bit of the listed files
> ...
> But the usage output stated that the "=" was mandatory before.

I am not sure if it is healthy to be _that_ strict when interpreting
the boilerplate elements in the output.  Between "git add -h" that
gives

    (1) git add --chmod( |=)(+|-)x
    (2) git add --chmod=(+|-)x

I would prefer the latter 10x as much as the former.  The choice
"You can give either plus or minus" is very much what the reader
must understand and it is worth reminding in the help.  Compared to
that, "You can use the stuck form that is recommended by gitcli
documentation when giving the argument to the --chmod option, or you
can give the argument to the option as a separate command line
argument", while technically correct, is not a choice that is worth
cluttering the output and making it harder to read.

To put it differently, the choice (+|-) is something the user needs
to pick correctly to make what they want to happen happen.  On the
other hand, the choice ( |=) is not.  As this is a boilerplate
choice that is shared by any and all options that take an argument,
once you are aware that stuck form is recommended but that separate
form is also accepted, you'd see "git add --chmod=blah" in the help
and would not hesitate to type "git add --chmod blah".  And if you
are not aware of the existence of the alternative, nothing is lost.
You can type '=' and see what you want to see happen happen.

Not cluttering the help text with an extra choice that the user does
not have to make has a value.



^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track documentation
  2022-01-20 12:35           ` [PATCH] branch,checkout: fix --track documentation René Scharfe
  2022-01-20 13:57             ` Ævar Arnfjörð Bjarmason
@ 2022-01-20 19:08             ` Junio C Hamano
  1 sibling, 0 replies; 103+ messages in thread
From: Junio C Hamano @ 2022-01-20 19:08 UTC (permalink / raw)
  To: René Scharfe
  Cc: Ævar Arnfjörð Bjarmason, Josh Steadmon, git, chooglen

René Scharfe <l.s.r@web.de> writes:

> Document that the accepted variants of the --track option are --track,
> --track=direct, and --track=inherit.  The equal sign in the latter two
> cannot be replaced with whitespace; in general optional arguments need
> to be attached firmly to their option.
>
> Put "direct" consistently before "inherit", if only for the reasons
> that the former is the default, explained first in the documentation,
> and comes before the latter alphabetically.

;-)  I see too many good reasons to modestly say "if only for" ;-)

> -'git branch' [--track [direct|inherit] | --no-track] [-f] <branchname> [<start-point>]
> +'git branch' [--track[=(direct|inherit)] | --no-track] [-f] <branchname> [<start-point>]

Good.

>  -t::
> ---track [inherit|direct]::
> +--track[=(direct|inherit)]::

Good.

> @@ -216,11 +216,11 @@ This option is only applicable in non-verbose mode.
>  	upstream when the new branch is checked out.
>  +
>  The exact upstream branch is chosen depending on the optional argument:
> -`--track` or `--track direct` means to use the start-point branch itself as the
> -upstream; `--track inherit` means to copy the upstream configuration of the
> -start-point branch.
> +`-t`, `--track`, or `--track=direct` means to use the start-point branch
> +itself as the upstream; `--track=inherit` means to copy the upstream
> +configuration of the start-point branch.

When "-x" and "--long-x" both do the same thing, we list both in the
heading but omit "-x" from the text, but in this case I fully agree
with the updated text as "-t" and "--track[=...]" work a bit
differently and there is no way to say "we want inherit" with "-t".

> -`--track direct` is the default when the start point is a remote-tracking branch.
> +`--track=direct` is the default when the start point is a remote-tracking branch.

Good.

> diff --git a/builtin/branch.c b/builtin/branch.c
> index 0c8d8a8827..4ce2a24754 100644
> --- a/builtin/branch.c
> +++ b/builtin/branch.c
> @@ -638,7 +638,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
>  		OPT__VERBOSE(&filter.verbose,
>  			N_("show hash and subject, give twice for upstream branch")),
>  		OPT__QUIET(&quiet, N_("suppress informational messages")),
> -		OPT_CALLBACK_F('t', "track",  &track, N_("mode"),
> +		OPT_CALLBACK_F('t', "track",  &track, "(direct|inherit)",
>  			N_("set branch tracking configuration"),
>  			PARSE_OPT_OPTARG,
>  			parse_opt_tracking_mode),

Good.

> diff --git a/builtin/checkout.c b/builtin/checkout.c
> index 6a5dd2a2a2..0bc2e63510 100644
> --- a/builtin/checkout.c
> +++ b/builtin/checkout.c
> @@ -1549,7 +1549,7 @@ static struct option *add_common_switch_branch_options(
>  {
>  	struct option options[] = {
>  		OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")),
> -		OPT_CALLBACK_F('t', "track",  &opts->track, N_("mode"),
> +		OPT_CALLBACK_F('t', "track",  &opts->track, "(direct|inherit)",
>  			N_("set branch tracking configuration"),
>  			PARSE_OPT_OPTARG,
>  			parse_opt_tracking_mode),

Good.

Thanks.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-20 18:25                 ` Junio C Hamano
@ 2022-01-21  9:42                   ` René Scharfe
  2022-01-21 20:59                     ` Junio C Hamano
  0 siblings, 1 reply; 103+ messages in thread
From: René Scharfe @ 2022-01-21  9:42 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Josh Steadmon, git, avarab

Am 20.01.22 um 19:25 schrieb Junio C Hamano:
> René Scharfe <l.s.r@web.de> writes:
>
>> Now that I read the whole comment, I think the right place to introduce
>> the automatic brackets is the description of argh some lines up.
>>
>> --- >8 ---
>> Subject: [PATCH 5/5] parse-options: document bracketing of argh
>>
>> Signed-off-by: René Scharfe <l.s.r@web.de>
>> ---
>>  parse-options.h | 5 +++++
>>  1 file changed, 5 insertions(+)
>>
>> diff --git a/parse-options.h b/parse-options.h
>> index e22846d3b7..88d589d159 100644
>> --- a/parse-options.h
>> +++ b/parse-options.h
>> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
>>   *   token to explain the kind of argument this option wants. Does not
>>   *   begin in capital letter, and does not end with a full stop.
>>   *   Should be wrapped by N_() for translation.
>> + *   Is automatically enclosed in brackets when printed, unless it
>> + *   contains any of the following characters: ()<>[]|
>> + *   E.g. "name" is shown as "<name>" to indicate that a name value
>> + *   needs to be supplied, not the literal string "name", but
>> + *   "<start>,<end>" and "(this|that)" are printed verbatim.
>>   *
>>   * `help`::
>>   *   the short help associated to what the option does.
>
> Very nice.
>
> This version gives the necessary information in (almost) one place.
>
> I said (almost) because "it contains any of ..." is not the only way
> to decline the <automatic angle brackets>, and am wondering if it is
> more helpful to say something like
>
> 	... when printed, unless PARSE_OPT_LITERAL_ARGHELP is set in
> 	the flags, or it contains any of the following characters: ...
>
> and then shrink the description of the flag bit to
>
>     PARSE_OPT_LITERAL_ARGHELP: controls if `argh` is enclosed in in
>     brackets when shown (see the description on `argh` above).
>

The idea was to document the base behavior at the top and the effects of
flags at the bottom.  Blurring this distinction and cross-referencing
gives a better whole, though, I agree.  It would have helped me find the
right place to put the list of special chars, for one thing.  So how
about this?

--- >8 ---
Subject: [PATCH v2] parse-options: document bracketing of argh

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
 parse-options.h | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/parse-options.h b/parse-options.h
index e22846d3b7..2e801b3c9a 100644
--- a/parse-options.h
+++ b/parse-options.h
@@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *   token to explain the kind of argument this option wants. Does not
  *   begin in capital letter, and does not end with a full stop.
  *   Should be wrapped by N_() for translation.
+ *   Is enclosed in brackets when printed, unless PARSE_OPT_LITERAL_ARGHELP
+ *   is set in `flags` or it contains any of the following characters: ()<>[]|
+ *   E.g. "name" is shown as "<name>" to indicate that a name value
+ *   needs to be supplied, not the literal string "name", but
+ *   "<start>,<end>" and "(this|that)" are printed verbatim.
  *
  * `help`::
  *   the short help associated to what the option does.
@@ -105,9 +110,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
  *				not last it will require an argument.
  *				Should not be used with PARSE_OPT_OPTARG.
  *   PARSE_OPT_NODASH: this option doesn't start with a dash.
- *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
- *				(i.e. '<argh>') in the help message.
- *				Useful for options with multiple parameters.
+ *   PARSE_OPT_LITERAL_ARGHELP: says that `argh` shouldn't be enclosed in
+ *				brackets (see `argh` description above).
  *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
  *			   by git-completion.bash. This option suppresses that.
  *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to
--
2.34.1

^ permalink raw reply related	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-20 18:38           ` Junio C Hamano
@ 2022-01-21 11:27             ` Ævar Arnfjörð Bjarmason
  2022-01-21 21:12               ` Junio C Hamano
  0 siblings, 1 reply; 103+ messages in thread
From: Ævar Arnfjörð Bjarmason @ 2022-01-21 11:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Josh Steadmon, git


On Thu, Jan 20 2022, Junio C Hamano wrote:

> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:
>
>> With that we'll now emit:
>>     
>>     $ ./git add -h 2>&1|grep chmod
>>         --chmod[=](+|-)x      override the executable bit of the listed files
>> ...
>> But the usage output stated that the "=" was mandatory before.
>
> I am not sure if it is healthy to be _that_ strict when interpreting
> the boilerplate elements in the output.  Between "git add -h" that
> gives
>
>     (1) git add --chmod( |=)(+|-)x
>     (2) git add --chmod=(+|-)x
>
> I would prefer the latter 10x as much as the former.  The choice
> "You can give either plus or minus" is very much what the reader
> must understand and it is worth reminding in the help.  Compared to
> that, "You can use the stuck form that is recommended by gitcli
> documentation when giving the argument to the --chmod option, or you
> can give the argument to the option as a separate command line
> argument", while technically correct, is not a choice that is worth
> cluttering the output and making it harder to read.
>
> To put it differently, the choice (+|-) is something the user needs
> to pick correctly to make what they want to happen happen.  On the
> other hand, the choice ( |=) is not.  As this is a boilerplate
> choice that is shared by any and all options that take an argument,
> once you are aware that stuck form is recommended but that separate
> form is also accepted, you'd see "git add --chmod=blah" in the help
> and would not hesitate to type "git add --chmod blah".  And if you
> are not aware of the existence of the alternative, nothing is lost.
> You can type '=' and see what you want to see happen happen.
>
> Not cluttering the help text with an extra choice that the user does
> not have to make has a value.

I.e. if we're not going for pedantic accuracy you'd prefer the below
diff instead?

I.e. when an option doesn't take an optional argument we're going out of
our way to say that you can omit the "=", but we can instead just
include it and have the the explanation in gitcli(7) about when "=" is
optional suffice.

Also, with the sh completion if you do "git add --chm<TAB>" it expands
it to "git add --chmod=", i.e. the cursor is left after the "=" that's
not shown in the "git add -h". So always including it would be
consistent with that.
    
    diff --git a/parse-options.c b/parse-options.c
    index a8283037be9..75c345bb738 100644
    --- a/parse-options.c
    +++ b/parse-options.c
    @@ -916,7 +916,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)
                    else
                            s = literal ? "[%s]" : "[<%s>]";
            else
    -               s = literal ? " %s" : " <%s>";
    +               s = literal ? "=%s" : "=<%s>";
            return utf8_fprintf(outfile, s, opts->argh ? _(opts->argh) : _("..."));
     }
     
Just this patch will make getopts test fail, because we'll need
e.g. this test_cmp adjusted:
    
    + diff -u expect output
    --- expect      2022-01-21 11:31:17.395492260 +0000
    +++ output      2022-01-21 11:31:17.395492260 +0000
    @@ -5,7 +5,7 @@
     
         -h, --help            show the help
         --foo                 some nifty option --foo
    -    --bar ...             some cool option --bar with an argument
    +    --bar=...             some cool option --bar with an argument
         -b, --baz             a short and long option
     
     An option group Header
    @@ -13,20 +13,20 @@
         -d, --data[=...]      short and long option with an optional argument
     
     Argument hints
    -    -B <arg>              short option required argument
    -    --bar2 <arg>          long option required argument
    -    -e, --fuz <with-space>
    +    -B=<arg>              short option required argument
    +    --bar2=<arg>          long option required argument
    +    -e, --fuz=<with-space>
                               short and long option required argument
         -s[<some>]            short option optional argument
         --long[=<data>]       long option optional argument
         -g, --fluf[=<path>]   short and long option optional argument
    -    --longest <very-long-argument-hint>
    +    --longest=<very-long-argument-hint>
                               a very long argument hint
    -    --pair <key=value>    with an equals sign in the hint
    +    --pair=<key=value>    with an equals sign in the hint
         --aswitch             help te=t contains? fl*g characters!`
    -    --bswitch <hint>      hint has trailing tab character
    +    --bswitch=<hint>      hint has trailing tab character
         --cswitch             switch has trailing tab character
    -    --short-hint <a>      with a one symbol hint
    +    --short-hint=<a>      with a one symbol hint

It's arguably a bit odd to see the "=" for those that have
!opts->long_name, but on the other hand I can't think of a reason other
than it looking unusual to me for why we wouldn't include the "=" there
for consistency.

It's odd that we don't have the short options in --git-completion-helper
at all, so "git am -C<tab>" isn't completed", but looking at
show_gitcomp() we'd need some further adjusting to make that work.

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP
  2022-01-21  9:42                   ` René Scharfe
@ 2022-01-21 20:59                     ` Junio C Hamano
  0 siblings, 0 replies; 103+ messages in thread
From: Junio C Hamano @ 2022-01-21 20:59 UTC (permalink / raw)
  To: René Scharfe; +Cc: Josh Steadmon, git, avarab

René Scharfe <l.s.r@web.de> writes:

> The idea was to document the base behavior at the top and the effects of
> flags at the bottom.  Blurring this distinction and cross-referencing
> gives a better whole, though, I agree.  It would have helped me find the
> right place to put the list of special chars, for one thing.  So how
> about this?
>
> --- >8 ---
> Subject: [PATCH v2] parse-options: document bracketing of argh
>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Signed-off-by: René Scharfe <l.s.r@web.de>
> ---
>  parse-options.h | 10 +++++++---
>  1 file changed, 7 insertions(+), 3 deletions(-)
>
> diff --git a/parse-options.h b/parse-options.h
> index e22846d3b7..2e801b3c9a 100644
> --- a/parse-options.h
> +++ b/parse-options.h
> @@ -85,6 +85,11 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
>   *   token to explain the kind of argument this option wants. Does not
>   *   begin in capital letter, and does not end with a full stop.
>   *   Should be wrapped by N_() for translation.
> + *   Is enclosed in brackets when printed, unless PARSE_OPT_LITERAL_ARGHELP
> + *   is set in `flags` or it contains any of the following characters: ()<>[]|
> + *   E.g. "name" is shown as "<name>" to indicate that a name value
> + *   needs to be supplied, not the literal string "name", but
> + *   "<start>,<end>" and "(this|that)" are printed verbatim.

Since "does not begin", "does not end", "should be wrapped" are
about how it appears in the initializer of the struct option, I had
to read twice before realizing that "Is enclosed" is *not* about
what the developer has to do.

Making it clear that this is not part of "enumeration of sentences,
which omit the same subject, that describe how the value appears in
the source", e.g.

	When "git cmd -h" shows it, `argh` is enclosed in
	<brackets>, unless ...

would have avoided the confusion.

Other than that, I think this version is the clearest and easiest to
understand among the options in this thread we have seen so far.

Thanks.


>   *
>   * `help`::
>   *   the short help associated to what the option does.
> @@ -105,9 +110,8 @@ typedef enum parse_opt_result parse_opt_ll_cb(struct parse_opt_ctx_t *ctx,
>   *				not last it will require an argument.
>   *				Should not be used with PARSE_OPT_OPTARG.
>   *   PARSE_OPT_NODASH: this option doesn't start with a dash.
> - *   PARSE_OPT_LITERAL_ARGHELP: says that argh shouldn't be enclosed in brackets
> - *				(i.e. '<argh>') in the help message.
> - *				Useful for options with multiple parameters.
> + *   PARSE_OPT_LITERAL_ARGHELP: says that `argh` shouldn't be enclosed in
> + *				brackets (see `argh` description above).
>   *   PARSE_OPT_NOCOMPLETE: by default all visible options are completable
>   *			   by git-completion.bash. This option suppresses that.
>   *   PARSE_OPT_COMP_ARG: this option forces to git-completion.bash to
> --
> 2.34.1

^ permalink raw reply	[flat|nested] 103+ messages in thread

* Re: [PATCH] branch,checkout: fix --track usage strings
  2022-01-21 11:27             ` Ævar Arnfjörð Bjarmason
@ 2022-01-21 21:12               ` Junio C Hamano
  0 siblings, 0 replies; 103+ messages in thread
From: Junio C Hamano @ 2022-01-21 21:12 UTC (permalink / raw)
  To: Ævar Arnfjörð Bjarmason; +Cc: Josh Steadmon, git

Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:

> Also, with the sh completion if you do "git add --chm<TAB>" it expands
> it to "git add --chmod=", i.e. the cursor is left after the "=" that's
> not shown in the "git add -h". So always including it would be
> consistent with that.
>     
>     diff --git a/parse-options.c b/parse-options.c
>     index a8283037be9..75c345bb738 100644
>     --- a/parse-options.c
>     +++ b/parse-options.c
>     @@ -916,7 +916,7 @@ static int usage_argh(const struct option *opts, FILE *outfile)
>                     else
>                             s = literal ? "[%s]" : "[<%s>]";
>             else
>     -               s = literal ? " %s" : " <%s>";
>     +               s = literal ? "=%s" : "=<%s>";

If the option has a long name, I think it is a good change.  I do
not offhand know if it is a good change for a short option, though.

    $ git diff -B=20/60
    error: break-rewrites expects <n>/<m> form
    $ git diff -B 20/60
    fatal: ambiguous argument '20/60': unknown revision or path not in the working tree.
    $ git diff -B20/60

gitcli.txt has this (I didn't check with the parse-options
implementation, though):

 * when a command-line option takes an argument, use the 'stuck' form.  In
   other words, write `git foo -oArg` instead of `git foo -o Arg` for short
   options, and `git foo --long-opt=Arg` instead of `git foo --long-opt Arg`
   for long options.  An option that takes optional option-argument must be
   written in the 'stuck' form.

So probably you'd need the same "show differently depending on which
between short and long we will show" on this side of the if/else.

	else {
		if (opts->long_name)
			s = literal ? "=%s" : "=<%s>";
		else
			s = literal ? "%s" : "<%s>";
	}

perhaps?

^ permalink raw reply	[flat|nested] 103+ messages in thread

end of thread, other threads:[~2022-01-21 21:12 UTC | newest]

Thread overview: 103+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-09-08 20:15 [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Josh Steadmon
2021-09-08 20:44 ` Josh Steadmon
2021-09-11  0:25 ` [PATCH v2] " Josh Steadmon
2021-09-11  0:52   ` Junio C Hamano
2021-10-17  4:35     ` Josh Steadmon
2021-10-17  5:50       ` Junio C Hamano
2021-11-15 21:57         ` Josh Steadmon
2021-10-17  4:45 ` [PATCH v3] branch: add flags and config to inherit tracking Josh Steadmon
2021-10-18 18:31   ` Ævar Arnfjörð Bjarmason
2021-10-18 21:44     ` Junio C Hamano
2021-10-18 22:11       ` Ævar Arnfjörð Bjarmason
2021-11-15 22:22     ` Josh Steadmon
2021-10-18 17:50 ` [RFC PATCH] branch: add "inherit" option for branch.autoSetupMerge Glen Choo
2021-10-18 18:08   ` Glen Choo
2021-11-15 21:44   ` Josh Steadmon
2021-11-16 18:25 ` [PATCH v4] branch: add flags and config to inherit tracking Josh Steadmon
2021-11-17  0:33   ` Glen Choo
2021-11-18 22:29   ` Junio C Hamano
2021-11-30 22:05     ` Josh Steadmon
2021-11-19  6:47   ` Ævar Arnfjörð Bjarmason
2021-11-30 21:34     ` Josh Steadmon
2021-12-01  9:11       ` Ævar Arnfjörð Bjarmason
2021-12-07  7:12 ` [PATCH v5 0/2] branch: inherit tracking configs Josh Steadmon
2021-12-07  7:12   ` [PATCH v5 1/2] branch: accept multiple upstream branches for tracking Josh Steadmon
2021-12-07  8:57     ` Ævar Arnfjörð Bjarmason
2021-12-09 23:03       ` Josh Steadmon
2021-12-10  1:00         ` Ævar Arnfjörð Bjarmason
2021-12-07 19:28     ` Junio C Hamano
2021-12-14 20:35       ` Josh Steadmon
2021-12-08  0:16     ` Glen Choo
2021-12-08  0:17     ` Glen Choo
2021-12-09 22:45       ` Josh Steadmon
2021-12-09 23:47         ` Glen Choo
2021-12-10  1:03           ` Ævar Arnfjörð Bjarmason
2021-12-10 17:32             ` Glen Choo
2021-12-11  2:18               ` Ævar Arnfjörð Bjarmason
2021-12-08 23:53     ` Glen Choo
2021-12-09  0:08       ` Glen Choo
2021-12-09 22:49         ` Josh Steadmon
2021-12-09 23:43           ` Glen Choo
2021-12-07  7:12   ` [PATCH v5 2/2] branch: add flags and config to inherit tracking Josh Steadmon
2021-12-07  9:08     ` Ævar Arnfjörð Bjarmason
2021-12-08  0:35       ` Glen Choo
2021-12-14 22:15         ` Josh Steadmon
2021-12-14 22:27       ` Josh Steadmon
2021-12-07 19:41     ` Junio C Hamano
2021-12-14 20:37       ` Josh Steadmon
2021-12-08  1:02     ` Glen Choo
2021-12-14 22:10       ` Josh Steadmon
2021-12-07 18:52   ` [PATCH v5 0/2] branch: inherit tracking configs Junio C Hamano
2021-12-08 17:06     ` Glen Choo
2021-12-10 22:48     ` Johannes Schindelin
2021-12-14 22:11       ` Josh Steadmon
2021-12-14 23:44 ` [PATCH v6 0/3] " Josh Steadmon
2021-12-14 23:44   ` [PATCH v6 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
2021-12-15 21:30     ` Junio C Hamano
2021-12-16 19:57     ` Glen Choo
2021-12-17  5:10       ` Josh Steadmon
2021-12-20 18:29         ` Glen Choo
2021-12-21  3:27           ` Josh Steadmon
2021-12-14 23:44   ` [PATCH v6 2/3] branch: add flags and config to inherit tracking Josh Steadmon
2021-12-16 21:27     ` Glen Choo
2021-12-17  5:11       ` Josh Steadmon
2021-12-14 23:44   ` [PATCH v6 3/3] config: require lowercase for branch.autosetupmerge Josh Steadmon
2021-12-15  0:43   ` [PATCH v6 0/3] branch: inherit tracking configs Josh Steadmon
2021-12-16  0:02   ` Junio C Hamano
2021-12-16  0:37     ` Glen Choo
2021-12-16  1:20       ` Junio C Hamano
2021-12-17  5:12 ` [PATCH v7 " Josh Steadmon
2021-12-17  5:12   ` [PATCH v7 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
2021-12-17  5:12   ` [PATCH v7 2/3] branch: add flags and config to inherit tracking Josh Steadmon
2021-12-17  5:12   ` [PATCH v7 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
2021-12-20 21:05   ` [PATCH v7 0/3] branch: inherit tracking configs Glen Choo
2021-12-21  3:30 ` [PATCH v8 " Josh Steadmon
2021-12-21  3:30   ` [PATCH v8 1/3] branch: accept multiple upstream branches for tracking Josh Steadmon
2021-12-21  6:55     ` Junio C Hamano
2021-12-21 18:25       ` Glen Choo
2021-12-21  3:30   ` [PATCH v8 2/3] branch: add flags and config to inherit tracking Josh Steadmon
2021-12-21 18:17     ` Glen Choo
2022-01-11  1:57     ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) Ævar Arnfjörð Bjarmason
2022-01-18 20:49       ` [PATCH] branch,checkout: fix --track usage strings Josh Steadmon
2022-01-18 22:26         ` Junio C Hamano
2022-01-19 10:56           ` [PATCH] parse-options: document automatic PARSE_OPT_LITERAL_ARGHELP René Scharfe
2022-01-19 14:41             ` Ævar Arnfjörð Bjarmason
     [not found]             ` <CA++g3E-azP3wFTtNkbFdmT7VW3hvULL0WkkAdwfrMb6HDtcXdg@mail.gmail.com>
2022-01-19 15:30               ` René Scharfe
2022-01-19 18:16             ` Junio C Hamano
2022-01-20 10:30               ` René Scharfe
2022-01-20 18:25                 ` Junio C Hamano
2022-01-21  9:42                   ` René Scharfe
2022-01-21 20:59                     ` Junio C Hamano
2022-01-20 12:05         ` [PATCH] branch,checkout: fix --track usage strings Ævar Arnfjörð Bjarmason
2022-01-20 12:18           ` Andreas Schwab
2022-01-20 14:00             ` Ævar Arnfjörð Bjarmason
2022-01-20 18:38           ` Junio C Hamano
2022-01-21 11:27             ` Ævar Arnfjörð Bjarmason
2022-01-21 21:12               ` Junio C Hamano
2022-01-19 10:20       ` incorrect 'git (checkout|branch) -h' output with new --track modes (was: [PATCH v8 2/3] branch: add flags and config to inherit tracking) René Scharfe
2022-01-20 12:00         ` Ævar Arnfjörð Bjarmason
2022-01-20 12:35           ` [PATCH] branch,checkout: fix --track documentation René Scharfe
2022-01-20 13:57             ` Ævar Arnfjörð Bjarmason
2022-01-20 19:08             ` Junio C Hamano
2021-12-21  3:30   ` [PATCH v8 3/3] config: require lowercase for branch.*.autosetupmerge Josh Steadmon
2021-12-21 18:13   ` [PATCH v8 0/3] branch: inherit tracking configs Glen Choo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.