]> git.proxmox.com Git - libgit2.git/blame - src/transports/local.c
Local fetch: add tests
[libgit2.git] / src / transports / local.c
CommitLineData
bb742ede 1/*
5e0de328 2 * Copyright (C) 2009-2012 the libgit2 contributors
bb742ede
VM
3 *
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.
6 */
8f866dae
CMN
7#include "common.h"
8#include "git2/types.h"
8f866dae
CMN
9#include "git2/net.h"
10#include "git2/repository.h"
d6258deb
CMN
11#include "git2/object.h"
12#include "git2/tag.h"
41fb1ca0 13#include "git2/transport.h"
505da062
BS
14#include "git2/revwalk.h"
15#include "git2/odb_backend.h"
16#include "git2/pack.h"
17#include "git2/commit.h"
18#include "git2/revparse.h"
19#include "pack-objects.h"
20#include "refs.h"
84dd3820 21#include "posix.h"
e2580375 22#include "path.h"
23#include "buffer.h"
505da062
BS
24#include "repository.h"
25#include "odb.h"
8f866dae 26
0a9a38e5 27typedef struct {
4e913309 28 git_transport parent;
41fb1ca0
PK
29 char *url;
30 int direction;
31 int flags;
32 git_atomic cancelled;
0a9a38e5 33 git_repository *repo;
41fb1ca0
PK
34 git_vector refs;
35 unsigned connected : 1;
4e913309 36} transport_local;
0a9a38e5 37
d88d4311 38static int add_ref(transport_local *t, const char *name)
d6258deb 39{
d6258deb
CMN
40 const char peeled[] = "^{}";
41 git_remote_head *head;
a62053a0
CMN
42 git_object *obj = NULL, *target = NULL;
43 git_buf buf = GIT_BUF_INIT;
d6258deb 44
505da062 45 head = (git_remote_head *)git__calloc(1, sizeof(git_remote_head));
a62053a0 46 GITERR_CHECK_ALLOC(head);
d6258deb 47
a62053a0
CMN
48 head->name = git__strdup(name);
49 GITERR_CHECK_ALLOC(head->name);
d6258deb 50
ad4b5beb 51 if (git_reference_name_to_oid(&head->oid, t->repo, name) < 0) {
41fb1ca0 52 git__free(head->name);
f201d613 53 git__free(head);
41fb1ca0 54 return -1;
ad4b5beb
CMN
55 }
56
41fb1ca0 57 if (git_vector_insert(&t->refs, head) < 0)
ad4b5beb 58 {
41fb1ca0
PK
59 git__free(head->name);
60 git__free(head);
a62053a0 61 return -1;
f201d613 62 }
d6258deb
CMN
63
64 /* If it's not a tag, we don't need to try to peel it */
65 if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
a62053a0 66 return 0;
d6258deb 67
41fb1ca0 68 if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
a62053a0 69 return -1;
d6258deb 70
db1f7e59 71 head = NULL;
72
d6258deb 73 /* If it's not an annotated tag, just get out */
a62053a0
CMN
74 if (git_object_type(obj) != GIT_OBJ_TAG) {
75 git_object_free(obj);
76 return 0;
77 }
d6258deb
CMN
78
79 /* And if it's a tag, peel it, and add it to the list */
41fb1ca0 80 head = (git_remote_head *)git__malloc(sizeof(git_remote_head));
a62053a0
CMN
81 GITERR_CHECK_ALLOC(head);
82 if (git_buf_join(&buf, 0, name, peeled) < 0)
83 return -1;
d88d4311 84
a62053a0 85 head->name = git_buf_detach(&buf);
d6258deb 86
a62053a0
CMN
87 if (git_tag_peel(&target, (git_tag *) obj) < 0)
88 goto on_error;
79fd4230 89
a62053a0
CMN
90 git_oid_cpy(&head->oid, git_object_id(target));
91 git_object_free(obj);
92 git_object_free(target);
d6258deb 93
41fb1ca0 94 if (git_vector_insert(&t->refs, head) < 0)
a62053a0 95 return -1;
d6258deb 96
a62053a0 97 return 0;
75abd2b9 98
a62053a0 99on_error:
45e79e37 100 git_object_free(obj);
a62053a0
CMN
101 git_object_free(target);
102 return -1;
d6258deb
CMN
103}
104
d88d4311 105static int store_refs(transport_local *t)
8f866dae 106{
7e305056 107 unsigned int i;
d88d4311 108 git_strarray ref_names = {0};
d6258deb 109
d88d4311 110 assert(t);
7e305056 111
4fbd1c00 112 if (git_reference_list(&ref_names, t->repo, GIT_REF_LISTALL) < 0 ||
41fb1ca0 113 git_vector_init(&t->refs, (unsigned int)ref_names.count, NULL) < 0)
a62053a0 114 goto on_error;
d6258deb 115
7e305056 116 /* Sort the references first */
d88d4311 117 git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
7e305056
CMN
118
119 /* Add HEAD */
a62053a0
CMN
120 if (add_ref(t, GIT_HEAD_FILE) < 0)
121 goto on_error;
7e305056 122
d88d4311 123 for (i = 0; i < ref_names.count; ++i) {
a62053a0
CMN
124 if (add_ref(t, ref_names.strings[i]) < 0)
125 goto on_error;
7e305056 126 }
d6258deb 127
a1515693 128 git_strarray_free(&ref_names);
a62053a0
CMN
129 return 0;
130
131on_error:
41fb1ca0 132 git_vector_free(&t->refs);
d88d4311 133 git_strarray_free(&ref_names);
a62053a0 134 return -1;
d88d4311 135}
0a9a38e5 136
d88d4311
VM
137/*
138 * Try to open the url as a git directory. The direction doesn't
139 * matter in this case because we're calulating the heads ourselves.
140 */
091361f5
PK
141static int local_connect(
142 git_transport *transport,
143 const char *url,
144 git_cred_acquire_cb cred_acquire_cb,
145 int direction, int flags)
d88d4311
VM
146{
147 git_repository *repo;
148 int error;
149 transport_local *t = (transport_local *) transport;
150 const char *path;
e2580375 151 git_buf buf = GIT_BUF_INIT;
152
091361f5
PK
153 GIT_UNUSED(cred_acquire_cb);
154
41fb1ca0
PK
155 t->url = git__strdup(url);
156 GITERR_CHECK_ALLOC(t->url);
157 t->direction = direction;
158 t->flags = flags;
d88d4311
VM
159
160 /* The repo layer doesn't want the prefix */
41fb1ca0
PK
161 if (!git__prefixcmp(t->url, "file://")) {
162 if (git_path_fromurl(&buf, t->url) < 0) {
e2580375 163 git_buf_free(&buf);
a62053a0 164 return -1;
e2580375 165 }
166 path = git_buf_cstr(&buf);
167
a62053a0 168 } else { /* We assume transport->url is already a path */
41fb1ca0 169 path = t->url;
a62053a0 170 }
d88d4311
VM
171
172 error = git_repository_open(&repo, path);
e2580375 173
174 git_buf_free(&buf);
175
a62053a0
CMN
176 if (error < 0)
177 return -1;
d88d4311 178
db1f7e59 179 t->repo = repo;
180
a62053a0
CMN
181 if (store_refs(t) < 0)
182 return -1;
d88d4311 183
41fb1ca0
PK
184 t->connected = 1;
185
186 return 0;
187}
188
189static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
190{
191 transport_local *t = (transport_local *)transport;
192 unsigned int i;
193 git_remote_head *head = NULL;
194
195 if (!t->connected) {
196 giterr_set(GITERR_NET, "The transport is not connected");
197 return -1;
198 }
199
200 git_vector_foreach(&t->refs, i, head) {
201 if (list_cb(head, payload))
202 return GIT_EUSER;
203 }
d88d4311 204
a62053a0 205 return 0;
d6258deb
CMN
206}
207
41fb1ca0
PK
208static int local_negotiate_fetch(
209 git_transport *transport,
210 git_repository *repo,
505da062
BS
211 const git_remote_head * const *refs,
212 size_t count)
a167002f 213{
505da062
BS
214 transport_local *t = (transport_local*)transport;
215 git_remote_head *rhead;
216 unsigned int i;
217
41fb1ca0
PK
218 GIT_UNUSED(refs);
219 GIT_UNUSED(count);
a167002f 220
505da062
BS
221 /* Fill in the loids */
222 git_vector_foreach(&t->refs, i, rhead) {
223 git_object *obj;
224
225 int error = git_revparse_single(&obj, repo, rhead->name);
226 if (!error)
227 git_oid_cpy(&rhead->loid, git_object_id(obj));
228 else if (error != GIT_ENOTFOUND)
229 return error;
230 giterr_clear();
231 }
232
233 return 0;
234}
235
236typedef struct foreach_data {
237 git_transfer_progress *stats;
238 git_transfer_progress_callback progress_cb;
239 void *progress_payload;
240 git_odb_writepack *writepack;
241} foreach_data;
242
243static int foreach_cb(void *buf, size_t len, void *payload)
244{
245 foreach_data *data = (foreach_data*)payload;
246
247 data->stats->received_bytes += len;
248 return data->writepack->add(data->writepack, buf, len, data->stats);
249}
250
251static int local_download_pack(
252 git_transport *transport,
253 git_repository *repo,
254 git_transfer_progress *stats,
255 git_transfer_progress_callback progress_cb,
256 void *progress_payload)
257{
258 transport_local *t = (transport_local*)transport;
259 git_revwalk *walk = NULL;
260 git_remote_head *rhead;
261 unsigned int i;
262 int error = -1;
263 git_oid oid;
264 git_packbuilder *pack = NULL;
265 git_odb_writepack *writepack = NULL;
266
267 if ((error = git_revwalk_new(&walk, t->repo)) < 0)
268 goto cleanup;
269 git_revwalk_sorting(walk, GIT_SORT_TIME);
270
271 if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
272 goto cleanup;
273
274 stats->total_objects = 0;
275 stats->indexed_objects = 0;
276 stats->received_objects = 0;
277 stats->received_bytes = 0;
278
279 git_vector_foreach(&t->refs, i, rhead) {
280 git_object *obj;
281 if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
282 goto cleanup;
283
284 if (git_object_type(obj) == GIT_OBJ_COMMIT) {
285 /* Revwalker includes only wanted commits */
286 error = git_revwalk_push(walk, &rhead->oid);
287 if (!git_oid_iszero(&rhead->loid))
288 error = git_revwalk_hide(walk, &rhead->loid);
289 } else {
290 /* Tag or some other wanted object. Add it on its own */
291 stats->total_objects++;
292 error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
293 }
294 git_object_free(obj);
295 }
296
297 /* Walk the objects, building a packfile */
298
299 while ((error = git_revwalk_next(&oid, walk)) == 0) {
300 git_commit *commit;
301
302 stats->total_objects++;
303
304 if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
305 const git_oid *tree_oid = git_commit_tree_oid(commit);
306 git_commit_free(commit);
307
308 /* Add the commit and its tree */
309 if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
310 (error = git_packbuilder_insert_tree(pack, tree_oid)))
311 goto cleanup;
312 }
313 }
314
315 if (progress_cb) progress_cb(stats, progress_payload);
316
317 {
318 git_odb *odb;
319 if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 ||
320 (error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
321 goto cleanup;
322 }
323
324 /* Write the data to the ODB */
325 {
326 foreach_data data = {0};
327 data.stats = stats;
328 data.progress_cb = progress_cb;
329 data.progress_payload = progress_payload;
330 data.writepack = writepack;
331
332 if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)
333 goto cleanup;
334 }
335 error = writepack->commit(writepack, stats);
336
337cleanup:
338 if (writepack) writepack->free(writepack);
339 git_packbuilder_free(pack);
340 git_revwalk_free(walk);
341 return error;
a167002f
MS
342}
343
41fb1ca0
PK
344static int local_is_connected(git_transport *transport, int *connected)
345{
346 transport_local *t = (transport_local *)transport;
347
348 *connected = t->connected;
349
350 return 0;
351}
352
353static int local_read_flags(git_transport *transport, int *flags)
354{
355 transport_local *t = (transport_local *)transport;
356
357 *flags = t->flags;
358
359 return 0;
360}
361
362static void local_cancel(git_transport *transport)
363{
364 transport_local *t = (transport_local *)transport;
365
366 git_atomic_set(&t->cancelled, 1);
367}
368
854eccbb 369static int local_close(git_transport *transport)
d6258deb 370{
d88d4311
VM
371 transport_local *t = (transport_local *)transport;
372
41fb1ca0 373 t->connected = 0;
d88d4311
VM
374 git_repository_free(t->repo);
375 t->repo = NULL;
376
a62053a0 377 return 0;
8f866dae
CMN
378}
379
d6258deb
CMN
380static void local_free(git_transport *transport)
381{
0a9a38e5 382 unsigned int i;
4e913309 383 transport_local *t = (transport_local *) transport;
41fb1ca0
PK
384 git_vector *vec = &t->refs;
385 git_remote_head *head;
0a9a38e5 386
d6258deb
CMN
387 assert(transport);
388
41fb1ca0
PK
389 git_vector_foreach (vec, i, head) {
390 git__free(head->name);
391 git__free(head);
0a9a38e5 392 }
d88d4311 393 git_vector_free(vec);
a2888919 394
41fb1ca0 395 git__free(t->url);
3286c408 396 git__free(t);
d6258deb
CMN
397}
398
8f866dae
CMN
399/**************
400 * Public API *
401 **************/
402
41fb1ca0 403int git_transport_local(git_transport **out, void *param)
8f866dae 404{
4e913309
CMN
405 transport_local *t;
406
41fb1ca0
PK
407 GIT_UNUSED(param);
408
4e913309 409 t = git__malloc(sizeof(transport_local));
a62053a0 410 GITERR_CHECK_ALLOC(t);
4e913309 411
a2888919 412 memset(t, 0x0, sizeof(transport_local));
41fb1ca0 413
4e913309 414 t->parent.connect = local_connect;
a167002f 415 t->parent.negotiate_fetch = local_negotiate_fetch;
505da062 416 t->parent.download_pack = local_download_pack;
4e913309
CMN
417 t->parent.close = local_close;
418 t->parent.free = local_free;
41fb1ca0
PK
419 t->parent.ls = local_ls;
420 t->parent.is_connected = local_is_connected;
421 t->parent.read_flags = local_read_flags;
422 t->parent.cancel = local_cancel;
4e913309
CMN
423
424 *out = (git_transport *) t;
8f866dae 425
a62053a0 426 return 0;
8f866dae 427}