smart.c 8.58 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
#include "git2.h"
#include "smart.h"
#include "refs.h"

static int git_smart__recv_cb(gitno_buffer *buf)
{
	transport_smart *t = (transport_smart *) buf->cb_data;
	size_t old_len, bytes_read;
	int error;

	assert(t->current_stream);

	old_len = buf->offset;

	if ((error = t->current_stream->read(t->current_stream, buf->data + buf->offset, buf->len - buf->offset, &bytes_read)) < 0)
		return error;

	buf->offset += bytes_read;

26 27 28
	if (t->packetsize_cb && !t->cancelled.val) {
		error = t->packetsize_cb(bytes_read, t->packetsize_payload);
		if (error) {
29
			git_atomic_set(&t->cancelled, 1);
30
			return GIT_EUSER;
31
		}
32
	}
33 34 35 36

	return (int)(buf->offset - old_len);
}

37
GIT_INLINE(int) git_smart__reset_stream(transport_smart *t, bool close_subtransport)
38 39 40 41 42
{
	if (t->current_stream) {
		t->current_stream->free(t->current_stream);
		t->current_stream = NULL;
	}
43 44 45 46 47 48

	if (close_subtransport &&
		t->wrapped->close(t->wrapped) < 0)
		return -1;

	return 0;
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
}

static int git_smart__set_callbacks(
	git_transport *transport,
	git_transport_message_cb progress_cb,
	git_transport_message_cb error_cb,
	void *message_cb_payload)
{
	transport_smart *t = (transport_smart *)transport;

	t->progress_cb = progress_cb;
	t->error_cb = error_cb;
	t->message_cb_payload = message_cb_payload;

	return 0;
}

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
int git_smart__update_heads(transport_smart *t)
{
	size_t i;
	git_pkt *pkt;

	git_vector_clear(&t->heads);
	git_vector_foreach(&t->refs, i, pkt) {
		git_pkt_ref *ref = (git_pkt_ref *) pkt;
		if (pkt->type != GIT_PKT_REF)
			continue;

		if (git_vector_insert(&t->heads, &ref->head) < 0)
			return -1;
	}

	return 0;
}

84 85 86 87
static int git_smart__connect(
	git_transport *transport,
	const char *url,
	git_cred_acquire_cb cred_acquire_cb,
88
	void *cred_acquire_payload,
89 90
	int direction,
	int flags)
91 92 93 94 95
{
	transport_smart *t = (transport_smart *)transport;
	git_smart_subtransport_stream *stream;
	int error;
	git_pkt *pkt;
96 97
	git_pkt_ref *first;
	git_smart_service_t service;
98

99 100
	if (git_smart__reset_stream(t, true) < 0)
		return -1;
101 102 103 104 105 106

	t->url = git__strdup(url);
	GITERR_CHECK_ALLOC(t->url);

	t->direction = direction;
	t->flags = flags;
107
	t->cred_acquire_cb = cred_acquire_cb;
108
	t->cred_acquire_payload = cred_acquire_payload;
109

110 111 112 113 114 115 116 117
	if (GIT_DIRECTION_FETCH == t->direction)
		service = GIT_SERVICE_UPLOADPACK_LS;
	else if (GIT_DIRECTION_PUSH == t->direction)
		service = GIT_SERVICE_RECEIVEPACK_LS;
	else {
		giterr_set(GITERR_NET, "Invalid direction");
		return -1;
	}
118

119 120
	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, service)) < 0)
		return error;
121

122 123
	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = stream;
124

125
	gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
126

127 128 129 130 131 132 133
	/* 2 flushes for RPC; 1 for stateful */
	if ((error = git_smart__store_refs(t, t->rpc ? 2 : 1)) < 0)
		return error;

	/* Strip the comment packet for RPC */
	if (t->rpc) {
		pkt = (git_pkt *)git_vector_get(&t->refs, 0);
134

135 136 137 138 139 140 141 142
		if (!pkt || GIT_PKT_COMMENT != pkt->type) {
			giterr_set(GITERR_NET, "Invalid response");
			return -1;
		} else {
			/* Remove the comment pkt from the list */
			git_vector_remove(&t->refs, 0);
			git__free(pkt);
		}
143
	}
144 145 146 147 148 149 150 151

	/* We now have loaded the refs. */
	t->have_refs = 1;

	first = (git_pkt_ref *)git_vector_get(&t->refs, 0);

	/* Detect capabilities */
	if (git_smart__detect_caps(first, &t->caps) < 0)
152
		return -1;
153 154 155 156 157 158

	/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
	if (1 == t->refs.length && !strcmp(first->head.name, "capabilities^{}") &&
		git_oid_iszero(&first->head.oid)) {
		git_vector_clear(&t->refs);
		git_pkt_free((git_pkt *)first);
159 160
	}

161
	/* Keep a list of heads for _ls */
162
	git_smart__update_heads(t);
163

164 165 166 167 168 169 170
	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;

	/* We're now logically connected. */
	t->connected = 1;

	return 0;
171 172
}

173
static int git_smart__ls(const git_remote_head ***out, size_t *size, git_transport *transport)
174 175 176 177 178 179 180 181
{
	transport_smart *t = (transport_smart *)transport;

	if (!t->have_refs) {
		giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
		return -1;
	}

182 183
	*out = (const git_remote_head **) t->heads.contents;
	*size = t->heads.length;
184 185 186 187 188 189 190 191 192 193

	return 0;
}

int git_smart__negotiation_step(git_transport *transport, void *data, size_t len)
{
	transport_smart *t = (transport_smart *)transport;
	git_smart_subtransport_stream *stream;
	int error;

194 195 196 197 198 199 200 201 202 203 204 205 206
	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;

	if (GIT_DIRECTION_FETCH != t->direction) {
		giterr_set(GITERR_NET, "This operation is only valid for fetch");
		return -1;
	}

	if ((error = t->wrapped->action(&stream, t->wrapped, t->url, GIT_SERVICE_UPLOADPACK)) < 0)
		return error;

	/* If this is a stateful implementation, the stream we get back should be the same */
	assert(t->rpc || t->current_stream == stream);
207

208 209
	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = stream;
210

211 212
	if ((error = stream->write(stream, (const char *)data, len)) < 0)
		return error;
213

214
	gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);
215

216 217
	return 0;
}
218

219 220 221 222 223 224
int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **stream)
{
	int error;

	if (t->rpc && git_smart__reset_stream(t, false) < 0)
		return -1;
225

226 227 228
	if (GIT_DIRECTION_PUSH != t->direction) {
		giterr_set(GITERR_NET, "This operation is only valid for push");
		return -1;
229 230
	}

231 232 233 234 235 236 237 238 239 240 241 242
	if ((error = t->wrapped->action(stream, t->wrapped, t->url, GIT_SERVICE_RECEIVEPACK)) < 0)
		return error;

	/* If this is a stateful implementation, the stream we get back should be the same */
	assert(t->rpc || t->current_stream == *stream);

	/* Save off the current stream (i.e. socket) that we are working with */
	t->current_stream = *stream;

	gitno_buffer_setup_callback(NULL, &t->buffer, t->buffer_data, sizeof(t->buffer_data), git_smart__recv_cb, t);

	return 0;
243 244 245 246 247 248 249 250 251
}

static void git_smart__cancel(git_transport *transport)
{
	transport_smart *t = (transport_smart *)transport;

	git_atomic_set(&t->cancelled, 1);
}

252
static int git_smart__is_connected(git_transport *transport)
253 254 255
{
	transport_smart *t = (transport_smart *)transport;

256
	return t->connected;
257 258 259 260 261 262 263 264 265 266 267 268 269 270
}

static int git_smart__read_flags(git_transport *transport, int *flags)
{
	transport_smart *t = (transport_smart *)transport;

	*flags = t->flags;

	return 0;
}

static int git_smart__close(git_transport *transport)
{
	transport_smart *t = (transport_smart *)transport;
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
	git_vector *common = &t->common;
	unsigned int i;
	git_pkt *p;
	int ret;

	ret = git_smart__reset_stream(t, true);

	git_vector_foreach(common, i, p)
		git_pkt_free(p);

	git_vector_free(common);

	if (t->url) {
		git__free(t->url);
		t->url = NULL;
	}
287 288 289

	t->connected = 0;

290
	return ret;
291 292 293 294 295
}

static void git_smart__free(git_transport *transport)
{
	transport_smart *t = (transport_smart *)transport;
296 297 298
	git_vector *refs = &t->refs;
	unsigned int i;
	git_pkt *p;
299 300 301 302 303 304 305

	/* Make sure that the current stream is closed, if we have one. */
	git_smart__close(transport);

	/* Free the subtransport */
	t->wrapped->free(t->wrapped);

306
	git_vector_free(&t->heads);
307 308 309 310 311
	git_vector_foreach(refs, i, p)
		git_pkt_free(p);

	git_vector_free(refs);

312 313 314
	git__free(t);
}

315 316 317 318 319 320 321
static int ref_name_cmp(const void *a, const void *b)
{
	const git_pkt_ref *ref_a = a, *ref_b = b;

	return strcmp(ref_a->head.name, ref_b->head.name);
}

322
int git_transport_smart(git_transport **out, git_remote *owner, void *param)
323 324 325 326 327 328 329
{
	transport_smart *t;
	git_smart_subtransport_definition *definition = (git_smart_subtransport_definition *)param;

	if (!param)
		return -1;

330
	t = git__calloc(sizeof(transport_smart), 1);
331 332
	GITERR_CHECK_ALLOC(t);

333
	t->parent.version = GIT_TRANSPORT_VERSION;
334 335 336 337 338 339
	t->parent.set_callbacks = git_smart__set_callbacks;
	t->parent.connect = git_smart__connect;
	t->parent.close = git_smart__close;
	t->parent.free = git_smart__free;
	t->parent.negotiate_fetch = git_smart__negotiate_fetch;
	t->parent.download_pack = git_smart__download_pack;
340
	t->parent.push = git_smart__push;
341 342 343 344
	t->parent.ls = git_smart__ls;
	t->parent.is_connected = git_smart__is_connected;
	t->parent.read_flags = git_smart__read_flags;
	t->parent.cancel = git_smart__cancel;
345

346
	t->owner = owner;
347 348
	t->rpc = definition->rpc;

349
	if (git_vector_init(&t->refs, 16, ref_name_cmp) < 0) {
350 351 352 353
		git__free(t);
		return -1;
	}

354 355 356 357 358
	if (git_vector_init(&t->heads, 16, ref_name_cmp) < 0) {
		git__free(t);
		return -1;
	}

359 360 361
	if (definition->callback(&t->wrapped, &t->parent) < 0) {
		git__free(t);
		return -1;
362
	}
363 364 365 366

	*out = (git_transport *) t;
	return 0;
}