git.c 11.2 KB
Newer Older
1
/*
Vicent Marti committed
2
 * Copyright (C) 2009-2011 the libgit2 contributors
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6 7 8 9 10 11
 */

#include "git2/net.h"
#include "git2/common.h"
#include "git2/types.h"
#include "git2/errors.h"
12 13
#include "git2/net.h"
#include "git2/revwalk.h"
14 15 16

#include "vector.h"
#include "transport.h"
Vicent Marti committed
17
#include "pkt.h"
18
#include "common.h"
19
#include "netops.h"
Carlos Martín Nieto committed
20 21
#include "filebuf.h"
#include "repository.h"
22
#include "fetch.h"
23
#include "protocol.h"
24 25

typedef struct {
26
	git_transport parent;
27
	git_protocol proto;
28
	GIT_SOCKET socket;
29 30
	git_vector refs;
	git_remote_head **heads;
31
	git_transport_caps caps;
32 33
	char buff[1024];
	gitno_buffer buf;
34 35 36
#ifdef GIT_WIN32
	WSADATA wsd;
#endif
37 38
} transport_git;

39 40 41 42 43
/*
 * Create a git procol request.
 *
 * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
 */
44
static int gen_proto(git_buf *request, const char *cmd, const char *url)
45
{
46
	char *delim, *repo;
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
	char default_command[] = "git-upload-pack";
	char host[] = "host=";
	int len;

	delim = strchr(url, '/');
	if (delim == NULL)
		return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL");

	repo = delim;

	delim = strchr(url, ':');
	if (delim == NULL)
		delim = strchr(url, '/');

	if (cmd == NULL)
		cmd = default_command;

64
	len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1;
65

66 67 68 69
	git_buf_grow(request, len);
	git_buf_printf(request, "%04x%s %s%c%s", len, cmd, repo, 0, host);
	git_buf_put(request, url, delim - url);
	git_buf_putc(request, '\0');
70

71
	return git_buf_lasterror(request);
72 73
}

74
static int send_request(GIT_SOCKET s, const char *cmd, const char *url)
75
{
76 77
	int error;
	git_buf request = GIT_BUF_INIT;
78

79
	error = gen_proto(&request, cmd, url);
80 81 82
	if (error < GIT_SUCCESS)
		goto cleanup;

83
	error = gitno_send(s, request.ptr, request.size, 0);
84 85

cleanup:
86
	git_buf_free(&request);
87 88
	return error;
}
89 90 91 92 93 94

/*
 * Parse the URL and connect to a server, storing the socket in
 * out. For convenience this also takes care of asking for the remote
 * refs
 */
95
static int do_connect(transport_git *t, const char *url)
96
{
97
	GIT_SOCKET s;
98
	char *host, *port;
99
	const char prefix[] = "git://";
100
	int error, connected = 0;
101 102

	if (!git__prefixcmp(url, prefix))
103
		url += strlen(prefix);
104

105
	error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT);
106 107
	if (error < GIT_SUCCESS)
		return error;
108

109 110
	s = gitno_connect(host, port);
	connected = 1;
111
	error = send_request(s, NULL, url);
112
	t->socket = s;
113

114 115
	git__free(host);
	git__free(port);
116 117 118 119 120 121 122 123 124 125 126 127

	if (error < GIT_SUCCESS && s > 0)
		close(s);
	if (!connected)
		error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses");

	return error;
}

/*
 * Read from the socket and store the references in the vector
 */
128
static int store_refs(transport_git *t)
129
{
130
	gitno_buffer *buf = &t->buf;
131
	int error = GIT_SUCCESS;
132

133
	while (1) {
134
		error = gitno_recv(buf);
135 136 137
		if (error < GIT_SUCCESS)
			return git__rethrow(GIT_EOSERR, "Failed to receive data");
		if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */
138
			return GIT_SUCCESS;
139

140 141 142 143 144
		error = git_protocol_store_refs(&t->proto, buf->data, buf->offset);
		if (error == GIT_ESHORTBUFFER) {
			gitno_consume_n(buf, buf->len);
			continue;
		}
145

146 147
		if (error < GIT_SUCCESS)
			return git__rethrow(error, "Failed to store refs");
148

149
		gitno_consume_n(buf, buf->offset);
150

151 152 153
		if (t->proto.flush) { /* No more refs */
			t->proto.flush = 0;
			return GIT_SUCCESS;
154 155 156 157 158 159
		}
	}

	return error;
}

160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
static int detect_caps(transport_git *t)
{
	git_vector *refs = &t->refs;
	git_pkt_ref *pkt;
	git_transport_caps *caps = &t->caps;
	const char *ptr;

	pkt = git_vector_get(refs, 0);
	/* No refs or capabilites, odd but not a problem */
	if (pkt == NULL || pkt->capabilities == NULL)
		return GIT_SUCCESS;

	ptr = pkt->capabilities;
	while (ptr != NULL && *ptr != '\0') {
		if (*ptr == ' ')
			ptr++;

		if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) {
			caps->common = caps->ofs_delta = 1;
179
			ptr += strlen(GIT_CAP_OFS_DELTA);
180 181 182 183 184 185 186 187 188 189
			continue;
		}

		/* We don't know this capability, so skip it */
		ptr = strchr(ptr, ' ');
	}

	return GIT_SUCCESS;
}

190 191 192 193
/*
 * Since this is a network connection, we need to parse and store the
 * pkt-lines at this stage and keep them there.
 */
194
static int git_connect(git_transport *transport, int direction)
195
{
196
	transport_git *t = (transport_git *) transport;
197 198
	int error = GIT_SUCCESS;

199
	if (direction == GIT_DIR_PUSH)
200 201
		return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol");

202
	t->parent.direction = direction;
203
	error = git_vector_init(&t->refs, 16, NULL);
204 205 206 207
	if (error < GIT_SUCCESS)
		goto cleanup;

	/* Connect and ask for the refs */
208
	error = do_connect(t, transport->url);
209 210 211
	if (error < GIT_SUCCESS)
		return error;

212 213
	gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket);

214
	t->parent.connected = 1;
215
	error = store_refs(t);
216 217 218 219
	if (error < GIT_SUCCESS)
		return error;

	error = detect_caps(t);
220 221 222

cleanup:
	if (error < GIT_SUCCESS) {
223
		git_vector_free(&t->refs);
224 225 226 227 228
	}

	return error;
}

229
static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque)
230
{
231 232
	transport_git *t = (transport_git *) transport;
	git_vector *refs = &t->refs;
233
	unsigned int i;
234
	git_pkt *p = NULL;
235

236 237
	git_vector_foreach(refs, i, p) {
		git_pkt_ref *pkt = NULL;
238 239 240 241

		if (p->type != GIT_PKT_REF)
			continue;

242 243 244 245 246
		pkt = (git_pkt_ref *)p;

		if (list_cb(&pkt->head, opaque) < 0)
			return git__throw(GIT_ERROR,
				"The user callback returned an error code");
247 248 249 250 251
	}

	return GIT_SUCCESS;
}

252
static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
253 254 255 256 257 258 259 260
{
	transport_git *t = (transport_git *) transport;
	git_revwalk *walk;
	git_reference *ref;
	git_strarray refs;
	git_oid oid;
	int error;
	unsigned int i;
261
	gitno_buffer *buf = &t->buf;
262

263
	error = git_pkt_send_wants(wants, &t->caps, t->socket);
264 265
	if (error < GIT_SUCCESS)
		return git__rethrow(error, "Failed to send wants list");
266

267 268 269 270 271 272 273 274 275 276 277 278
	error = git_reference_listall(&refs, repo, GIT_REF_LISTALL);
	if (error < GIT_ERROR)
		return git__rethrow(error, "Failed to list all references");

	error = git_revwalk_new(&walk, repo);
	if (error < GIT_ERROR) {
		error = git__rethrow(error, "Failed to list all references");
		goto cleanup;
	}
	git_revwalk_sorting(walk, GIT_SORT_TIME);

	for (i = 0; i < refs.count; ++i) {
279 280 281 282
		/* No tags */
		if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR))
			continue;

283 284 285 286 287 288
		error = git_reference_lookup(&ref, repo, refs.strings[i]);
		if (error < GIT_ERROR) {
			error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]);
			goto cleanup;
		}

289 290
		if (git_reference_type(ref) == GIT_REF_SYMBOLIC)
			continue;
291

292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
		error = git_revwalk_push(walk, git_reference_oid(ref));
		if (error < GIT_ERROR) {
			error = git__rethrow(error, "Failed to push %s", refs.strings[i]);
			goto cleanup;
		}
	}
	git_strarray_free(&refs);

	/*
	 * 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.
	 */
	i = 0;
	while ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS) {
307
		error = git_pkt_send_have(&oid, t->socket);
308
		i++;
309
		if (i % 20 == 0) {
310
			const char *ptr = buf->data, *line_end;
311
			git_pkt *pkt;
312
			git_pkt_send_flush(t->socket);
313
			while (1) {
314
				/* Wait for max. 1 second */
315
				error = gitno_select_in(buf, 1, 0);
316 317 318 319 320 321 322 323 324 325 326
				if (error < GIT_SUCCESS) {
					error = git__throw(GIT_EOSERR, "Error in select");
				} else if (error == 0) {
				/*
				 * Some servers don't respond immediately, so if this
				 * happens, we keep sending information until it
				 * answers.
				 */
					break;
				}

327
				error = gitno_recv(buf);
328
				if (error < GIT_SUCCESS) {
Vicent Marti committed
329 330
				 error = git__rethrow(error, "Error receiving data");
				 goto cleanup;
331
				}
332
				error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
333 334 335 336 337 338 339
				if (error == GIT_ESHORTBUFFER)
					continue;
				if (error < GIT_SUCCESS) {
					error = git__rethrow(error, "Failed to get answer");
					goto cleanup;
				}

340
				gitno_consume(buf, line_end);
341 342

				if (pkt->type == GIT_PKT_ACK) {
343
					git__free(pkt);
344 345 346
					error = GIT_SUCCESS;
					goto done;
				} else if (pkt->type == GIT_PKT_NAK) {
347
					git__free(pkt);
348 349 350 351 352 353 354
					break;
				} else {
					error = git__throw(GIT_ERROR, "Got unexpected pkt type");
					goto cleanup;
				}
			}
		}
355 356 357 358
	}
	if (error == GIT_EREVWALKOVER)
		error = GIT_SUCCESS;

359
done:
360 361
	git_pkt_send_flush(t->socket);
	git_pkt_send_done(t->socket);
362 363 364

cleanup:
	git_revwalk_free(walk);
365

366 367 368
	return error;
}

Carlos Martín Nieto committed
369 370 371 372
static int git_send_flush(git_transport *transport)
{
	transport_git *t = (transport_git *) transport;

373
	return git_pkt_send_flush(t->socket);
Carlos Martín Nieto committed
374 375
}

Carlos Martín Nieto committed
376 377 378 379
static int git_send_done(git_transport *transport)
{
	transport_git *t = (transport_git *) transport;

380
	return git_pkt_send_done(t->socket);
381 382
}

383
static int git_download_pack(char **out, git_transport *transport, git_repository *repo)
Carlos Martín Nieto committed
384 385
{
	transport_git *t = (transport_git *) transport;
386 387
	int error = GIT_SUCCESS;
	gitno_buffer *buf = &t->buf;
Carlos Martín Nieto committed
388 389 390 391
	git_pkt *pkt;
	const char *line_end, *ptr;

	/*
392
	 * For now, we ignore everything and wait for the pack
Carlos Martín Nieto committed
393 394
	 */
	while (1) {
395
		ptr = buf->data;
Carlos Martín Nieto committed
396
		/* Whilst we're searching for the pack */
397
		while (1) {
398
			if (buf->offset == 0) {
Carlos Martín Nieto committed
399
				break;
400 401 402
			}

			error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
Carlos Martín Nieto committed
403 404
			if (error == GIT_ESHORTBUFFER)
				break;
405

Carlos Martín Nieto committed
406 407 408
			if (error < GIT_SUCCESS)
				return error;

409
			if (pkt->type == GIT_PKT_PACK) {
410
				git__free(pkt);
411 412
				return git_fetch__download_pack(out, buf->data, buf->offset, t->socket, repo);
			}
413

Carlos Martín Nieto committed
414
			/* For now we don't care about anything */
415
			git__free(pkt);
416 417 418 419 420 421 422 423
			gitno_consume(buf, line_end);
		}

		error = gitno_recv(buf);
		if (error < GIT_SUCCESS)
			return git__rethrow(GIT_EOSERR, "Failed to receive data");
		if (error == 0) { /* Orderly shutdown */
			return GIT_SUCCESS;
Carlos Martín Nieto committed
424
		}
425

Carlos Martín Nieto committed
426 427 428 429
	}
}


430 431
static int git_close(git_transport *transport)
{
432
	transport_git *t = (transport_git*) transport;
433 434
	int error;

435
	/* Can't do anything if there's an error, so don't bother checking  */
436
	git_pkt_send_flush(t->socket);
437
	error = gitno_close(t->socket);
438

439 440 441
	if (error < 0)
		error = git__throw(GIT_EOSERR, "Failed to close socket");

442 443 444 445
#ifdef GIT_WIN32
	WSACleanup();
#endif

446 447 448 449 450
	return error;
}

static void git_free(git_transport *transport)
{
451 452
	transport_git *t = (transport_git *) transport;
	git_vector *refs = &t->refs;
453 454 455 456
	unsigned int i;

	for (i = 0; i < refs->length; ++i) {
		git_pkt *p = git_vector_get(refs, i);
457
		git_pkt_free(p);
458 459 460
	}

	git_vector_free(refs);
461
	git__free(t->heads);
462
	git_buf_free(&t->proto.buf);
463 464
	git__free(t->parent.url);
	git__free(t);
465 466
}

467
int git_transport_git(git_transport **out)
468
{
469
	transport_git *t;
470 471 472
#ifdef GIT_WIN32
	int ret;
#endif
473 474 475 476 477

	t = git__malloc(sizeof(transport_git));
	if (t == NULL)
		return GIT_ENOMEM;

478 479
	memset(t, 0x0, sizeof(transport_git));

480 481
	t->parent.connect = git_connect;
	t->parent.ls = git_ls;
482
	t->parent.negotiate_fetch = git_negotiate_fetch;
Carlos Martín Nieto committed
483
	t->parent.send_flush = git_send_flush;
Carlos Martín Nieto committed
484
	t->parent.send_done = git_send_done;
Carlos Martín Nieto committed
485
	t->parent.download_pack = git_download_pack;
486 487
	t->parent.close = git_close;
	t->parent.free = git_free;
488 489
	t->proto.refs = &t->refs;
	t->proto.transport = (git_transport *) t;
490 491

	*out = (git_transport *) t;
492

493 494 495 496 497 498 499 500
#ifdef GIT_WIN32
	ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
	if (ret != 0) {
		git_free(*out);
		return git__throw(GIT_EOSERR, "Winsock init failed");
	}
#endif

501 502
	return GIT_SUCCESS;
}