Skip to content

Commit

Permalink
helper: Support bundle2 responses to bundle2 pushes
Browse files Browse the repository at this point in the history
Even when the client doesn't indicate supporting bundle2 replies,
mercurial servers send errors through bundle2 replies. So we're pretty
much forced to support bundle2 replies, and now send a replycaps part
unconditionally, so that we always get bundle2 replies to bundle2
pushes through native wire protocol.
  • Loading branch information
glandium committed Aug 5, 2016
1 parent 21cbf82 commit 2790e9c
Show file tree
Hide file tree
Showing 5 changed files with 74 additions and 19 deletions.
5 changes: 2 additions & 3 deletions cinnabar/hg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -662,10 +662,9 @@ def revs():
if repo.local():
repo.local().ui.setconfig('server', 'validate', True)
b2caps = bundle2caps(repo) if unbundle20 else {}
if b2caps and (repo.url().startswith(('http://', 'https://')) or
not isinstance(repo, HelperRepo)):
b2caps['replycaps'] = True
logging.getLogger('bundle2').debug('%r', b2caps)
if b2caps:
b2caps['replycaps'] = encodecaps({'error': ['abort']})
cg = create_bundle(store, push_commits, b2caps)
if not isinstance(repo, HelperRepo):
cg = util.chunkbuffer(cg)
Expand Down
5 changes: 3 additions & 2 deletions cinnabar/hg/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,8 +443,9 @@ def create_bundle(store, commits, bundle2caps={}):
from mercurial.util import chunkbuffer
yield 'HG20'
yield '\0' * 4 # bundle parameters length: no params
if bundle2caps.get('replycaps'):
for chunk in bundlepart('REPLYCAPS'):
replycaps = bundle2caps.get('replycaps')
if replycaps:
for chunk in bundlepart('REPLYCAPS', data=chunkbuffer(replycaps)):
yield chunk
for chunk in bundlepart('CHANGEGROUP',
advisoryparams=(('version', version),),
Expand Down
59 changes: 50 additions & 9 deletions hg-bundle.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,53 @@
#include "hg-bundle.h"
#include <stdint.h>

static size_t copy_data(uint32_t len, FILE *in, FILE *out)
struct bundle_writer {
union {
FILE *file;
struct strbuf *buf;
} out;
int type;
};

#define WRITER_FILE 1
#define WRITER_STRBUF 2

static size_t write_data(const unsigned char *buf, size_t size,
struct bundle_writer *out)
{
switch (out->type) {
case WRITER_FILE:
return fwrite(buf, 1, size, out->out.file);
case WRITER_STRBUF:
strbuf_add(out->out.buf, buf, size);
return size;
default:
return 0;
}
}

static size_t copy_data(uint32_t len, FILE *in, struct bundle_writer *out)
{
unsigned char buf[4096];
size_t ret = len;
while (len) {
uint32_t sz = len > sizeof(buf) ? sizeof(buf) : len;
fread(buf, 1, sz, in);
fwrite(buf, 1, sz, out);
write_data(buf, sz, out);
len -= sz;
}
return ret;
}

static size_t copy_chunk(int adjust, FILE *in, FILE *out)
static size_t copy_chunk(int adjust, FILE *in, struct bundle_writer *out)
{
unsigned char buf[4];
const unsigned char *p = buf;
uint32_t len;
size_t ret = 0;
//TODO: Check for errors, etc.
fread(buf, 1, 4, in);
fwrite(buf, 1, 4, out);
write_data(buf, 4, out);
len = get_be32(p);
if (len <= adjust)
//TODO: len != 0 is actually invalid
Expand All @@ -33,12 +58,12 @@ static size_t copy_chunk(int adjust, FILE *in, FILE *out)
return ret;
}

static size_t copy_changegroup_chunk(FILE *in, FILE *out)
static size_t copy_changegroup_chunk(FILE *in, struct bundle_writer *out)
{
return copy_chunk(4, in, out);
}

static void copy_changegroup(FILE *in, FILE *out)
static void copy_changegroup(FILE *in, struct bundle_writer *out)
{
/* changesets */
while (copy_changegroup_chunk(in, out)) {}
Expand All @@ -50,18 +75,18 @@ static void copy_changegroup(FILE *in, FILE *out)
}
}

static size_t copy_bundle2_chunk(FILE *in, FILE *out)
static size_t copy_bundle2_chunk(FILE *in, struct bundle_writer *out)
{
return copy_chunk(0, in, out);
}

void copy_bundle(FILE *in, FILE *out)
static void copy_bundle_internal(FILE *in, struct bundle_writer *out)
{
unsigned char buf[4];
const unsigned char *p = buf;
//TODO: Check for errors, etc.
fread(buf, 1, 4, in);
fwrite(buf, 1, 4, out);
write_data(buf, 4, out);
if (memcmp(buf, "HG20", 4)) {
copy_data(get_be32(p) - 4, in, out);
copy_changegroup(in, out);
Expand All @@ -74,3 +99,19 @@ void copy_bundle(FILE *in, FILE *out)
while (copy_bundle2_chunk(in, out)) {}
}
}

void copy_bundle(FILE *in, FILE *out)
{
struct bundle_writer writer;
writer.type = WRITER_FILE;
writer.out.file = out;
copy_bundle_internal(in, &writer);
}

void copy_bundle_to_strbuf(FILE *in, struct strbuf *out)
{
struct bundle_writer writer;
writer.type = WRITER_STRBUF;
writer.out.buf = out;
copy_bundle_internal(in, &writer);
}
2 changes: 2 additions & 0 deletions hg-bundle.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#ifndef BUNDLE_H
#define BUNDLE_H

#include "strbuf.h"
#include <stdio.h>

extern void copy_bundle(FILE *in, FILE *out);
extern void copy_bundle_to_strbuf(FILE *in, struct strbuf *out);

#endif
22 changes: 17 additions & 5 deletions hg-connect-stdio.c
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ static void stdio_push_command(struct hg_connection *conn,
struct strbuf *response, FILE *in, off_t len,
const char *command, ...)
{
int is_bundle2 = 0;
char buf[4096];
struct strbuf header = STRBUF_INIT;
va_list ap;
Expand All @@ -137,6 +138,13 @@ static void stdio_push_command(struct hg_connection *conn,
xwrite(conn->stdio.proc.in, header.buf, header.len);
strbuf_release(&header);

if (len > 4) {
char header[4] = { 0, };
fread(header, 4, 1, in);
fseek(in, 0L, SEEK_SET);
is_bundle2 = memcmp(header, "HG20", 4) == 0;
}

while (len) {
size_t read = sizeof(buf) > len ? len : sizeof(buf);
read = fread(buf, 1, read, in);
Expand All @@ -145,11 +153,15 @@ static void stdio_push_command(struct hg_connection *conn,
}

xwrite(conn->stdio.proc.in, "0\n", 2);
/* There are two responses, one for output, one for actual response. */
//TODO: actually handle output here
stdio_read_response(conn, &header);
strbuf_release(&header);
stdio_read_response(conn, response);
if (is_bundle2) {
copy_bundle_to_strbuf(conn->stdio.out, response);
} else {
/* There are two responses, one for output, one for actual response. */
//TODO: actually handle output here
stdio_read_response(conn, &header);
strbuf_release(&header);
stdio_read_response(conn, response);
}
}

static int stdio_finish(struct hg_connection *conn)
Expand Down

0 comments on commit 2790e9c

Please sign in to comment.