Merge branch 'ka/want-ref-in-namespace' into maint

"git upload-pack" which runs on the other side of "git fetch"
forgot to take the ref namespaces into account when handling
want-ref requests.

* ka/want-ref-in-namespace:
  docs: clarify the interaction of transfer.hideRefs and namespaces
  upload-pack.c: treat want-ref relative to namespace
  t5730: introduce fetch command helper
This commit is contained in:
Junio C Hamano 2021-10-12 13:51:42 -07:00
commit 0a5af02acb
3 changed files with 192 additions and 48 deletions

View File

@ -52,13 +52,17 @@ If you have multiple hideRefs values, later entries override earlier ones
(and entries in more-specific config files override less-specific 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 If a namespace is in use, the namespace prefix is stripped from each
reference before it is matched against `transfer.hiderefs` patterns. reference before it is matched against `transfer.hiderefs` patterns. In
order to match refs before stripping, add a `^` in front of the ref name. If
you combine `!` and `^`, `!` must be specified first.
+
For example, if `refs/heads/master` is specified in `transfer.hideRefs` and For example, if `refs/heads/master` is specified in `transfer.hideRefs` and
the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master` the current namespace is `foo`, then `refs/namespaces/foo/refs/heads/master`
is omitted from the advertisements but `refs/heads/master` and is omitted from the advertisements. If `uploadpack.allowRefInWant` is set,
`refs/namespaces/bar/refs/heads/master` are still advertised as so-called `upload-pack` will treat `want-ref refs/heads/master` in a protocol v2
"have" lines. In order to match refs before stripping, add a `^` in front of `fetch` command as if `refs/namespaces/foo/refs/heads/master` did not exist.
the ref name. If you combine `!` and `^`, `!` must be specified first. `receive-pack`, on the other hand, will still advertise the object id the
ref is pointing to without mentioning its name (a so-called ".have" line).
+ +
Even if you hide refs, a client may still be able to steal the target Even if you hide refs, a client may still be able to steal the target
objects via the techniques described in the "SECURITY" section of the objects via the techniques described in the "SECURITY" section of the

View File

@ -40,6 +40,30 @@ write_command () {
fi fi
} }
# Write a complete fetch command to stdout, suitable for use with `test-tool
# pkt-line`. "want-ref", "want", and "have" lines are read from stdin.
#
# Examples:
#
# write_fetch_command <<-EOF
# want-ref refs/heads/main
# have $(git rev-parse a)
# EOF
#
# write_fetch_command <<-EOF
# want $(git rev-parse b)
# have $(git rev-parse a)
# EOF
#
write_fetch_command () {
write_command fetch &&
echo "0001" &&
echo "no-progress" &&
cat &&
echo "done" &&
echo "0000"
}
# c(o/foo) d(o/bar) # c(o/foo) d(o/bar)
# \ / # \ /
# b e(baz) f(main) # b e(baz) f(main)
@ -77,15 +101,11 @@ test_expect_success 'config controls ref-in-want advertisement' '
' '
test_expect_success 'invalid want-ref line' ' test_expect_success 'invalid want-ref line' '
test-tool pkt-line pack >in <<-EOF && write_fetch_command >pkt <<-EOF &&
$(write_command fetch)
0001
no-progress
want-ref refs/heads/non-existent want-ref refs/heads/non-existent
done
0000
EOF EOF
test-tool pkt-line pack <pkt >in &&
test_must_fail test-tool serve-v2 --stateless-rpc 2>out <in && test_must_fail test-tool serve-v2 --stateless-rpc 2>out <in &&
grep "unknown ref" out grep "unknown ref" out
' '
@ -97,16 +117,11 @@ test_expect_success 'basic want-ref' '
EOF EOF
git rev-parse f >expected_commits && git rev-parse f >expected_commits &&
oid=$(git rev-parse a) && write_fetch_command >pkt <<-EOF &&
test-tool pkt-line pack >in <<-EOF &&
$(write_command fetch)
0001
no-progress
want-ref refs/heads/main want-ref refs/heads/main
have $oid have $(git rev-parse a)
done
0000
EOF EOF
test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in && test-tool serve-v2 --stateless-rpc >out <in &&
check_output check_output
@ -121,17 +136,12 @@ test_expect_success 'multiple want-ref lines' '
EOF EOF
git rev-parse c d >expected_commits && git rev-parse c d >expected_commits &&
oid=$(git rev-parse b) && write_fetch_command >pkt <<-EOF &&
test-tool pkt-line pack >in <<-EOF &&
$(write_command fetch)
0001
no-progress
want-ref refs/heads/o/foo want-ref refs/heads/o/foo
want-ref refs/heads/o/bar want-ref refs/heads/o/bar
have $oid have $(git rev-parse b)
done
0000
EOF EOF
test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in && test-tool serve-v2 --stateless-rpc >out <in &&
check_output check_output
@ -144,16 +154,12 @@ test_expect_success 'mix want and want-ref' '
EOF EOF
git rev-parse e f >expected_commits && git rev-parse e f >expected_commits &&
test-tool pkt-line pack >in <<-EOF && write_fetch_command >pkt <<-EOF &&
$(write_command fetch)
0001
no-progress
want-ref refs/heads/main want-ref refs/heads/main
want $(git rev-parse e) want $(git rev-parse e)
have $(git rev-parse a) have $(git rev-parse a)
done
0000
EOF EOF
test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in && test-tool serve-v2 --stateless-rpc >out <in &&
check_output check_output
@ -166,16 +172,11 @@ test_expect_success 'want-ref with ref we already have commit for' '
EOF EOF
>expected_commits && >expected_commits &&
oid=$(git rev-parse c) && write_fetch_command >pkt <<-EOF &&
test-tool pkt-line pack >in <<-EOF &&
$(write_command fetch)
0001
no-progress
want-ref refs/heads/o/foo want-ref refs/heads/o/foo
have $oid have $(git rev-parse c)
done
0000
EOF EOF
test-tool pkt-line pack <pkt >in &&
test-tool serve-v2 --stateless-rpc >out <in && test-tool serve-v2 --stateless-rpc >out <in &&
check_output check_output
@ -298,6 +299,141 @@ test_expect_success 'fetching with wildcard that matches multiple refs' '
grep "want-ref refs/heads/o/bar" log grep "want-ref refs/heads/o/bar" log
' '
REPO="$(pwd)/repo-ns"
test_expect_success 'setup namespaced repo' '
(
git init -b main "$REPO" &&
cd "$REPO" &&
test_commit a &&
test_commit b &&
git checkout a &&
test_commit c &&
git checkout a &&
test_commit d &&
git update-ref refs/heads/ns-no b &&
git update-ref refs/namespaces/ns/refs/heads/ns-yes c &&
git update-ref refs/namespaces/ns/refs/heads/hidden d
) &&
git -C "$REPO" config uploadpack.allowRefInWant true
'
test_expect_success 'with namespace: want-ref is considered relative to namespace' '
wanted_ref=refs/heads/ns-yes &&
oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
cat >expected_refs <<-EOF &&
$oid $wanted_ref
EOF
cat >expected_commits <<-EOF &&
$oid
$(git -C "$REPO" rev-parse a)
EOF
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
check_output
'
test_expect_success 'with namespace: want-ref outside namespace is unknown' '
wanted_ref=refs/heads/ns-no &&
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
test_must_fail env GIT_NAMESPACE=ns \
test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
grep "unknown ref" out
'
# Cross-check refs/heads/ns-no indeed exists
test_expect_success 'without namespace: want-ref outside namespace succeeds' '
wanted_ref=refs/heads/ns-no &&
oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
cat >expected_refs <<-EOF &&
$oid $wanted_ref
EOF
cat >expected_commits <<-EOF &&
$oid
$(git -C "$REPO" rev-parse a)
EOF
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
check_output
'
test_expect_success 'with namespace: hideRefs is matched, relative to namespace' '
wanted_ref=refs/heads/hidden &&
git -C "$REPO" config transfer.hideRefs $wanted_ref &&
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
test_must_fail env GIT_NAMESPACE=ns \
test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
grep "unknown ref" out
'
# Cross-check refs/heads/hidden indeed exists
test_expect_success 'with namespace: want-ref succeeds if hideRefs is removed' '
wanted_ref=refs/heads/hidden &&
git -C "$REPO" config --unset transfer.hideRefs $wanted_ref &&
oid=$(git -C "$REPO" rev-parse "refs/namespaces/ns/$wanted_ref") &&
cat >expected_refs <<-EOF &&
$oid $wanted_ref
EOF
cat >expected_commits <<-EOF &&
$oid
$(git -C "$REPO" rev-parse a)
EOF
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
GIT_NAMESPACE=ns test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
check_output
'
test_expect_success 'without namespace: relative hideRefs does not match' '
wanted_ref=refs/namespaces/ns/refs/heads/hidden &&
git -C "$REPO" config transfer.hideRefs refs/heads/hidden &&
oid=$(git -C "$REPO" rev-parse $wanted_ref) &&
cat >expected_refs <<-EOF &&
$oid $wanted_ref
EOF
cat >expected_commits <<-EOF &&
$oid
$(git -C "$REPO" rev-parse a)
EOF
write_fetch_command >pkt <<-EOF &&
want-ref $wanted_ref
EOF
test-tool pkt-line pack <pkt >in &&
test-tool -C "$REPO" serve-v2 --stateless-rpc >out <in &&
check_output
'
. "$TEST_DIRECTORY"/lib-httpd.sh . "$TEST_DIRECTORY"/lib-httpd.sh
start_httpd start_httpd

View File

@ -1417,21 +1417,25 @@ static int parse_want_ref(struct packet_writer *writer, const char *line,
struct string_list *wanted_refs, struct string_list *wanted_refs,
struct object_array *want_obj) struct object_array *want_obj)
{ {
const char *arg; const char *refname_nons;
if (skip_prefix(line, "want-ref ", &arg)) { if (skip_prefix(line, "want-ref ", &refname_nons)) {
struct object_id oid; struct object_id oid;
struct string_list_item *item; struct string_list_item *item;
struct object *o; struct object *o;
struct strbuf refname = STRBUF_INIT;
if (read_ref(arg, &oid)) { strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
packet_writer_error(writer, "unknown ref %s", arg); if (ref_is_hidden(refname_nons, refname.buf) ||
die("unknown ref %s", arg); read_ref(refname.buf, &oid)) {
packet_writer_error(writer, "unknown ref %s", refname_nons);
die("unknown ref %s", refname_nons);
} }
strbuf_release(&refname);
item = string_list_append(wanted_refs, arg); item = string_list_append(wanted_refs, refname_nons);
item->util = oiddup(&oid); item->util = oiddup(&oid);
o = parse_object_or_die(&oid, arg); o = parse_object_or_die(&oid, refname_nons);
if (!(o->flags & WANTED)) { if (!(o->flags & WANTED)) {
o->flags |= WANTED; o->flags |= WANTED;
add_object_array(o, NULL, want_obj); add_object_array(o, NULL, want_obj);