threads / patch / 58584

patchsymbolic-ref: teach "--[no-]recurse" option

Subject: [PATCH] symbolic-ref: teach "--[no-]recurse" option

## tl;dr

13 messages between Oct 7, 2022 and Oct 11, 2022. Diffs are folded; open one to read it.

replies: 12people: 4as markdown or json

Junio C Hamano· Oct 7, 2022, 22:00 UTC · lore

Suppose you are managing many maintenance tracks in your project, and some of the more recent ones are maint-2.36 and maint-2.37. Further imagine that your project recently tagged the official 2.38 release, which means you would need to start maint-2.38 track soon, by doing:

  $ git checkout -b maint-2.38 v2.38.0^0
  $ git branch --list 'maint-2.3[6-9]'
  * maint-2.38
    maint-2.36
    maint-2.37

So far, so good. But it also is reasonable to want not to have to worry about which maintenance track is the latest, by pointing a more generic-sounding 'maint' branch at it, by doing:

  $ git symbolic-ref refs/heads/maint refs/heads/maint-2.38

which would allow you to say "whichever it is, check out the latest maintenance track", by doing:

  $ git checkout maint
  $ git branch --show-current
  maint-2.38

It is arguably better to say that we are on 'maint-2.38' rather than on 'maint', and "git merge/pull" would record "into maint-2.38" and not "into maint", so I think what we have is a good behaviour.

One thing that is slightly irritating, however, is that I do not think there is a good way (other than "cat .git/HEAD") to learn that you checked out 'maint' to get into that state. Just like the output of "git branch --show-current" shows above, "git symbolic-ref HEAD" would report 'refs/heads/maint-2.38', bypassing the intermediate symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.

The internal resolve_ref() API already has the necessary support for stopping after resolving a single level of a symbolic-ref, and we can expose it by adding a "--[no-]recurse" option to the command.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 builtin/symbolic-ref.c  | 16 ++++++++++------
 t/t1401-symbolic-ref.sh | 14 ++++++++++++++
 2 files changed, 24 insertions(+), 6 deletions(-)
Show changes to diff +24 −6
diff --git c/builtin/symbolic-ref.c w/builtin/symbolic-ref.c
index 1b0f10225f..5c9de63267 100644
--- c/builtin/symbolic-ref.c
+++ w/builtin/symbolic-ref.c
@@ -10,10 +10,13 @@ static const char * const git_symbolic_ref_usage[] = {
 	NULL
 };
 
-static int check_symref(const char *HEAD, int quiet, int shorten, int print)
+static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, int print)
 {
-	int flag;
-	const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
+	int resolve_flags, flag;
+	const char *refname;
+
+	resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE);
+	refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag);
 
 	if (!refname)
 		die("No such ref: %s", HEAD);
@@ -35,13 +38,14 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 {
-	int quiet = 0, delete = 0, shorten = 0, ret = 0;
+	int quiet = 0, delete = 0, shorten = 0, recurse = 1, ret = 0;
 	const char *msg = NULL;
 	struct option options[] = {
 		OPT__QUIET(&quiet,
 			N_("suppress error message for non-symbolic (detached) refs")),
 		OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
 		OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
+		OPT_BOOL(0, "recurse", &recurse, N_("recursively dereference (default)")),
 		OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
 		OPT_END(),
 	};
@@ -55,7 +59,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 	if (delete) {
 		if (argc != 1)
 			usage_with_options(git_symbolic_ref_usage, options);
-		ret = check_symref(argv[0], 1, 0, 0);
+		ret = check_symref(argv[0], 1, 0, 0, 0);
 		if (ret)
 			die("Cannot delete %s, not a symbolic ref", argv[0]);
 		if (!strcmp(argv[0], "HEAD"))
@@ -65,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 
 	switch (argc) {
 	case 1:
-		ret = check_symref(argv[0], quiet, shorten, 1);
+		ret = check_symref(argv[0], quiet, shorten, recurse, 1);
 		break;
 	case 2:
 		if (!strcmp(argv[0], "HEAD") &&
diff --git c/t/t1401-symbolic-ref.sh w/t/t1401-symbolic-ref.sh
index 0c204089b8..d708acdb81 100755
--- c/t/t1401-symbolic-ref.sh
+++ w/t/t1401-symbolic-ref.sh
@@ -175,4 +175,18 @@ test_expect_success 'symbolic-ref allows top-level target for non-HEAD' '
 	test_cmp_rev top-level HEAD
 '
 
+test_expect_success 'symbolic-ref pointing at another' '
+	git update-ref refs/heads/maint-2.37 HEAD &&
+	git symbolic-ref refs/heads/maint refs/heads/maint-2.37 &&
+	git checkout maint &&
+
+	git symbolic-ref HEAD >actual &&
+	echo refs/heads/maint-2.37 >expect &&
+	test_cmp expect actual &&
+
+	git symbolic-ref --no-recurse HEAD >actual &&
+	echo refs/heads/maint >expect &&
+	test_cmp expect actual
+'
+
 test_done
Eric Sunshine· Oct 8, 2022, 03:35 UTC · re: Junio C Hamano · lore

Re: [PATCH] symbolic-ref: teach "--[no-]recurse" option

On Fri, Oct 7, 2022 at 6:19 PM Junio C Hamano <gitster@pobox.com> wrote:
Show 17 quoted lines
> [...]
> One thing that is slightly irritating, however, is that I do not
> think there is a good way (other than "cat .git/HEAD") to learn that
> you checked out 'maint' to get into that state.  Just like the output
> of "git branch --show-current" shows above, "git symbolic-ref HEAD"
> would report 'refs/heads/maint-2.38', bypassing the intermediate
> symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.
>
> The internal resolve_ref() API already has the necessary support for
> stopping after resolving a single level of a symbolic-ref, and we
> can expose it by adding a "--[no-]recurse" option to the command.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>  builtin/symbolic-ref.c  | 16 ++++++++++------
>  t/t1401-symbolic-ref.sh | 14 ++++++++++++++
>  2 files changed, 24 insertions(+), 6 deletions(-)

Should this be accompanied by a documentation update or is the patch intended as an RFC?

Junio C Hamano· Oct 8, 2022, 04:20 UTC · re: Eric Sunshine · lore

Re: [PATCH] symbolic-ref: teach "--[no-]recurse" option

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 21 quoted lines
> On Fri, Oct 7, 2022 at 6:19 PM Junio C Hamano <gitster@pobox.com> wrote:
>> [...]
>> One thing that is slightly irritating, however, is that I do not
>> think there is a good way (other than "cat .git/HEAD") to learn that
>> you checked out 'maint' to get into that state.  Just like the output
>> of "git branch --show-current" shows above, "git symbolic-ref HEAD"
>> would report 'refs/heads/maint-2.38', bypassing the intermediate
>> symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.
>>
>> The internal resolve_ref() API already has the necessary support for
>> stopping after resolving a single level of a symbolic-ref, and we
>> can expose it by adding a "--[no-]recurse" option to the command.
>>
>> Signed-off-by: Junio C Hamano <gitster@pobox.com>
>> ---
>>  builtin/symbolic-ref.c  | 16 ++++++++++------
>>  t/t1401-symbolic-ref.sh | 14 ++++++++++++++
>>  2 files changed, 24 insertions(+), 6 deletions(-)
>
> Should this be accompanied by a documentation update or is the patch
> intended as an RFC?

It started its life as an RFC but I had an actual need during today's integration run ;-). Will try to come up with a documentation patch when I have time.

Thanks.
Junio C Hamano· Oct 8, 2022, 04:34 UTC · re: Junio C Hamano · lore

[PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Suppose you are managing many maintenance tracks in your project, and some of the more recent ones are maint-2.36 and maint-2.37. Further imagine that your project recently tagged the official 2.38 release, which means you would need to start maint-2.38 track soon, by doing:

  $ git checkout -b maint-2.38 v2.38.0^0
  $ git branch --list 'maint-2.3[6-9]'
  * maint-2.38
    maint-2.36
    maint-2.37

So far, so good. But it also is reasonable to want not to have to worry about which maintenance track is the latest, by pointing a more generic-sounding 'maint' branch at it, by doing:

  $ git symbolic-ref refs/heads/maint refs/heads/maint-2.38

which would allow you to say "whichever it is, check out the latest maintenance track", by doing:

  $ git checkout maint
  $ git branch --show-current
  maint-2.38

It is arguably better to say that we are on 'maint-2.38' rather than on 'maint', and "git merge/pull" would record "into maint-2.38" and not "into maint", so I think what we have is a good behaviour.

One thing that is slightly irritating, however, is that I do not think there is a good way (other than "cat .git/HEAD") to learn that you checked out 'maint' to get into that state. Just like the output of "git branch --show-current" shows above, "git symbolic-ref HEAD" would report 'refs/heads/maint-2.38', bypassing the intermediate symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.

The internal resolve_ref() API already has the necessary support for stopping after resolving a single level of a symbolic-ref, and we can expose it by adding a "--[no-]recurse" option to the command.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * Updates documentation, which was entirely lacking in v1, with no
   other changes.
 Documentation/git-symbolic-ref.txt |  9 +++++++++
 builtin/symbolic-ref.c             | 16 ++++++++++------
 t/t1401-symbolic-ref.sh            | 14 ++++++++++++++
 3 files changed, 33 insertions(+), 6 deletions(-)
Show changes to 3 files +33 −6

Documentation/git-symbolic-ref.txt, builtin/symbolic-ref.c, t/t1401-symbolic-ref.sh

diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index ef68ad2b71..3b5a97ea2b 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -46,6 +46,15 @@ OPTIONS
 	When showing the value of <name> as a symbolic ref, try to shorten the
 	value, e.g. from `refs/heads/master` to `master`.
 
+--recurse::
+--no-recurse::
+	When showing the value of <name> as a symbolic ref, if
+	<name> refers to another symbolic ref, follow such a chain
+	of symbolic refs until the result no longer points at a
+	symbolic ref (`--recurse`, which is the default).
+	`--no-recurse` stops after dereferencing only a single level
+	of symbolic ref.
+
 -m::
 	Update the reflog for <name> with <reason>.  This is valid only
 	when creating or updating a symbolic ref.
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index e547a08d6c..1af27b3a68 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -10,10 +10,13 @@ static const char * const git_symbolic_ref_usage[] = {
 	NULL
 };
 
-static int check_symref(const char *HEAD, int quiet, int shorten, int print)
+static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, int print)
 {
-	int flag;
-	const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
+	int resolve_flags, flag;
+	const char *refname;
+
+	resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE);
+	refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag);
 
 	if (!refname)
 		die("No such ref: %s", HEAD);
@@ -35,13 +38,14 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 {
-	int quiet = 0, delete = 0, shorten = 0, ret = 0;
+	int quiet = 0, delete = 0, shorten = 0, recurse = 1, ret = 0;
 	const char *msg = NULL;
 	struct option options[] = {
 		OPT__QUIET(&quiet,
 			N_("suppress error message for non-symbolic (detached) refs")),
 		OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
 		OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
+		OPT_BOOL(0, "recurse", &recurse, N_("recursively dereference (default)")),
 		OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
 		OPT_END(),
 	};
@@ -55,7 +59,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 	if (delete) {
 		if (argc != 1)
 			usage_with_options(git_symbolic_ref_usage, options);
-		ret = check_symref(argv[0], 1, 0, 0);
+		ret = check_symref(argv[0], 1, 0, 0, 0);
 		if (ret)
 			die("Cannot delete %s, not a symbolic ref", argv[0]);
 		if (!strcmp(argv[0], "HEAD"))
@@ -65,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 
 	switch (argc) {
 	case 1:
-		ret = check_symref(argv[0], quiet, shorten, 1);
+		ret = check_symref(argv[0], quiet, shorten, recurse, 1);
 		break;
 	case 2:
 		if (!strcmp(argv[0], "HEAD") &&
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
index 132a1b885a..773a6e8e01 100755
--- a/t/t1401-symbolic-ref.sh
+++ b/t/t1401-symbolic-ref.sh
@@ -163,4 +163,18 @@ test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' '
 	test_cmp expect actual
 '
 
+test_expect_success 'symbolic-ref pointing at another' '
+	git update-ref refs/heads/maint-2.37 HEAD &&
+	git symbolic-ref refs/heads/maint refs/heads/maint-2.37 &&
+	git checkout maint &&
+
+	git symbolic-ref HEAD >actual &&
+	echo refs/heads/maint-2.37 >expect &&
+	test_cmp expect actual &&
+
+	git symbolic-ref --no-recurse HEAD >actual &&
+	echo refs/heads/maint >expect &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.38.0-165-ga5df27c8e1
Eric Sunshine· Oct 8, 2022, 04:54 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

On Sat, Oct 8, 2022 at 12:34 AM Junio C Hamano <gitster@pobox.com> wrote:
Show 20 quoted lines
> [...]
> One thing that is slightly irritating, however, is that I do not
> think there is a good way (other than "cat .git/HEAD") to learn that
> you checked out 'maint' to get into that state.  Just like the output
> of "git branch --show-current" shows above, "git symbolic-ref HEAD"
> would report 'refs/heads/maint-2.38', bypassing the intermediate
> symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.
> [...]
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
> diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
> @@ -46,6 +46,15 @@ OPTIONS
> +--recurse::
> +--no-recurse::
> +       When showing the value of <name> as a symbolic ref, if
> +       <name> refers to another symbolic ref, follow such a chain
> +       of symbolic refs until the result no longer points at a
> +       symbolic ref (`--recurse`, which is the default).
> +       `--no-recurse` stops after dereferencing only a single level
> +       of symbolic ref.

With his recent documentation-normalization work, I suspect Ævar would appreciate an update to the synopsis, as well:

    SYNOPSIS
    --------
    [verse]
    'git symbolic-ref' [-m <reason>] <name> <ref>
   -'git symbolic-ref' [-q] [--short] <name>
   +'git symbolic-ref' [-q] [--short] [--no-recurse] <name>
    'git symbolic-ref' --delete [-q] <name>

or something similar (i.e. plain [--recurse] or composite [--[no-]-recurse] or [--recurse | --no-recurse]).

Junio C Hamano· Oct 9, 2022, 04:41 UTC · re: Eric Sunshine · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Eric Sunshine <sunshine@sunshineco.com> writes:
Show 13 quoted lines
> With his recent documentation-normalization work, I suspect Ævar would
> appreciate an update to the synopsis, as well:
>
>     SYNOPSIS
>     --------
>     [verse]
>     'git symbolic-ref' [-m <reason>] <name> <ref>
>    -'git symbolic-ref' [-q] [--short] <name>
>    +'git symbolic-ref' [-q] [--short] [--no-recurse] <name>
>     'git symbolic-ref' --delete [-q] <name>
>
> or something similar (i.e. plain [--recurse] or composite
> [--[no-]-recurse] or [--recurse | --no-recurse]).
Yeah, that looks good.

I checked the in-code usage that used [<options>] (hence I didn't have to do anything new), but apparently forgot about the doc.

As there is (and there will ever be) no configuration knob for this plumbing command to make the non-recursive behaviour the default, I think listing [--no-recurse] alone, not [--[no-]recurse], would be the right thing to do in the documentation.

Thanks.
Bagas Sanjaya· Oct 8, 2022, 09:37 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

On 10/8/22 11:34, Junio C Hamano wrote:
Show 40 quoted lines
> Suppose you are managing many maintenance tracks in your project,
> and some of the more recent ones are maint-2.36 and maint-2.37.
> Further imagine that your project recently tagged the official 2.38
> release, which means you would need to start maint-2.38 track soon,
> by doing:
> 
>   $ git checkout -b maint-2.38 v2.38.0^0
>   $ git branch --list 'maint-2.3[6-9]'
>   * maint-2.38
>     maint-2.36
>     maint-2.37
> 
> So far, so good.  But it also is reasonable to want not to have to
> worry about which maintenance track is the latest, by pointing a
> more generic-sounding 'maint' branch at it, by doing:
> 
>   $ git symbolic-ref refs/heads/maint refs/heads/maint-2.38
> 
> which would allow you to say "whichever it is, check out the latest
> maintenance track", by doing:
> 
>   $ git checkout maint
>   $ git branch --show-current
>   maint-2.38
> 
> It is arguably better to say that we are on 'maint-2.38' rather than
> on 'maint', and "git merge/pull" would record "into maint-2.38" and
> not "into maint", so I think what we have is a good behaviour.
> 
> One thing that is slightly irritating, however, is that I do not
> think there is a good way (other than "cat .git/HEAD") to learn that
> you checked out 'maint' to get into that state.  Just like the output
> of "git branch --show-current" shows above, "git symbolic-ref HEAD"
> would report 'refs/heads/maint-2.38', bypassing the intermediate
> symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.
> 
> The internal resolve_ref() API already has the necessary support for
> stopping after resolving a single level of a symbolic-ref, and we
> can expose it by adding a "--[no-]recurse" option to the command.
> 
The example case above is from recent Git releases, right?

I think the wording should instead use generalized version numbers. For example the still maintained release tracks are X.Y-1 and X.Y, but now X+1 have been tagged, which creates X+1.Y track.

Thanks.
-- 
An old man doll... just what I always wanted! - Clara
Junio C Hamano· Oct 9, 2022, 12:47 UTC · re: Bagas Sanjaya · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Bagas Sanjaya <bagasdotme@gmail.com> writes:
Show 5 quoted lines
> On 10/8/22 11:34, Junio C Hamano wrote:
>> Suppose you are managing many maintenance tracks in your project,
>> and some of the more recent ones are maint-2.36 and maint-2.37.
>
> The example case above is from recent Git releases, right?

It is left to reader's imagination. Concrete numbers are easier to understand, as it would be clear that 2.36 comes before 2.37 which comes before 2.38.

Philip Oakley· Oct 8, 2022, 12:28 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Hi Junio
On 08/10/2022 05:34, Junio C Hamano wrote:
Show 47 quoted lines
> Suppose you are managing many maintenance tracks in your project,
> and some of the more recent ones are maint-2.36 and maint-2.37.
> Further imagine that your project recently tagged the official 2.38
> release, which means you would need to start maint-2.38 track soon,
> by doing:
>
>   $ git checkout -b maint-2.38 v2.38.0^0
>   $ git branch --list 'maint-2.3[6-9]'
>   * maint-2.38
>     maint-2.36
>     maint-2.37
>
> So far, so good.  But it also is reasonable to want not to have to
> worry about which maintenance track is the latest, by pointing a
> more generic-sounding 'maint' branch at it, by doing:
>
>   $ git symbolic-ref refs/heads/maint refs/heads/maint-2.38
>
> which would allow you to say "whichever it is, check out the latest
> maintenance track", by doing:
>
>   $ git checkout maint
>   $ git branch --show-current
>   maint-2.38
>
> It is arguably better to say that we are on 'maint-2.38' rather than
> on 'maint', and "git merge/pull" would record "into maint-2.38" and
> not "into maint", so I think what we have is a good behaviour.
>
> One thing that is slightly irritating, however, is that I do not
> think there is a good way (other than "cat .git/HEAD") to learn that
> you checked out 'maint' to get into that state.  Just like the output
> of "git branch --show-current" shows above, "git symbolic-ref HEAD"
> would report 'refs/heads/maint-2.38', bypassing the intermediate
> symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.
>
> The internal resolve_ref() API already has the necessary support for
> stopping after resolving a single level of a symbolic-ref, and we
> can expose it by adding a "--[no-]recurse" option to the command.
>
> Signed-off-by: Junio C Hamano <gitster@pobox.com>
> ---
>
>  * Updates documentation, which was entirely lacking in v1, with no
>    other changes.
>
>  Documentation/git-symbolic-ref.txt |  9 +++++++++

Is there also a need to update SubmittingPatches to clarify the distinctions between the generic name 'maint', the specific `maint-xx` branches, and the symbolic ref linkages from `maint`.

The existence of the `maint-xx` branches, and where they came from, was something that eluded me for a long time.

The descriptions give the impression there is just a single main branch, with a singular name, and that was the end of it. It was only much later when some security patches were pushed down to numerous older `maint-xx` branches. The use of the singular `maint` is also used in examples a number of places further cementing the singular maintenance view.

A few words clarifying the `maint` vs `maint-x.yy` branches could help users past the confusion. -- Philip

Show 102 quoted lines
>  builtin/symbolic-ref.c             | 16 ++++++++++------
>  t/t1401-symbolic-ref.sh            | 14 ++++++++++++++
>  3 files changed, 33 insertions(+), 6 deletions(-)
>
> diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
> index ef68ad2b71..3b5a97ea2b 100644
> --- a/Documentation/git-symbolic-ref.txt
> +++ b/Documentation/git-symbolic-ref.txt
> @@ -46,6 +46,15 @@ OPTIONS
>  	When showing the value of <name> as a symbolic ref, try to shorten the
>  	value, e.g. from `refs/heads/master` to `master`.
>  
> +--recurse::
> +--no-recurse::
> +	When showing the value of <name> as a symbolic ref, if
> +	<name> refers to another symbolic ref, follow such a chain
> +	of symbolic refs until the result no longer points at a
> +	symbolic ref (`--recurse`, which is the default).
> +	`--no-recurse` stops after dereferencing only a single level
> +	of symbolic ref.
> +
>  -m::
>  	Update the reflog for <name> with <reason>.  This is valid only
>  	when creating or updating a symbolic ref.
> diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
> index e547a08d6c..1af27b3a68 100644
> --- a/builtin/symbolic-ref.c
> +++ b/builtin/symbolic-ref.c
> @@ -10,10 +10,13 @@ static const char * const git_symbolic_ref_usage[] = {
>  	NULL
>  };
>  
> -static int check_symref(const char *HEAD, int quiet, int shorten, int print)
> +static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, int print)
>  {
> -	int flag;
> -	const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
> +	int resolve_flags, flag;
> +	const char *refname;
> +
> +	resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE);
> +	refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag);
>  
>  	if (!refname)
>  		die("No such ref: %s", HEAD);
> @@ -35,13 +38,14 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
>  
>  int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
>  {
> -	int quiet = 0, delete = 0, shorten = 0, ret = 0;
> +	int quiet = 0, delete = 0, shorten = 0, recurse = 1, ret = 0;
>  	const char *msg = NULL;
>  	struct option options[] = {
>  		OPT__QUIET(&quiet,
>  			N_("suppress error message for non-symbolic (detached) refs")),
>  		OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
>  		OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
> +		OPT_BOOL(0, "recurse", &recurse, N_("recursively dereference (default)")),
>  		OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
>  		OPT_END(),
>  	};
> @@ -55,7 +59,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
>  	if (delete) {
>  		if (argc != 1)
>  			usage_with_options(git_symbolic_ref_usage, options);
> -		ret = check_symref(argv[0], 1, 0, 0);
> +		ret = check_symref(argv[0], 1, 0, 0, 0);
>  		if (ret)
>  			die("Cannot delete %s, not a symbolic ref", argv[0]);
>  		if (!strcmp(argv[0], "HEAD"))
> @@ -65,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
>  
>  	switch (argc) {
>  	case 1:
> -		ret = check_symref(argv[0], quiet, shorten, 1);
> +		ret = check_symref(argv[0], quiet, shorten, recurse, 1);
>  		break;
>  	case 2:
>  		if (!strcmp(argv[0], "HEAD") &&
> diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
> index 132a1b885a..773a6e8e01 100755
> --- a/t/t1401-symbolic-ref.sh
> +++ b/t/t1401-symbolic-ref.sh
> @@ -163,4 +163,18 @@ test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' '
>  	test_cmp expect actual
>  '
>  
> +test_expect_success 'symbolic-ref pointing at another' '
> +	git update-ref refs/heads/maint-2.37 HEAD &&
> +	git symbolic-ref refs/heads/maint refs/heads/maint-2.37 &&
> +	git checkout maint &&
> +
> +	git symbolic-ref HEAD >actual &&
> +	echo refs/heads/maint-2.37 >expect &&
> +	test_cmp expect actual &&
> +
> +	git symbolic-ref --no-recurse HEAD >actual &&
> +	echo refs/heads/maint >expect &&
> +	test_cmp expect actual
> +'
> +
>  test_done
Philip Oakley· Oct 8, 2022, 15:28 UTC · re: Philip Oakley · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

spelling correction
On 08/10/2022 13:28, Philip Oakley wrote:
> The existence of the `maint-xx` branches, and where they came from, was
> something that eluded me for a long time.
>
> The descriptions give the impression there is just a single main branch,
s/main/`maint`/ 
Show 7 quoted lines
> with a singular name, and that was the end of it. It was only much later
> when some security patches were pushed down to numerous older `maint-xx`
> branches. The use of the singular `maint` is also used in examples a
> number of places further cementing the singular maintenance view.
>
> A few words clarifying the `maint` vs `maint-x.yy` branches could help
> users past the confusion.
Junio C Hamano· Oct 9, 2022, 12:07 UTC · re: Philip Oakley · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Philip Oakley <philipoakley@iee.email> writes:
> Is there also a need to update SubmittingPatches to clarify the
> distinctions between the generic name 'maint', the specific `maint-xx`
> branches, and the symbolic ref linkages from `maint`.

Not at all. Perhaps you are thinking about MaintNotes, but even there, general public do not have to be concerned about maint-xx most of the time. I myself would not be keeping branches for ancient maintenance tracks anywhere. As the older maintenance tracks, other than the most recent ones, do not get topics graduated to 'master' merged down by me (even though topic branches that fix notable bugs may fork from an older version to allow motivated third party maintainers to merge them down to older maintenance releases), there is not really a reason to keep branches like maint-2.15 for daily operation. Only when we need security updates that go back to older maintenance tracks, I may do

    $ git checkout -b maint-2.30 v2.30.5
    $ git merge cve-something-fix
    ... edit release notes etc. ...
    $ git commit -m 'Git 2.30.6'
    $ git tag -s -m 'Git 2.30.6' v2.30.6

but after that is done, there is no reason to keep an old branch like maint-2.30 lying around, other than to make it easier to prepare for v2.30.7 (as maint-2.30 will remember what tag was the latest on that particular maintenance track).

> The descriptions give the impression there is just a single main branch,
> with a singular name, and that was the end of it.
And that is the correct world view for most of the  general public.
    https://github.com/git/git/

shows 'maint', 'master' ('main'), 'next', and 'seen' from the project code, plus 'todo' which holds an unrelated history that keeps track of maintenance tools and misc stuff.

By the way, I do not personally have 'main' in my working repository. The only trick I have to help folks who expect to see 'main' instead of 'master' is the push refspec to distribute the integration results to various repositories. My 'master' is pushed to both 'master' and 'main' in destination repositories.

The mirror/backup repository of my private working repository does have tentive branches, like the broken-out topic branches that are still active, which are pruned when they no longer are needed. Some maint-xx branches are also there but they are there not because they are necessary but because I just haven't bothered to clean them up ;-)

Philip Oakley· Oct 11, 2022, 14:49 UTC · re: Junio C Hamano · lore

Re: [PATCH v2] symbolic-ref: teach "--[no-]recurse" option

Hi Junio,
On 09/10/2022 13:07, Junio C Hamano wrote:
Show 51 quoted lines
> Philip Oakley <philipoakley@iee.email> writes:
>
>> Is there also a need to update SubmittingPatches to clarify the
>> distinctions between the generic name 'maint', the specific `maint-xx`
>> branches, and the symbolic ref linkages from `maint`.
> Not at all.  Perhaps you are thinking about MaintNotes, but even
> there, general public do not have to be concerned about maint-xx
> most of the time.  I myself would not be keeping branches for
> ancient maintenance tracks anywhere.  As the older maintenance
> tracks, other than the most recent ones, do not get topics graduated
> to 'master' merged down by me (even though topic branches that fix
> notable bugs may fork from an older version to allow motivated third
> party maintainers to merge them down to older maintenance releases),
> there is not really a reason to keep branches like maint-2.15 for
> daily operation.  Only when we need security updates that go back to
> older maintenance tracks, I may do
>
>     $ git checkout -b maint-2.30 v2.30.5
>     $ git merge cve-something-fix
>     ... edit release notes etc. ...
>     $ git commit -m 'Git 2.30.6'
>     $ git tag -s -m 'Git 2.30.6' v2.30.6
>
> but after that is done, there is no reason to keep an old branch
> like maint-2.30 lying around, other than to make it easier to
> prepare for v2.30.7 (as maint-2.30 will remember what tag was the
> latest on that particular maintenance track).
>
>> The descriptions give the impression there is just a single main branch,
>> with a singular name, and that was the end of it.
> And that is the correct world view for most of the  general public.
>
>     https://github.com/git/git/
>
> shows 'maint', 'master' ('main'), 'next', and 'seen' from the
> project code, plus 'todo' which holds an unrelated history that
> keeps track of maintenance tools and misc stuff.
>
> By the way, I do not personally have 'main' in my working
> repository.  The only trick I have to help folks who expect to see
> 'main' instead of 'master' is the push refspec to distribute the
> integration results to various repositories.  My 'master' is pushed
> to both 'master' and 'main' in destination repositories.
>
> The mirror/backup repository of my private working repository does
> have tentive branches, like the broken-out topic branches that are
> still active, which are pruned when they no longer are needed.
> Some maint-xx branches are also there but they are there not because
> they are necessary but because I just haven't bothered to clean them
> up ;-)
>
Thanks for the clarification.

My confusion about `maint` started quite a few years back and may be a mixture of a number of different issues, or simply my misunderstanding.

I'm guessing here, but it may have been, that back then, that the maint branch was a link to the relevant maint-xx, rather than a 'symref' and that at that time the Git-for-Windows didn't really handle them, so `maint` may not have shown for me (similar to how RelNotes is configured in the Git repo).

It may also have been early confusion about remotes and their branches, and the impression that I needed (or should expect) a local branch of the same name and this wasn't happening (obvious in retrospect), so I never found any `maint` in my repo.

The whole 'remote tracking branches' stuff took a long time to understand and was non-obvious from my perspective. I suspect many newbies have similar issues (e.g. the current thread https://lore.kernel.org/git/DU2P194MB15841850A17436C7AE34C149E3239@DU2P194MB1584.EURP194.PROD.OUTLOOK.COM/T/#t)

-- Philip

Junio C Hamano· Oct 9, 2022, 22:13 UTC · re: Junio C Hamano · lore

[PATCH v3] symbolic-ref: teach "--[no-]recurse" option

Suppose you are managing many maintenance tracks in your project, and some of the more recent ones are maint-2.36 and maint-2.37. Further imagine that your project recently tagged the official 2.38 release, which means you would need to start maint-2.38 track soon, by doing:

  $ git checkout -b maint-2.38 v2.38.0^0
  $ git branch --list 'maint-2.3[6-9]'
  * maint-2.38
    maint-2.36
    maint-2.37

So far, so good. But it also is reasonable to want not to have to worry about which maintenance track is the latest, by pointing a more generic-sounding 'maint' branch at it, by doing:

  $ git symbolic-ref refs/heads/maint refs/heads/maint-2.38

which would allow you to say "whichever it is, check out the latest maintenance track", by doing:

  $ git checkout maint
  $ git branch --show-current
  maint-2.38

It is arguably better to say that we are on 'maint-2.38' rather than on 'maint', and "git merge/pull" would record "into maint-2.38" and not "into maint", so I think what we have is a good behaviour.

One thing that is slightly irritating, however, is that I do not think there is a good way (other than "cat .git/HEAD") to learn that you checked out 'maint' to get into that state. Just like the output of "git branch --show-current" shows above, "git symbolic-ref HEAD" would report 'refs/heads/maint-2.38', bypassing the intermediate symbolic ref at 'refs/heads/maint' that is pointed at by HEAD.

The internal resolve_ref() API already has the necessary support for stopping after resolving a single level of a symbolic-ref, and we can expose it by adding a "--[no-]recurse" option to the command.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
 * The third time is the charm?  The only change since v2 is to make
   sure that Ævar's "doc SYNOPSIS and cmd -h help must match" topic
   is not upset with the new option.
 Documentation/git-symbolic-ref.txt | 11 ++++++++++-
 builtin/symbolic-ref.c             | 18 +++++++++++-------
 t/t1401-symbolic-ref.sh            | 14 ++++++++++++++
 3 files changed, 35 insertions(+), 8 deletions(-)
Show changes to 3 files +35 −8

Documentation/git-symbolic-ref.txt, builtin/symbolic-ref.c, t/t1401-symbolic-ref.sh

diff --git a/Documentation/git-symbolic-ref.txt b/Documentation/git-symbolic-ref.txt
index ef68ad2b71..102c83eb19 100644
--- a/Documentation/git-symbolic-ref.txt
+++ b/Documentation/git-symbolic-ref.txt
@@ -9,7 +9,7 @@ SYNOPSIS
 --------
 [verse]
 'git symbolic-ref' [-m <reason>] <name> <ref>
-'git symbolic-ref' [-q] [--short] <name>
+'git symbolic-ref' [-q] [--short] [--no-recurse] <name>
 'git symbolic-ref' --delete [-q] <name>
 
 DESCRIPTION
@@ -46,6 +46,15 @@ OPTIONS
 	When showing the value of <name> as a symbolic ref, try to shorten the
 	value, e.g. from `refs/heads/master` to `master`.
 
+--recurse::
+--no-recurse::
+	When showing the value of <name> as a symbolic ref, if
+	<name> refers to another symbolic ref, follow such a chain
+	of symbolic refs until the result no longer points at a
+	symbolic ref (`--recurse`, which is the default).
+	`--no-recurse` stops after dereferencing only a single level
+	of symbolic ref.
+
 -m::
 	Update the reflog for <name> with <reason>.  This is valid only
 	when creating or updating a symbolic ref.
diff --git a/builtin/symbolic-ref.c b/builtin/symbolic-ref.c
index e547a08d6c..d5a70e7bd8 100644
--- a/builtin/symbolic-ref.c
+++ b/builtin/symbolic-ref.c
@@ -6,14 +6,17 @@
 
 static const char * const git_symbolic_ref_usage[] = {
 	N_("git symbolic-ref [<options>] <name> [<ref>]"),
-	N_("git symbolic-ref -d [-q] <name>"),
+	N_("git symbolic-ref -d [-q] [--no-recurse] <name>"),
 	NULL
 };
 
-static int check_symref(const char *HEAD, int quiet, int shorten, int print)
+static int check_symref(const char *HEAD, int quiet, int shorten, int recurse, int print)
 {
-	int flag;
-	const char *refname = resolve_ref_unsafe(HEAD, 0, NULL, &flag);
+	int resolve_flags, flag;
+	const char *refname;
+
+	resolve_flags = (recurse ? 0 : RESOLVE_REF_NO_RECURSE);
+	refname = resolve_ref_unsafe(HEAD, resolve_flags, NULL, &flag);
 
 	if (!refname)
 		die("No such ref: %s", HEAD);
@@ -35,13 +38,14 @@ static int check_symref(const char *HEAD, int quiet, int shorten, int print)
 
 int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 {
-	int quiet = 0, delete = 0, shorten = 0, ret = 0;
+	int quiet = 0, delete = 0, shorten = 0, recurse = 1, ret = 0;
 	const char *msg = NULL;
 	struct option options[] = {
 		OPT__QUIET(&quiet,
 			N_("suppress error message for non-symbolic (detached) refs")),
 		OPT_BOOL('d', "delete", &delete, N_("delete symbolic ref")),
 		OPT_BOOL(0, "short", &shorten, N_("shorten ref output")),
+		OPT_BOOL(0, "recurse", &recurse, N_("recursively dereference (default)")),
 		OPT_STRING('m', NULL, &msg, N_("reason"), N_("reason of the update")),
 		OPT_END(),
 	};
@@ -55,7 +59,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 	if (delete) {
 		if (argc != 1)
 			usage_with_options(git_symbolic_ref_usage, options);
-		ret = check_symref(argv[0], 1, 0, 0);
+		ret = check_symref(argv[0], 1, 0, 0, 0);
 		if (ret)
 			die("Cannot delete %s, not a symbolic ref", argv[0]);
 		if (!strcmp(argv[0], "HEAD"))
@@ -65,7 +69,7 @@ int cmd_symbolic_ref(int argc, const char **argv, const char *prefix)
 
 	switch (argc) {
 	case 1:
-		ret = check_symref(argv[0], quiet, shorten, 1);
+		ret = check_symref(argv[0], quiet, shorten, recurse, 1);
 		break;
 	case 2:
 		if (!strcmp(argv[0], "HEAD") &&
diff --git a/t/t1401-symbolic-ref.sh b/t/t1401-symbolic-ref.sh
index 132a1b885a..773a6e8e01 100755
--- a/t/t1401-symbolic-ref.sh
+++ b/t/t1401-symbolic-ref.sh
@@ -163,4 +163,18 @@ test_expect_success 'symbolic-ref can resolve d/f name (ENOTDIR)' '
 	test_cmp expect actual
 '
 
+test_expect_success 'symbolic-ref pointing at another' '
+	git update-ref refs/heads/maint-2.37 HEAD &&
+	git symbolic-ref refs/heads/maint refs/heads/maint-2.37 &&
+	git checkout maint &&
+
+	git symbolic-ref HEAD >actual &&
+	echo refs/heads/maint-2.37 >expect &&
+	test_cmp expect actual &&
+
+	git symbolic-ref --no-recurse HEAD >actual &&
+	echo refs/heads/maint >expect &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.38.0-167-gf9a88ca9e9

← back to recent threads