Subject: [PATCH v2 0/3] show-ref: add --symbolic-name option

From: "John Cai via GitGitGadget" <[email-masked]>
Date: Mon, 08 Apr 2024 17:38:10 +0000

For reftable development, it would be handy to have a tool to provide the
direct value of any ref whether it be a symbolic ref or not. Currently there
is git-symbolic-ref, which only works for symbolic refs, and git-rev-parse,
which will resolve the ref. Let's add a --symbolic-name option that will
print out the value the ref directly points to without dereferencing it.

Changes since V1:

 * changed output format to print out values as a third column
 * made plumbing changes to enable the value of a symbolic ref to be read
   from the iterator
 * changed the name of the flag

John Cai (3):
  refs: keep track of unresolved reference value in iterator
  refs: add referent to each_repo_ref_fn
  show-ref: add --symbolic-name option

 Documentation/git-show-ref.txt | 21 ++++++++++++++++++-
 builtin/replace.c              |  1 +
 builtin/show-ref.c             | 38 ++++++++++++++++++++++++----------
 builtin/submodule--helper.c    |  2 +-
 refs.c                         | 31 ++++++++++++++++++---------
 refs.h                         |  6 ++++--
 refs/files-backend.c           | 20 ++++++++++--------
 refs/iterator.c                |  3 ++-
 refs/ref-cache.c               |  3 +++
 refs/ref-cache.h               |  2 ++
 refs/refs-internal.h           |  1 +
 refs/reftable-backend.c        | 13 ++++++++----
 remote.c                       |  2 +-
 replace-object.c               |  1 +
 sequencer.c                    |  4 ++--
 t/helper/test-ref-store.c      |  2 +-
 t/t1403-show-ref.sh            | 20 ++++++++++++++++++
 worktree.c                     |  4 +++-
 18 files changed, 130 insertions(+), 44 deletions(-)

base-commit: 7774cfed6261ce2900c84e55906da708c711d601
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1684%2Fjohn-cai%2Fjc%2Fshow-ref-direct-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1684/john-cai/jc/show-ref-direct-v2
Pull-Request: https://github.com/git/git/pull/1684

Range-diff vs v1:

 -:  ----------- > 1:  6adc9dd26da refs: keep track of unresolved reference value in iterator
 -:  ----------- > 2:  b60e78560e0 refs: add referent to each_repo_ref_fn
 1:  c32572a8d0b ! 3:  a9e6644327a show-ref: add --unresolved option
     @@ Metadata
      Author: John Cai <[email-masked]>
      
       ## Commit message ##
     -    show-ref: add --unresolved option
     +    show-ref: add --symbolic-name option
      
          For reftable development, it would be handy to have a tool to provide
          the direct value of any ref whether it be a symbolic ref or not.
          Currently there is git-symbolic-ref, which only works for symbolic refs,
     -    and git-rev-parse, which will resolve the ref. Let's add a --unresolved
     -    option that will only take one ref and return whatever it points to
     -    without dereferencing it.
     +    and git-rev-parse, which will resolve the ref. Let's teach show-ref a
     +    --symbolic-name option that will cause git-show-ref(1) to print out the
     +    value symbolic references points to.
      
          Signed-off-by: John Cai <[email-masked]>
      
       ## Documentation/git-show-ref.txt ##
      @@ Documentation/git-show-ref.txt: SYNOPSIS
     + [verse]
     + 'git show-ref' [--head] [-d | --dereference]
     + 	     [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]
     +-	     [--heads] [--] [<pattern>...]
     ++	     [--heads] [--symbolic-name] [--] [<pattern>...]
     + 'git show-ref' --verify [-q | --quiet] [-d | --dereference]
     + 	     [-s | --hash[=<n>]] [--abbrev[=<n>]]
       	     [--] [<ref>...]
     - 'git show-ref' --exclude-existing[=<pattern>]
     - 'git show-ref' --exists <ref>
     -+'git show-ref' --unresolved <ref>
     - 
     - DESCRIPTION
     - -----------
      @@ Documentation/git-show-ref.txt: OPTIONS
     - 	it does, 2 if it is missing, and 1 in case looking up the reference
     - 	failed with an error other than the reference being missing.
     + 	Dereference tags into object IDs as well. They will be shown with `^{}`
     + 	appended.
     + 
     ++--symbolic-name::
     ++
     ++	Print out the value the reference points to without dereferencing. This
     ++	is useful to know the reference that a symbolic ref is pointing to.
     ++
     + -s::
     + --hash[=<n>]::
     + 
     +@@ Documentation/git-show-ref.txt: $ git show-ref --heads --hash
     + ...
     + -----------------------------------------------------------------------------
       
     -+--unresolved::
     ++When using `--symbolic-name`, the output is in the format:
     ++
     ++-----------
     ++<oid> SP <ref> SP <symbolic-name>
     ++-----------
      +
     -+	Prints out what the reference points to without resolving it. Returns
     -+	an exit code of 0 if it does, 2 if it is missing, and 1 in case looking
     -+	up the reference failed with an error other than the reference being
     -+	missing.
     ++For example,
      +
     - --abbrev[=<n>]::
     ++-----------------------------------------------------------------------------
     ++$ git show-ref --symbolic-name
     ++b75428bae1d090f60bdd4b67185f814bc8f0819d refs/heads/SYMBOLIC_REF ref:refs/heads/main
     ++...
     ++-----------------------------------------------------------------------------
     ++
     + EXAMPLES
     + --------
       
     - 	Abbreviate the object name.  When using `--hash`, you do
      
       ## builtin/show-ref.c ##
     -@@ builtin/show-ref.c: static const char * const show_ref_usage[] = {
     +@@
     + static const char * const show_ref_usage[] = {
     + 	N_("git show-ref [--head] [-d | --dereference]\n"
     + 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]] [--tags]\n"
     +-	   "             [--heads] [--] [<pattern>...]"),
     ++	   "             [--heads] [--symbolic-name] [--] [<pattern>...]"),
     + 	N_("git show-ref --verify [-q | --quiet] [-d | --dereference]\n"
     + 	   "             [-s | --hash[=<n>]] [--abbrev[=<n>]]\n"
       	   "             [--] [<ref>...]"),
     - 	N_("git show-ref --exclude-existing[=<pattern>]"),
     - 	N_("git show-ref --exists <ref>"),
     -+	N_("git show-ref --unresolved <ref>"),
     - 	NULL
     +@@ builtin/show-ref.c: struct show_one_options {
     + 	int hash_only;
     + 	int abbrev;
     + 	int deref_tags;
     ++	int symbolic_name;
       };
       
     -@@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_options *opts,
     - 	return 0;
     - }
     + static void show_one(const struct show_one_options *opts,
     +-		     const char *refname, const struct object_id *oid)
     ++		     const char *refname,
     ++		     const char *referent,
     ++		     const struct object_id *oid, const int is_symref)
     + {
     + 	const char *hex;
     + 	struct object_id peeled;
     +@@ builtin/show-ref.c: static void show_one(const struct show_one_options *opts,
     + 	hex = repo_find_unique_abbrev(the_repository, oid, opts->abbrev);
     + 	if (opts->hash_only)
     + 		printf("%s\n", hex);
     +-	else
     ++	else if (opts->symbolic_name & is_symref) {
     ++		printf("%s %s ref:%s\n", hex, refname, referent);
     ++	} else
     + 		printf("%s %s\n", hex, refname);
       
     --static int cmd_show_ref__exists(const char **refs)
     -+static int cmd_show_ref__raw(const char **refs, int show)
     + 	if (!opts->deref_tags)
     +@@ builtin/show-ref.c: struct show_ref_data {
     + 	int show_head;
     + };
     + 
     +-static int show_ref(const char *refname, const struct object_id *oid,
     +-		    int flag UNUSED, void *cbdata)
     ++static int show_ref_referent(struct repository *repo UNUSED,
     ++			     const char *refname,
     ++			     const char *referent,
     ++			     const struct object_id *oid,
     ++			     int flag, void *cbdata)
       {
     --	struct strbuf unused_referent = STRBUF_INIT;
     --	struct object_id unused_oid;
     --	unsigned int unused_type;
     -+	struct strbuf referent = STRBUF_INIT;
     -+	struct object_id oid;
     -+	unsigned int type;
     - 	int failure_errno = 0;
     - 	const char *ref;
     - 	int ret = 0;
     -@@ builtin/show-ref.c: static int cmd_show_ref__exists(const char **refs)
     - 		die("--exists requires exactly one reference");
     - 
     - 	if (refs_read_raw_ref(get_main_ref_store(the_repository), ref,
     --			      &unused_oid, &unused_referent, &unused_type,
     -+			      &oid, &referent, &type,
     - 			      &failure_errno)) {
     - 		if (failure_errno == ENOENT || failure_errno == EISDIR) {
     - 			error(_("reference does not exist"));
     -@@ builtin/show-ref.c: static int cmd_show_ref__exists(const char **refs)
     - 		goto out;
     - 	}
     + 	struct show_ref_data *data = cbdata;
       
     -+		if (!show)
     -+			goto out;
     -+
     -+		if (type & REF_ISSYMREF)
     -+			printf("ref: %s\n", referent.buf);
     -+		else
     -+			printf("ref: %s\n", oid_to_hex(&oid));
     -+
     - out:
     --	strbuf_release(&unused_referent);
     -+	strbuf_release(&referent);
     - 	return ret;
     +@@ builtin/show-ref.c: static int show_ref(const char *refname, const struct object_id *oid,
     + match:
     + 	data->found_match++;
     + 
     +-	show_one(data->show_one_opts, refname, oid);
     ++	show_one(data->show_one_opts, refname, referent, oid, flag & REF_ISSYMREF);
     + 
     + 	return 0;
       }
       
     ++static int show_ref(const char *refname, const struct object_id *oid,
     ++		    int flag, void *cbdata)
     ++{
     ++	return show_ref_referent(NULL, refname, NULL, oid, flag, cbdata);
     ++}
     ++
     + static int add_existing(const char *refname,
     + 			const struct object_id *oid UNUSED,
     + 			int flag UNUSED, void *cbdata)
     +@@ builtin/show-ref.c: static int cmd_show_ref__verify(const struct show_one_options *show_one_opts,
     + 
     + 	while (*refs) {
     + 		struct object_id oid;
     ++		int flags = 0;
     + 
     + 		if ((starts_with(*refs, "refs/") || refname_is_safe(*refs)) &&
     +-		    !read_ref(*refs, &oid)) {
     +-			show_one(show_one_opts, *refs, &oid);
     ++		    !read_ref_full(*refs, 0, &oid, &flags)) {
     ++			show_one(show_one_opts, *refs, NULL, &oid, flags & REF_ISSYMREF);
     + 		}
     + 		else if (!show_one_opts->quiet)
     + 			die("'%s' - not a valid ref", *refs);
     +@@ builtin/show-ref.c: static int cmd_show_ref__patterns(const struct patterns_options *opts,
     + 		head_ref(show_ref, &show_ref_data);
     + 	if (opts->heads_only || opts->tags_only) {
     + 		if (opts->heads_only)
     +-			for_each_fullref_in("refs/heads/", show_ref, &show_ref_data);
     ++			for_each_ref_all("refs/heads/", show_ref_referent, &show_ref_data);
     + 		if (opts->tags_only)
     +-			for_each_fullref_in("refs/tags/", show_ref, &show_ref_data);
     ++			for_each_ref_all("refs/tags/", show_ref_referent, &show_ref_data);
     + 	} else {
     +-		for_each_ref(show_ref, &show_ref_data);
     ++		for_each_ref_all("", show_ref_referent, &show_ref_data);
     + 	}
     + 	if (!show_ref_data.found_match)
     + 		return 1;
      @@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *prefix)
     - 	struct exclude_existing_options exclude_existing_opts = {0};
     - 	struct patterns_options patterns_opts = {0};
     - 	struct show_one_options show_one_opts = {0};
     --	int verify = 0, exists = 0;
     -+	int verify = 0, exists = 0, unresolved = 0;
     - 	const struct option show_ref_options[] = {
       		OPT_BOOL(0, "tags", &patterns_opts.tags_only, N_("only show tags (can be combined with heads)")),
       		OPT_BOOL(0, "heads", &patterns_opts.heads_only, N_("only show heads (can be combined with tags)")),
       		OPT_BOOL(0, "exists", &exists, N_("check for reference existence without resolving")),
     -+		OPT_BOOL(0, "unresolved", &unresolved, N_("print out unresolved value of reference")),
     ++		OPT_BOOL(0, "symbolic-name", &show_one_opts.symbolic_name, N_("print out symbolic reference values")),
       		OPT_BOOL(0, "verify", &verify, N_("stricter reference checking, "
       			    "requires exact ref path")),
       		OPT_HIDDEN_BOOL('h', NULL, &patterns_opts.show_head,
     -@@ builtin/show-ref.c: int cmd_show_ref(int argc, const char **argv, const char *prefix)
     - 	argc = parse_options(argc, argv, prefix, show_ref_options,
     - 			     show_ref_usage, 0);
     - 
     --	die_for_incompatible_opt3(exclude_existing_opts.enabled, "--exclude-existing",
     -+	die_for_incompatible_opt4(exclude_existing_opts.enabled, "--exclude-existing",
     - 				  verify, "--verify",
     --				  exists, "--exists");
     -+				  exists, "--exists",
     -+				  unresolved, "--unresolved");
     - 
     - 	if (exclude_existing_opts.enabled)
     - 		return cmd_show_ref__exclude_existing(&exclude_existing_opts);
     - 	else if (verify)
     - 		return cmd_show_ref__verify(&show_one_opts, argv);
     --	else if (exists)
     --		return cmd_show_ref__exists(argv);
     -+	else if (exists || unresolved)
     -+		return cmd_show_ref__raw(argv, unresolved);
     - 	else
     - 		return cmd_show_ref__patterns(&patterns_opts, &show_one_opts, argv);
     - }
      
     - ## t/t1403-show-ref.sh ##
     -@@ t/t1403-show-ref.sh: test_expect_success 'show-ref sub-modes are mutually exclusive' '
     - 	test_must_fail git show-ref --exclude-existing --exists 2>err &&
     - 	grep "exclude-existing" err &&
     - 	grep "exists" err &&
     -+	grep "cannot be used together" err &&
     -+
     -+	test_must_fail git show-ref --exclude-existing --unresolved 2>err &&
     -+	grep "exclude-existing" err &&
     -+	grep "unresolved" err &&
     -+	grep "cannot be used together" err &&
     + ## refs.c ##
     +@@ refs.c: int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_dat
     + 				    DO_FOR_EACH_INCLUDE_BROKEN, cb_data);
     + }
     + 
     ++int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data)
     ++{
     ++	return do_for_each_repo_ref(the_repository, prefix, fn, 0,
     ++				    0, cb_data);
     ++}
      +
     -+	test_must_fail git show-ref --verify --unresolved 2>err &&
     -+	grep "verify" err &&
     -+	grep "unresolved" err &&
     - 	grep "cannot be used together" err
     - '
     + int for_each_namespaced_ref(const char **exclude_patterns,
     + 			    each_ref_fn fn, void *cb_data)
     + {
     +
     + ## refs.h ##
     +@@ refs.h: int refs_for_each_branch_ref(struct ref_store *refs,
     + 			     each_ref_fn fn, void *cb_data);
     + int refs_for_each_remote_ref(struct ref_store *refs,
     + 			     each_ref_fn fn, void *cb_data);
     +-
     + /* just iterates the head ref. */
     + int head_ref(each_ref_fn fn, void *cb_data);
     + 
     +@@ refs.h: int for_each_tag_ref(each_ref_fn fn, void *cb_data);
     + int for_each_branch_ref(each_ref_fn fn, void *cb_data);
     + int for_each_remote_ref(each_ref_fn fn, void *cb_data);
     + int for_each_replace_ref(struct repository *r, each_repo_ref_fn fn, void *cb_data);
     ++int for_each_ref_all(const char *prefix, each_repo_ref_fn fn, void *cb_data);
       
     + /* iterates all refs that match the specified glob pattern. */
     + int for_each_glob_ref(each_ref_fn fn, const char *pattern, void *cb_data);
     +
     + ## t/t1403-show-ref.sh ##
      @@ t/t1403-show-ref.sh: test_expect_success '--exists with existing special ref' '
       	git show-ref --exists FETCH_HEAD
       '
       
     -+test_expect_success '--unresolved with existing reference' '
     ++test_expect_success '--symbolic-name with a non symbolic ref' '
      +	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
      +	cat >expect <<-EOF &&
     -+	ref: $commit_oid
     -+	EOF
     -+	git show-ref --unresolved refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME >actual &&
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success '--unresolved with symbolic ref' '
     -+	test_when_finished "git symbolic-ref -d SYMBOLIC_REF_A" &&
     -+	cat >expect <<-EOF &&
     -+	ref: refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
     ++	$commit_oid refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
      +	EOF
     -+	git symbolic-ref SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
     -+	git show-ref --unresolved SYMBOLIC_REF_A >actual &&
     ++	git show-ref --symbolic-name refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME >actual &&
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success '--unresolved with nonexistent object ID' '
     -+	oid=$(test_oid 002) &&
     -+	test-tool ref-store main update-ref msg refs/heads/missing-oid-2 $oid $ZERO_OID REF_SKIP_OID_VERIFICATION &&
     ++test_expect_success '--symbolic-name with symbolic ref' '
     ++	test_when_finished "git symbolic-ref -d refs/heads/SYMBOLIC_REF_A" &&
     ++	commit_oid=$(git rev-parse refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME) &&
      +	cat >expect <<-EOF &&
     -+	ref: $oid
     ++	$commit_oid refs/heads/SYMBOLIC_REF_A ref:refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
      +	EOF
     -+	git show-ref --unresolved refs/heads/missing-oid-2 >actual &&
     ++	git symbolic-ref refs/heads/SYMBOLIC_REF_A refs/heads/$GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME &&
     ++	git show-ref --symbolic-name SYMBOLIC_REF_A >actual &&
      +	test_cmp expect actual
      +'
     -+
     -+test_expect_success '--unresolved with nonexistent reference' '
     -+	cat >expect <<-EOF &&
     -+	error: reference does not exist
     -+	EOF
     -+	test_expect_code 2 git show-ref --unresolved refs/heads/not-exist 2>err &&
     -+	test_cmp expect err
     -+'
      +
       test_done

-- 
gitgitgadget