Skip to content

Commit ff6fa29

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 e576970 commit ff6fa29

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

510545
/*
511546
* We are blending several somewhat independent concepts here:
@@ -560,13 +595,13 @@ static enum worker_result send_loose_object(const struct object_info *oi,
560595
/* [1a] */
561596
object_header_len = 1 + xsnprintf(object_header, MAX_HEADER_LEN,
562597
"%s %"PRIuMAX,
563-
type_name(*oi->typep),
564-
(uintmax_t)*oi->sizep);
598+
type_name(*oi.typep),
599+
(uintmax_t)*oi.sizep);
565600

566601
/* [2] */
567602
the_hash_algo->init_fn(&c);
568603
the_hash_algo->update_fn(&c, object_header, object_header_len);
569-
the_hash_algo->update_fn(&c, *oi->contentp, *oi->sizep);
604+
the_hash_algo->update_fn(&c, *oi.contentp, *oi.sizep);
570605
the_hash_algo->final_fn(oid_check.hash, &c);
571606
if (!oideq(oid, &oid_check))
572607
BUG("send_loose_object[2]: invalid construction '%s' '%s'",
@@ -586,8 +621,8 @@ static enum worker_result send_loose_object(const struct object_info *oi,
586621
the_hash_algo->update_fn(&c, object_header, object_header_len);
587622

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

655671
if (!req->slash_args.len ||
656672
get_oid_hex(req->slash_args.buf, &oid)) {
@@ -661,29 +677,13 @@ static enum worker_result do__gvfs_objects__get(struct req *req)
661677

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

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

684683
static enum worker_result read_json_post_body(
685684
struct req *req,
686-
struct oidset *oids)
685+
struct oidset *oids,
686+
int *nr_oids)
687687
{
688688
struct object_id oid;
689689
struct string_list_item *item;
@@ -752,7 +752,8 @@ static enum worker_result read_json_post_body(
752752

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

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

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

904-
wr = read_json_post_body(req, &oids);
959+
wr = read_json_post_body(req, &oids, &nr_oids);
905960
if (wr & WR_STOP_THE_MUSIC)
906961
goto done;
907962

908-
wr = get_packfile_from_oids(&oids, &packfile);
963+
wr = classify_oids_in_post(&oids, nr_oids, &need_packfile);
909964
if (wr & WR_STOP_THE_MUSIC)
910965
goto done;
911966

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

914983
done:
915984
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)