Commit c6d7d877fc891d829695069cbbd17f57e378fcd3

Vicent Martí 2013-03-12T12:32:12

Merge pull request #1410 from phkelley/push_sideband Advertise and support side-band-64k when calling receive-pack

diff --git a/src/transports/smart.h b/src/transports/smart.h
index a9e894b..c52401a 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -88,6 +88,7 @@ typedef git_pkt_data git_pkt_progress;
 
 typedef struct {
 	enum git_pkt_type type;
+	int len;
 	char error[GIT_FLEX_ARRAY];
 } git_pkt_err;
 
diff --git a/src/transports/smart_pkt.c b/src/transports/smart_pkt.c
index 51edd91..99da375 100644
--- a/src/transports/smart_pkt.c
+++ b/src/transports/smart_pkt.c
@@ -122,6 +122,7 @@ static int err_pkt(git_pkt **out, const char *line, size_t len)
 	GITERR_CHECK_ALLOC(pkt);
 
 	pkt->type = GIT_PKT_ERR;
+	pkt->len = (int)len;
 	memcpy(pkt->error, line, len);
 	pkt->error[len] = '\0';
 
@@ -166,6 +167,25 @@ static int progress_pkt(git_pkt **out, const char *line, size_t len)
 	return 0;
 }
 
+static int sideband_error_pkt(git_pkt **out, const char *line, size_t len)
+{
+	git_pkt_err *pkt;
+
+	line++;
+	len--;
+	pkt = git__malloc(sizeof(git_pkt_err) + len + 1);
+	GITERR_CHECK_ALLOC(pkt);
+
+	pkt->type = GIT_PKT_ERR;
+	pkt->len = (int)len;
+	memcpy(pkt->error, line, len);
+	pkt->error[len] = '\0';
+
+	*out = (git_pkt *)pkt;
+
+	return 0;
+}
+
 /*
  * Parse an other-ref line.
  */
@@ -380,6 +400,8 @@ int git_pkt_parse_line(
 		ret = data_pkt(head, line, len);
 	else if (*line == GIT_SIDE_BAND_PROGRESS)
 		ret = progress_pkt(head, line, len);
+	else if (*line == GIT_SIDE_BAND_ERROR)
+		ret = sideband_error_pkt(head, line, len);
 	else if (!git__prefixcmp(line, "ACK"))
 		ret = ack_pkt(head, line, len);
 	else if (!git__prefixcmp(line, "NAK"))
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 75494b2..c653ad5 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -536,7 +536,8 @@ static int gen_pktline(git_buf *buf, git_push *push)
 		if (i == 0) {
 			++len; /* '\0' */
 			if (push->report_status)
-				len += strlen(GIT_CAP_REPORT_STATUS);
+				len += strlen(GIT_CAP_REPORT_STATUS) + 1;
+			len += strlen(GIT_CAP_SIDE_BAND_64K) + 1;
 		}
 
 		git_oid_fmt(old_id, &spec->roid);
@@ -546,8 +547,13 @@ static int gen_pktline(git_buf *buf, git_push *push)
 
 		if (i == 0) {
 			git_buf_putc(buf, '\0');
-			if (push->report_status)
+			/* Core git always starts their capabilities string with a space */
+			if (push->report_status) {
+				git_buf_putc(buf, ' ');
 				git_buf_printf(buf, GIT_CAP_REPORT_STATUS);
+			}
+			git_buf_putc(buf, ' ');
+			git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K);
 		}
 
 		git_buf_putc(buf, '\n');
@@ -557,6 +563,74 @@ static int gen_pktline(git_buf *buf, git_push *push)
 	return git_buf_oom(buf) ? -1 : 0;
 }
 
+static int add_push_report_pkt(git_push *push, git_pkt *pkt)
+{
+	push_status *status;
+
+	switch (pkt->type) {
+		case GIT_PKT_OK:
+			status = git__malloc(sizeof(push_status));
+			GITERR_CHECK_ALLOC(status);
+			status->msg = NULL;
+			status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
+			if (!status->ref ||
+				git_vector_insert(&push->status, status) < 0) {
+				git_push_status_free(status);
+				return -1;
+			}
+			break;
+		case GIT_PKT_NG:
+			status = git__calloc(sizeof(push_status), 1);
+			GITERR_CHECK_ALLOC(status);
+			status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
+			status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
+			if (!status->ref || !status->msg ||
+				git_vector_insert(&push->status, status) < 0) {
+				git_push_status_free(status);
+				return -1;
+			}
+			break;
+		case GIT_PKT_UNPACK:
+			push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
+			break;
+		case GIT_PKT_FLUSH:
+			return GIT_ITEROVER;
+		default:
+			giterr_set(GITERR_NET, "report-status: protocol error");
+			return -1;
+	}
+
+	return 0;
+}
+
+static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt)
+{
+	git_pkt *pkt;
+	const char *line = data_pkt->data, *line_end;
+	size_t line_len = data_pkt->len;
+	int error;
+
+	while (line_len > 0) {
+		error = git_pkt_parse_line(&pkt, line, &line_end, line_len);
+
+		if (error < 0)
+			return error;
+
+		/* Advance in the buffer */
+		line_len -= (line_end - line);
+		line = line_end;
+
+		error = add_push_report_pkt(push, pkt);
+
+		git_pkt_free(pkt);
+
+		if (error < 0 && error != GIT_ITEROVER)
+			return error;
+	}
+
+	return 0;
+}
+
 static int parse_report(gitno_buffer *buf, git_push *push)
 {
 	git_pkt *pkt;
@@ -586,46 +660,33 @@ static int parse_report(gitno_buffer *buf, git_push *push)
 
 		gitno_consume(buf, line_end);
 
-		if (pkt->type == GIT_PKT_OK) {
-			push_status *status = git__malloc(sizeof(push_status));
-			GITERR_CHECK_ALLOC(status);
-			status->ref = git__strdup(((git_pkt_ok *)pkt)->ref);
-			status->msg = NULL;
-			git_pkt_free(pkt);
-			if (git_vector_insert(&push->status, status) < 0) {
-				git__free(status);
-				return -1;
-			}
-			continue;
-		}
+		error = 0;
 
-		if (pkt->type == GIT_PKT_NG) {
-			push_status *status = git__malloc(sizeof(push_status));
-			GITERR_CHECK_ALLOC(status);
-			status->ref = git__strdup(((git_pkt_ng *)pkt)->ref);
-			status->msg = git__strdup(((git_pkt_ng *)pkt)->msg);
-			git_pkt_free(pkt);
-			if (git_vector_insert(&push->status, status) < 0) {
-				git__free(status);
-				return -1;
-			}
-			continue;
+		switch (pkt->type) {
+			case GIT_PKT_DATA:
+				/* This is a sideband packet which contains other packets */
+				error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt);
+				break;
+			case GIT_PKT_ERR:
+				giterr_set(GITERR_NET, "report-status: Error reported: %s",
+					((git_pkt_err *)pkt)->error);
+				error = -1;
+				break;
+			case GIT_PKT_PROGRESS:
+				break;
+			default:
+				error = add_push_report_pkt(push, pkt);
+				break;
 		}
 
-		if (pkt->type == GIT_PKT_UNPACK) {
-			push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok;
-			git_pkt_free(pkt);
-			continue;
-		}
+		git_pkt_free(pkt);
 
-		if (pkt->type == GIT_PKT_FLUSH) {
-			git_pkt_free(pkt);
+		/* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */
+		if (error == GIT_ITEROVER)
 			return 0;
-		}
 
-		git_pkt_free(pkt);
-		giterr_set(GITERR_NET, "report-status: protocol error");
-		return -1;
+		if (error < 0)
+			return error;
 	}
 }