Skip to content

Commit 820a536

Browse files
jonathantanmygitster
authored andcommitted
upload-pack: send part of packfile response as uri
Teach upload-pack to send part of its packfile response as URIs. An administrator may configure a repository with one or more "uploadpack.blobpackfileuri" lines, each line containing an OID, a pack hash, and a URI. A client may configure fetch.uriprotocols to be a comma-separated list of protocols that it is willing to use to fetch additional packfiles - this list will be sent to the server. Whenever an object with one of those OIDs would appear in the packfile transmitted by upload-pack, the server may exclude that object, and instead send the URI. The client will then download the packs referred to by those URIs before performing the connectivity check. Signed-off-by: Jonathan Tan <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent bf01639 commit 820a536

File tree

4 files changed

+312
-11
lines changed

4 files changed

+312
-11
lines changed

builtin/pack-objects.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,8 @@ static unsigned long window_memory_limit = 0;
110110

111111
static struct list_objects_filter_options filter_options;
112112

113+
static struct string_list uri_protocols = STRING_LIST_INIT_NODUP;
114+
113115
enum missing_action {
114116
MA_ERROR = 0, /* fail if any missing objects are encountered */
115117
MA_ALLOW_ANY, /* silently allow ALL missing objects */
@@ -118,6 +120,15 @@ enum missing_action {
118120
static enum missing_action arg_missing_action;
119121
static show_object_fn fn_show_object;
120122

123+
struct configured_exclusion {
124+
struct oidmap_entry e;
125+
char *pack_hash_hex;
126+
char *uri;
127+
};
128+
static struct oidmap configured_exclusions;
129+
130+
static struct oidset excluded_by_config;
131+
121132
/*
122133
* stats
123134
*/
@@ -832,6 +843,25 @@ static off_t write_reused_pack(struct hashfile *f)
832843
return reuse_packfile_offset - sizeof(struct pack_header);
833844
}
834845

846+
static void write_excluded_by_configs(void)
847+
{
848+
struct oidset_iter iter;
849+
const struct object_id *oid;
850+
851+
oidset_iter_init(&excluded_by_config, &iter);
852+
while ((oid = oidset_iter_next(&iter))) {
853+
struct configured_exclusion *ex =
854+
oidmap_get(&configured_exclusions, oid);
855+
856+
if (!ex)
857+
BUG("configured exclusion wasn't configured");
858+
write_in_full(1, ex->pack_hash_hex, strlen(ex->pack_hash_hex));
859+
write_in_full(1, " ", 1);
860+
write_in_full(1, ex->uri, strlen(ex->uri));
861+
write_in_full(1, "\n", 1);
862+
}
863+
}
864+
835865
static const char no_split_warning[] = N_(
836866
"disabling bitmap writing, packs are split due to pack.packSizeLimit"
837867
);
@@ -1125,6 +1155,25 @@ static int want_object_in_pack(const struct object_id *oid,
11251155
}
11261156
}
11271157

1158+
if (uri_protocols.nr) {
1159+
struct configured_exclusion *ex =
1160+
oidmap_get(&configured_exclusions, oid);
1161+
int i;
1162+
const char *p;
1163+
1164+
if (ex) {
1165+
for (i = 0; i < uri_protocols.nr; i++) {
1166+
if (skip_prefix(ex->uri,
1167+
uri_protocols.items[i].string,
1168+
&p) &&
1169+
*p == ':') {
1170+
oidset_insert(&excluded_by_config, oid);
1171+
return 0;
1172+
}
1173+
}
1174+
}
1175+
}
1176+
11281177
return 1;
11291178
}
11301179

@@ -2726,6 +2775,29 @@ static int git_pack_config(const char *k, const char *v, void *cb)
27262775
pack_idx_opts.version);
27272776
return 0;
27282777
}
2778+
if (!strcmp(k, "uploadpack.blobpackfileuri")) {
2779+
struct configured_exclusion *ex = xmalloc(sizeof(*ex));
2780+
const char *oid_end, *pack_end;
2781+
/*
2782+
* Stores the pack hash. This is not a true object ID, but is
2783+
* of the same form.
2784+
*/
2785+
struct object_id pack_hash;
2786+
2787+
if (parse_oid_hex(v, &ex->e.oid, &oid_end) ||
2788+
*oid_end != ' ' ||
2789+
parse_oid_hex(oid_end + 1, &pack_hash, &pack_end) ||
2790+
*pack_end != ' ')
2791+
die(_("value of uploadpack.blobpackfileuri must be "
2792+
"of the form '<object-hash> <pack-hash> <uri>' (got '%s')"), v);
2793+
if (oidmap_get(&configured_exclusions, &ex->e.oid))
2794+
die(_("object already configured in another "
2795+
"uploadpack.blobpackfileuri (got '%s')"), v);
2796+
ex->pack_hash_hex = xcalloc(1, pack_end - oid_end);
2797+
memcpy(ex->pack_hash_hex, oid_end + 1, pack_end - oid_end - 1);
2798+
ex->uri = xstrdup(pack_end + 1);
2799+
oidmap_put(&configured_exclusions, ex);
2800+
}
27292801
return git_default_config(k, v, cb);
27302802
}
27312803

@@ -3318,6 +3390,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
33183390
N_("do not pack objects in promisor packfiles")),
33193391
OPT_BOOL(0, "delta-islands", &use_delta_islands,
33203392
N_("respect islands during delta compression")),
3393+
OPT_STRING_LIST(0, "uri-protocol", &uri_protocols,
3394+
N_("protocol"),
3395+
N_("exclude any configured uploadpack.blobpackfileuri with this protocol")),
33213396
OPT_END(),
33223397
};
33233398

@@ -3492,6 +3567,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
34923567
return 0;
34933568
if (nr_result)
34943569
prepare_pack(window, depth);
3570+
write_excluded_by_configs();
34953571
write_pack_file();
34963572
if (progress)
34973573
fprintf_ln(stderr,

fetch-pack.c

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ static struct lock_file shallow_lock;
3838
static const char *alternate_shallow_file;
3939
static char *negotiation_algorithm;
4040
static struct strbuf fsck_msg_types = STRBUF_INIT;
41+
static struct string_list uri_protocols = STRING_LIST_INIT_DUP;
4142

4243
/* Remember to update object flag allocation in object.h */
4344
#define COMPLETE (1U << 0)
@@ -755,7 +756,8 @@ static int sideband_demux(int in, int out, void *data)
755756
}
756757

757758
static int get_pack(struct fetch_pack_args *args,
758-
int xd[2], struct string_list *pack_lockfiles)
759+
int xd[2], struct string_list *pack_lockfiles,
760+
int only_packfile)
759761
{
760762
struct async demux;
761763
int do_keep = args->keep_pack;
@@ -815,8 +817,15 @@ static int get_pack(struct fetch_pack_args *args,
815817
"--keep=fetch-pack %"PRIuMAX " on %s",
816818
(uintmax_t)getpid(), hostname);
817819
}
818-
if (args->check_self_contained_and_connected)
820+
if (only_packfile && args->check_self_contained_and_connected)
819821
argv_array_push(&cmd.args, "--check-self-contained-and-connected");
822+
else
823+
/*
824+
* We cannot perform any connectivity checks because
825+
* not all packs have been downloaded; let the caller
826+
* have this responsibility.
827+
*/
828+
args->check_self_contained_and_connected = 0;
820829
if (args->from_promisor)
821830
argv_array_push(&cmd.args, "--promisor");
822831
}
@@ -993,7 +1002,7 @@ static struct ref *do_fetch_pack(struct fetch_pack_args *args,
9931002
alternate_shallow_file = setup_temporary_shallow(si->shallow);
9941003
else
9951004
alternate_shallow_file = NULL;
996-
if (get_pack(args, fd, pack_lockfiles))
1005+
if (get_pack(args, fd, pack_lockfiles, 1))
9971006
die(_("git fetch-pack: fetch failed."));
9981007

9991008
all_done:
@@ -1148,6 +1157,26 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out,
11481157
warning("filtering not recognized by server, ignoring");
11491158
}
11501159

1160+
if (server_supports_feature("fetch", "packfile-uris", 0)) {
1161+
int i;
1162+
struct strbuf to_send = STRBUF_INIT;
1163+
1164+
for (i = 0; i < uri_protocols.nr; i++) {
1165+
const char *s = uri_protocols.items[i].string;
1166+
1167+
if (!strcmp(s, "https") || !strcmp(s, "http")) {
1168+
if (to_send.len)
1169+
strbuf_addch(&to_send, ',');
1170+
strbuf_addstr(&to_send, s);
1171+
}
1172+
}
1173+
if (to_send.len) {
1174+
packet_buf_write(&req_buf, "packfile-uris %s",
1175+
to_send.buf);
1176+
strbuf_release(&to_send);
1177+
}
1178+
}
1179+
11511180
/* add wants */
11521181
add_wants(args->no_dependents, wants, &req_buf);
11531182

@@ -1323,6 +1352,21 @@ static void receive_wanted_refs(struct packet_reader *reader,
13231352
die(_("error processing wanted refs: %d"), reader->status);
13241353
}
13251354

1355+
static void receive_packfile_uris(struct packet_reader *reader,
1356+
struct string_list *uris)
1357+
{
1358+
process_section_header(reader, "packfile-uris", 0);
1359+
while (packet_reader_read(reader) == PACKET_READ_NORMAL) {
1360+
if (reader->pktlen < the_hash_algo->hexsz ||
1361+
reader->line[the_hash_algo->hexsz] != ' ')
1362+
die("expected '<hash> <uri>', got: %s\n", reader->line);
1363+
1364+
string_list_append(uris, reader->line);
1365+
}
1366+
if (reader->status != PACKET_READ_DELIM)
1367+
die("expected DELIM");
1368+
}
1369+
13261370
enum fetch_state {
13271371
FETCH_CHECK_LOCAL = 0,
13281372
FETCH_SEND_REQUEST,
@@ -1344,6 +1388,9 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
13441388
int in_vain = 0;
13451389
int haves_to_send = INITIAL_FLUSH;
13461390
struct fetch_negotiator negotiator;
1391+
struct string_list packfile_uris = STRING_LIST_INIT_DUP;
1392+
int i;
1393+
13471394
fetch_negotiator_init(&negotiator, negotiation_algorithm);
13481395
packet_reader_init(&reader, fd[0], NULL, 0,
13491396
PACKET_READ_CHOMP_NEWLINE |
@@ -1414,9 +1461,12 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
14141461
if (process_section_header(&reader, "wanted-refs", 1))
14151462
receive_wanted_refs(&reader, sought, nr_sought);
14161463

1417-
/* get the pack */
1464+
/* get the pack(s) */
1465+
if (process_section_header(&reader, "packfile-uris", 1))
1466+
receive_packfile_uris(&reader, &packfile_uris);
14181467
process_section_header(&reader, "packfile", 0);
1419-
if (get_pack(args, fd, pack_lockfiles))
1468+
if (get_pack(args, fd, pack_lockfiles,
1469+
!packfile_uris.nr))
14201470
die(_("git fetch-pack: fetch failed."));
14211471

14221472
state = FETCH_DONE;
@@ -1426,6 +1476,50 @@ static struct ref *do_fetch_pack_v2(struct fetch_pack_args *args,
14261476
}
14271477
}
14281478

1479+
for (i = 0; i < packfile_uris.nr; i++) {
1480+
struct child_process cmd = CHILD_PROCESS_INIT;
1481+
char packname[GIT_MAX_HEXSZ + 1];
1482+
const char *uri = packfile_uris.items[i].string +
1483+
the_hash_algo->hexsz + 1;
1484+
1485+
argv_array_push(&cmd.args, "http-fetch");
1486+
argv_array_push(&cmd.args, "--packfile");
1487+
argv_array_push(&cmd.args, uri);
1488+
cmd.git_cmd = 1;
1489+
cmd.no_stdin = 1;
1490+
cmd.out = -1;
1491+
if (start_command(&cmd))
1492+
die("fetch-pack: unable to spawn http-fetch");
1493+
1494+
if (read_in_full(cmd.out, packname, 5) < 0 ||
1495+
memcmp(packname, "keep\t", 5))
1496+
die("fetch-pack: expected keep then TAB at start of http-fetch output");
1497+
1498+
if (read_in_full(cmd.out, packname,
1499+
the_hash_algo->hexsz + 1) < 0 ||
1500+
packname[the_hash_algo->hexsz] != '\n')
1501+
die("fetch-pack: expected hash then LF at end of http-fetch output");
1502+
1503+
packname[the_hash_algo->hexsz] = '\0';
1504+
1505+
close(cmd.out);
1506+
1507+
if (finish_command(&cmd))
1508+
die("fetch-pack: unable to finish http-fetch");
1509+
1510+
if (memcmp(packfile_uris.items[i].string, packname,
1511+
the_hash_algo->hexsz))
1512+
die("fetch-pack: pack downloaded from %s does not match expected hash %.*s",
1513+
uri, (int) the_hash_algo->hexsz,
1514+
packfile_uris.items[i].string);
1515+
1516+
string_list_append_nodup(pack_lockfiles,
1517+
xstrfmt("%s/pack/pack-%s.keep",
1518+
get_object_directory(),
1519+
packname));
1520+
}
1521+
string_list_clear(&packfile_uris, 0);
1522+
14291523
negotiator.release(&negotiator);
14301524
oidset_clear(&common);
14311525
return ref;
@@ -1465,6 +1559,14 @@ static void fetch_pack_config(void)
14651559
git_config_get_bool("transfer.fsckobjects", &transfer_fsck_objects);
14661560
git_config_get_string("fetch.negotiationalgorithm",
14671561
&negotiation_algorithm);
1562+
if (!uri_protocols.nr) {
1563+
char *str;
1564+
1565+
if (!git_config_get_string("fetch.uriprotocols", &str) && str) {
1566+
string_list_split(&uri_protocols, str, ',', -1);
1567+
free(str);
1568+
}
1569+
}
14681570

14691571
git_config(fetch_pack_config_cb, NULL);
14701572
}

t/t5702-protocol-v2.sh

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,63 @@ test_expect_success 'when server does not send "ready", expect FLUSH' '
656656
test_i18ngrep "expected no other sections to be sent after no .ready." err
657657
'
658658

659+
configure_exclusion () {
660+
git -C "$1" hash-object "$2" >objh &&
661+
git -C "$1" pack-objects "$HTTPD_DOCUMENT_ROOT_PATH/mypack" <objh >packh &&
662+
git -C "$1" config --add \
663+
"uploadpack.blobpackfileuri" \
664+
"$(cat objh) $(cat packh) $HTTPD_URL/dumb/mypack-$(cat packh).pack" &&
665+
cat objh
666+
}
667+
668+
test_expect_success 'part of packfile response provided as URI' '
669+
P="$HTTPD_DOCUMENT_ROOT_PATH/http_parent" &&
670+
rm -rf "$P" http_child log &&
671+
672+
git init "$P" &&
673+
git -C "$P" config "uploadpack.allowsidebandall" "true" &&
674+
675+
echo my-blob >"$P/my-blob" &&
676+
git -C "$P" add my-blob &&
677+
echo other-blob >"$P/other-blob" &&
678+
git -C "$P" add other-blob &&
679+
git -C "$P" commit -m x &&
680+
681+
configure_exclusion "$P" my-blob >h &&
682+
configure_exclusion "$P" other-blob >h2 &&
683+
684+
GIT_TRACE=1 GIT_TRACE_PACKET="$(pwd)/log" GIT_TEST_SIDEBAND_ALL=1 \
685+
git -c protocol.version=2 \
686+
-c fetch.uriprotocols=http,https \
687+
clone "$HTTPD_URL/smart/http_parent" http_child &&
688+
689+
# Ensure that my-blob and other-blob are in separate packfiles.
690+
for idx in http_child/.git/objects/pack/*.idx
691+
do
692+
git verify-pack --verbose $idx >out &&
693+
{
694+
grep "^[0-9a-f]\{16,\} " out || :
695+
} >out.objectlist &&
696+
if test_line_count = 1 out.objectlist
697+
then
698+
if grep $(cat h) out
699+
then
700+
>hfound
701+
fi &&
702+
if grep $(cat h2) out
703+
then
704+
>h2found
705+
fi
706+
fi
707+
done &&
708+
test -f hfound &&
709+
test -f h2found &&
710+
711+
# Ensure that there are exactly 6 files (3 .pack and 3 .idx).
712+
ls http_child/.git/objects/pack/* >filelist &&
713+
test_line_count = 6 filelist
714+
'
715+
659716
stop_httpd
660717

661718
test_done

0 commit comments

Comments
 (0)