Skip to content

Commit e8543f7

Browse files
jeffhostetlerderrickstolee
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 <[email protected]>
1 parent f97ef3b commit e8543f7

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
@@ -491,8 +491,7 @@ static enum worker_result do__gvfs_config__get(struct req *req)
491491
* write_object_file_prepare()
492492
* write_loose_object()
493493
*/
494-
static enum worker_result send_loose_object(const struct object_info *oi,
495-
const struct object_id *oid,
494+
static enum worker_result send_loose_object(const struct object_id *oid,
496495
int fd)
497496
{
498497
#define MAX_HEADER_LEN 32
@@ -505,6 +504,42 @@ static enum worker_result send_loose_object(const struct object_info *oi,
505504
git_hash_ctx c;
506505
int object_header_len;
507506
int ret;
507+
unsigned flags = 0;
508+
void *content;
509+
unsigned long size;
510+
enum object_type type;
511+
struct object_info oi = OBJECT_INFO_INIT;
512+
513+
/*
514+
* Since `test-gvfs-protocol` is mocking a real GVFS server (cache or
515+
* main), we don't want a request for a missing object to cause the
516+
* implicit dynamic fetch mechanism to try to fault-it-in (and cause
517+
* our call to oid_object_info_extended() to launch another instance
518+
* of `gvfs-helper` to magically fetch it (which would connect to a
519+
* new instance of `test-gvfs-protocol`)).
520+
*
521+
* Rather, we want a missing object to fail, so we can respond with
522+
* a 404, for example.
523+
*/
524+
flags |= OBJECT_INFO_FOR_PREFETCH;
525+
flags |= OBJECT_INFO_LOOKUP_REPLACE;
526+
527+
oi.typep = &type;
528+
oi.sizep = &size;
529+
oi.contentp = &content;
530+
531+
if (oid_object_info_extended(the_repository, oid, &oi, flags)) {
532+
logerror("Could not find OID: '%s'", oid_to_hex(oid));
533+
return send_http_error(1, 404, "Not Found", -1, WR_OK);
534+
}
535+
536+
if (string_list_has_string(&mayhem_list, "http_404")) {
537+
logmayhem("http_404");
538+
return send_http_error(1, 404, "Not Found", -1, WR_MAYHEM);
539+
}
540+
541+
trace2_printf("%s: OBJECT type=%d len=%ld '%.40s'", TR2_CAT,
542+
type, size, (const char *)content);
508543

509544
/*
510545
* We are blending several somewhat independent concepts here:
@@ -559,13 +594,13 @@ static enum worker_result send_loose_object(const struct object_info *oi,
559594
/* [1a] */
560595
object_header_len = 1 + xsnprintf(object_header, MAX_HEADER_LEN,
561596
"%s %"PRIuMAX,
562-
type_name(*oi->typep),
563-
(uintmax_t)*oi->sizep);
597+
type_name(*oi.typep),
598+
(uintmax_t)*oi.sizep);
564599

565600
/* [2] */
566601
the_hash_algo->init_fn(&c);
567602
the_hash_algo->update_fn(&c, object_header, object_header_len);
568-
the_hash_algo->update_fn(&c, *oi->contentp, *oi->sizep);
603+
the_hash_algo->update_fn(&c, *oi.contentp, *oi.sizep);
569604
the_hash_algo->final_fn(oid_check.hash, &c);
570605
if (!oideq(oid, &oid_check))
571606
BUG("send_loose_object[2]: invalid construction '%s' '%s'",
@@ -585,8 +620,8 @@ static enum worker_result send_loose_object(const struct object_info *oi,
585620
the_hash_algo->update_fn(&c, object_header, object_header_len);
586621

587622
/* [3, 1b, 5, 6] */
588-
stream.next_in = *oi->contentp;
589-
stream.avail_in = *oi->sizep;
623+
stream.next_in = *oi.contentp;
624+
stream.avail_in = *oi.sizep;
590625
do {
591626
enum worker_result wr;
592627
unsigned char *in0 = stream.next_in;
@@ -631,25 +666,6 @@ static enum worker_result send_loose_object(const struct object_info *oi,
631666
static enum worker_result do__gvfs_objects__get(struct req *req)
632667
{
633668
struct object_id oid;
634-
void *content;
635-
unsigned long size;
636-
enum object_type type;
637-
struct object_info oi = OBJECT_INFO_INIT;
638-
unsigned flags = 0;
639-
640-
/*
641-
* Since `test-gvfs-protocol` is mocking a real GVFS server (cache or
642-
* main), we don't want a request for a missing object to cause the
643-
* implicit dynamic fetch mechanism to try to fault-it-in (and cause
644-
* our call to oid_object_info_extended() to launch another instance
645-
* of `gvfs-helper` to magically fetch it (which would connect to a
646-
* new instance of `test-gvfs-protocol`)).
647-
*
648-
* Rather, we want a missing object to fail, so we can respond with
649-
* a 404, for example.
650-
*/
651-
flags |= OBJECT_INFO_FOR_PREFETCH;
652-
flags |= OBJECT_INFO_LOOKUP_REPLACE;
653669

654670
if (!req->slash_args.len ||
655671
get_oid_hex(req->slash_args.buf, &oid)) {
@@ -660,29 +676,13 @@ static enum worker_result do__gvfs_objects__get(struct req *req)
660676

661677
trace2_printf("%s: GET %s", TR2_CAT, oid_to_hex(&oid));
662678

663-
oi.typep = &type;
664-
oi.sizep = &size;
665-
oi.contentp = &content;
666-
667-
if (oid_object_info_extended(the_repository, &oid, &oi, flags)) {
668-
logerror("Could not find OID: '%s'", oid_to_hex(&oid));
669-
return send_http_error(1, 404, "Not Found", -1, WR_OK);
670-
}
671-
672-
if (string_list_has_string(&mayhem_list, "http_404")) {
673-
logmayhem("http_404");
674-
return send_http_error(1, 404, "Not Found", -1, WR_MAYHEM);
675-
}
676-
677-
trace2_printf("%s: OBJECT type=%d len=%ld '%.40s'", TR2_CAT,
678-
type, size, (const char *)content);
679-
680-
return send_loose_object(&oi, &oid, 1);
679+
return send_loose_object(&oid, 1);
681680
}
682681

683682
static enum worker_result read_json_post_body(
684683
struct req *req,
685-
struct oidset *oids)
684+
struct oidset *oids,
685+
int *nr_oids)
686686
{
687687
struct object_id oid;
688688
struct string_list_item *item;
@@ -751,7 +751,8 @@ static enum worker_result read_json_post_body(
751751

752752
if (get_oid_hex(pstart, &oid))
753753
goto could_not_parse_json;
754-
oidset_insert(oids, &oid);
754+
if (!oidset_insert(oids, &oid))
755+
*nr_oids += 1;
755756
trace2_printf("%s: POST %s", TR2_CAT, oid_to_hex(&oid));
756757

757758
/* Eat trailing whitespace after trailing DQUOTE */
@@ -795,16 +796,6 @@ static enum worker_result read_json_post_body(
795796
*
796797
* My assumption here is that we're not testing with GBs
797798
* of data....
798-
*
799-
* Note: The GVFS Protocol POST verb behaves like GET for
800-
* Note: non-commit objects (in that it just returns the
801-
* Note: requested object), but for commit objects POST
802-
* Note: *also* returns all trees referenced by the commit.
803-
* Note:
804-
* Note: Since the goal of this test is to confirm that
805-
* Note: gvfs-helper can request and receive a packfile
806-
* Note: *at all*, I'm not going to blur the issue and
807-
* Note: support the extra semantics for commit objects.
808799
*/
809800
static enum worker_result get_packfile_from_oids(
810801
struct oidset *oids,
@@ -894,21 +885,99 @@ static enum worker_result send_packfile_from_buffer(const struct strbuf *packfil
894885
return wr;
895886
}
896887

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

903-
wr = read_json_post_body(req, &oids);
958+
wr = read_json_post_body(req, &oids, &nr_oids);
904959
if (wr & WR_STOP_THE_MUSIC)
905960
goto done;
906961

907-
wr = get_packfile_from_oids(&oids, &packfile);
962+
wr = classify_oids_in_post(&oids, nr_oids, &need_packfile);
908963
if (wr & WR_STOP_THE_MUSIC)
909964
goto done;
910965

911-
wr = send_packfile_from_buffer(&packfile);
966+
if (!need_packfile) {
967+
struct oidset_iter iter;
968+
struct object_id *oid;
969+
970+
oidset_iter_init(&oids, &iter);
971+
oid = oidset_iter_next(&iter);
972+
973+
wr = send_loose_object(oid, 1);
974+
} else {
975+
wr = get_packfile_from_oids(&oids, &packfile);
976+
if (wr & WR_STOP_THE_MUSIC)
977+
goto done;
978+
979+
wr = send_packfile_from_buffer(&packfile);
980+
}
912981

913982
done:
914983
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
#
@@ -159,7 +165,8 @@ test_expect_success 'setup repos' '
159165
#
160166
get_list_of_oids 30 &&
161167
get_list_of_commit_and_tree_oids 30 &&
162-
get_list_of_blobs_oids
168+
get_list_of_blobs_oids &&
169+
get_one_commit_oid
163170
'
164171

165172
stop_gvfs_protocol_server () {
@@ -509,6 +516,73 @@ test_expect_success 'basic: POST origin blobs' '
509516
verify_connection_count 1
510517
'
511518

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

0 commit comments

Comments
 (0)