git.c 9.72 KB
Newer Older
1
/*
schu committed
2
 * Copyright (C) 2009-2012 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 29
	git_vector refs;
	git_remote_head **heads;
30
	git_transport_caps caps;
31 32
	char buff[1024];
	gitno_buffer buf;
33 34 35
#ifdef GIT_WIN32
	WSADATA wsd;
#endif
36 37
} transport_git;

38 39 40 41 42
/*
 * Create a git procol request.
 *
 * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0
 */
43
static int gen_proto(git_buf *request, const char *cmd, const char *url)
44
{
45
	char *delim, *repo;
46 47
	char default_command[] = "git-upload-pack";
	char host[] = "host=";
48
	size_t len;
49 50

	delim = strchr(url, '/');
51 52 53 54
	if (delim == NULL) {
		giterr_set(GITERR_NET, "Malformed URL");
		return -1;
	}
55 56 57 58 59 60 61 62 63 64

	repo = delim;

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

	if (cmd == NULL)
		cmd = default_command;

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

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

73
	if (git_buf_oom(request))
74
		return -1;
75 76

	return 0;
77 78
}

79
static int send_request(git_transport *t, const char *cmd, const char *url)
80
{
81 82
	int error;
	git_buf request = GIT_BUF_INIT;
83

84
	error = gen_proto(&request, cmd, url);
85
	if (error < 0)
86 87
		goto cleanup;

88
	error = gitno_send(t, request.ptr, request.size, 0);
89 90

cleanup:
91
	git_buf_free(&request);
92 93
	return error;
}
94 95 96 97 98 99

/*
 * 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
 */
100
static int do_connect(transport_git *t, const char *url)
101
{
102
	char *host, *port;
103 104 105
	const char prefix[] = "git://";

	if (!git__prefixcmp(url, prefix))
106
		url += strlen(prefix);
107

108 109
	if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
		return -1;
110

111 112 113 114 115
	if (gitno_connect((git_transport *)t, host, port) < 0)
		goto on_error;

	if (send_request((git_transport *)t, NULL, url) < 0)
		goto on_error;
116

117 118
	git__free(host);
	git__free(port);
119

120
	return 0;
121 122 123 124 125 126

on_error:
	git__free(host);
	git__free(port);
	gitno_close(t->parent.socket);
	return -1;
127 128 129 130 131
}

/*
 * Read from the socket and store the references in the vector
 */
132
static int store_refs(transport_git *t)
133
{
134
	gitno_buffer *buf = &t->buf;
135
	int ret = 0;
136

137
	while (1) {
138 139 140 141 142 143
		if ((ret = gitno_recv(buf)) < 0)
			return -1;
		if (ret == 0) /* Orderly shutdown, so exit */
			return 0;

		ret = git_protocol_store_refs(&t->proto, buf->data, buf->offset);
144
		if (ret == GIT_EBUFS) {
145 146 147
			gitno_consume_n(buf, buf->len);
			continue;
		}
148

149 150
		if (ret < 0)
			return ret;
151

152
		gitno_consume_n(buf, buf->offset);
153

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

161 162 163 164 165 166 167 168 169 170
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)
171
		return 0;
172 173 174 175 176 177 178 179

	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;
180
			ptr += strlen(GIT_CAP_OFS_DELTA);
181 182 183 184 185 186 187
			continue;
		}

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

188
	return 0;
189 190
}

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

199 200 201 202
	if (direction == GIT_DIR_PUSH) {
		giterr_set(GITERR_NET, "Pushing over git:// is not supported");
		return -1;
	}
203

204
	t->parent.direction = direction;
205 206
	if (git_vector_init(&t->refs, 16, NULL) < 0)
		return -1;
207 208

	/* Connect and ask for the refs */
209 210
	if (do_connect(t, transport->url) < 0)
		goto cleanup;
211

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

214
	t->parent.connected = 1;
215 216
	if (store_refs(t) < 0)
		goto cleanup;
217

218 219
	if (detect_caps(t) < 0)
		goto cleanup;
220

221
	return 0;
222
cleanup:
223 224
	git_vector_free(&t->refs);
	return -1;
225 226
}

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

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

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

240 241
		pkt = (git_pkt_ref *)p;

242 243 244 245
		if (list_cb(&pkt->head, opaque) < 0) {
			giterr_set(GITERR_NET, "User callback returned error");
			return -1;
		}
246 247
	}

248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
	return 0;
}

/* Wait until we get an ack from the */
static int recv_pkt(gitno_buffer *buf)
{
	const char *ptr = buf->data, *line_end;
	git_pkt *pkt;
	int pkt_type, error;

	do {
		/* Wait for max. 1 second */
		if ((error = gitno_select_in(buf, 1, 0)) < 0) {
			return -1;
		} else if (error == 0) {
			/*
			 * Some servers don't respond immediately, so if this
			 * happens, we keep sending information until it
			 * answers. Pretend we received a NAK to convince higher
			 * layers to do so.
			 */
			return GIT_PKT_NAK;
		}

		if ((error = gitno_recv(buf)) < 0)
			return -1;

		error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
276
		if (error == GIT_EBUFS)
277 278 279 280 281 282 283 284 285 286
			continue;
		if (error < 0)
			return -1;
	} while (error);

	gitno_consume(buf, line_end);
	pkt_type = pkt->type;
	git__free(pkt);

	return pkt_type;
287 288
}

289
static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants)
290 291 292 293 294 295
{
	transport_git *t = (transport_git *) transport;
	git_revwalk *walk;
	git_oid oid;
	int error;
	unsigned int i;
296
	git_buf data = GIT_BUF_INIT;
297
	gitno_buffer *buf = &t->buf;
298

299
	if (git_pkt_buffer_wants(wants, &t->caps, &data) < 0)
300
		return -1;
301

302 303
	if (git_fetch_setup_walk(&walk, repo) < 0)
		goto on_error;
304

305
	if (gitno_send(transport, data.ptr, data.size, 0) < 0)
306
		goto on_error;
307

308
	git_buf_clear(&data);
309 310 311 312 313 314
	/*
	 * 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;
315
	while ((error = git_revwalk_next(&oid, walk)) == 0) {
316
		git_pkt_buffer_have(&oid, &data);
317
		i++;
318
		if (i % 20 == 0) {
319 320
			int pkt_type;

321 322 323 324
			git_pkt_buffer_flush(&data);
			if (git_buf_oom(&data))
				goto on_error;

325
			if (gitno_send(transport, data.ptr, data.size, 0) < 0)
326 327
				goto on_error;

328 329 330 331 332 333 334 335 336
			pkt_type = recv_pkt(buf);

			if (pkt_type == GIT_PKT_ACK) {
				break;
			} else if (pkt_type == GIT_PKT_NAK) {
				continue;
			} else {
				giterr_set(GITERR_NET, "Unexpected pkt type");
				goto on_error;
337
			}
338

339
		}
340
	}
341
	if (error < 0 && error != GIT_REVWALKOVER)
342
		goto on_error;
343

344 345 346 347
	/* Tell the other end that we're done negotiating */
	git_buf_clear(&data);
	git_pkt_buffer_flush(&data);
	git_pkt_buffer_done(&data);
348
	if (gitno_send(transport, data.ptr, data.size, 0) < 0)
349
		goto on_error;
350

351
	git_buf_free(&data);
352
	git_revwalk_free(walk);
353
	return 0;
354

355
on_error:
356
	git_buf_free(&data);
357 358
	git_revwalk_free(walk);
	return -1;
359 360
}

361
static int git_download_pack(git_transport *transport, git_repository *repo, git_off_t *bytes, git_indexer_stats *stats)
Carlos Martín Nieto committed
362 363
{
	transport_git *t = (transport_git *) transport;
364
	int error = 0, read_bytes;
365
	gitno_buffer *buf = &t->buf;
Carlos Martín Nieto committed
366 367 368 369
	git_pkt *pkt;
	const char *line_end, *ptr;

	/*
370
	 * For now, we ignore everything and wait for the pack
Carlos Martín Nieto committed
371
	 */
372
	do {
373
		ptr = buf->data;
Carlos Martín Nieto committed
374
		/* Whilst we're searching for the pack */
375
		while (1) {
376
			if (buf->offset == 0) {
Carlos Martín Nieto committed
377
				break;
378 379 380
			}

			error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset);
381
			if (error == GIT_EBUFS)
Carlos Martín Nieto committed
382
				break;
383

384
			if (error < 0)
Carlos Martín Nieto committed
385 386
				return error;

387
			if (pkt->type == GIT_PKT_PACK) {
388
				git__free(pkt);
389
				return git_fetch__download_pack(buf->data, buf->offset, transport, repo, bytes, stats);
390
			}
391

Carlos Martín Nieto committed
392
			/* For now we don't care about anything */
393
			git__free(pkt);
394 395 396
			gitno_consume(buf, line_end);
		}

397 398
		read_bytes = gitno_recv(buf);
	} while (read_bytes);
399

400
	return read_bytes;
Carlos Martín Nieto committed
401 402
}

403
static int git_close(git_transport *t)
404
{
405
	git_buf buf = GIT_BUF_INIT;
406

407 408
	if (git_pkt_buffer_flush(&buf) < 0)
		return -1;
409
	/* Can't do anything if there's an error, so don't bother checking  */
410 411
	gitno_send(t, buf.ptr, buf.size, 0);

412 413 414 415
	if (gitno_close(t->socket) < 0) {
		giterr_set(GITERR_NET, "Failed to close socket");
		return -1;
	}
416

417
	t->connected = 0;
418

419 420 421 422
#ifdef GIT_WIN32
	WSACleanup();
#endif

423
	return 0;
424 425 426 427
}

static void git_free(git_transport *transport)
{
428 429
	transport_git *t = (transport_git *) transport;
	git_vector *refs = &t->refs;
430 431 432 433
	unsigned int i;

	for (i = 0; i < refs->length; ++i) {
		git_pkt *p = git_vector_get(refs, i);
434
		git_pkt_free(p);
435 436 437
	}

	git_vector_free(refs);
438
	git__free(t->heads);
439
	git_buf_free(&t->proto.buf);
440 441
	git__free(t->parent.url);
	git__free(t);
442 443
}

444
int git_transport_git(git_transport **out)
445
{
446
	transport_git *t;
447 448 449
#ifdef GIT_WIN32
	int ret;
#endif
450 451

	t = git__malloc(sizeof(transport_git));
452
	GITERR_CHECK_ALLOC(t);
453

454 455
	memset(t, 0x0, sizeof(transport_git));

456 457
	t->parent.connect = git_connect;
	t->parent.ls = git_ls;
458
	t->parent.negotiate_fetch = git_negotiate_fetch;
Carlos Martín Nieto committed
459
	t->parent.download_pack = git_download_pack;
460 461
	t->parent.close = git_close;
	t->parent.free = git_free;
462 463
	t->proto.refs = &t->refs;
	t->proto.transport = (git_transport *) t;
464 465

	*out = (git_transport *) t;
466

467 468 469 470
#ifdef GIT_WIN32
	ret = WSAStartup(MAKEWORD(2,2), &t->wsd);
	if (ret != 0) {
		git_free(*out);
471 472
		giterr_set(GITERR_NET, "Winsock init failed");
		return -1;
473 474 475
	}
#endif

476
	return 0;
477
}