Skip to content

Commit cf1e7c0

Browse files
jonathantanmygitster
authored andcommitted
fetch-pack: write shallow, then check connectivity
When fetching, connectivity is checked after the shallow file is updated. There are 2 issues with this: (1) the connectivity check is only performed up to ancestors of existing refs (which is not thorough enough if we were deepening an existing ref in the first place), and (2) there is no rollback of the shallow file if the connectivity check fails. To solve (1), update the connectivity check to check the ancestry chain completely in the case of a deepening fetch by refraining from passing "--not --all" when invoking rev-list in connected.c. To solve (2), have fetch_pack() perform its own connectivity check before updating the shallow file. To support existing use cases in which "git fetch-pack" is used to download objects without much regard as to the connectivity of the resulting objects with respect to the existing repository, the connectivity check is only done if necessary (that is, the fetch is not a clone, and the fetch involves shallow/deepen functionality). "git fetch" still performs its own connectivity check, preserving correctness but sometimes performing redundant work. This redundancy is mitigated by the fact that fetch_pack() reports if it has performed a connectivity check itself, and if the transport supports connect or stateless-connect, it will bubble up that report so that "git fetch" knows not to perform the connectivity check in such a case. This was noticed when a user tried to deepen an existing repository by fetching with --no-shallow from a server that did not send all necessary objects - the connectivity check as run by "git fetch" succeeded, but a subsequent "git fsck" failed. Signed-off-by: Jonathan Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 7330205 commit cf1e7c0

File tree

8 files changed

+122
-7
lines changed

8 files changed

+122
-7
lines changed

builtin/fetch.c

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid)
769769
}
770770

771771
static int store_updated_refs(const char *raw_url, const char *remote_name,
772-
struct ref *ref_map)
772+
int connectivity_checked, struct ref *ref_map)
773773
{
774774
FILE *fp;
775775
struct commit *commit;
@@ -791,10 +791,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name,
791791
else
792792
url = xstrdup("foreign");
793793

794-
rm = ref_map;
795-
if (check_connected(iterate_ref_map, &rm, NULL)) {
796-
rc = error(_("%s did not send all necessary objects\n"), url);
797-
goto abort;
794+
if (!connectivity_checked) {
795+
rm = ref_map;
796+
if (check_connected(iterate_ref_map, &rm, NULL)) {
797+
rc = error(_("%s did not send all necessary objects\n"), url);
798+
goto abort;
799+
}
798800
}
799801

800802
prepare_format_display(ref_map);
@@ -966,8 +968,11 @@ static int fetch_refs(struct transport *transport, struct ref *ref_map,
966968
/* Update local refs based on the ref values fetched from a remote */
967969
static int consume_refs(struct transport *transport, struct ref *ref_map)
968970
{
971+
int connectivity_checked = transport->smart_options
972+
? transport->smart_options->connectivity_checked : 0;
969973
int ret = store_updated_refs(transport->url,
970974
transport->remote->name,
975+
connectivity_checked,
971976
ref_map);
972977
transport_unlock_pack(transport);
973978
return ret;

connected.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,10 @@ int check_connected(oid_iterate_fn fn, void *cb_data,
5858
argv_array_push(&rev_list.args, "--stdin");
5959
if (repository_format_partial_clone)
6060
argv_array_push(&rev_list.args, "--exclude-promisor-objects");
61-
argv_array_push(&rev_list.args, "--not");
62-
argv_array_push(&rev_list.args, "--all");
61+
if (!opt->is_deepening_fetch) {
62+
argv_array_push(&rev_list.args, "--not");
63+
argv_array_push(&rev_list.args, "--all");
64+
}
6365
argv_array_push(&rev_list.args, "--quiet");
6466
if (opt->progress)
6567
argv_array_pushf(&rev_list.args, "--progress=%s",

connected.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,13 @@ struct check_connected_options {
3838
* Insert these variables into the environment of the child process.
3939
*/
4040
const char **env;
41+
42+
/*
43+
* If non-zero, check the ancestry chain completely, not stopping at
44+
* any existing ref. This is necessary when deepening existing refs
45+
* during a fetch.
46+
*/
47+
unsigned is_deepening_fetch : 1;
4148
};
4249

4350
#define CHECK_CONNECTED_INIT { 0 }

fetch-pack.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "sha1-array.h"
2020
#include "oidset.h"
2121
#include "packfile.h"
22+
#include "connected.h"
2223

2324
static int transfer_unpack_limit = -1;
2425
static int fetch_unpack_limit = -1;
@@ -1596,6 +1597,18 @@ static void update_shallow(struct fetch_pack_args *args,
15961597
oid_array_clear(&ref);
15971598
}
15981599

1600+
static int iterate_ref_map(void *cb_data, struct object_id *oid)
1601+
{
1602+
struct ref **rm = cb_data;
1603+
struct ref *ref = *rm;
1604+
1605+
if (!ref)
1606+
return -1; /* end of the list */
1607+
*rm = ref->next;
1608+
oidcpy(oid, &ref->old_oid);
1609+
return 0;
1610+
}
1611+
15991612
struct ref *fetch_pack(struct fetch_pack_args *args,
16001613
int fd[], struct child_process *conn,
16011614
const struct ref *ref,
@@ -1624,7 +1637,25 @@ struct ref *fetch_pack(struct fetch_pack_args *args,
16241637
ref_cpy = do_fetch_pack(args, fd, ref, sought, nr_sought,
16251638
&si, pack_lockfile);
16261639
reprepare_packed_git(the_repository);
1640+
1641+
if (!args->cloning && args->deepen) {
1642+
struct check_connected_options opt = CHECK_CONNECTED_INIT;
1643+
struct ref *iterator = ref_cpy;
1644+
opt.shallow_file = alternate_shallow_file;
1645+
if (args->deepen)
1646+
opt.is_deepening_fetch = 1;
1647+
if (check_connected(iterate_ref_map, &iterator, &opt)) {
1648+
error(_("remote did not send all necessary objects"));
1649+
free_refs(ref_cpy);
1650+
ref_cpy = NULL;
1651+
rollback_lock_file(&shallow_lock);
1652+
goto cleanup;
1653+
}
1654+
args->connectivity_checked = 1;
1655+
}
1656+
16271657
update_shallow(args, ref_cpy, &si);
1658+
cleanup:
16281659
clear_shallow_info(&si);
16291660
return ref_cpy;
16301661
}

fetch-pack.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ struct fetch_pack_args {
4141
* regardless of which object flags it uses (if any).
4242
*/
4343
unsigned no_dependents:1;
44+
45+
/*
46+
* Because fetch_pack() overwrites the shallow file upon a
47+
* successful deepening non-clone fetch, if this struct
48+
* specifies such a fetch, fetch_pack() needs to perform a
49+
* connectivity check before deciding if a fetch is successful
50+
* (and overwriting the shallow file). fetch_pack() sets this
51+
* field to 1 if such a connectivity check was performed.
52+
*
53+
* This is different from check_self_contained_and_connected
54+
* in that the former allows existing objects in the
55+
* repository to satisfy connectivity needs, whereas the
56+
* latter doesn't.
57+
*/
58+
unsigned connectivity_checked:1;
4459
};
4560

4661
/*

t/t5537-fetch-shallow.sh

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,4 +186,47 @@ EOF
186186
test_cmp expect actual
187187
'
188188

189+
. "$TEST_DIRECTORY"/lib-httpd.sh
190+
start_httpd
191+
192+
REPO="$HTTPD_DOCUMENT_ROOT_PATH/repo"
193+
194+
test_expect_success 'shallow fetches check connectivity before writing shallow file' '
195+
rm -rf "$REPO" client &&
196+
197+
git init "$REPO" &&
198+
test_commit -C "$REPO" one &&
199+
test_commit -C "$REPO" two &&
200+
test_commit -C "$REPO" three &&
201+
202+
git init client &&
203+
204+
# Use protocol v2 to ensure that shallow information is sent exactly
205+
# once by the server, since we are planning to manipulate it.
206+
git -C "$REPO" config protocol.version 2 &&
207+
git -C client config protocol.version 2 &&
208+
209+
git -C client fetch --depth=2 "$HTTPD_URL/one_time_sed/repo" master:a_branch &&
210+
211+
# Craft a situation in which the server sends back an unshallow request
212+
# with an empty packfile. This is done by refetching with a shorter
213+
# depth (to ensure that the packfile is empty), and overwriting the
214+
# shallow line in the response with the unshallow line we want.
215+
printf "s/0034shallow %s/0036unshallow %s/" \
216+
"$(git -C "$REPO" rev-parse HEAD)" \
217+
"$(git -C "$REPO" rev-parse HEAD^)" \
218+
>"$HTTPD_ROOT_PATH/one-time-sed" &&
219+
test_must_fail git -C client fetch --depth=1 "$HTTPD_URL/one_time_sed/repo" \
220+
master:a_branch &&
221+
222+
# Ensure that the one-time-sed script was used.
223+
! test -e "$HTTPD_ROOT_PATH/one-time-sed" &&
224+
225+
# Ensure that the resulting repo is consistent, despite our failure to
226+
# fetch.
227+
git -C client fsck
228+
'
229+
230+
stop_httpd
231+
189232
test_done

transport.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,7 @@ static int fetch_refs_via_pack(struct transport *transport,
350350
data->got_remote_heads = 0;
351351
data->options.self_contained_and_connected =
352352
args.self_contained_and_connected;
353+
data->options.connectivity_checked = args.connectivity_checked;
353354

354355
if (refs == NULL)
355356
ret = -1;

transport.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ struct git_transport_options {
1818
unsigned deepen_relative : 1;
1919
unsigned from_promisor : 1;
2020
unsigned no_dependents : 1;
21+
22+
/*
23+
* If this transport supports connect or stateless-connect,
24+
* the corresponding field in struct fetch_pack_args is copied
25+
* here after fetching.
26+
*
27+
* See the definition of connectivity_checked in struct
28+
* fetch_pack_args for more information.
29+
*/
30+
unsigned connectivity_checked:1;
31+
2132
int depth;
2233
const char *deepen_since;
2334
const struct string_list *deepen_not;

0 commit comments

Comments
 (0)