revision: add new parameter to exclude hidden refs

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>

Patrick Steinhardt committed Nov 17, 2022 at 06:46 UTC 8c1bc2a71a7680161532e5eabf4dbfbc81dd07be
5 files changed +241 -1
Documentation/rev-list-options.txt
+7
@@ -195,6 +195,13 @@ respectively, and they must begin with `refs/` when applied to `--glob`
195 or `--all`. If a trailing '/{asterisk}' is intended, it must be given
196 explicitly.
197
198 +--exclude-hidden=[receive|uploadpack]::
199 + Do not include refs that would be hidden by `git-receive-pack` or
200 + `git-upload-pack` by consulting the appropriate `receive.hideRefs` or
201 + `uploadpack.hideRefs` configuration along with `transfer.hideRefs` (see
202 + linkgit:git-config[1]). This option affects the next pseudo-ref option
203 + `--all` or `--glob` and is cleared after processing them.
204 +
205 --reflog::
206 Pretend as if all objects mentioned by reflogs are listed on the
207 command line as `<commit>`.
builtin/rev-list.c
+1
@@ -37,6 +37,7 @@ static const char rev_list_usage[] =
37 " --tags\n"
38 " --remotes\n"
39 " --stdin\n"
40 +" --exclude-hidden=[receive|uploadpack]\n"
41 " --quiet\n"
42 " ordering output:\n"
43 " --topo-order\n"
revision.c
+54 -1
@@ -1,4 +1,5 @@
1 #include "cache.h"
2 +#include "config.h"
3 #include "object-store.h"
4 #include "tag.h"
5 #include "blob.h"
@@ -1519,11 +1520,17 @@ static void add_rev_cmdline_list(struct rev_info *revs,
1520
1521 int ref_excluded(const struct ref_exclusions *exclusions, const char *path)
1522 {
1523 + const char *stripped_path = strip_namespace(path);
1524 struct string_list_item *item;
1525 +
1526 for_each_string_list_item(item, &exclusions->excluded_refs) {
1527 if (!wildmatch(item->string, path, 0))
1528 return 1;
1529 }
1530 +
1531 + if (ref_is_hidden(stripped_path, path, &exclusions->hidden_refs))
1532 + return 1;
1533 +
1534 return 0;
1535 }
1536
@@ -1536,6 +1543,8 @@ void init_ref_exclusions(struct ref_exclusions *exclusions)
1543 void clear_ref_exclusions(struct ref_exclusions *exclusions)
1544 {
1545 string_list_clear(&exclusions->excluded_refs, 0);
1546 + string_list_clear(&exclusions->hidden_refs, 0);
1547 + exclusions->hidden_refs_configured = 0;
1548 }
1549
1550 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)
1552 string_list_append(&exclusions->excluded_refs, exclude);
1553 }
1554
1555 +struct exclude_hidden_refs_cb {
1556 + struct ref_exclusions *exclusions;
1557 + const char *section;
1558 +};
1559 +
1560 +static int hide_refs_config(const char *var, const char *value, void *cb_data)
1561 +{
1562 + struct exclude_hidden_refs_cb *cb = cb_data;
1563 + cb->exclusions->hidden_refs_configured = 1;
1564 + return parse_hide_refs_config(var, value, cb->section,
1565 + &cb->exclusions->hidden_refs);
1566 +}
1567 +
1568 +void exclude_hidden_refs(struct ref_exclusions *exclusions, const char *section)
1569 +{
1570 + struct exclude_hidden_refs_cb cb;
1571 +
1572 + if (strcmp(section, "receive") && strcmp(section, "uploadpack"))
1573 + die(_("unsupported section for hidden refs: %s"), section);
1574 +
1575 + if (exclusions->hidden_refs_configured)
1576 + die(_("--exclude-hidden= passed more than once"));
1577 +
1578 + cb.exclusions = exclusions;
1579 + cb.section = section;
1580 +
1581 + git_config(hide_refs_config, &cb);
1582 +}
1583 +
1584 struct all_refs_cb {
1585 int all_flags;
1586 int warned_bad_reflog;
@@ -2221,7 +2259,7 @@ static int handle_revision_opt(struct rev_info *revs, int argc, const char **arg
2259 !strcmp(arg, "--bisect") || starts_with(arg, "--glob=") ||
2260 !strcmp(arg, "--indexed-objects") ||
2261 !strcmp(arg, "--alternate-refs") ||
2224 - starts_with(arg, "--exclude=") ||
2262 + starts_with(arg, "--exclude=") || starts_with(arg, "--exclude-hidden=") ||
2263 starts_with(arg, "--branches=") || starts_with(arg, "--tags=") ||
2264 starts_with(arg, "--remotes=") || starts_with(arg, "--no-walk="))
2265 {
@@ -2687,6 +2725,8 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
2725 }
2726 clear_ref_exclusions(&revs->ref_excludes);
2727 } else if (!strcmp(arg, "--branches")) {
2728 + if (revs->ref_excludes.hidden_refs_configured)
2729 + return error(_("--exclude-hidden cannot be used together with --branches"));
2730 handle_refs(refs, revs, *flags, refs_for_each_branch_ref);
2731 clear_ref_exclusions(&revs->ref_excludes);
2732 } else if (!strcmp(arg, "--bisect")) {
@@ -2696,9 +2736,13 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
2736 for_each_good_bisect_ref);
2737 revs->bisect = 1;
2738 } else if (!strcmp(arg, "--tags")) {
2739 + if (revs->ref_excludes.hidden_refs_configured)
2740 + return error(_("--exclude-hidden cannot be used together with --tags"));
2741 handle_refs(refs, revs, *flags, refs_for_each_tag_ref);
2742 clear_ref_exclusions(&revs->ref_excludes);
2743 } else if (!strcmp(arg, "--remotes")) {
2744 + if (revs->ref_excludes.hidden_refs_configured)
2745 + return error(_("--exclude-hidden cannot be used together with --remotes"));
2746 handle_refs(refs, revs, *flags, refs_for_each_remote_ref);
2747 clear_ref_exclusions(&revs->ref_excludes);
2748 } else if ((argcount = parse_long_opt("glob", argv, &optarg))) {
@@ -2710,18 +2754,27 @@ static int handle_revision_pseudo_opt(struct rev_info *revs,
2754 } else if ((argcount = parse_long_opt("exclude", argv, &optarg))) {
2755 add_ref_exclusion(&revs->ref_excludes, optarg);
2756 return argcount;
2757 + } else if ((argcount = parse_long_opt("exclude-hidden", argv, &optarg))) {
2758 + exclude_hidden_refs(&revs->ref_excludes, optarg);
2759 + return argcount;
2760 } else if (skip_prefix(arg, "--branches=", &optarg)) {
2761 struct all_refs_cb cb;
2762 + if (revs->ref_excludes.hidden_refs_configured)
2763 + return error(_("--exclude-hidden cannot be used together with --branches"));
2764 init_all_refs_cb(&cb, revs, *flags);
2765 for_each_glob_ref_in(handle_one_ref, optarg, "refs/heads/", &cb);
2766 clear_ref_exclusions(&revs->ref_excludes);
2767 } else if (skip_prefix(arg, "--tags=", &optarg)) {
2768 struct all_refs_cb cb;
2769 + if (revs->ref_excludes.hidden_refs_configured)
2770 + return error(_("--exclude-hidden cannot be used together with --tags"));
2771 init_all_refs_cb(&cb, revs, *flags);
2772 for_each_glob_ref_in(handle_one_ref, optarg, "refs/tags/", &cb);
2773 clear_ref_exclusions(&revs->ref_excludes);
2774 } else if (skip_prefix(arg, "--remotes=", &optarg)) {
2775 struct all_refs_cb cb;
2776 + if (revs->ref_excludes.hidden_refs_configured)
2777 + return error(_("--exclude-hidden cannot be used together with --remotes"));
2778 init_all_refs_cb(&cb, revs, *flags);
2779 for_each_glob_ref_in(handle_one_ref, optarg, "refs/remotes/", &cb);
2780 clear_ref_exclusions(&revs->ref_excludes);
revision.h
+16
@@ -87,6 +87,19 @@ struct ref_exclusions {
87 * patterns matches, the reference will be excluded.
88 */
89 struct string_list excluded_refs;
90 +
91 + /*
92 + * Hidden refs is a list of patterns that is to be hidden via
93 + * `ref_is_hidden()`.
94 + */
95 + struct string_list hidden_refs;
96 +
97 + /*
98 + * Indicates whether hidden refs have been configured. This is to
99 + * distinguish between no hidden refs existing and hidden refs not
100 + * being parsed.
101 + */
102 + char hidden_refs_configured;
103 };
104
105 /**
@@ -94,6 +107,7 @@ struct ref_exclusions {
107 */
108 #define REF_EXCLUSIONS_INIT { \
109 .excluded_refs = STRING_LIST_INIT_DUP, \
110 + .hidden_refs = STRING_LIST_INIT_DUP, \
111 }
112
113 struct oidset;
@@ -456,10 +470,12 @@ void show_object_with_name(FILE *, struct object *, const char *);
470 /**
471 * Helpers to check if a reference should be excluded.
472 */
473 +
474 int ref_excluded(const struct ref_exclusions *exclusions, const char *path);
475 void init_ref_exclusions(struct ref_exclusions *);
476 void clear_ref_exclusions(struct ref_exclusions *);
477 void add_ref_exclusion(struct ref_exclusions *, const char *exclude);
478 +void exclude_hidden_refs(struct ref_exclusions *, const char *section);
479
480 /**
481 * This function can be used if you want to add commit objects as revision
t/t6021-rev-list-exclude-hidden.sh new
+163
@@ -0,0 +1,163 @@
1 +#!/bin/sh
2 +
3 +test_description='git rev-list --exclude-hidden test'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + test_commit_bulk --id=commit --ref=refs/heads/branch 1 &&
9 + COMMIT=$(git rev-parse refs/heads/branch) &&
10 + test_commit_bulk --id=tag --ref=refs/tags/lightweight 1 &&
11 + TAG=$(git rev-parse refs/tags/lightweight) &&
12 + test_commit_bulk --id=hidden --ref=refs/hidden/commit 1 &&
13 + HIDDEN=$(git rev-parse refs/hidden/commit) &&
14 + test_commit_bulk --id=namespace --ref=refs/namespaces/namespace/refs/namespaced/commit 1 &&
15 + NAMESPACE=$(git rev-parse refs/namespaces/namespace/refs/namespaced/commit)
16 +'
17 +
18 +test_expect_success 'invalid section' '
19 + echo "fatal: unsupported section for hidden refs: unsupported" >expected &&
20 + test_must_fail git rev-list --exclude-hidden=unsupported 2>err &&
21 + test_cmp expected err
22 +'
23 +
24 +for section in receive uploadpack
25 +do
26 + test_expect_success "$section: passed multiple times" '
27 + echo "fatal: --exclude-hidden= passed more than once" >expected &&
28 + test_must_fail git rev-list --exclude-hidden=$section --exclude-hidden=$section 2>err &&
29 + test_cmp expected err
30 + '
31 +
32 + test_expect_success "$section: without hiddenRefs" '
33 + git rev-list --exclude-hidden=$section --all >out &&
34 + cat >expected <<-EOF &&
35 + $NAMESPACE
36 + $HIDDEN
37 + $TAG
38 + $COMMIT
39 + EOF
40 + test_cmp expected out
41 + '
42 +
43 + test_expect_success "$section: hidden via transfer.hideRefs" '
44 + git -c transfer.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
45 + cat >expected <<-EOF &&
46 + $NAMESPACE
47 + $TAG
48 + $COMMIT
49 + EOF
50 + test_cmp expected out
51 + '
52 +
53 + test_expect_success "$section: hidden via $section.hideRefs" '
54 + git -c $section.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
55 + cat >expected <<-EOF &&
56 + $NAMESPACE
57 + $TAG
58 + $COMMIT
59 + EOF
60 + test_cmp expected out
61 + '
62 +
63 + test_expect_success "$section: respects both transfer.hideRefs and $section.hideRefs" '
64 + git -c transfer.hideRefs=refs/tags/ -c $section.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --all >out &&
65 + cat >expected <<-EOF &&
66 + $NAMESPACE
67 + $COMMIT
68 + EOF
69 + test_cmp expected out
70 + '
71 +
72 + test_expect_success "$section: negation without hidden refs marks everything as uninteresting" '
73 + git rev-list --all --exclude-hidden=$section --not --all >out &&
74 + test_must_be_empty out
75 + '
76 +
77 + test_expect_success "$section: negation with hidden refs marks them as interesting" '
78 + git -c transfer.hideRefs=refs/hidden/ rev-list --all --exclude-hidden=$section --not --all >out &&
79 + cat >expected <<-EOF &&
80 + $HIDDEN
81 + EOF
82 + test_cmp expected out
83 + '
84 +
85 + test_expect_success "$section: hidden refs and excludes work together" '
86 + git -c transfer.hideRefs=refs/hidden/ rev-list --exclude=refs/tags/* --exclude-hidden=$section --all >out &&
87 + cat >expected <<-EOF &&
88 + $NAMESPACE
89 + $COMMIT
90 + EOF
91 + test_cmp expected out
92 + '
93 +
94 + test_expect_success "$section: excluded hidden refs get reset" '
95 + git -c transfer.hideRefs=refs/ rev-list --exclude-hidden=$section --all --all >out &&
96 + cat >expected <<-EOF &&
97 + $NAMESPACE
98 + $HIDDEN
99 + $TAG
100 + $COMMIT
101 + EOF
102 + test_cmp expected out
103 + '
104 +
105 + test_expect_success "$section: excluded hidden refs can be used with multiple pseudo-refs" '
106 + git -c transfer.hideRefs=refs/ rev-list --exclude-hidden=$section --all --exclude-hidden=$section --all >out &&
107 + test_must_be_empty out
108 + '
109 +
110 + test_expect_success "$section: works with --glob" '
111 + git -c transfer.hideRefs=refs/hidden/ rev-list --exclude-hidden=$section --glob=refs/h* >out &&
112 + cat >expected <<-EOF &&
113 + $COMMIT
114 + EOF
115 + test_cmp expected out
116 + '
117 +
118 + test_expect_success "$section: operates on stripped refs by default" '
119 + GIT_NAMESPACE=namespace git -c transfer.hideRefs=refs/namespaced/ rev-list --exclude-hidden=$section --all >out &&
120 + cat >expected <<-EOF &&
121 + $HIDDEN
122 + $TAG
123 + $COMMIT
124 + EOF
125 + test_cmp expected out
126 + '
127 +
128 + test_expect_success "$section: does not hide namespace by default" '
129 + GIT_NAMESPACE=namespace git -c transfer.hideRefs=refs/namespaces/namespace/ rev-list --exclude-hidden=$section --all >out &&
130 + cat >expected <<-EOF &&
131 + $NAMESPACE
132 + $HIDDEN
133 + $TAG
134 + $COMMIT
135 + EOF
136 + test_cmp expected out
137 + '
138 +
139 + test_expect_success "$section: can operate on unstripped refs" '
140 + GIT_NAMESPACE=namespace git -c transfer.hideRefs=^refs/namespaces/namespace/ rev-list --exclude-hidden=$section --all >out &&
141 + cat >expected <<-EOF &&
142 + $HIDDEN
143 + $TAG
144 + $COMMIT
145 + EOF
146 + test_cmp expected out
147 + '
148 +
149 + for pseudoopt in remotes branches tags
150 + do
151 + test_expect_success "$section: fails with --$pseudoopt" '
152 + test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt 2>err &&
153 + test_i18ngrep "error: --exclude-hidden cannot be used together with --$pseudoopt" err
154 + '
155 +
156 + test_expect_success "$section: fails with --$pseudoopt=pattern" '
157 + test_must_fail git rev-list --exclude-hidden=$section --$pseudoopt=pattern 2>err &&
158 + test_i18ngrep "error: --exclude-hidden cannot be used together with --$pseudoopt" err
159 + '
160 + done
161 +done
162 +
163 +test_done