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