Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge branch 'lf/ref-is-hidden-namespace'
Extend transfer.hideRefs to work better with use of namespaces.

* lf/ref-is-hidden-namespace:
  t5509: add basic tests for hideRefs
  hideRefs: add support for matching full refs
  upload-pack: strip refs before calling ref_is_hidden()
  config.txt: document the semantics of hideRefs with namespaces
  • Loading branch information
Jeff King committed Nov 20, 2015
2 parents 45014be + 948bfa2 commit dbba85e
Show file tree
Hide file tree
Showing 6 changed files with 100 additions and 15 deletions.
9 changes: 9 additions & 0 deletions Documentation/config.txt
Expand Up @@ -2673,6 +2673,15 @@ You may also include a `!` in front of the ref name to negate the entry,
explicitly exposing it, even if an earlier entry marked it as hidden.
If you have multiple hideRefs values, later entries override earlier ones
(and entries in more-specific config files override less-specific ones).
+
If a namespace is in use, the namespace prefix is stripped from each
reference before it is matched against `transfer.hiderefs` patterns.
For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
is omitted from the advertisements but `refs/heads/master` and
`refs/namespaces/bar/refs/heads/master` are still advertised as so-called
"have" lines. In order to match refs before stripping, add a `^` in front of
the ref name. If you combine `!` and `^`, `!` must be specified first.

transfer.unpackLimit::
When `fetch.unpackLimit` or `receive.unpackLimit` are
Expand Down
27 changes: 21 additions & 6 deletions builtin/receive-pack.c
Expand Up @@ -195,9 +195,6 @@ static int receive_pack_config(const char *var, const char *value, void *cb)

static void show_ref(const char *path, const unsigned char *sha1)
{
if (ref_is_hidden(path))
return;

if (sent_capabilities) {
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
} else {
Expand All @@ -219,9 +216,14 @@ static void show_ref(const char *path, const unsigned char *sha1)
}
}

static int show_ref_cb(const char *path, const struct object_id *oid, int flag, void *unused)
static int show_ref_cb(const char *path_full, const struct object_id *oid,
int flag, void *unused)
{
path = strip_namespace(path);
const char *path = strip_namespace(path_full);

if (ref_is_hidden(path, path_full))
return 0;

/*
* Advertise refs outside our current namespace as ".have"
* refs, so that the client can use them to minimize data
Expand Down Expand Up @@ -1195,16 +1197,29 @@ static int iterate_receive_command_list(void *cb_data, unsigned char sha1[20])

static void reject_updates_to_hidden(struct command *commands)
{
struct strbuf refname_full = STRBUF_INIT;
size_t prefix_len;
struct command *cmd;

strbuf_addstr(&refname_full, get_git_namespace());
prefix_len = refname_full.len;

for (cmd = commands; cmd; cmd = cmd->next) {
if (cmd->error_string || !ref_is_hidden(cmd->ref_name))
if (cmd->error_string)
continue;

strbuf_setlen(&refname_full, prefix_len);
strbuf_addstr(&refname_full, cmd->ref_name);

if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
continue;
if (is_null_sha1(cmd->new_sha1))
cmd->error_string = "deny deleting a hidden ref";
else
cmd->error_string = "deny updating a hidden ref";
}

strbuf_release(&refname_full);
}

static int should_process_cmd(struct command *cmd)
Expand Down
15 changes: 12 additions & 3 deletions refs.c
Expand Up @@ -4534,14 +4534,15 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
return 0;
}

int ref_is_hidden(const char *refname)
int ref_is_hidden(const char *refname, const char *refname_full)
{
int i;

if (!hide_refs)
return 0;
for (i = hide_refs->nr - 1; i >= 0; i--) {
const char *match = hide_refs->items[i].string;
const char *subject;
int neg = 0;
int len;

Expand All @@ -4550,10 +4551,18 @@ int ref_is_hidden(const char *refname)
match++;
}

if (!starts_with(refname, match))
if (*match == '^') {
subject = refname_full;
match++;
} else {
subject = refname;
}

/* refname can be NULL when namespaces are used. */
if (!subject || !starts_with(subject, match))
continue;
len = strlen(match);
if (!refname[len] || refname[len] == '/')
if (!subject[len] || subject[len] == '/')
return !neg;
}
return 0;
Expand Down
10 changes: 9 additions & 1 deletion refs.h
Expand Up @@ -444,7 +444,15 @@ int update_ref(const char *msg, const char *refname,

extern int parse_hide_refs_config(const char *var, const char *value, const char *);

extern int ref_is_hidden(const char *);
/*
* Check whether a ref is hidden. If no namespace is set, both the first and
* the second parameter point to the full ref name. If a namespace is set and
* the ref is inside that namespace, the first parameter is a pointer to the
* name of the ref with the namespace prefix removed. If a namespace is set and
* the ref is outside that namespace, the first parameter is NULL. The second
* parameter always points to the full ref name.
*/
extern int ref_is_hidden(const char *, const char *);

enum ref_type {
REF_TYPE_PER_WORKTREE,
Expand Down
41 changes: 41 additions & 0 deletions t/t5509-fetch-push-namespaces.sh
Expand Up @@ -82,4 +82,45 @@ test_expect_success 'mirroring a repository using a ref namespace' '
)
'

test_expect_success 'hide namespaced refs with transfer.hideRefs' '
GIT_NAMESPACE=namespace \
git -C pushee -c transfer.hideRefs=refs/tags \
ls-remote "ext::git %s ." >actual &&
printf "$commit1\trefs/heads/master\n" >expected &&
test_cmp expected actual
'

test_expect_success 'check that transfer.hideRefs does not match unstripped refs' '
GIT_NAMESPACE=namespace \
git -C pushee -c transfer.hideRefs=refs/namespaces/namespace/refs/tags \
ls-remote "ext::git %s ." >actual &&
printf "$commit1\trefs/heads/master\n" >expected &&
printf "$commit0\trefs/tags/0\n" >>expected &&
printf "$commit1\trefs/tags/1\n" >>expected &&
test_cmp expected actual
'

test_expect_success 'hide full refs with transfer.hideRefs' '
GIT_NAMESPACE=namespace \
git -C pushee -c transfer.hideRefs="^refs/namespaces/namespace/refs/tags" \
ls-remote "ext::git %s ." >actual &&
printf "$commit1\trefs/heads/master\n" >expected &&
test_cmp expected actual
'

test_expect_success 'try to update a hidden ref' '
test_config -C pushee transfer.hideRefs refs/heads/master &&
test_must_fail git -C original push pushee-namespaced master
'

test_expect_success 'try to update a ref that is not hidden' '
test_config -C pushee transfer.hideRefs refs/namespaces/namespace/refs/heads/master &&
git -C original push pushee-namespaced master
'

test_expect_success 'try to update a hidden full ref' '
test_config -C pushee transfer.hideRefs "^refs/namespaces/namespace/refs/heads/master" &&
test_must_fail git -C original push pushee-namespaced master
'

test_done
13 changes: 8 additions & 5 deletions upload-pack.c
Expand Up @@ -688,22 +688,25 @@ static void receive_needs(void)
}

/* return non-zero if the ref is hidden, otherwise 0 */
static int mark_our_ref(const char *refname, const struct object_id *oid)
static int mark_our_ref(const char *refname, const char *refname_full,
const struct object_id *oid)
{
struct object *o = lookup_unknown_object(oid->hash);

if (ref_is_hidden(refname)) {
if (ref_is_hidden(refname, refname_full)) {
o->flags |= HIDDEN_REF;
return 1;
}
o->flags |= OUR_REF;
return 0;
}

static int check_ref(const char *refname, const struct object_id *oid,
static int check_ref(const char *refname_full, const struct object_id *oid,
int flag, void *cb_data)
{
mark_our_ref(refname, oid);
const char *refname = strip_namespace(refname_full);

mark_our_ref(refname, refname_full, oid);
return 0;
}

Expand All @@ -726,7 +729,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
const char *refname_nons = strip_namespace(refname);
struct object_id peeled;

if (mark_our_ref(refname, oid))
if (mark_our_ref(refname_nons, refname, oid))
return 0;

if (capabilities) {
Expand Down

0 comments on commit dbba85e

Please sign in to comment.