]>
Commit | Line | Data |
---|---|---|
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 | 27 | typedef 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 | 38 | static 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 | 99 | on_error: |
45e79e37 | 100 | git_object_free(obj); |
a62053a0 CMN |
101 | git_object_free(target); |
102 | return -1; | |
d6258deb CMN |
103 | } |
104 | ||
d88d4311 | 105 | static 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 | ||
131 | on_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 |
141 | static 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 | ||
189 | static 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 |
208 | static 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 | ||
236 | typedef 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 | ||
243 | static 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 | ||
251 | static 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 | ||
337 | cleanup: | |
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 |
344 | static 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 | ||
353 | static 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 | ||
362 | static 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 | 369 | static 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 |
380 | static 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 | 403 | int 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 | } |