Skip to content

Commit 7330205

Browse files
bmwillgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 989b8c4 commit 7330205

File tree

4 files changed

+186
-3
lines changed

4 files changed

+186
-3
lines changed

fetch-pack.c

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,9 +1102,10 @@ static void add_shallow_requests(struct strbuf *req_buf,
11021102

11031103
static void add_wants(const struct ref *wants, struct strbuf *req_buf)
11041104
{
1105+
int use_ref_in_want = server_supports_feature("fetch", "ref-in-want", 0);
1106+
11051107
for ( ; wants ; wants = wants->next) {
11061108
const struct object_id *remote = &wants->old_oid;
1107-
const char *remote_hex;
11081109
struct object *o;
11091110

11101111
/*
@@ -1122,8 +1123,10 @@ static void add_wants(const struct ref *wants, struct strbuf *req_buf)
11221123
continue;
11231124
}
11241125

1125-
remote_hex = oid_to_hex(remote);
1126-
packet_buf_write(req_buf, "want %s\n", remote_hex);
1126+
if (!use_ref_in_want || wants->exact_oid)
1127+
packet_buf_write(req_buf, "want %s\n", oid_to_hex(remote));
1128+
else
1129+
packet_buf_write(req_buf, "want-ref %s\n", wants->name);
11271130
}
11281131
}
11291132

@@ -1334,6 +1337,32 @@ static void receive_shallow_info(struct fetch_pack_args *args,
13341337
args->deepen = 1;
13351338
}
13361339

1340+
static void receive_wanted_refs(struct packet_reader *reader, struct ref *refs)
1341+
{
1342+
process_section_header(reader, "wanted-refs", 0);
1343+
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
1344+
struct object_id oid;
1345+
const char *end;
1346+
struct ref *r = NULL;
1347+
1348+
if (parse_oid_hex(reader->line, &oid, &end) || *end++ != ' ')
1349+
die("expected wanted-ref, got '%s'", reader->line);
1350+
1351+
for (r = refs; r; r = r->next) {
1352+
if (!strcmp(end, r->name)) {
1353+
oidcpy(&r->old_oid, &oid);
1354+
break;
1355+
}
1356+
}
1357+
1358+
if (!r)
1359+
die("unexpected wanted-ref: '%s'", reader->line);
1360+
}
1361+
1362+
if (reader->status != PACKET_READ_DELIM)
1363+
die("error processing wanted refs: %d", reader->status);
1364+
}
1365+
13371366
enum fetch_state {
13381367
FETCH_CHECK_LOCAL = 0,
13391368
FETCH_SEND_REQUEST,
@@ -1408,6 +1437,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
14081437
if (process_section_header(&reader, "shallow-info", 1))
14091438
receive_shallow_info(args, &reader);
14101439

1440+
if (process_section_header(&reader, "wanted-refs", 1))
1441+
receive_wanted_refs(&reader, ref);
1442+
14111443
/* get the pack */
14121444
process_section_header(&reader, "packfile", 0);
14131445
if (get_pack(args, fd, pack_lockfile))

remote.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1735,6 +1735,7 @@ int get_fetch_map(const struct ref *remote_refs,
17351735
if (refspec->exact_sha1) {
17361736
ref_map = alloc_ref(name);
17371737
get_oid_hex(name, &ref_map->old_oid);
1738+
ref_map->exact_oid = 1;
17381739
} else {
17391740
ref_map = get_remote_ref(remote_refs, name);
17401741
}

remote.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ struct ref {
7373
force:1,
7474
forced_update:1,
7575
expect_old_sha1:1,
76+
exact_oid:1,
7677
deletion:1;
7778

7879
enum {

t/t5703-upload-pack-ref-in-want.sh

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,18 @@ test_expect_success 'server is initially ahead - no ref in want' '
211211
grep "ERR upload-pack: not our ref" err
212212
'
213213

214+
test_expect_success 'server is initially ahead - ref in want' '
215+
git -C "$REPO" config uploadpack.allowRefInWant true &&
216+
rm -rf local &&
217+
cp -r "$LOCAL_PRISTINE" local &&
218+
inconsistency master 1234567890123456789012345678901234567890 &&
219+
git -C local fetch &&
220+
221+
git -C "$REPO" rev-parse --verify master >expected &&
222+
git -C local rev-parse --verify refs/remotes/origin/master >actual &&
223+
test_cmp expected actual
224+
'
225+
214226
test_expect_success 'server is initially behind - no ref in want' '
215227
git -C "$REPO" config uploadpack.allowRefInWant false &&
216228
rm -rf local &&
@@ -223,6 +235,143 @@ test_expect_success 'server is initially behind - no ref in want' '
223235
test_cmp expected actual
224236
'
225237

238+
test_expect_success 'server is initially behind - ref in want' '
239+
git -C "$REPO" config uploadpack.allowRefInWant true &&
240+
rm -rf local &&
241+
cp -r "$LOCAL_PRISTINE" local &&
242+
inconsistency master "master^" &&
243+
git -C local fetch &&
244+
245+
git -C "$REPO" rev-parse --verify "master" >expected &&
246+
git -C local rev-parse --verify refs/remotes/origin/master >actual &&
247+
test_cmp expected actual
248+
'
249+
250+
test_expect_success 'server loses a ref - ref in want' '
251+
git -C "$REPO" config uploadpack.allowRefInWant true &&
252+
rm -rf local &&
253+
cp -r "$LOCAL_PRISTINE" local &&
254+
echo "s/master/raster/" >"$HTTPD_ROOT_PATH/one-time-sed" &&
255+
test_must_fail git -C local fetch 2>err &&
256+
257+
grep "ERR unknown ref refs/heads/raster" err
258+
'
259+
226260
stop_httpd
227261

262+
REPO="$(pwd)/repo"
263+
LOCAL_PRISTINE="$(pwd)/local_pristine"
264+
265+
# $REPO
266+
# c(o/foo) d(o/bar)
267+
# \ /
268+
# b e(baz) f(master)
269+
# \__ | __/
270+
# \ | /
271+
# a
272+
#
273+
# $LOCAL_PRISTINE
274+
# s32(side)
275+
# |
276+
# .
277+
# .
278+
# |
279+
# a(master)
280+
test_expect_success 'setup repos for fetching with ref-in-want tests' '
281+
(
282+
git init "$REPO" &&
283+
cd "$REPO" &&
284+
test_commit a &&
285+
286+
# Local repo with many commits (so that negotiation will take
287+
# more than 1 request/response pair)
288+
rm -rf "$LOCAL_PRISTINE" &&
289+
git clone "file://$REPO" "$LOCAL_PRISTINE" &&
290+
cd "$LOCAL_PRISTINE" &&
291+
git checkout -b side &&
292+
for i in $(seq 1 33); do test_commit s$i; done &&
293+
294+
# Add novel commits to upstream
295+
git checkout master &&
296+
cd "$REPO" &&
297+
git checkout -b o/foo &&
298+
test_commit b &&
299+
test_commit c &&
300+
git checkout -b o/bar b &&
301+
test_commit d &&
302+
git checkout -b baz a &&
303+
test_commit e &&
304+
git checkout master &&
305+
test_commit f
306+
) &&
307+
git -C "$REPO" config uploadpack.allowRefInWant true &&
308+
git -C "$LOCAL_PRISTINE" config protocol.version 2
309+
'
310+
311+
test_expect_success 'fetching with exact OID' '
312+
test_when_finished "rm -f log" &&
313+
314+
rm -rf local &&
315+
cp -r "$LOCAL_PRISTINE" local &&
316+
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
317+
$(git -C "$REPO" rev-parse d):refs/heads/actual &&
318+
319+
git -C "$REPO" rev-parse "d" >expected &&
320+
git -C local rev-parse refs/heads/actual >actual &&
321+
test_cmp expected actual &&
322+
grep "want $(git -C "$REPO" rev-parse d)" log
323+
'
324+
325+
test_expect_success 'fetching multiple refs' '
326+
test_when_finished "rm -f log" &&
327+
328+
rm -rf local &&
329+
cp -r "$LOCAL_PRISTINE" local &&
330+
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin master baz &&
331+
332+
git -C "$REPO" rev-parse "master" "baz" >expected &&
333+
git -C local rev-parse refs/remotes/origin/master refs/remotes/origin/baz >actual &&
334+
test_cmp expected actual &&
335+
grep "want-ref refs/heads/master" log &&
336+
grep "want-ref refs/heads/baz" log
337+
'
338+
339+
test_expect_success 'fetching ref and exact OID' '
340+
test_when_finished "rm -f log" &&
341+
342+
rm -rf local &&
343+
cp -r "$LOCAL_PRISTINE" local &&
344+
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin \
345+
master $(git -C "$REPO" rev-parse b):refs/heads/actual &&
346+
347+
git -C "$REPO" rev-parse "master" "b" >expected &&
348+
git -C local rev-parse refs/remotes/origin/master refs/heads/actual >actual &&
349+
test_cmp expected actual &&
350+
grep "want $(git -C "$REPO" rev-parse b)" log &&
351+
grep "want-ref refs/heads/master" log
352+
'
353+
354+
test_expect_success 'fetching with wildcard that does not match any refs' '
355+
test_when_finished "rm -f log" &&
356+
357+
rm -rf local &&
358+
cp -r "$LOCAL_PRISTINE" local &&
359+
git -C local fetch origin refs/heads/none*:refs/heads/* >out &&
360+
test_must_be_empty out
361+
'
362+
363+
test_expect_success 'fetching with wildcard that matches multiple refs' '
364+
test_when_finished "rm -f log" &&
365+
366+
rm -rf local &&
367+
cp -r "$LOCAL_PRISTINE" local &&
368+
GIT_TRACE_PACKET="$(pwd)/log" git -C local fetch origin refs/heads/o*:refs/heads/o* &&
369+
370+
git -C "$REPO" rev-parse "o/foo" "o/bar" >expected &&
371+
git -C local rev-parse "o/foo" "o/bar" >actual &&
372+
test_cmp expected actual &&
373+
grep "want-ref refs/heads/o/foo" log &&
374+
grep "want-ref refs/heads/o/bar" log
375+
'
376+
228377
test_done

0 commit comments

Comments
 (0)