fetch-pack: implement ref-in-want

Implement ref-in-want on the client side so that when a server supports
the "ref-in-want" feature, a client will send "want-ref" lines for each
reference the client wants to fetch.  This feature allows clients to
tolerate inconsistencies that exist when a remote repository's refs
change during the course of negotiation.

This allows a client to request to request a particular ref without
specifying the OID of the ref.  This means that instead of hitting an
error when a ref no longer points at the OID it did at the beginning of
negotiation, negotiation can continue and the value of that ref will be
sent at the termination of negotiation, just before a packfile is sent.

More information on the ref-in-want feature can be found in
Documentation/technical/protocol-v2.txt.

Signed-off-by: Brandon Williams <bmwill@google.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Brandon Williams 2018-06-27 15:30:23 -07:00 committed by Junio C Hamano
parent 989b8c4452
commit 733020517a
4 changed files with 186 additions and 3 deletions

View File

@ -1102,9 +1102,10 @@ static void add_shallow_requests(struct strbuf *req_buf,
static void add_wants(const struct ref *wants, struct strbuf *req_buf)
{
int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0);
for ( ; wants ; wants = wants->next) {
const struct object_id *remote = &wants->old_oid;
const char *remote_hex;
struct object *o;
/*
@ -1122,8 +1123,10 @@ static void add_wants(const struct ref *wants, struct strbuf *req_buf)
continue;
}
remote_hex = oid_to_hex(remote);
packet_buf_write(req_buf, "want %s\n", remote_hex);
if (!use_ref_in_want || wants->exact_oid)
packet_buf_write(req_buf, "want %s\n", oid_to_hex(remote));
else
packet_buf_write(req_buf, "want-ref %s\n", wants->name);
}
}
@ -1334,6 +1337,32 @@ static void receive_shallow_info(struct fetch_pack_args *args,
args->deepen = 1;
}
static void receive_wanted_refs(struct packet_reader *reader, struct ref *refs)
{
process_section_header(reader, "wanted-refs", 0);
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
struct object_id oid;
const char *end;
struct ref *r = NULL;
if (parse_oid_hex(reader->line, &oid, &end) || *end++ != ' ')
die("expected wanted-ref, got '%s'", reader->line);
for (r = refs; r; r = r->next) {
if (!strcmp(end, r->name)) {
oidcpy(&r->old_oid, &oid);
break;
}
}
if (!r)
die("unexpected wanted-ref: '%s'", reader->line);
}
if (reader->status != PACKET_READ_DELIM)
die("error processing wanted refs: %d", reader->status);
}
enum fetch_state {
FETCH_CHECK_LOCAL = 0,
FETCH_SEND_REQUEST,
@ -1408,6 +1437,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
if (process_section_header(&reader, "shallow-info", 1))
receive_shallow_info(args, &reader);
if (process_section_header(&reader, "wanted-refs", 1))
receive_wanted_refs(&reader, ref);
/* get the pack */
process_section_header(&reader, "packfile", 0);
if (get_pack(args, fd, pack_lockfile))

View File

@ -1735,6 +1735,7 @@ int get_fetch_map(const struct ref *remote_refs,
if (refspec->exact_sha1) {
ref_map = alloc_ref(name);
get_oid_hex(name, &ref_map->old_oid);
ref_map->exact_oid = 1;
} else {
ref_map = get_remote_ref(remote_refs, name);
}

View File

@ -73,6 +73,7 @@ struct ref {
force:1,
forced_update:1,
expect_old_sha1:1,
exact_oid:1,
deletion:1;
enum {

View File

@ -211,6 +211,18 @@ test_expect_success 'server is initially ahead - no ref in want' '
grep "ERR upload-pack: not our ref" err
'
test_expect_success 'server is initially ahead - ref in want' '
git -C "$REPO" config uploadpack.allowRefInWant true &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
inconsistency master 1234567890123456789012345678901234567890 &&
git -C local fetch &&
git -C "$REPO" rev-parse --verify master >expected &&
git -C local rev-parse --verify refs/remotes/origin/master >actual &&
test_cmp expected actual
'
test_expect_success 'server is initially behind - no ref in want' '
git -C "$REPO" config uploadpack.allowRefInWant false &&
rm -rf local &&
@ -223,6 +235,143 @@ test_expect_success 'server is initially behind - no ref in want' '
test_cmp expected actual
'
test_expect_success 'server is initially behind - ref in want' '
git -C "$REPO" config uploadpack.allowRefInWant true &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
inconsistency master "master^" &&
git -C local fetch &&
git -C "$REPO" rev-parse --verify "master" >expected &&
git -C local rev-parse --verify refs/remotes/origin/master >actual &&
test_cmp expected actual
'
test_expect_success 'server loses a ref - ref in want' '
git -C "$REPO" config uploadpack.allowRefInWant true &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" &&
test_must_fail git -C local fetch 2>err &&
grep "ERR unknown ref refs/heads/raster" err
'
stop_httpd
REPO="$(pwd)/repo"
LOCAL_PRISTINE="$(pwd)/local_pristine"
# $REPO
# c(o/foo) d(o/bar)
# \ /
# b e(baz) f(master)
# \__ | __/
# \ | /
# a
#
# $LOCAL_PRISTINE
# s32(side)
# |
# .
# .
# |
# a(master)
test_expect_success 'setup repos for fetching with ref-in-want tests' '
(
git init "$REPO" &&
cd "$REPO" &&
test_commit a &&
# Local repo with many commits (so that negotiation will take
# more than 1 request/response pair)
rm -rf "$LOCAL_PRISTINE" &&
git clone "file://$REPO" "$LOCAL_PRISTINE" &&
cd "$LOCAL_PRISTINE" &&
git checkout -b side &&
for i in $(seq 1 33); do test_commit s$i; done &&
# Add novel commits to upstream
git checkout master &&
cd "$REPO" &&
git checkout -b o/foo &&
test_commit b &&
test_commit c &&
git checkout -b o/bar b &&
test_commit d &&
git checkout -b baz a &&
test_commit e &&
git checkout master &&
test_commit f
) &&
git -C "$REPO" config uploadpack.allowRefInWant true &&
git -C "$LOCAL_PRISTINE" config protocol.version 2
'
test_expect_success 'fetching with exact OID' '
test_when_finished "rm -f log" &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
$(git -C "$REPO" rev-parse d):refs/heads/actual &&
git -C "$REPO" rev-parse "d" >expected &&
git -C local rev-parse refs/heads/actual >actual &&
test_cmp expected actual &&
grep "want $(git -C "$REPO" rev-parse d)" log
'
test_expect_success 'fetching multiple refs' '
test_when_finished "rm -f log" &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin master baz &&
git -C "$REPO" rev-parse "master" "baz" >expected &&
git -C local rev-parse refs/remotes/origin/master refs/remotes/origin/baz >actual &&
test_cmp expected actual &&
grep "want-ref refs/heads/master" log &&
grep "want-ref refs/heads/baz" log
'
test_expect_success 'fetching ref and exact OID' '
test_when_finished "rm -f log" &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
master $(git -C "$REPO" rev-parse b):refs/heads/actual &&
git -C "$REPO" rev-parse "master" "b" >expected &&
git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual &&
test_cmp expected actual &&
grep "want $(git -C "$REPO" rev-parse b)" log &&
grep "want-ref refs/heads/master" log
'
test_expect_success 'fetching with wildcard that does not match any refs' '
test_when_finished "rm -f log" &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
git -C local fetch origin refs/heads/none*:refs/heads/* >out &&
test_must_be_empty out
'
test_expect_success 'fetching with wildcard that matches multiple refs' '
test_when_finished "rm -f log" &&
rm -rf local &&
cp -r "$LOCAL_PRISTINE" local &&
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin refs/heads/o*:refs/heads/o* &&
git -C "$REPO" rev-parse "o/foo" "o/bar" >expected &&
git -C local rev-parse "o/foo" "o/bar" >actual &&
test_cmp expected actual &&
grep "want-ref refs/heads/o/foo" log &&
grep "want-ref refs/heads/o/bar" log
'
test_done