Skip to content

Commit de1a2fd

Browse files
spearcegitster
authored andcommitted
Smart push over HTTP: client side
The git-remote-curl backend detects if the remote server supports the git-receive-pack service, and if so, runs git-send-pack in a pipe to dump the command and pack data as a single POST request. The advertisements from the server that were obtained during the discovery are passed into git-send-pack before the POST request starts. This permits git-send-pack to operate largely unmodified. For smaller packs (those under 1 MiB) a HTTP/1.0 POST with a Content-Length is used, permitting interaction with any server. The 1 MiB limit is arbitrary, but is sufficent to fit most deltas created by human authors against text sources with the occasional small binary file (e.g. few KiB icon image). The configuration option http.postBuffer can be used to increase (or shink) this buffer if the default is not sufficient. For larger packs which cannot be spooled entirely into the helper's memory space (due to http.postBuffer being too small), the POST request requires HTTP/1.1 and sets "Transfer-Encoding: chunked". This permits the client to upload an unknown amount of data in one HTTP transaction without needing to pregenerate the entire pack file locally. Signed-off-by: Shawn O. Pearce <[email protected]> CC: Daniel Barkalow <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 97cc7bc commit de1a2fd

File tree

8 files changed

+374
-15
lines changed

8 files changed

+374
-15
lines changed

Documentation/config.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,6 +1089,14 @@ http.maxRequests::
10891089
How many HTTP requests to launch in parallel. Can be overridden
10901090
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.
10911091

1092+
http.postBuffer::
1093+
Maximum size in bytes of the buffer used by smart HTTP
1094+
transports when POSTing data to the remote system.
1095+
For requests larger than this buffer size, HTTP/1.1 and
1096+
Transfer-Encoding: chunked is used to avoid creating a
1097+
massive pack file locally. Default is 1 MiB, which is
1098+
sufficient for most requests.
1099+
10921100
http.lowSpeedLimit, http.lowSpeedTime::
10931101
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
10941102
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.

builtin-send-pack.c

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#include "commit.h"
33
#include "refs.h"
44
#include "pkt-line.h"
5+
#include "sideband.h"
56
#include "run-command.h"
67
#include "remote.h"
78
#include "send-pack.h"
9+
#include "quote.h"
810

911
static const char send_pack_usage[] =
1012
"git send-pack [--all | --mirror] [--dry-run] [--force] [--receive-pack=<git-receive-pack>] [--verbose] [--thin] [<host>:]<directory> [<ref>...]\n"
@@ -59,7 +61,7 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
5961
memset(&po, 0, sizeof(po));
6062
po.argv = argv;
6163
po.in = -1;
62-
po.out = fd;
64+
po.out = args->stateless_rpc ? -1 : fd;
6365
po.git_cmd = 1;
6466
if (start_command(&po))
6567
die_errno("git pack-objects failed");
@@ -83,6 +85,20 @@ static int pack_objects(int fd, struct ref *refs, struct extra_have_objects *ext
8385
}
8486

8587
close(po.in);
88+
89+
if (args->stateless_rpc) {
90+
char *buf = xmalloc(LARGE_PACKET_MAX);
91+
while (1) {
92+
ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
93+
if (n <= 0)
94+
break;
95+
send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
96+
}
97+
free(buf);
98+
close(po.out);
99+
po.out = -1;
100+
}
101+
86102
if (finish_command(&po))
87103
return error("pack-objects died with strange error");
88104
return 0;
@@ -303,13 +319,67 @@ static int refs_pushed(struct ref *ref)
303319
return 0;
304320
}
305321

322+
static void print_helper_status(struct ref *ref)
323+
{
324+
struct strbuf buf = STRBUF_INIT;
325+
326+
for (; ref; ref = ref->next) {
327+
const char *msg = NULL;
328+
const char *res;
329+
330+
switch(ref->status) {
331+
case REF_STATUS_NONE:
332+
res = "error";
333+
msg = "no match";
334+
break;
335+
336+
case REF_STATUS_OK:
337+
res = "ok";
338+
break;
339+
340+
case REF_STATUS_UPTODATE:
341+
res = "ok";
342+
msg = "up to date";
343+
break;
344+
345+
case REF_STATUS_REJECT_NONFASTFORWARD:
346+
res = "error";
347+
msg = "non-fast forward";
348+
break;
349+
350+
case REF_STATUS_REJECT_NODELETE:
351+
case REF_STATUS_REMOTE_REJECT:
352+
res = "error";
353+
break;
354+
355+
case REF_STATUS_EXPECTING_REPORT:
356+
default:
357+
continue;
358+
}
359+
360+
strbuf_reset(&buf);
361+
strbuf_addf(&buf, "%s %s", res, ref->name);
362+
if (ref->remote_status)
363+
msg = ref->remote_status;
364+
if (msg) {
365+
strbuf_addch(&buf, ' ');
366+
quote_two_c_style(&buf, "", msg, 0);
367+
}
368+
strbuf_addch(&buf, '\n');
369+
370+
safe_write(1, buf.buf, buf.len);
371+
}
372+
strbuf_release(&buf);
373+
}
374+
306375
int send_pack(struct send_pack_args *args,
307376
int fd[], struct child_process *conn,
308377
struct ref *remote_refs,
309378
struct extra_have_objects *extra_have)
310379
{
311380
int in = fd[0];
312381
int out = fd[1];
382+
struct strbuf req_buf = STRBUF_INIT;
313383
struct ref *ref;
314384
int new_refs;
315385
int ask_for_status_report = 0;
@@ -391,34 +461,48 @@ int send_pack(struct send_pack_args *args,
391461
char *new_hex = sha1_to_hex(ref->new_sha1);
392462

393463
if (ask_for_status_report) {
394-
packet_write(out, "%s %s %s%c%s",
464+
packet_buf_write(&req_buf, "%s %s %s%c%s",
395465
old_hex, new_hex, ref->name, 0,
396466
"report-status");
397467
ask_for_status_report = 0;
398468
expect_status_report = 1;
399469
}
400470
else
401-
packet_write(out, "%s %s %s",
471+
packet_buf_write(&req_buf, "%s %s %s",
402472
old_hex, new_hex, ref->name);
403473
}
404474
ref->status = expect_status_report ?
405475
REF_STATUS_EXPECTING_REPORT :
406476
REF_STATUS_OK;
407477
}
408478

409-
packet_flush(out);
479+
if (args->stateless_rpc) {
480+
if (!args->dry_run) {
481+
packet_buf_flush(&req_buf);
482+
send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
483+
}
484+
} else {
485+
safe_write(out, req_buf.buf, req_buf.len);
486+
packet_flush(out);
487+
}
488+
strbuf_release(&req_buf);
489+
410490
if (new_refs && !args->dry_run) {
411491
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
412492
for (ref = remote_refs; ref; ref = ref->next)
413493
ref->status = REF_STATUS_NONE;
414494
return -1;
415495
}
416496
}
497+
if (args->stateless_rpc && !args->dry_run)
498+
packet_flush(out);
417499

418500
if (expect_status_report)
419501
ret = receive_status(in, remote_refs);
420502
else
421503
ret = 0;
504+
if (args->stateless_rpc)
505+
packet_flush(out);
422506

423507
if (ret < 0)
424508
return ret;
@@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
478562
struct extra_have_objects extra_have;
479563
struct ref *remote_refs, *local_refs;
480564
int ret;
565+
int helper_status = 0;
481566
int send_all = 0;
482567
const char *receivepack = "git-receive-pack";
483568
int flags;
@@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
523608
args.use_thin_pack = 1;
524609
continue;
525610
}
611+
if (!strcmp(arg, "--stateless-rpc")) {
612+
args.stateless_rpc = 1;
613+
continue;
614+
}
615+
if (!strcmp(arg, "--helper-status")) {
616+
helper_status = 1;
617+
continue;
618+
}
526619
usage(send_pack_usage);
527620
}
528621
if (!dest) {
@@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
551644
}
552645
}
553646

554-
conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
647+
if (args.stateless_rpc) {
648+
conn = NULL;
649+
fd[0] = 0;
650+
fd[1] = 1;
651+
} else {
652+
conn = git_connect(fd, dest, receivepack,
653+
args.verbose ? CONNECT_VERBOSE : 0);
654+
}
555655

556656
memset(&extra_have, 0, sizeof(extra_have));
557657

@@ -575,12 +675,16 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
575675

576676
ret = send_pack(&args, fd, conn, remote_refs, &extra_have);
577677

678+
if (helper_status)
679+
print_helper_status(remote_refs);
680+
578681
close(fd[1]);
579682
close(fd[0]);
580683

581684
ret |= finish_connect(conn);
582685

583-
print_push_status(dest, remote_refs);
686+
if (!helper_status)
687+
print_push_status(dest, remote_refs);
584688

585689
if (!args.dry_run && remote) {
586690
struct ref *ref;

http.c

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
#include "http.h"
22
#include "pack.h"
3+
#include "sideband.h"
34

45
int data_received;
56
int active_requests;
67
int http_is_verbose;
8+
size_t http_post_buffer = 16 * LARGE_PACKET_MAX;
79

810
#ifdef USE_CURL_MULTI
911
static int max_requests = -1;
@@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
9799
return eltsize * nmemb;
98100
}
99101

100-
static void finish_active_slot(struct active_request_slot *slot);
101-
102102
#ifdef USE_CURL_MULTI
103103
static void process_curl_messages(void)
104104
{
@@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb)
174174
if (!strcmp("http.proxy", var))
175175
return git_config_string(&curl_http_proxy, var, value);
176176

177+
if (!strcmp("http.postbuffer", var)) {
178+
http_post_buffer = git_config_int(var, value);
179+
if (http_post_buffer < LARGE_PACKET_MAX)
180+
http_post_buffer = LARGE_PACKET_MAX;
181+
return 0;
182+
}
183+
177184
/* Fall back on the default ones */
178185
return git_default_config(var, value, cb);
179186
}
@@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot)
638645
#endif
639646
}
640647

641-
static void finish_active_slot(struct active_request_slot *slot)
648+
void finish_active_slot(struct active_request_slot *slot)
642649
{
643650
closedown_active_slot(slot);
644651
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);

http.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
7979
extern struct active_request_slot *get_active_slot(void);
8080
extern int start_active_slot(struct active_request_slot *slot);
8181
extern void run_active_slot(struct active_request_slot *slot);
82+
extern void finish_active_slot(struct active_request_slot *slot);
8283
extern void finish_all_active_slots(void);
8384
extern void release_active_slot(struct active_request_slot *slot);
8485

@@ -94,6 +95,7 @@ extern void http_cleanup(void);
9495
extern int data_received;
9596
extern int active_requests;
9697
extern int http_is_verbose;
98+
extern size_t http_post_buffer;
9799

98100
extern char curl_errorstr[CURL_ERROR_SIZE];
99101

0 commit comments

Comments
 (0)