Skip to content

Commit a22ab61

Browse files
jeffhostetlerdscho
authored andcommitted
gvfs-helper: expose gvfs/objects GET and POST semantics
Expose the differences in the semantics of GET and POST for the "gvfs/objects" API: HTTP GET: fetches a single loose object over the network. When a commit object is requested, it just returns the single object. HTTP POST: fetches a batch of objects over the network. When the oid-set contains a commit object, all referenced trees are also included in the response. gvfs-helper is updated to take "get" and "post" command line options. the gvfs-helper "server" mode is updated to take "objects.get" and "objects.post" verbs. For convenience, the "get" option and the "objects.get" verb do allow more than one object to be requested. gvfs-helper will automatically issue a series of (single object) HTTP GET requests and creating a series of loose objects. The "post" option and the "objects.post" verb will perform bulk object fetching using the batch-size chunking. Individual HTTP POST requests containing more than one object will be created as a packfile. A HTTP POST for a single object will create a loose object. This commit also contains some refactoring to eliminate the assumption that POST is always associated with packfiles. In gvfs-helper-client.c, gh_client__get_immediate() now uses the "objects.get" verb and ignores any currently queued objects. In gvfs-helper-client.c, the OIDSET built by gh_client__queue_oid() is only processed when gh_client__drain_queue() is called. The queue is processed using the "object.post" verb. Signed-off-by: Jeff Hostetler <[email protected]>
1 parent 55b1c71 commit a22ab61

File tree

2 files changed

+607
-331
lines changed

2 files changed

+607
-331
lines changed

gvfs-helper-client.c

Lines changed: 150 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
static struct oidset gh_client__oidset_queued = OIDSET_INIT;
1919
static unsigned long gh_client__oidset_count;
20-
static int gh_client__includes_immediate;
2120

2221
struct gh_server__process {
2322
struct subprocess_entry subprocess; /* must be first */
@@ -28,13 +27,20 @@ static int gh_server__subprocess_map_initialized;
2827
static struct hashmap gh_server__subprocess_map;
2928
static struct object_directory *gh_client__chosen_odb;
3029

31-
#define CAP_GET (1u<<1)
30+
/*
31+
* The "objects" capability has 2 verbs: "get" and "post".
32+
*/
33+
#define CAP_OBJECTS (1u<<1)
34+
#define CAP_OBJECTS_NAME "objects"
35+
36+
#define CAP_OBJECTS__VERB_GET1_NAME "get"
37+
#define CAP_OBJECTS__VERB_POST_NAME "post"
3238

3339
static int gh_client__start_fn(struct subprocess_entry *subprocess)
3440
{
3541
static int versions[] = {1, 0};
3642
static struct subprocess_capability capabilities[] = {
37-
{ "get", CAP_GET },
43+
{ CAP_OBJECTS_NAME, CAP_OBJECTS },
3844
{ NULL, 0 }
3945
};
4046

@@ -46,14 +52,16 @@ static int gh_client__start_fn(struct subprocess_entry *subprocess)
4652
}
4753

4854
/*
49-
* Send:
55+
* Send the queued OIDs in the OIDSET to gvfs-helper for it to
56+
* fetch from the cache-server or main Git server using "/gvfs/objects"
57+
* POST semantics.
5058
*
51-
* get LF
59+
* objects.post LF
5260
* (<hex-oid> LF)*
5361
* <flush>
5462
*
5563
*/
56-
static int gh_client__get__send_command(struct child_process *process)
64+
static int gh_client__send__objects_post(struct child_process *process)
5765
{
5866
struct oidset_iter iter;
5967
struct object_id *oid;
@@ -64,7 +72,9 @@ static int gh_client__get__send_command(struct child_process *process)
6472
* so that we don't have to.
6573
*/
6674

67-
err = packet_write_fmt_gently(process->in, "get\n");
75+
err = packet_write_fmt_gently(
76+
process->in,
77+
(CAP_OBJECTS_NAME "." CAP_OBJECTS__VERB_POST_NAME "\n"));
6878
if (err)
6979
return err;
7080

@@ -83,6 +93,46 @@ static int gh_client__get__send_command(struct child_process *process)
8393
return 0;
8494
}
8595

96+
/*
97+
* Send the given OID to gvfs-helper for it to fetch from the
98+
* cache-server or main Git server using "/gvfs/objects" GET
99+
* semantics.
100+
*
101+
* This ignores any queued OIDs.
102+
*
103+
* objects.get LF
104+
* <hex-oid> LF
105+
* <flush>
106+
*
107+
*/
108+
static int gh_client__send__objects_get(struct child_process *process,
109+
const struct object_id *oid)
110+
{
111+
int err;
112+
113+
/*
114+
* We assume that all of the packet_ routines call error()
115+
* so that we don't have to.
116+
*/
117+
118+
err = packet_write_fmt_gently(
119+
process->in,
120+
(CAP_OBJECTS_NAME "." CAP_OBJECTS__VERB_GET1_NAME "\n"));
121+
if (err)
122+
return err;
123+
124+
err = packet_write_fmt_gently(process->in, "%s\n",
125+
oid_to_hex(oid));
126+
if (err)
127+
return err;
128+
129+
err = packet_flush_gently(process->in);
130+
if (err)
131+
return err;
132+
133+
return 0;
134+
}
135+
86136
/*
87137
* Update the loose object cache to include the newly created
88138
* object.
@@ -131,7 +181,7 @@ static void gh_client__update_packed_git(const char *line)
131181
}
132182

133183
/*
134-
* We expect:
184+
* Both CAP_OBJECTS verbs return the same format response:
135185
*
136186
* <odb>
137187
* <data>*
@@ -162,7 +212,7 @@ static void gh_client__update_packed_git(const char *line)
162212
* grouped with a queued request for a blob. The tree-walk *might* be
163213
* able to continue and let the 404 blob be handled later.
164214
*/
165-
static int gh_client__get__receive_response(
215+
static int gh_client__objects__receive_response(
166216
struct child_process *process,
167217
enum gh_client__created *p_ghc,
168218
int *p_nr_loose, int *p_nr_packfile)
@@ -241,17 +291,12 @@ static void gh_client__choose_odb(void)
241291
}
242292
}
243293

244-
static int gh_client__get(enum gh_client__created *p_ghc)
294+
static struct gh_server__process *gh_client__find_long_running_process(
295+
unsigned int cap_needed)
245296
{
246297
struct gh_server__process *entry;
247-
struct child_process *process;
248298
struct strvec argv = STRVEC_INIT;
249299
struct strbuf quoted = STRBUF_INIT;
250-
int nr_loose = 0;
251-
int nr_packfile = 0;
252-
int err = 0;
253-
254-
trace2_region_enter("gh-client", "get", the_repository);
255300

256301
gh_client__choose_odb();
257302

@@ -267,6 +312,11 @@ static int gh_client__get(enum gh_client__created *p_ghc)
267312

268313
sq_quote_argv_pretty(&quoted, argv.v);
269314

315+
/*
316+
* Find an existing long-running process with the above command
317+
* line -or- create a new long-running process for this and
318+
* subsequent 'get' requests.
319+
*/
270320
if (!gh_server__subprocess_map_initialized) {
271321
gh_server__subprocess_map_initialized = 1;
272322
hashmap_init(&gh_server__subprocess_map,
@@ -280,70 +330,24 @@ static int gh_client__get(enum gh_client__created *p_ghc)
280330
entry = xmalloc(sizeof(*entry));
281331
entry->supported_capabilities = 0;
282332

283-
err = subprocess_start_strvec(
284-
&gh_server__subprocess_map, &entry->subprocess, 1,
285-
&argv, gh_client__start_fn);
286-
if (err) {
287-
free(entry);
288-
goto leave_region;
289-
}
333+
if (subprocess_start_strvec(&gh_server__subprocess_map,
334+
&entry->subprocess, 1,
335+
&argv, gh_client__start_fn))
336+
FREE_AND_NULL(entry);
290337
}
291338

292-
process = &entry->subprocess.process;
293-
294-
if (!(CAP_GET & entry->supported_capabilities)) {
295-
error("gvfs-helper: does not support GET");
296-
subprocess_stop(&gh_server__subprocess_map,
297-
(struct subprocess_entry *)entry);
298-
free(entry);
299-
err = -1;
300-
goto leave_region;
301-
}
302-
303-
sigchain_push(SIGPIPE, SIG_IGN);
304-
305-
err = gh_client__get__send_command(process);
306-
if (!err)
307-
err = gh_client__get__receive_response(process, p_ghc,
308-
&nr_loose, &nr_packfile);
309-
310-
sigchain_pop(SIGPIPE);
311-
312-
if (err) {
339+
if (entry &&
340+
(entry->supported_capabilities & cap_needed) != cap_needed) {
341+
error("gvfs-helper: does not support needed capabilities");
313342
subprocess_stop(&gh_server__subprocess_map,
314343
(struct subprocess_entry *)entry);
315-
free(entry);
344+
FREE_AND_NULL(entry);
316345
}
317346

318-
leave_region:
319347
strvec_clear(&argv);
320348
strbuf_release(&quoted);
321349

322-
trace2_data_intmax("gh-client", the_repository,
323-
"get/immediate", gh_client__includes_immediate);
324-
325-
trace2_data_intmax("gh-client", the_repository,
326-
"get/nr_objects", gh_client__oidset_count);
327-
328-
if (nr_loose)
329-
trace2_data_intmax("gh-client", the_repository,
330-
"get/nr_loose", nr_loose);
331-
332-
if (nr_packfile)
333-
trace2_data_intmax("gh-client", the_repository,
334-
"get/nr_packfile", nr_packfile);
335-
336-
if (err)
337-
trace2_data_intmax("gh-client", the_repository,
338-
"get/error", err);
339-
340-
trace2_region_leave("gh-client", "get", the_repository);
341-
342-
oidset_clear(&gh_client__oidset_queued);
343-
gh_client__oidset_count = 0;
344-
gh_client__includes_immediate = 0;
345-
346-
return err;
350+
return entry;
347351
}
348352

349353
void gh_client__queue_oid(const struct object_id *oid)
@@ -370,27 +374,97 @@ void gh_client__queue_oid_array(const struct object_id *oids, int oid_nr)
370374
gh_client__queue_oid(&oids[k]);
371375
}
372376

377+
/*
378+
* Bulk fetch all of the queued OIDs in the OIDSET.
379+
*/
373380
int gh_client__drain_queue(enum gh_client__created *p_ghc)
374381
{
382+
struct gh_server__process *entry;
383+
struct child_process *process;
384+
int nr_loose = 0;
385+
int nr_packfile = 0;
386+
int err = 0;
387+
375388
*p_ghc = GHC__CREATED__NOTHING;
376389

377390
if (!gh_client__oidset_count)
378391
return 0;
379392

380-
return gh_client__get(p_ghc);
393+
entry = gh_client__find_long_running_process(CAP_OBJECTS);
394+
if (!entry)
395+
return -1;
396+
397+
trace2_region_enter("gh-client", "objects/post", the_repository);
398+
399+
process = &entry->subprocess.process;
400+
401+
sigchain_push(SIGPIPE, SIG_IGN);
402+
403+
err = gh_client__send__objects_post(process);
404+
if (!err)
405+
err = gh_client__objects__receive_response(
406+
process, p_ghc, &nr_loose, &nr_packfile);
407+
408+
sigchain_pop(SIGPIPE);
409+
410+
if (err) {
411+
subprocess_stop(&gh_server__subprocess_map,
412+
(struct subprocess_entry *)entry);
413+
FREE_AND_NULL(entry);
414+
}
415+
416+
trace2_data_intmax("gh-client", the_repository,
417+
"objects/post/nr_objects", gh_client__oidset_count);
418+
trace2_region_leave("gh-client", "objects/post", the_repository);
419+
420+
oidset_clear(&gh_client__oidset_queued);
421+
gh_client__oidset_count = 0;
422+
423+
return err;
381424
}
425+
426+
/*
427+
* Get exactly 1 object immediately.
428+
* Ignore any queued objects.
429+
*/
382430
int gh_client__get_immediate(const struct object_id *oid,
383431
enum gh_client__created *p_ghc)
384432
{
385-
gh_client__includes_immediate = 1;
433+
struct gh_server__process *entry;
434+
struct child_process *process;
435+
int nr_loose = 0;
436+
int nr_packfile = 0;
437+
int err = 0;
386438

387439
// TODO consider removing this trace2. it is useful for interactive
388440
// TODO debugging, but may generate way too much noise for a data
389441
// TODO event.
390442
trace2_printf("gh_client__get_immediate: %s", oid_to_hex(oid));
391443

392-
if (!oidset_insert(&gh_client__oidset_queued, oid))
393-
gh_client__oidset_count++;
444+
entry = gh_client__find_long_running_process(CAP_OBJECTS);
445+
if (!entry)
446+
return -1;
447+
448+
trace2_region_enter("gh-client", "objects/get", the_repository);
394449

395-
return gh_client__drain_queue(p_ghc);
450+
process = &entry->subprocess.process;
451+
452+
sigchain_push(SIGPIPE, SIG_IGN);
453+
454+
err = gh_client__send__objects_get(process, oid);
455+
if (!err)
456+
err = gh_client__objects__receive_response(
457+
process, p_ghc, &nr_loose, &nr_packfile);
458+
459+
sigchain_pop(SIGPIPE);
460+
461+
if (err) {
462+
subprocess_stop(&gh_server__subprocess_map,
463+
(struct subprocess_entry *)entry);
464+
FREE_AND_NULL(entry);
465+
}
466+
467+
trace2_region_leave("gh-client", "objects/get", the_repository);
468+
469+
return err;
396470
}

0 commit comments

Comments
 (0)