Skip to content

Commit

Permalink
Smart push over HTTP: client side
Browse files Browse the repository at this point in the history
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 <spearce@spearce.org>
CC: Daniel Barkalow <barkalow@iabervon.org>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
  • Loading branch information
spearce authored and gitster committed Nov 5, 2009
1 parent 97cc7bc commit de1a2fd
Show file tree
Hide file tree
Showing 8 changed files with 374 additions and 15 deletions.
8 changes: 8 additions & 0 deletions Documentation/config.txt
Expand Up @@ -1089,6 +1089,14 @@ http.maxRequests::
How many HTTP requests to launch in parallel. Can be overridden
by the 'GIT_HTTP_MAX_REQUESTS' environment variable. Default is 5.

http.postBuffer::
Maximum size in bytes of the buffer used by smart HTTP
transports when POSTing data to the remote system.
For requests larger than this buffer size, HTTP/1.1 and
Transfer-Encoding: chunked is used to avoid creating a
massive pack file locally. Default is 1 MiB, which is
sufficient for most requests.

http.lowSpeedLimit, http.lowSpeedTime::
If the HTTP transfer speed is less than 'http.lowSpeedLimit'
for longer than 'http.lowSpeedTime' seconds, the transfer is aborted.
Expand Down
116 changes: 110 additions & 6 deletions builtin-send-pack.c
Expand Up @@ -2,9 +2,11 @@
#include "commit.h"
#include "refs.h"
#include "pkt-line.h"
#include "sideband.h"
#include "run-command.h"
#include "remote.h"
#include "send-pack.h"
#include "quote.h"

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

close(po.in);

if (args->stateless_rpc) {
char *buf = xmalloc(LARGE_PACKET_MAX);
while (1) {
ssize_t n = xread(po.out, buf, LARGE_PACKET_MAX);
if (n <= 0)
break;
send_sideband(fd, -1, buf, n, LARGE_PACKET_MAX);
}
free(buf);
close(po.out);
po.out = -1;
}

if (finish_command(&po))
return error("pack-objects died with strange error");
return 0;
Expand Down Expand Up @@ -303,13 +319,67 @@ static int refs_pushed(struct ref *ref)
return 0;
}

static void print_helper_status(struct ref *ref)
{
struct strbuf buf = STRBUF_INIT;

for (; ref; ref = ref->next) {
const char *msg = NULL;
const char *res;

switch(ref->status) {
case REF_STATUS_NONE:
res = "error";
msg = "no match";
break;

case REF_STATUS_OK:
res = "ok";
break;

case REF_STATUS_UPTODATE:
res = "ok";
msg = "up to date";
break;

case REF_STATUS_REJECT_NONFASTFORWARD:
res = "error";
msg = "non-fast forward";
break;

case REF_STATUS_REJECT_NODELETE:
case REF_STATUS_REMOTE_REJECT:
res = "error";
break;

case REF_STATUS_EXPECTING_REPORT:
default:
continue;
}

strbuf_reset(&buf);
strbuf_addf(&buf, "%s %s", res, ref->name);
if (ref->remote_status)
msg = ref->remote_status;
if (msg) {
strbuf_addch(&buf, ' ');
quote_two_c_style(&buf, "", msg, 0);
}
strbuf_addch(&buf, '\n');

safe_write(1, buf.buf, buf.len);
}
strbuf_release(&buf);
}

int send_pack(struct send_pack_args *args,
int fd[], struct child_process *conn,
struct ref *remote_refs,
struct extra_have_objects *extra_have)
{
int in = fd[0];
int out = fd[1];
struct strbuf req_buf = STRBUF_INIT;
struct ref *ref;
int new_refs;
int ask_for_status_report = 0;
Expand Down Expand Up @@ -391,34 +461,48 @@ int send_pack(struct send_pack_args *args,
char *new_hex = sha1_to_hex(ref->new_sha1);

if (ask_for_status_report) {
packet_write(out, "%s %s %s%c%s",
packet_buf_write(&req_buf, "%s %s %s%c%s",
old_hex, new_hex, ref->name, 0,
"report-status");
ask_for_status_report = 0;
expect_status_report = 1;
}
else
packet_write(out, "%s %s %s",
packet_buf_write(&req_buf, "%s %s %s",
old_hex, new_hex, ref->name);
}
ref->status = expect_status_report ?
REF_STATUS_EXPECTING_REPORT :
REF_STATUS_OK;
}

packet_flush(out);
if (args->stateless_rpc) {
if (!args->dry_run) {
packet_buf_flush(&req_buf);
send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
}
} else {
safe_write(out, req_buf.buf, req_buf.len);
packet_flush(out);
}
strbuf_release(&req_buf);

if (new_refs && !args->dry_run) {
if (pack_objects(out, remote_refs, extra_have, args) < 0) {
for (ref = remote_refs; ref; ref = ref->next)
ref->status = REF_STATUS_NONE;
return -1;
}
}
if (args->stateless_rpc && !args->dry_run)
packet_flush(out);

if (expect_status_report)
ret = receive_status(in, remote_refs);
else
ret = 0;
if (args->stateless_rpc)
packet_flush(out);

if (ret < 0)
return ret;
Expand Down Expand Up @@ -478,6 +562,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
struct extra_have_objects extra_have;
struct ref *remote_refs, *local_refs;
int ret;
int helper_status = 0;
int send_all = 0;
const char *receivepack = "git-receive-pack";
int flags;
Expand Down Expand Up @@ -523,6 +608,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
args.use_thin_pack = 1;
continue;
}
if (!strcmp(arg, "--stateless-rpc")) {
args.stateless_rpc = 1;
continue;
}
if (!strcmp(arg, "--helper-status")) {
helper_status = 1;
continue;
}
usage(send_pack_usage);
}
if (!dest) {
Expand Down Expand Up @@ -551,7 +644,14 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
}
}

conn = git_connect(fd, dest, receivepack, args.verbose ? CONNECT_VERBOSE : 0);
if (args.stateless_rpc) {
conn = NULL;
fd[0] = 0;
fd[1] = 1;
} else {
conn = git_connect(fd, dest, receivepack,
args.verbose ? CONNECT_VERBOSE : 0);
}

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

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

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

if (helper_status)
print_helper_status(remote_refs);

close(fd[1]);
close(fd[0]);

ret |= finish_connect(conn);

print_push_status(dest, remote_refs);
if (!helper_status)
print_push_status(dest, remote_refs);

if (!args.dry_run && remote) {
struct ref *ref;
Expand Down
13 changes: 10 additions & 3 deletions http.c
@@ -1,9 +1,11 @@
#include "http.h"
#include "pack.h"
#include "sideband.h"

int data_received;
int active_requests;
int http_is_verbose;
size_t http_post_buffer = 16 * LARGE_PACKET_MAX;

#ifdef USE_CURL_MULTI
static int max_requests = -1;
Expand Down Expand Up @@ -97,8 +99,6 @@ size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf)
return eltsize * nmemb;
}

static void finish_active_slot(struct active_request_slot *slot);

#ifdef USE_CURL_MULTI
static void process_curl_messages(void)
{
Expand Down Expand Up @@ -174,6 +174,13 @@ static int http_options(const char *var, const char *value, void *cb)
if (!strcmp("http.proxy", var))
return git_config_string(&curl_http_proxy, var, value);

if (!strcmp("http.postbuffer", var)) {
http_post_buffer = git_config_int(var, value);
if (http_post_buffer < LARGE_PACKET_MAX)
http_post_buffer = LARGE_PACKET_MAX;
return 0;
}

/* Fall back on the default ones */
return git_default_config(var, value, cb);
}
Expand Down Expand Up @@ -638,7 +645,7 @@ void release_active_slot(struct active_request_slot *slot)
#endif
}

static void finish_active_slot(struct active_request_slot *slot)
void finish_active_slot(struct active_request_slot *slot)
{
closedown_active_slot(slot);
curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code);
Expand Down
2 changes: 2 additions & 0 deletions http.h
Expand Up @@ -79,6 +79,7 @@ extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
extern struct active_request_slot *get_active_slot(void);
extern int start_active_slot(struct active_request_slot *slot);
extern void run_active_slot(struct active_request_slot *slot);
extern void finish_active_slot(struct active_request_slot *slot);
extern void finish_all_active_slots(void);
extern void release_active_slot(struct active_request_slot *slot);

Expand All @@ -94,6 +95,7 @@ extern void http_cleanup(void);
extern int data_received;
extern int active_requests;
extern int http_is_verbose;
extern size_t http_post_buffer;

extern char curl_errorstr[CURL_ERROR_SIZE];

Expand Down

0 comments on commit de1a2fd

Please sign in to comment.