]> git.ipfire.org Git - thirdparty/git.git/commitdiff
revision: add new parameter to exclude hidden refs
authorPatrick Steinhardt <ps@pks.im>
Thu, 17 Nov 2022 05:46:56 +0000 (06:46 +0100)
committerTaylor Blau <me@ttaylorr.com>
Thu, 17 Nov 2022 21:22:52 +0000 (16:22 -0500)
Users can optionally hide refs from remote users in git-upload-pack(1),
git-receive-pack(1) and others via the `transfer.hideRefs`, but there is
not an easy way to obtain the list of all visible or hidden refs right
now. We'll require just that though for a performance improvement in our
connectivity check.

Add a new option `--exclude-hidden=` that excludes any hidden refs from
the next pseudo-ref like `--all` or `--branches`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Taylor Blau <me@ttaylorr.com>
Documentation/rev-list-options.txt
builtin/rev-list.c
revision.c
revision.h
t/t6021-rev-list-exclude-hidden.sh [new file with mode: 0755]

index 1837509566a79a36a5f607837bc13af082d81b86..ff68e484069a84854c97d141dbef376badbaec2c 100644 (file)
@@ -195,6 +195,13 @@ respectively, and they must begin with `refs/` when applied to `--glob`
 or `--all`. If a trailing '/{asterisk}' is intended, it must be given
 explicitly.
 
+--exclude-hidden=[receive|uploadpack]::
+       Do not include refs that would be hidden by `git-receive-pack` or
+       `git-upload-pack` by consulting the appropriate `receive.hideRefs` or
+       `uploadpack.hideRefs` configuration along with `transfer.hideRefs` (see
+       linkgit:git-config[1]). This option affects the next pseudo-ref option
+       `--all` or `--glob` and is cleared after processing them.
+
 --reflog::
        Pretend as if all objects mentioned by reflogs are listed on the
        command line as `<commit>`.
index fba6f5d51f32d1217b89298e1361169b43feb38b..6858b4305681708d5e20e50325da2ccca8a672e9 100644 (file)
@@ -37,6 +37,7 @@ static const char rev_list_usage[] =
 "    --tags\n"
 "    --remotes\n"
 "    --stdin\n"
+"    --exclude-hidden=[receive|uploadpack]\n"
 "    --quiet\n"
 "  ordering output:\n"
 "    --topo-order\n"
index fe3ec98f467208c5051ba758243bb6988da1960b..bc32fb819a151df72c0e889954f9573445d32602 100644 (file)
@@ -1,4 +1,5 @@
 #include "cache.h"
+#include "config.h"
 #include "object-store.h"
 #include "tag.h"
 #include "blob.h"
@@ -1519,11 +1520,17 @@ static void add_rev_cmdline_list(struct rev_info *revs,
 
 int ref_excluded(const struct ref_exclusions *exclusions, const char *path)
 {
+       const char *stripped_path = strip_namespace(path);
        struct string_list_item *item;
+
        for_each_string_list_item(item, &exclusions->excluded_refs) {
                if (!wildmatch(item->string, path, 0))
                        return 1;
        }
+
+       if (ref_is_hidden(stripped_path, path, &exclusions->hidden_refs))
+               return 1;
+
        return 0;
 }
 
@@ -1536,6 +1543,8 @@ void init_ref_exclusions(struct ref_exclusions *exclusions)
 void clear_ref_exclusions(struct ref_exclusions *exclusions)
 {
        string_list_clear(&exclusions->excluded_refs, 0);
+       string_list_clear(&exclusions->hidden_refs, 0);
+       exclusions->hidden_refs_configured = 0;
 }
 
 void add_ref_exclusion(struct ref_exclusions *exclusions, const char *exclude)
@@ -1543,6 +1552,35 @@ void add_ref_exclusion(struct ref_exclusions *exclusions, const char *exclude)
        string_list_append(&exclusions->excluded_refs, exclude);
 }
 
+struct exclude_hidden_refs_cb {
+       struct ref_exclusions *exclusions;
+       const char *section;
+};
+
+static int hide_refs_config(const char *var, const char *value, void *cb_data)
+{
+       struct exclude_hidden_refs_cb *cb = cb_data;
+       cb->exclusions->hidden_refs_configured = 1;
+       return parse_hide_refs_config(var, value, cb->section,
+                                     &cb->exclusions->hidden_refs);
+}
+
+void exclude_hidden_refs(struct ref_exclusions *exclusions, const char *section)
+{
+       struct exclude_hidden_refs_cb cb;
+
+       if (strcmp(section, "receive") && strcmp(section, "uploadpack"))
+               die(_("unsupported section for hidden refs: %s"), section);
+
+       if (exclusions->hidden_refs_configured)
+               die(_("--exclude-hidden= passed more than once"));
+
+       cb.exclusions = exclusions;
+       cb.section = section;
+
+       git_config(hide_refs_config, &cb);
+}
+
 struct all_refs_cb {
        int all_flags;
        int warned_bad_reflog;
@@ -2221,7 +2259,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
            !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
            !strcmp(arg, "--indexed-objects") ||
            !strcmp(arg, "--alternate-refs") ||
-           starts_with(arg, "--exclude=") ||
+           starts_with(arg, "--exclude=") || starts_with(arg, "--exclude-hidden=") ||
            starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
            starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
        {
@@ -2687,6 +2725,8 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
                }
                clear_ref_exclusions(&revs->ref_excludes);
        } else if (!strcmp(arg, "--branches")) {
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --branches"));
                handle_refs(refs, revs, *flags, refs_for_each_branch_ref);
                clear_ref_exclusions(&revs->ref_excludes);
        } else if (!strcmp(arg, "--bisect")) {
@@ -2696,9 +2736,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
                            for_each_good_bisect_ref);
                revs->bisect = 1;
        } else if (!strcmp(arg, "--tags")) {
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --tags"));
                handle_refs(refs, revs, *flags, refs_for_each_tag_ref);
                clear_ref_exclusions(&revs->ref_excludes);
        } else if (!strcmp(arg, "--remotes")) {
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --remotes"));
                handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
                clear_ref_exclusions(&revs->ref_excludes);
        } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
@@ -2710,18 +2754,27 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
        } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
                add_ref_exclusion(&revs->ref_excludes, optarg);
                return argcount;
+       } else if ((argcount = parse_long_opt("exclude-hidden", argv, &optarg))) {
+               exclude_hidden_refs(&revs->ref_excludes, optarg);
+               return argcount;
        } else if (skip_prefix(arg, "--branches=", &optarg)) {
                struct all_refs_cb cb;
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --branches"));
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
                clear_ref_exclusions(&revs->ref_excludes);
        } else if (skip_prefix(arg, "--tags=", &optarg)) {
                struct all_refs_cb cb;
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --tags"));
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
                clear_ref_exclusions(&revs->ref_excludes);
        } else if (skip_prefix(arg, "--remotes=", &optarg)) {
                struct all_refs_cb cb;
+               if (revs->ref_excludes.hidden_refs_configured)
+                       return error(_("--exclude-hidden cannot be used together with --remotes"));
                init_all_refs_cb(&cb, revs, *flags);
                for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
                clear_ref_exclusions(&revs->ref_excludes);
index 5c8ab1604791297bbae49de320826f20dc3e65ac..adb810c2f87a9e161eb6dcab1e2bad05c400e7cb 100644 (file)
@@ -87,6 +87,19 @@ struct ref_exclusions {
         * patterns matches, the reference will be excluded.
         */
        struct string_list excluded_refs;
+
+       /*
+        * Hidden refs is a list of patterns that is to be hidden via
+        * `ref_is_hidden()`.
+        */
+       struct string_list hidden_refs;
+
+       /*
+        * Indicates whether hidden refs have been configured. This is to
+        * distinguish between no hidden refs existing and hidden refs not
+        * being parsed.
+        */
+       char hidden_refs_configured;
 };
 
 /**
@@ -94,6 +107,7 @@ struct ref_exclusions {
  */
 #define REF_EXCLUSIONS_INIT { \
        .excluded_refs = STRING_LIST_INIT_DUP, \
+       .hidden_refs = STRING_LIST_INIT_DUP, \
 }
 
 struct oidset;
@@ -456,10 +470,12 @@ void show_object_with_name(FILE *, struct object *, const char *);
 /**
  * Helpers to check if a reference should be excluded.
  */
+
 int ref_excluded(const struct ref_exclusions *exclusions, const char *path);
 void init_ref_exclusions(struct ref_exclusions *);
 void clear_ref_exclusions(struct ref_exclusions *);
 void add_ref_exclusion(struct ref_exclusions *, const char *exclude);
+void exclude_hidden_refs(struct ref_exclusions *, const char *section);
 
 /**
  * This function can be used if you want to add commit objects as revision
diff --git a/t/t6021-rev-list-exclude-hidden.sh b/t/t6021-rev-list-exclude-hidden.sh
new file mode 100755 (executable)
index 0000000..32b2b09
--- /dev/null
@@ -0,0 +1,163 @@
+#!/bin/sh
+
+test_description='git rev-list --exclude-hidden test'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+       test_commit_bulk --id=commit --ref=refs/heads/branch 1 &&
+       COMMIT=$(git rev-parse refs/heads/branch) &&
+       test_commit_bulk --id=tag --ref=refs/tags/lightweight 1 &&
+       TAG=$(git rev-parse refs/tags/lightweight) &&
+       test_commit_bulk --id=hidden --ref=refs/hidden/commit 1 &&
+       HIDDEN=$(git rev-parse refs/hidden/commit) &&
+       test_commit_bulk --id=namespace --ref=refs/namespaces/namespace/refs/namespaced/commit 1 &&
+       NAMESPACE=$(git rev-parse refs/namespaces/namespace/refs/namespaced/commit)
+'
+
+test_expect_success 'invalid section' '
+       echo "fatal: unsupported section for hidden refs: unsupported" >expected &&
+       test_must_fail git rev-list --exclude-hidden=unsupported 2>err &&
+       test_cmp expected err
+'
+
+for section in receive uploadpack
+do
+       test_expect_success "$section: passed multiple times" '
+               echo "fatal: --exclude-hidden= passed more than once" >expected &&
+               test_must_fail git rev-list --exclude-hidden=$section --exclude-hidden=$section 2>err &&
+               test_cmp expected err
+       '
+
+       test_expect_success "$section: without hiddenRefs" '
+               git rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $HIDDEN
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: hidden via transfer.hideRefs" '
+               git -c transfer.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: hidden via $section.hideRefs" '
+               git -c $section.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: respects both transfer.hideRefs and $section.hideRefs" '
+               git -c transfer.hideRefs=refs/tags/ -c $section.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: negation without hidden refs marks everything as uninteresting" '
+               git rev-list --all --exclude-hidden=$section --not --all >out &&
+               test_must_be_empty out
+       '
+
+       test_expect_success "$section: negation with hidden refs marks them as interesting" '
+               git -c transfer.hideRefs=refs/hidden/ rev-list --all --exclude-hidden=$section --not --all >out &&
+               cat >expected <<-EOF &&
+               $HIDDEN
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: hidden refs and excludes work together" '
+               git -c transfer.hideRefs=refs/hidden/ rev-list --exclude=refs/tags/* --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: excluded hidden refs get reset" '
+               git -c transfer.hideRefs=refs/ rev-list --exclude-hidden=$section --all --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $HIDDEN
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: excluded hidden refs can be used with multiple pseudo-refs" '
+               git -c transfer.hideRefs=refs/ rev-list --exclude-hidden=$section --all --exclude-hidden=$section --all >out &&
+               test_must_be_empty out
+       '
+
+       test_expect_success "$section: works with --glob" '
+               git -c transfer.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --glob=refs/h* >out &&
+               cat >expected <<-EOF &&
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: operates on stripped refs by default" '
+               GIT_NAMESPACE=namespace git -c transfer.hideRefs=refs/namespaced/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $HIDDEN
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: does not hide namespace by default" '
+               GIT_NAMESPACE=namespace git -c transfer.hideRefs=refs/namespaces/namespace/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $NAMESPACE
+               $HIDDEN
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       test_expect_success "$section: can operate on unstripped refs" '
+               GIT_NAMESPACE=namespace git -c transfer.hideRefs=^refs/namespaces/namespace/ rev-list --exclude-hidden=$section --all >out &&
+               cat >expected <<-EOF &&
+               $HIDDEN
+               $TAG
+               $COMMIT
+               EOF
+               test_cmp expected out
+       '
+
+       for pseudoopt in remotes branches tags
+       do
+               test_expect_success "$section: fails with --$pseudoopt" '
+                       test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt 2>err &&
+                       test_i18ngrep "error: --exclude-hidden cannot be used together with --$pseudoopt" err
+               '
+
+               test_expect_success "$section: fails with --$pseudoopt=pattern" '
+                       test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt=pattern 2>err &&
+                       test_i18ngrep "error: --exclude-hidden cannot be used together with --$pseudoopt" err
+               '
+       done
+done
+
+test_done