]>
Commit | Line | Data |
---|---|---|
ecb6ca0e | 1 | /* |
bb742ede | 2 | * Copyright (C) 2009-2011 the libgit2 contributors |
ecb6ca0e | 3 | * |
bb742ede VM |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
ecb6ca0e CMN |
6 | */ |
7 | ||
ecb6ca0e | 8 | #include "git2/net.h" |
ecb6ca0e CMN |
9 | #include "git2/common.h" |
10 | #include "git2/types.h" | |
11 | #include "git2/errors.h" | |
22f65b9e CMN |
12 | #include "git2/net.h" |
13 | #include "git2/revwalk.h" | |
ecb6ca0e CMN |
14 | |
15 | #include "vector.h" | |
16 | #include "transport.h" | |
bdd18829 | 17 | #include "pkt.h" |
ecb6ca0e | 18 | #include "common.h" |
1b4f8140 | 19 | #include "netops.h" |
da290220 CMN |
20 | #include "filebuf.h" |
21 | #include "repository.h" | |
fc3e3c55 | 22 | #include "fetch.h" |
40a40e8e | 23 | #include "protocol.h" |
ecb6ca0e CMN |
24 | |
25 | typedef struct { | |
4e913309 | 26 | git_transport parent; |
40a40e8e | 27 | git_protocol proto; |
ccc9872d | 28 | GIT_SOCKET socket; |
ecb6ca0e CMN |
29 | git_vector refs; |
30 | git_remote_head **heads; | |
0437d991 | 31 | git_transport_caps caps; |
fc3e3c55 CMN |
32 | char buff[1024]; |
33 | gitno_buffer buf; | |
ccc9872d CMN |
34 | #ifdef GIT_WIN32 |
35 | WSADATA wsd; | |
36 | #endif | |
4e913309 CMN |
37 | } transport_git; |
38 | ||
fd679021 CMN |
39 | /* |
40 | * Create a git procol request. | |
41 | * | |
42 | * For example: 0035git-upload-pack /libgit2/libgit2\0host=github.com\0 | |
43 | */ | |
10063aeb | 44 | static int gen_proto(git_buf *request, const char *cmd, const char *url) |
fd679021 | 45 | { |
a95aeb48 | 46 | char *delim, *repo; |
fd679021 CMN |
47 | char default_command[] = "git-upload-pack"; |
48 | char host[] = "host="; | |
49 | int len; | |
50 | ||
51 | delim = strchr(url, '/'); | |
52 | if (delim == NULL) | |
53 | return git__throw(GIT_EOBJCORRUPTED, "Failed to create proto-request: malformed URL"); | |
54 | ||
55 | repo = delim; | |
56 | ||
57 | delim = strchr(url, ':'); | |
58 | if (delim == NULL) | |
59 | delim = strchr(url, '/'); | |
60 | ||
61 | if (cmd == NULL) | |
62 | cmd = default_command; | |
63 | ||
7ad994bb | 64 | len = 4 + strlen(cmd) + 1 + strlen(repo) + 1 + strlen(host) + (delim - url) + 1; |
fd679021 | 65 | |
10063aeb VM |
66 | git_buf_grow(request, len); |
67 | git_buf_printf(request, "%04x%s %s%c%s", len, cmd, repo, 0, host); | |
68 | git_buf_put(request, url, delim - url); | |
69 | git_buf_putc(request, '\0'); | |
fd679021 | 70 | |
97769280 | 71 | return git_buf_lasterror(request); |
fd679021 CMN |
72 | } |
73 | ||
ccc9872d | 74 | static int send_request(GIT_SOCKET s, const char *cmd, const char *url) |
fd679021 | 75 | { |
10063aeb VM |
76 | int error; |
77 | git_buf request = GIT_BUF_INIT; | |
fd679021 | 78 | |
10063aeb | 79 | error = gen_proto(&request, cmd, url); |
fd679021 CMN |
80 | if (error < GIT_SUCCESS) |
81 | goto cleanup; | |
82 | ||
10063aeb | 83 | error = gitno_send(s, request.ptr, request.size, 0); |
fd679021 CMN |
84 | |
85 | cleanup: | |
10063aeb | 86 | git_buf_free(&request); |
fd679021 CMN |
87 | return error; |
88 | } | |
ecb6ca0e | 89 | |
ecb6ca0e CMN |
90 | /* |
91 | * Parse the URL and connect to a server, storing the socket in | |
92 | * out. For convenience this also takes care of asking for the remote | |
93 | * refs | |
94 | */ | |
4e913309 | 95 | static int do_connect(transport_git *t, const char *url) |
ecb6ca0e | 96 | { |
ccc9872d | 97 | GIT_SOCKET s; |
c4d0fa85 | 98 | char *host, *port; |
ecb6ca0e | 99 | const char prefix[] = "git://"; |
c4d0fa85 | 100 | int error, connected = 0; |
ecb6ca0e CMN |
101 | |
102 | if (!git__prefixcmp(url, prefix)) | |
932669b8 | 103 | url += strlen(prefix); |
ecb6ca0e | 104 | |
db84b798 | 105 | error = gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT); |
c75a890b KS |
106 | if (error < GIT_SUCCESS) |
107 | return error; | |
db84b798 | 108 | |
1b4f8140 CMN |
109 | s = gitno_connect(host, port); |
110 | connected = 1; | |
fd679021 | 111 | error = send_request(s, NULL, url); |
4e913309 | 112 | t->socket = s; |
ecb6ca0e | 113 | |
3286c408 VM |
114 | git__free(host); |
115 | git__free(port); | |
ecb6ca0e CMN |
116 | |
117 | if (error < GIT_SUCCESS && s > 0) | |
118 | close(s); | |
119 | if (!connected) | |
120 | error = git__throw(GIT_EOSERR, "Failed to connect to any of the addresses"); | |
121 | ||
122 | return error; | |
123 | } | |
124 | ||
125 | /* | |
126 | * Read from the socket and store the references in the vector | |
127 | */ | |
4e913309 | 128 | static int store_refs(transport_git *t) |
ecb6ca0e | 129 | { |
fc3e3c55 | 130 | gitno_buffer *buf = &t->buf; |
ecb6ca0e | 131 | int error = GIT_SUCCESS; |
7632e249 | 132 | |
ecb6ca0e | 133 | while (1) { |
fc3e3c55 | 134 | error = gitno_recv(buf); |
c7c787ce CMN |
135 | if (error < GIT_SUCCESS) |
136 | return git__rethrow(GIT_EOSERR, "Failed to receive data"); | |
137 | if (error == GIT_SUCCESS) /* Orderly shutdown, so exit */ | |
7632e249 | 138 | return GIT_SUCCESS; |
ecb6ca0e | 139 | |
40a40e8e CMN |
140 | error = git_protocol_store_refs(&t->proto, buf->data, buf->offset); |
141 | if (error == GIT_ESHORTBUFFER) { | |
142 | gitno_consume_n(buf, buf->len); | |
143 | continue; | |
144 | } | |
c7c787ce | 145 | |
40a40e8e CMN |
146 | if (error < GIT_SUCCESS) |
147 | return git__rethrow(error, "Failed to store refs"); | |
ecb6ca0e | 148 | |
40a40e8e | 149 | gitno_consume_n(buf, buf->offset); |
ecb6ca0e | 150 | |
40a40e8e CMN |
151 | if (t->proto.flush) { /* No more refs */ |
152 | t->proto.flush = 0; | |
153 | return GIT_SUCCESS; | |
ecb6ca0e | 154 | } |
ecb6ca0e CMN |
155 | } |
156 | ||
157 | return error; | |
158 | } | |
159 | ||
0437d991 CMN |
160 | static int detect_caps(transport_git *t) |
161 | { | |
162 | git_vector *refs = &t->refs; | |
163 | git_pkt_ref *pkt; | |
164 | git_transport_caps *caps = &t->caps; | |
165 | const char *ptr; | |
166 | ||
167 | pkt = git_vector_get(refs, 0); | |
168 | /* No refs or capabilites, odd but not a problem */ | |
169 | if (pkt == NULL || pkt->capabilities == NULL) | |
170 | return GIT_SUCCESS; | |
171 | ||
172 | ptr = pkt->capabilities; | |
173 | while (ptr != NULL && *ptr != '\0') { | |
174 | if (*ptr == ' ') | |
175 | ptr++; | |
176 | ||
177 | if(!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { | |
178 | caps->common = caps->ofs_delta = 1; | |
932669b8 | 179 | ptr += strlen(GIT_CAP_OFS_DELTA); |
0437d991 CMN |
180 | continue; |
181 | } | |
182 | ||
183 | /* We don't know this capability, so skip it */ | |
184 | ptr = strchr(ptr, ' '); | |
185 | } | |
186 | ||
187 | return GIT_SUCCESS; | |
188 | } | |
189 | ||
ecb6ca0e CMN |
190 | /* |
191 | * Since this is a network connection, we need to parse and store the | |
192 | * pkt-lines at this stage and keep them there. | |
193 | */ | |
0ac2726f | 194 | static int git_connect(git_transport *transport, int direction) |
ecb6ca0e | 195 | { |
4e913309 | 196 | transport_git *t = (transport_git *) transport; |
ecb6ca0e CMN |
197 | int error = GIT_SUCCESS; |
198 | ||
0ac2726f | 199 | if (direction == GIT_DIR_PUSH) |
ecb6ca0e CMN |
200 | return git__throw(GIT_EINVALIDARGS, "Pushing is not supported with the git protocol"); |
201 | ||
0ac2726f | 202 | t->parent.direction = direction; |
4e913309 | 203 | error = git_vector_init(&t->refs, 16, NULL); |
ecb6ca0e CMN |
204 | if (error < GIT_SUCCESS) |
205 | goto cleanup; | |
206 | ||
207 | /* Connect and ask for the refs */ | |
4e913309 | 208 | error = do_connect(t, transport->url); |
ecb6ca0e CMN |
209 | if (error < GIT_SUCCESS) |
210 | return error; | |
211 | ||
fc3e3c55 CMN |
212 | gitno_buffer_setup(&t->buf, t->buff, sizeof(t->buff), t->socket); |
213 | ||
5da5321d | 214 | t->parent.connected = 1; |
4e913309 | 215 | error = store_refs(t); |
0437d991 CMN |
216 | if (error < GIT_SUCCESS) |
217 | return error; | |
218 | ||
219 | error = detect_caps(t); | |
ecb6ca0e CMN |
220 | |
221 | cleanup: | |
222 | if (error < GIT_SUCCESS) { | |
4e913309 | 223 | git_vector_free(&t->refs); |
ecb6ca0e CMN |
224 | } |
225 | ||
226 | return error; | |
227 | } | |
228 | ||
d88d4311 | 229 | static int git_ls(git_transport *transport, git_headlist_cb list_cb, void *opaque) |
ecb6ca0e | 230 | { |
4e913309 CMN |
231 | transport_git *t = (transport_git *) transport; |
232 | git_vector *refs = &t->refs; | |
ecb6ca0e | 233 | unsigned int i; |
d88d4311 | 234 | git_pkt *p = NULL; |
ecb6ca0e | 235 | |
d88d4311 VM |
236 | git_vector_foreach(refs, i, p) { |
237 | git_pkt_ref *pkt = NULL; | |
ecb6ca0e | 238 | |
ecb6ca0e CMN |
239 | if (p->type != GIT_PKT_REF) |
240 | continue; | |
241 | ||
d88d4311 VM |
242 | pkt = (git_pkt_ref *)p; |
243 | ||
244 | if (list_cb(&pkt->head, opaque) < 0) | |
245 | return git__throw(GIT_ERROR, | |
246 | "The user callback returned an error code"); | |
ecb6ca0e | 247 | } |
ecb6ca0e CMN |
248 | |
249 | return GIT_SUCCESS; | |
250 | } | |
251 | ||
d88d4311 | 252 | static int git_negotiate_fetch(git_transport *transport, git_repository *repo, const git_vector *wants) |
22f65b9e CMN |
253 | { |
254 | transport_git *t = (transport_git *) transport; | |
255 | git_revwalk *walk; | |
256 | git_reference *ref; | |
257 | git_strarray refs; | |
258 | git_oid oid; | |
259 | int error; | |
260 | unsigned int i; | |
fc3e3c55 | 261 | gitno_buffer *buf = &t->buf; |
1636ba5a | 262 | |
51760bc1 | 263 | error = git_pkt_send_wants(wants, &t->caps, t->socket); |
1636ba5a CMN |
264 | if (error < GIT_SUCCESS) |
265 | return git__rethrow(error, "Failed to send wants list"); | |
427ca3d3 | 266 | |
22f65b9e CMN |
267 | error = git_reference_listall(&refs, repo, GIT_REF_LISTALL); |
268 | if (error < GIT_ERROR) | |
269 | return git__rethrow(error, "Failed to list all references"); | |
270 | ||
271 | error = git_revwalk_new(&walk, repo); | |
272 | if (error < GIT_ERROR) { | |
273 | error = git__rethrow(error, "Failed to list all references"); | |
274 | goto cleanup; | |
275 | } | |
276 | git_revwalk_sorting(walk, GIT_SORT_TIME); | |
277 | ||
278 | for (i = 0; i < refs.count; ++i) { | |
427ca3d3 CMN |
279 | /* No tags */ |
280 | if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) | |
281 | continue; | |
282 | ||
22f65b9e CMN |
283 | error = git_reference_lookup(&ref, repo, refs.strings[i]); |
284 | if (error < GIT_ERROR) { | |
285 | error = git__rethrow(error, "Failed to lookup %s", refs.strings[i]); | |
286 | goto cleanup; | |
287 | } | |
288 | ||
427ca3d3 CMN |
289 | if (git_reference_type(ref) == GIT_REF_SYMBOLIC) |
290 | continue; | |
d88d4311 | 291 | |
22f65b9e CMN |
292 | error = git_revwalk_push(walk, git_reference_oid(ref)); |
293 | if (error < GIT_ERROR) { | |
294 | error = git__rethrow(error, "Failed to push %s", refs.strings[i]); | |
295 | goto cleanup; | |
296 | } | |
297 | } | |
298 | git_strarray_free(&refs); | |
299 | ||
300 | /* | |
301 | * We don't support any kind of ACK extensions, so the negotiation | |
302 | * boils down to sending what we have and listening for an ACK | |
303 | * every once in a while. | |
304 | */ | |
305 | i = 0; | |
306 | while ((error = git_revwalk_next(&oid, walk)) == GIT_SUCCESS) { | |
51760bc1 | 307 | error = git_pkt_send_have(&oid, t->socket); |
22f65b9e | 308 | i++; |
427ca3d3 | 309 | if (i % 20 == 0) { |
fc3e3c55 | 310 | const char *ptr = buf->data, *line_end; |
427ca3d3 | 311 | git_pkt *pkt; |
51760bc1 | 312 | git_pkt_send_flush(t->socket); |
427ca3d3 | 313 | while (1) { |
74bd343a | 314 | /* Wait for max. 1 second */ |
fc3e3c55 | 315 | error = gitno_select_in(buf, 1, 0); |
7adba5f4 CMN |
316 | if (error < GIT_SUCCESS) { |
317 | error = git__throw(GIT_EOSERR, "Error in select"); | |
318 | } else if (error == 0) { | |
319 | /* | |
320 | * Some servers don't respond immediately, so if this | |
321 | * happens, we keep sending information until it | |
322 | * answers. | |
323 | */ | |
324 | break; | |
325 | } | |
326 | ||
fc3e3c55 | 327 | error = gitno_recv(buf); |
427ca3d3 | 328 | if (error < GIT_SUCCESS) { |
87d9869f VM |
329 | error = git__rethrow(error, "Error receiving data"); |
330 | goto cleanup; | |
427ca3d3 | 331 | } |
fc3e3c55 | 332 | error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); |
427ca3d3 CMN |
333 | if (error == GIT_ESHORTBUFFER) |
334 | continue; | |
335 | if (error < GIT_SUCCESS) { | |
336 | error = git__rethrow(error, "Failed to get answer"); | |
337 | goto cleanup; | |
338 | } | |
339 | ||
fc3e3c55 | 340 | gitno_consume(buf, line_end); |
427ca3d3 CMN |
341 | |
342 | if (pkt->type == GIT_PKT_ACK) { | |
3286c408 | 343 | git__free(pkt); |
427ca3d3 CMN |
344 | error = GIT_SUCCESS; |
345 | goto done; | |
346 | } else if (pkt->type == GIT_PKT_NAK) { | |
3286c408 | 347 | git__free(pkt); |
427ca3d3 CMN |
348 | break; |
349 | } else { | |
350 | error = git__throw(GIT_ERROR, "Got unexpected pkt type"); | |
351 | goto cleanup; | |
352 | } | |
353 | } | |
354 | } | |
22f65b9e CMN |
355 | } |
356 | if (error == GIT_EREVWALKOVER) | |
357 | error = GIT_SUCCESS; | |
358 | ||
427ca3d3 | 359 | done: |
51760bc1 CMN |
360 | git_pkt_send_flush(t->socket); |
361 | git_pkt_send_done(t->socket); | |
22f65b9e CMN |
362 | |
363 | cleanup: | |
364 | git_revwalk_free(walk); | |
fc3e3c55 | 365 | |
22f65b9e CMN |
366 | return error; |
367 | } | |
368 | ||
da290220 CMN |
369 | static int git_send_flush(git_transport *transport) |
370 | { | |
371 | transport_git *t = (transport_git *) transport; | |
372 | ||
51760bc1 | 373 | return git_pkt_send_flush(t->socket); |
da290220 CMN |
374 | } |
375 | ||
7e1a94db CMN |
376 | static int git_send_done(git_transport *transport) |
377 | { | |
378 | transport_git *t = (transport_git *) transport; | |
379 | ||
51760bc1 | 380 | return git_pkt_send_done(t->socket); |
b4c90630 CMN |
381 | } |
382 | ||
9cf0f287 | 383 | static int git_download_pack(char **out, git_transport *transport, git_repository *repo) |
da290220 CMN |
384 | { |
385 | transport_git *t = (transport_git *) transport; | |
fc3e3c55 CMN |
386 | int error = GIT_SUCCESS; |
387 | gitno_buffer *buf = &t->buf; | |
da290220 CMN |
388 | git_pkt *pkt; |
389 | const char *line_end, *ptr; | |
390 | ||
da290220 | 391 | /* |
7284c105 | 392 | * For now, we ignore everything and wait for the pack |
da290220 CMN |
393 | */ |
394 | while (1) { | |
fc3e3c55 | 395 | ptr = buf->data; |
da290220 | 396 | /* Whilst we're searching for the pack */ |
7284c105 | 397 | while (1) { |
fc3e3c55 | 398 | if (buf->offset == 0) { |
da290220 | 399 | break; |
fc3e3c55 CMN |
400 | } |
401 | ||
402 | error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); | |
da290220 CMN |
403 | if (error == GIT_ESHORTBUFFER) |
404 | break; | |
fc3e3c55 | 405 | |
da290220 CMN |
406 | if (error < GIT_SUCCESS) |
407 | return error; | |
408 | ||
fc3e3c55 | 409 | if (pkt->type == GIT_PKT_PACK) { |
3286c408 | 410 | git__free(pkt); |
fc3e3c55 CMN |
411 | return git_fetch__download_pack(out, buf->data, buf->offset, t->socket, repo); |
412 | } | |
427ca3d3 | 413 | |
da290220 | 414 | /* For now we don't care about anything */ |
3286c408 | 415 | git__free(pkt); |
fc3e3c55 CMN |
416 | gitno_consume(buf, line_end); |
417 | } | |
418 | ||
419 | error = gitno_recv(buf); | |
420 | if (error < GIT_SUCCESS) | |
421 | return git__rethrow(GIT_EOSERR, "Failed to receive data"); | |
422 | if (error == 0) { /* Orderly shutdown */ | |
423 | return GIT_SUCCESS; | |
da290220 | 424 | } |
fc3e3c55 | 425 | |
da290220 | 426 | } |
da290220 CMN |
427 | } |
428 | ||
429 | ||
ecb6ca0e CMN |
430 | static int git_close(git_transport *transport) |
431 | { | |
4e913309 | 432 | transport_git *t = (transport_git*) transport; |
ecb6ca0e CMN |
433 | int error; |
434 | ||
34bfb4b0 | 435 | /* Can't do anything if there's an error, so don't bother checking */ |
51760bc1 | 436 | git_pkt_send_flush(t->socket); |
bad53552 | 437 | error = gitno_close(t->socket); |
34bfb4b0 | 438 | |
ecb6ca0e CMN |
439 | if (error < 0) |
440 | error = git__throw(GIT_EOSERR, "Failed to close socket"); | |
441 | ||
bad53552 CMN |
442 | #ifdef GIT_WIN32 |
443 | WSACleanup(); | |
444 | #endif | |
445 | ||
ecb6ca0e CMN |
446 | return error; |
447 | } | |
448 | ||
449 | static void git_free(git_transport *transport) | |
450 | { | |
4e913309 CMN |
451 | transport_git *t = (transport_git *) transport; |
452 | git_vector *refs = &t->refs; | |
ecb6ca0e CMN |
453 | unsigned int i; |
454 | ||
455 | for (i = 0; i < refs->length; ++i) { | |
456 | git_pkt *p = git_vector_get(refs, i); | |
be9fe679 | 457 | git_pkt_free(p); |
ecb6ca0e CMN |
458 | } |
459 | ||
460 | git_vector_free(refs); | |
3286c408 | 461 | git__free(t->heads); |
40a40e8e | 462 | git_buf_free(&t->proto.buf); |
3286c408 VM |
463 | git__free(t->parent.url); |
464 | git__free(t); | |
ecb6ca0e CMN |
465 | } |
466 | ||
4e913309 | 467 | int git_transport_git(git_transport **out) |
ecb6ca0e | 468 | { |
4e913309 | 469 | transport_git *t; |
ccc9872d CMN |
470 | #ifdef GIT_WIN32 |
471 | int ret; | |
472 | #endif | |
4e913309 CMN |
473 | |
474 | t = git__malloc(sizeof(transport_git)); | |
475 | if (t == NULL) | |
476 | return GIT_ENOMEM; | |
477 | ||
5da5321d CMN |
478 | memset(t, 0x0, sizeof(transport_git)); |
479 | ||
4e913309 CMN |
480 | t->parent.connect = git_connect; |
481 | t->parent.ls = git_ls; | |
22f65b9e | 482 | t->parent.negotiate_fetch = git_negotiate_fetch; |
da290220 | 483 | t->parent.send_flush = git_send_flush; |
7e1a94db | 484 | t->parent.send_done = git_send_done; |
da290220 | 485 | t->parent.download_pack = git_download_pack; |
4e913309 CMN |
486 | t->parent.close = git_close; |
487 | t->parent.free = git_free; | |
40a40e8e CMN |
488 | t->proto.refs = &t->refs; |
489 | t->proto.transport = (git_transport *) t; | |
4e913309 CMN |
490 | |
491 | *out = (git_transport *) t; | |
ecb6ca0e | 492 | |
ccc9872d CMN |
493 | #ifdef GIT_WIN32 |
494 | ret = WSAStartup(MAKEWORD(2,2), &t->wsd); | |
495 | if (ret != 0) { | |
496 | git_free(*out); | |
497 | return git__throw(GIT_EOSERR, "Winsock init failed"); | |
498 | } | |
499 | #endif | |
500 | ||
ecb6ca0e CMN |
501 | return GIT_SUCCESS; |
502 | } |