Skip to content

Commit 25b0141

Browse files
jeffhostetlerdscho
authored andcommitted
t5799: add support for POST to return either a loose object or packfile
Earlier versions of the test always returned a packfile in response to a POST. Now we look at the number of objects in the POST request. If > 1, always send a packfile. If = 1 and it is a commit, send a packfile. Otherwise, send a loose object. This is to better model the behavior of the GVFS server/protocol which treats commits differently. Signed-off-by: Jeff Hostetler <jeffhost@microsoft.com>
1 parent afe2706 commit 25b0141

File tree

2 files changed

+203
-60
lines changed

2 files changed

+203
-60
lines changed

t/helper/test-gvfs-protocol.c

Lines changed: 128 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,7 @@ static enum worker_result do__gvfs_config__get(struct req *req)
499499
* write_object_file_prepare()
500500
* write_loose_object()
501501
*/
502-
static enum worker_result send_loose_object(const struct object_info *oi,
503-
const struct object_id *oid,
502+
static enum worker_result send_loose_object(const struct object_id *oid,
504503
int fd)
505504
{
506505
#define MAX_HEADER_LEN 32
@@ -513,6 +512,42 @@ static enum worker_result send_loose_object(const struct object_info *oi,
513512
git_hash_ctx c;
514513
int object_header_len;
515514
int ret;
515+
unsigned flags = 0;
516+
void *content;
517+
unsigned long size;
518+
enum object_type type;
519+
struct object_info oi = OBJECT_INFO_INIT;
520+
521+
/*
522+
* Since `test-gvfs-protocol` is mocking a real GVFS server (cache or
523+
* main), we don't want a request for a missing object to cause the
524+
* implicit dynamic fetch mechanism to try to fault-it-in (and cause
525+
* our call to oid_object_info_extended() to launch another instance
526+
* of `gvfs-helper` to magically fetch it (which would connect to a
527+
* new instance of `test-gvfs-protocol`)).
528+
*
529+
* Rather, we want a missing object to fail, so we can respond with
530+
* a 404, for example.
531+
*/
532+
flags |= OBJECT_INFO_FOR_PREFETCH;
533+
flags |= OBJECT_INFO_LOOKUP_REPLACE;
534+
535+
oi.typep = &type;
536+
oi.sizep = &size;
537+
oi.contentp = &content;
538+
539+
if (oid_object_info_extended(the_repository, oid, &oi, flags)) {
540+
logerror("Could not find OID: '%s'", oid_to_hex(oid));
541+
return send_http_error(1, 404, "Not Found", -1, WR_OK);
542+
}
543+
544+
if (string_list_has_string(&mayhem_list, "http_404")) {
545+
logmayhem("http_404");
546+
return send_http_error(1, 404, "Not Found", -1, WR_MAYHEM);
547+
}
548+
549+
trace2_printf("%s: OBJECT type=%d len=%ld '%.40s'", TR2_CAT,
550+
type, size, (const char *)content);
516551

517552
/*
518553
* We are blending several somewhat independent concepts here:
@@ -567,13 +602,13 @@ static enum worker_result send_loose_object(const struct object_info *oi,
567602
/* [1a] */
568603
object_header_len = 1 + xsnprintf(object_header, MAX_HEADER_LEN,
569604
"%s %"PRIuMAX,
570-
type_name(*oi->typep),
571-
(uintmax_t)*oi->sizep);
605+
type_name(*oi.typep),
606+
(uintmax_t)*oi.sizep);
572607

573608
/* [2] */
574609
the_hash_algo->init_fn(&c);
575610
the_hash_algo->update_fn(&c, object_header, object_header_len);
576-
the_hash_algo->update_fn(&c, *oi->contentp, *oi->sizep);
611+
the_hash_algo->update_fn(&c, *oi.contentp, *oi.sizep);
577612
the_hash_algo->final_fn(oid_check.hash, &c);
578613
if (!oideq(oid, &oid_check))
579614
BUG("send_loose_object[2]: invalid construction '%s' '%s'",
@@ -593,8 +628,8 @@ static enum worker_result send_loose_object(const struct object_info *oi,
593628
the_hash_algo->update_fn(&c, object_header, object_header_len);
594629

595630
/* [3, 1b, 5, 6] */
596-
stream.next_in = *oi->contentp;
597-
stream.avail_in = *oi->sizep;
631+
stream.next_in = *oi.contentp;
632+
stream.avail_in = *oi.sizep;
598633
do {
599634
enum worker_result wr;
600635
unsigned char *in0 = stream.next_in;
@@ -639,25 +674,6 @@ static enum worker_result send_loose_object(const struct object_info *oi,
639674
static enum worker_result do__gvfs_objects__get(struct req *req)
640675
{
641676
struct object_id oid;
642-
void *content;
643-
unsigned long size;
644-
enum object_type type;
645-
struct object_info oi = OBJECT_INFO_INIT;
646-
unsigned flags = 0;
647-
648-
/*
649-
* Since `test-gvfs-protocol` is mocking a real GVFS server (cache or
650-
* main), we don't want a request for a missing object to cause the
651-
* implicit dynamic fetch mechanism to try to fault-it-in (and cause
652-
* our call to oid_object_info_extended() to launch another instance
653-
* of `gvfs-helper` to magically fetch it (which would connect to a
654-
* new instance of `test-gvfs-protocol`)).
655-
*
656-
* Rather, we want a missing object to fail, so we can respond with
657-
* a 404, for example.
658-
*/
659-
flags |= OBJECT_INFO_FOR_PREFETCH;
660-
flags |= OBJECT_INFO_LOOKUP_REPLACE;
661677

662678
if (!req->slash_args.len ||
663679
get_oid_hex(req->slash_args.buf, &oid)) {
@@ -668,29 +684,13 @@ static enum worker_result do__gvfs_objects__get(struct req *req)
668684

669685
trace2_printf("%s: GET %s", TR2_CAT, oid_to_hex(&oid));
670686

671-
oi.typep = &type;
672-
oi.sizep = &size;
673-
oi.contentp = &content;
674-
675-
if (oid_object_info_extended(the_repository, &oid, &oi, flags)) {
676-
logerror("Could not find OID: '%s'", oid_to_hex(&oid));
677-
return send_http_error(1, 404, "Not Found", -1, WR_OK);
678-
}
679-
680-
if (string_list_has_string(&mayhem_list, "http_404")) {
681-
logmayhem("http_404");
682-
return send_http_error(1, 404, "Not Found", -1, WR_MAYHEM);
683-
}
684-
685-
trace2_printf("%s: OBJECT type=%d len=%ld '%.40s'", TR2_CAT,
686-
type, size, (const char *)content);
687-
688-
return send_loose_object(&oi, &oid, 1);
687+
return send_loose_object(&oid, 1);
689688
}
690689

691690
static enum worker_result read_json_post_body(
692691
struct req *req,
693-
struct oidset *oids)
692+
struct oidset *oids,
693+
int *nr_oids)
694694
{
695695
struct object_id oid;
696696
struct string_list_item *item;
@@ -759,7 +759,8 @@ static enum worker_result read_json_post_body(
759759

760760
if (get_oid_hex(pstart, &oid))
761761
goto could_not_parse_json;
762-
oidset_insert(oids, &oid);
762+
if (!oidset_insert(oids, &oid))
763+
*nr_oids += 1;
763764
trace2_printf("%s: POST %s", TR2_CAT, oid_to_hex(&oid));
764765

765766
/* Eat trailing whitespace after trailing DQUOTE */
@@ -803,16 +804,6 @@ static enum worker_result read_json_post_body(
803804
*
804805
* My assumption here is that we're not testing with GBs
805806
* of data....
806-
*
807-
* Note: The GVFS Protocol POST verb behaves like GET for
808-
* Note: non-commit objects (in that it just returns the
809-
* Note: requested object), but for commit objects POST
810-
* Note: *also* returns all trees referenced by the commit.
811-
* Note:
812-
* Note: Since the goal of this test is to confirm that
813-
* Note: gvfs-helper can request and receive a packfile
814-
* Note: *at all*, I'm not going to blur the issue and
815-
* Note: support the extra semantics for commit objects.
816807
*/
817808
static enum worker_result get_packfile_from_oids(
818809
struct oidset *oids,
@@ -902,21 +893,99 @@ static enum worker_result send_packfile_from_buffer(const struct strbuf *packfil
902893
return wr;
903894
}
904895

896+
/*
897+
* The GVFS Protocol POST verb behaves like GET for non-commit objects
898+
* (in that it just returns the requested object), but for commit
899+
* objects POST *also* returns all trees referenced by the commit.
900+
*
901+
* The goal of this test is to confirm that:
902+
* [] `gvfs-helper post` can request and receive a packfile at all.
903+
* [] `gvfs-helper post` can handle getting either a packfile or a
904+
* loose object.
905+
*
906+
* Therefore, I'm not going to blur the issue and support the custom
907+
* semantics for commit objects.
908+
*
909+
* If one of the OIDs is a commit, `git pack-objects` will completely
910+
* walk the trees and blobs for it and we get that for free. This is
911+
* good enough for our testing.
912+
*
913+
* TODO A proper solution would separate the commit objects and do a
914+
* TODO `rev-list --filter=blobs:none` for them (or use the internal
915+
* TODO list-objects API) and a regular enumeration for the non-commit
916+
* TODO objects. And build an new oidset with union of those and then
917+
* TODO call pack-objects on it instead.
918+
* TODO
919+
* TODO But that's too much trouble for now.
920+
*
921+
* For now, we just need to know if the post asks for a single object,
922+
* is it a commit or non-commit. That is sufficient to know whether
923+
* we should send a packfile or loose object.
924+
*/
925+
static enum worker_result classify_oids_in_post(
926+
struct oidset *oids, int nr_oids, int *need_packfile)
927+
{
928+
struct oidset_iter iter;
929+
struct object_id *oid;
930+
enum object_type type;
931+
struct object_info oi = OBJECT_INFO_INIT;
932+
unsigned flags = 0;
933+
934+
if (nr_oids > 1) {
935+
*need_packfile = 1;
936+
return WR_OK;
937+
}
938+
939+
/* disable missing-object faulting */
940+
flags |= OBJECT_INFO_FOR_PREFETCH;
941+
flags |= OBJECT_INFO_LOOKUP_REPLACE;
942+
943+
oi.typep = &type;
944+
945+
oidset_iter_init(oids, &iter);
946+
while ((oid = oidset_iter_next(&iter))) {
947+
if (!oid_object_info_extended(the_repository, oid, &oi, flags) &&
948+
type == OBJ_COMMIT) {
949+
*need_packfile = 1;
950+
return WR_OK;
951+
}
952+
}
953+
954+
*need_packfile = 0;
955+
return WR_OK;
956+
}
957+
905958
static enum worker_result do__gvfs_objects__post(struct req *req)
906959
{
907960
struct oidset oids = OIDSET_INIT;
908961
struct strbuf packfile = STRBUF_INIT;
909962
enum worker_result wr;
963+
int nr_oids = 0;
964+
int need_packfile = 0;
910965

911-
wr = read_json_post_body(req, &oids);
966+
wr = read_json_post_body(req, &oids, &nr_oids);
912967
if (wr & WR_STOP_THE_MUSIC)
913968
goto done;
914969

915-
wr = get_packfile_from_oids(&oids, &packfile);
970+
wr = classify_oids_in_post(&oids, nr_oids, &need_packfile);
916971
if (wr & WR_STOP_THE_MUSIC)
917972
goto done;
918973

919-
wr = send_packfile_from_buffer(&packfile);
974+
if (!need_packfile) {
975+
struct oidset_iter iter;
976+
struct object_id *oid;
977+
978+
oidset_iter_init(&oids, &iter);
979+
oid = oidset_iter_next(&iter);
980+
981+
wr = send_loose_object(oid, 1);
982+
} else {
983+
wr = get_packfile_from_oids(&oids, &packfile);
984+
if (wr & WR_STOP_THE_MUSIC)
985+
goto done;
986+
987+
wr = send_packfile_from_buffer(&packfile);
988+
}
920989

921990
done:
922991
oidset_clear(&oids);

t/t5799-gvfs-helper.sh

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ OIDS_FILE="$PWD"/oid_list.txt
5959
OIDS_CT_FILE="$PWD"/oid_ct_list.txt
6060
OIDS_BLOBS_FILE="$PWD"/oids_blobs_file.txt
6161
OID_ONE_BLOB_FILE="$PWD"/oid_one_blob_file.txt
62+
OID_ONE_COMMIT_FILE="$PWD"/oid_one_commit_file.txt
6263

6364
# Get a list of available OIDs in repo_src so that we can try to fetch
6465
# them and so that we don't have to hard-code a list of known OIDs.
@@ -102,6 +103,11 @@ get_list_of_commit_and_tree_oids () {
102103
return 0
103104
}
104105

106+
get_one_commit_oid () {
107+
git -C "$REPO_SRC" rev-parse HEAD >"$OID_ONE_COMMIT_FILE"
108+
return 0
109+
}
110+
105111
test_expect_success 'setup repos' '
106112
test_create_repo "$REPO_SRC" &&
107113
git -C "$REPO_SRC" branch -M main &&
@@ -161,7 +167,8 @@ test_expect_success 'setup repos' '
161167
#
162168
get_list_of_oids 30 &&
163169
get_list_of_commit_and_tree_oids 30 &&
164-
get_list_of_blobs_oids
170+
get_list_of_blobs_oids &&
171+
get_one_commit_oid
165172
'
166173

167174
stop_gvfs_protocol_server () {
@@ -511,6 +518,73 @@ test_expect_success 'basic: POST origin blobs' '
511518
verify_connection_count 1
512519
'
513520

521+
# Request a single blob via POST. Per the GVFS Protocol, the server
522+
# should implicitly send a loose object for it. Confirm that.
523+
#
524+
test_expect_success 'basic: POST-request a single blob' '
525+
test_when_finished "per_test_cleanup" &&
526+
start_gvfs_protocol_server &&
527+
528+
# Connect to the origin server (w/o auth) and request a single
529+
# blob via POST.
530+
#
531+
git -C "$REPO_T1" gvfs-helper \
532+
--cache-server=disable \
533+
--remote=origin \
534+
--no-progress \
535+
post \
536+
<"$OID_ONE_BLOB_FILE" >OUT.output &&
537+
538+
# Stop the server to prevent the verification steps from faulting-in
539+
# any missing objects.
540+
#
541+
stop_gvfs_protocol_server &&
542+
543+
# gvfs-helper prints a "loose <oid>" message for each received
544+
# loose object.
545+
#
546+
sed "s/loose //" <OUT.output | sort >OUT.actual &&
547+
test_cmp "$OID_ONE_BLOB_FILE" OUT.actual &&
548+
549+
verify_connection_count 1
550+
'
551+
552+
# Request a single commit via POST. Per the GVFS Protocol, the server
553+
# should implicitly send us a packfile containing the commit and the
554+
# trees it references. Confirm that properly handled the receipt of
555+
# the packfile. (Here, we are testing that asking for a single object
556+
# yields a packfile rather than a loose object.)
557+
#
558+
# We DO NOT verify that the packfile contains commits/trees and no blobs
559+
# because our test helper doesn't implement the filtering.
560+
#
561+
test_expect_success 'basic: POST-request a single commit' '
562+
test_when_finished "per_test_cleanup" &&
563+
start_gvfs_protocol_server &&
564+
565+
# Connect to the origin server (w/o auth) and request a single
566+
# commit via POST.
567+
#
568+
git -C "$REPO_T1" gvfs-helper \
569+
--cache-server=disable \
570+
--remote=origin \
571+
--no-progress \
572+
post \
573+
<"$OID_ONE_COMMIT_FILE" >OUT.output &&
574+
575+
# Stop the server to prevent the verification steps from faulting-in
576+
# any missing objects.
577+
#
578+
stop_gvfs_protocol_server &&
579+
580+
# gvfs-helper prints a "packfile <path>" message for each received
581+
# packfile.
582+
#
583+
verify_received_packfile_count 1 &&
584+
585+
verify_connection_count 1
586+
'
587+
514588
#################################################################
515589
# Tests to see how gvfs-helper responds to network problems.
516590
#

0 commit comments

Comments
 (0)