summaryrefslogtreecommitdiff
path: root/src/transports/smart_protocol.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/transports/smart_protocol.c')
-rw-r--r--src/transports/smart_protocol.c232
1 files changed, 158 insertions, 74 deletions
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 636616717..3bf1f9329 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -13,8 +13,11 @@
#include "push.h"
#include "pack-objects.h"
#include "remote.h"
+#include "util.h"
#define NETWORK_XFER_THRESHOLD (100*1024)
+/* The minimal interval between progress updates (in seconds). */
+#define MIN_PROGRESS_UPDATE_INTERVAL 0.5
int git_smart__store_refs(transport_smart *t, int flushes)
{
@@ -46,7 +49,7 @@ int git_smart__store_refs(transport_smart *t, int flushes)
if (error == GIT_EBUFS) {
if ((recvd = gitno_recv(buf)) < 0)
- return -1;
+ return recvd;
if (recvd == 0 && !flush) {
giterr_set(GITERR_NET, "Early EOF");
@@ -94,6 +97,13 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
continue;
}
+ /* Keep multi_ack_detailed before multi_ack */
+ if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK_DETAILED)) {
+ caps->common = caps->multi_ack_detailed = 1;
+ ptr += strlen(GIT_CAP_MULTI_ACK_DETAILED);
+ continue;
+ }
+
if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) {
caps->common = caps->multi_ack = 1;
ptr += strlen(GIT_CAP_MULTI_ACK);
@@ -125,6 +135,12 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
continue;
}
+ if (!git__prefixcmp(ptr, GIT_CAP_THIN_PACK)) {
+ caps->common = caps->thin_pack = 1;
+ ptr += strlen(GIT_CAP_THIN_PACK);
+ continue;
+ }
+
/* We don't know this capability, so skip it */
ptr = strchr(ptr, ' ');
}
@@ -148,10 +164,10 @@ static int recv_pkt(git_pkt **out, gitno_buffer *buf)
break; /* return the pkt */
if (error < 0 && error != GIT_EBUFS)
- return -1;
+ return error;
if ((ret = gitno_recv(buf)) < 0)
- return -1;
+ return ret;
} while (error);
gitno_consume(buf, line_end);
@@ -168,10 +184,11 @@ static int store_common(transport_smart *t)
{
git_pkt *pkt = NULL;
gitno_buffer *buf = &t->buffer;
+ int error;
do {
- if (recv_pkt(&pkt, buf) < 0)
- return -1;
+ if ((error = recv_pkt(&pkt, buf)) < 0)
+ return error;
if (pkt->type == GIT_PKT_ACK) {
if (git_vector_insert(&t->common, pkt) < 0)
@@ -211,6 +228,7 @@ static int fetch_setup_walk(git_revwalk **out, git_repository *repo)
if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
continue;
+
if (git_revwalk_push(walk, git_reference_target(ref)) < 0)
goto on_error;
@@ -227,7 +245,33 @@ on_error:
return -1;
}
-int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count)
+static int wait_while_ack(gitno_buffer *buf)
+{
+ int error;
+ git_pkt_ack *pkt = NULL;
+
+ while (1) {
+ git__free(pkt);
+
+ if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)
+ return error;
+
+ if (pkt->type == GIT_PKT_NAK)
+ break;
+
+ if (pkt->type == GIT_PKT_ACK &&
+ (pkt->status != GIT_ACK_CONTINUE ||
+ pkt->status != GIT_ACK_COMMON)) {
+ git__free(pkt);
+ return 0;
+ }
+ }
+
+ git__free(pkt);
+ return 0;
+}
+
+int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *wants, size_t count)
{
transport_smart *t = (transport_smart *)transport;
gitno_buffer *buf = &t->buffer;
@@ -237,19 +281,20 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
unsigned int i;
git_oid oid;
- /* No own logic, do our thing */
- if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
return error;
if ((error = fetch_setup_walk(&walk, repo)) < 0)
goto on_error;
+
/*
- * We don't support any kind of ACK extensions, so the negotiation
- * boils down to sending what we have and listening for an ACK
- * every once in a while.
+ * Our support for ACK extensions is simply to parse them. On
+ * the first ACK we will accept that as enough common
+ * objects. We give up if we haven't found an answer in the
+ * first 256 we send.
*/
i = 0;
- while (true) {
+ while (i < 256) {
error = git_revwalk_next(&oid, walk);
if (error < 0) {
@@ -278,7 +323,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
goto on_error;
git_buf_clear(&data);
- if (t->caps.multi_ack) {
+ if (t->caps.multi_ack || t->caps.multi_ack_detailed) {
if ((error = store_common(t)) < 0)
goto on_error;
} else {
@@ -307,7 +352,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
git_pkt_ack *pkt;
unsigned int i;
- if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
goto on_error;
git_vector_foreach(&t->common, i, pkt) {
@@ -327,7 +372,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
git_pkt_ack *pkt;
unsigned int i;
- if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0)
+ if ((error = git_pkt_buffer_wants(wants, count, &t->caps, &data)) < 0)
goto on_error;
git_vector_foreach(&t->common, i, pkt) {
@@ -356,7 +401,7 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
git_revwalk_free(walk);
/* Now let's eat up whatever the server gives us */
- if (!t->caps.multi_ack) {
+ if (!t->caps.multi_ack && !t->caps.multi_ack_detailed) {
pkt_type = recv_pkt(NULL, buf);
if (pkt_type < 0) {
@@ -366,22 +411,10 @@ int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, c
return -1;
}
} else {
- git_pkt_ack *pkt;
- do {
- if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0)
- return error;
-
- if (pkt->type == GIT_PKT_NAK ||
- (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) {
- git__free(pkt);
- break;
- }
-
- git__free(pkt);
- } while (1);
+ error = wait_while_ack(buf);
}
- return 0;
+ return error;
on_error:
git_revwalk_free(walk);
@@ -399,16 +432,16 @@ static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack,
return GIT_EUSER;
}
- if (writepack->add(writepack, buf->data, buf->offset, stats) < 0)
+ if (writepack->append(writepack, buf->data, buf->offset, stats) < 0)
return -1;
gitno_consume_n(buf, buf->offset);
if ((recvd = gitno_recv(buf)) < 0)
- return -1;
+ return recvd;
} while(recvd > 0);
- if (writepack->commit(writepack, stats))
+ if (writepack->commit(writepack, stats) < 0)
return -1;
return 0;
@@ -422,7 +455,7 @@ struct network_packetsize_payload
size_t last_fired_bytes;
};
-static void network_packetsize(size_t received, void *payload)
+static int network_packetsize(size_t received, void *payload)
{
struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload;
@@ -432,8 +465,12 @@ static void network_packetsize(size_t received, void *payload)
/* Fire notification if the threshold is reached */
if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) {
npp->last_fired_bytes = npp->stats->received_bytes;
- npp->callback(npp->stats, npp->payload);
+
+ if (npp->callback(npp->stats, npp->payload))
+ return GIT_EUSER;
}
+
+ return 0;
}
int git_smart__download_pack(
@@ -447,7 +484,7 @@ int git_smart__download_pack(
gitno_buffer *buf = &t->buffer;
git_odb *odb;
struct git_odb_writepack *writepack = NULL;
- int error = -1;
+ int error = 0;
struct network_packetsize_payload npp = {0};
memset(stats, 0, sizeof(git_transfer_progress));
@@ -460,13 +497,14 @@ int git_smart__download_pack(
t->packetsize_payload = &npp;
/* We might have something in the buffer already from negotiate_fetch */
- if (t->buffer.offset > 0)
- t->packetsize_cb(t->buffer.offset, t->packetsize_payload);
+ if (t->buffer.offset > 0 && !t->cancelled.val)
+ if (t->packetsize_cb(t->buffer.offset, t->packetsize_payload))
+ git_atomic_set(&t->cancelled, 1);
}
if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0))
- goto on_error;
+ goto done;
/*
* If the remote doesn't support the side-band, we can feed
@@ -474,37 +512,46 @@ int git_smart__download_pack(
* check which one belongs there.
*/
if (!t->caps.side_band && !t->caps.side_band_64k) {
- if (no_sideband(t, writepack, buf, stats) < 0)
- goto on_error;
-
- goto on_success;
+ error = no_sideband(t, writepack, buf, stats);
+ goto done;
}
do {
git_pkt *pkt;
+ /* Check cancellation before network call */
if (t->cancelled.val) {
giterr_set(GITERR_NET, "The fetch was cancelled by the user");
error = GIT_EUSER;
- goto on_error;
+ goto done;
}
- if (recv_pkt(&pkt, buf) < 0)
- goto on_error;
+ if ((error = recv_pkt(&pkt, buf)) < 0)
+ goto done;
+
+ /* Check cancellation after network call */
+ if (t->cancelled.val) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ error = GIT_EUSER;
+ goto done;
+ }
if (pkt->type == GIT_PKT_PROGRESS) {
if (t->progress_cb) {
git_pkt_progress *p = (git_pkt_progress *) pkt;
- t->progress_cb(p->data, p->len, t->message_cb_payload);
+ if (t->progress_cb(p->data, p->len, t->message_cb_payload)) {
+ giterr_set(GITERR_NET, "The fetch was cancelled by the user");
+ return GIT_EUSER;
+ }
}
git__free(pkt);
} else if (pkt->type == GIT_PKT_DATA) {
git_pkt_data *p = (git_pkt_data *) pkt;
- error = writepack->add(writepack, p->data, p->len, stats);
+ error = writepack->append(writepack, p->data, p->len, stats);
git__free(pkt);
if (error < 0)
- goto on_error;
+ goto done;
} else if (pkt->type == GIT_PKT_FLUSH) {
/* A flush indicates the end of the packfile */
git__free(pkt);
@@ -512,13 +559,9 @@ int git_smart__download_pack(
}
} while (1);
- if (writepack->commit(writepack, stats) < 0)
- goto on_error;
-
-on_success:
- error = 0;
+ error = writepack->commit(writepack, stats);
-on_error:
+done:
if (writepack)
writepack->free(writepack);
@@ -656,7 +699,7 @@ static int parse_report(gitno_buffer *buf, git_push *push)
if (error == GIT_EBUFS) {
if ((recvd = gitno_recv(buf)) < 0)
- return -1;
+ return recvd;
if (recvd == 0) {
giterr_set(GITERR_NET, "Early EOF");
@@ -801,22 +844,56 @@ static int update_refs_from_report(
return 0;
}
+struct push_packbuilder_payload
+{
+ git_smart_subtransport_stream *stream;
+ git_packbuilder *pb;
+ git_push_transfer_progress cb;
+ void *cb_payload;
+ size_t last_bytes;
+ double last_progress_report_time;
+};
+
static int stream_thunk(void *buf, size_t size, void *data)
{
- git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data;
+ int error = 0;
+ struct push_packbuilder_payload *payload = data;
- return s->write(s, (const char *)buf, size);
+ if ((error = payload->stream->write(payload->stream, (const char *)buf, size)) < 0)
+ return error;
+
+ if (payload->cb) {
+ double current_time = git__timer();
+ payload->last_bytes += size;
+
+ if ((current_time - payload->last_progress_report_time) >= MIN_PROGRESS_UPDATE_INTERVAL) {
+ payload->last_progress_report_time = current_time;
+ if (payload->cb(payload->pb->nr_written, payload->pb->nr_objects, payload->last_bytes, payload->cb_payload)) {
+ giterr_clear();
+ error = GIT_EUSER;
+ }
+ }
+ }
+
+ return error;
}
int git_smart__push(git_transport *transport, git_push *push)
{
transport_smart *t = (transport_smart *)transport;
- git_smart_subtransport_stream *s;
+ struct push_packbuilder_payload packbuilder_payload = {0};
git_buf pktline = GIT_BUF_INIT;
- int error = -1, need_pack = 0;
+ int error = 0, need_pack = 0;
push_spec *spec;
unsigned int i;
+ packbuilder_payload.pb = push->pb;
+
+ if (push->transfer_progress_cb) {
+ packbuilder_payload.cb = push->transfer_progress_cb;
+ packbuilder_payload.cb_payload = push->transfer_progress_cb_payload;
+ }
+
#ifdef PUSH_DEBUG
{
git_remote_head *head;
@@ -848,29 +925,36 @@ int git_smart__push(git_transport *transport, git_push *push)
}
}
- if (git_smart__get_push_stream(t, &s) < 0 ||
- gen_pktline(&pktline, push) < 0 ||
- s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0)
- goto on_error;
+ if ((error = git_smart__get_push_stream(t, &packbuilder_payload.stream)) < 0 ||
+ (error = gen_pktline(&pktline, push)) < 0 ||
+ (error = packbuilder_payload.stream->write(packbuilder_payload.stream, git_buf_cstr(&pktline), git_buf_len(&pktline))) < 0)
+ goto done;
- if (need_pack && git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0)
- goto on_error;
+ if (need_pack &&
+ (error = git_packbuilder_foreach(push->pb, &stream_thunk, &packbuilder_payload)) < 0)
+ goto done;
/* If we sent nothing or the server doesn't support report-status, then
* we consider the pack to have been unpacked successfully */
if (!push->specs.length || !push->report_status)
push->unpack_ok = 1;
- else if (parse_report(&t->buffer, push) < 0)
- goto on_error;
+ else if ((error = parse_report(&t->buffer, push)) < 0)
+ goto done;
- if (push->status.length &&
- update_refs_from_report(&t->refs, &push->specs, &push->status) < 0)
- goto on_error;
+ /* If progress is being reported write the final report */
+ if (push->transfer_progress_cb) {
+ push->transfer_progress_cb(push->pb->nr_written, push->pb->nr_objects, packbuilder_payload.last_bytes, push->transfer_progress_cb_payload);
+ }
- error = 0;
+ if (push->status.length) {
+ error = update_refs_from_report(&t->refs, &push->specs, &push->status);
+ if (error < 0)
+ goto done;
-on_error:
- git_buf_free(&pktline);
+ error = git_smart__update_heads(t);
+ }
+done:
+ git_buf_free(&pktline);
return error;
}