Skip to content

Commit 155dc84

Browse files
pks-tgitster
authored andcommitted
refs: properly apply exclude patterns to namespaced refs
Reference namespaces allow commands like git-upload-pack(1) to serve different sets of references to the client depending on which namespace is enabled, which is for example useful in fork networks. Namespaced refs are stored with a `refs/namespaces/$namespace` prefix, but all the user will ultimately see is a stripped version where that prefix is removed. The way that this interacts with "transfer.hideRefs" is not immediately obvious: the hidden refs can either apply to the stripped references, or to the non-stripped ones that still have the namespace prefix. In fact, the "transfer.hideRefs" machinery does the former and applies to the stripped reference by default, but rules can have "^" prefixed to switch this behaviour to instead match against the full reference name. Namespaces are exclusively handled at the generic "refs" layer, the respective backends have no clue that such a thing even exists. This also has the consequence that they cannot handle hiding references as soon as reference namespaces come into play because they neither know whether a namespace is active, nor do they know how to strip references if they are active. Handling such exclude patterns in `refs_for_each_namespaced_ref()` and `refs_for_each_fullref_in_prefixes()` is broken though, as both support that the user passes both namespaces and exclude patterns. In the case where both are set we will exclude references with unstripped names, even though we really wanted to exclude references based on their stripped names. This only surfaces when: - A repository uses reference namespaces. - "transfer.hideRefs" is active. - The namespaced references are packed into the "packed-refs" file. None of our tests exercise this scenario, and thus we haven't ever hit it. While t5509 exercises both (1) and (2), it does not happen to hit (3). It is trivial to demonstrate the bug though by explicitly packing refs in the tests, and then we indeed surface the breakage. Fix this bug by prefixing exclude patterns with the namespace in the generic layer. The newly introduced function will be used outside of "refs.c" in the next patch, so we add a declaration to "refs.h". Signed-off-by: Patrick Steinhardt <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d706005 commit 155dc84

File tree

3 files changed

+41
-4
lines changed

3 files changed

+41
-4
lines changed

refs.c

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1517,6 +1517,19 @@ const char **hidden_refs_to_excludes(const struct strvec *hide_refs)
15171517
return hide_refs->v;
15181518
}
15191519

1520+
const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
1521+
const char *namespace,
1522+
struct strvec *out)
1523+
{
1524+
if (!namespace || !*namespace || !exclude_patterns || !*exclude_patterns)
1525+
return exclude_patterns;
1526+
1527+
for (size_t i = 0; exclude_patterns[i]; i++)
1528+
strvec_pushf(out, "%s%s", namespace, exclude_patterns[i]);
1529+
1530+
return out->v;
1531+
}
1532+
15201533
const char *find_descendant_ref(const char *dirname,
15211534
const struct string_list *extras,
15221535
const struct string_list *skip)
@@ -1634,11 +1647,19 @@ int refs_for_each_namespaced_ref(struct ref_store *refs,
16341647
const char **exclude_patterns,
16351648
each_ref_fn fn, void *cb_data)
16361649
{
1637-
struct strbuf buf = STRBUF_INIT;
1650+
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
1651+
struct strbuf prefix = STRBUF_INIT;
16381652
int ret;
1639-
strbuf_addf(&buf, "%srefs/", get_git_namespace());
1640-
ret = do_for_each_ref(refs, buf.buf, exclude_patterns, fn, 0, 0, cb_data);
1641-
strbuf_release(&buf);
1653+
1654+
exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1655+
get_git_namespace(),
1656+
&namespaced_exclude_patterns);
1657+
1658+
strbuf_addf(&prefix, "%srefs/", get_git_namespace());
1659+
ret = do_for_each_ref(refs, prefix.buf, exclude_patterns, fn, 0, 0, cb_data);
1660+
1661+
strvec_clear(&namespaced_exclude_patterns);
1662+
strbuf_release(&prefix);
16421663
return ret;
16431664
}
16441665

@@ -1719,6 +1740,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17191740
const char **exclude_patterns,
17201741
each_ref_fn fn, void *cb_data)
17211742
{
1743+
struct strvec namespaced_exclude_patterns = STRVEC_INIT;
17221744
struct string_list prefixes = STRING_LIST_INIT_DUP;
17231745
struct string_list_item *prefix;
17241746
struct strbuf buf = STRBUF_INIT;
@@ -1730,6 +1752,10 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17301752
strbuf_addstr(&buf, namespace);
17311753
namespace_len = buf.len;
17321754

1755+
exclude_patterns = get_namespaced_exclude_patterns(exclude_patterns,
1756+
namespace,
1757+
&namespaced_exclude_patterns);
1758+
17331759
for_each_string_list_item(prefix, &prefixes) {
17341760
strbuf_addstr(&buf, prefix->string);
17351761
ret = refs_for_each_fullref_in(ref_store, buf.buf,
@@ -1739,6 +1765,7 @@ int refs_for_each_fullref_in_prefixes(struct ref_store *ref_store,
17391765
strbuf_setlen(&buf, namespace_len);
17401766
}
17411767

1768+
strvec_clear(&namespaced_exclude_patterns);
17421769
string_list_clear(&prefixes, 0);
17431770
strbuf_release(&buf);
17441771
return ret;

refs.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,15 @@ int ref_is_hidden(const char *, const char *, const struct strvec *);
859859
*/
860860
const char **hidden_refs_to_excludes(const struct strvec *hide_refs);
861861

862+
/*
863+
* Prefix all exclude patterns with the namespace, if any. This is required
864+
* because exclude patterns apply to the stripped reference name, not the full
865+
* reference name with the namespace.
866+
*/
867+
const char **get_namespaced_exclude_patterns(const char **exclude_patterns,
868+
const char *namespace,
869+
struct strvec *out);
870+
862871
/* Is this a per-worktree ref living in the refs/ namespace? */
863872
int is_per_worktree_ref(const char *refname);
864873

t/t5509-fetch-push-namespaces.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ test_expect_success 'hide namespaced refs with transfer.hideRefs' '
9696
'
9797

9898
test_expect_success 'check that transfer.hideRefs does not match unstripped refs' '
99+
git -C pushee pack-refs --all &&
99100
GIT_NAMESPACE=namespace \
100101
git -C pushee -c transfer.hideRefs=refs/namespaces/namespace/refs/tags \
101102
ls-remote "ext::git %s ." >actual &&

0 commit comments

Comments
 (0)