/src/libgit2/src/libgit2/clone.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) the libgit2 contributors. All rights reserved. |
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 | | */ |
7 | | |
8 | | #include "clone.h" |
9 | | |
10 | | #include "git2/clone.h" |
11 | | #include "git2/remote.h" |
12 | | #include "git2/revparse.h" |
13 | | #include "git2/branch.h" |
14 | | #include "git2/config.h" |
15 | | #include "git2/checkout.h" |
16 | | #include "git2/commit.h" |
17 | | #include "git2/tree.h" |
18 | | |
19 | | #include "remote.h" |
20 | | #include "futils.h" |
21 | | #include "refs.h" |
22 | | #include "fs_path.h" |
23 | | #include "repository.h" |
24 | | #include "odb.h" |
25 | | |
26 | | static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link); |
27 | | |
28 | | static int create_branch( |
29 | | git_reference **branch, |
30 | | git_repository *repo, |
31 | | const git_oid *target, |
32 | | const char *name, |
33 | | const char *log_message) |
34 | 0 | { |
35 | 0 | git_commit *head_obj = NULL; |
36 | 0 | git_reference *branch_ref = NULL; |
37 | 0 | git_str refname = GIT_STR_INIT; |
38 | 0 | int error; |
39 | | |
40 | | /* Find the target commit */ |
41 | 0 | if ((error = git_commit_lookup(&head_obj, repo, target)) < 0) |
42 | 0 | return error; |
43 | | |
44 | | /* Create the new branch */ |
45 | 0 | if ((error = git_str_printf(&refname, GIT_REFS_HEADS_DIR "%s", name)) < 0) |
46 | 0 | return error; |
47 | | |
48 | 0 | error = git_reference_create(&branch_ref, repo, git_str_cstr(&refname), target, 0, log_message); |
49 | 0 | git_str_dispose(&refname); |
50 | 0 | git_commit_free(head_obj); |
51 | |
|
52 | 0 | if (!error) |
53 | 0 | *branch = branch_ref; |
54 | 0 | else |
55 | 0 | git_reference_free(branch_ref); |
56 | |
|
57 | 0 | return error; |
58 | 0 | } |
59 | | |
60 | | static int setup_tracking_config( |
61 | | git_repository *repo, |
62 | | const char *branch_name, |
63 | | const char *remote_name, |
64 | | const char *merge_target) |
65 | 0 | { |
66 | 0 | git_config *cfg; |
67 | 0 | git_str remote_key = GIT_STR_INIT, merge_key = GIT_STR_INIT; |
68 | 0 | int error = -1; |
69 | |
|
70 | 0 | if (git_repository_config__weakptr(&cfg, repo) < 0) |
71 | 0 | return -1; |
72 | | |
73 | 0 | if (git_str_printf(&remote_key, "branch.%s.remote", branch_name) < 0) |
74 | 0 | goto cleanup; |
75 | | |
76 | 0 | if (git_str_printf(&merge_key, "branch.%s.merge", branch_name) < 0) |
77 | 0 | goto cleanup; |
78 | | |
79 | 0 | if (git_config_set_string(cfg, git_str_cstr(&remote_key), remote_name) < 0) |
80 | 0 | goto cleanup; |
81 | | |
82 | 0 | if (git_config_set_string(cfg, git_str_cstr(&merge_key), merge_target) < 0) |
83 | 0 | goto cleanup; |
84 | | |
85 | 0 | error = 0; |
86 | |
|
87 | 0 | cleanup: |
88 | 0 | git_str_dispose(&remote_key); |
89 | 0 | git_str_dispose(&merge_key); |
90 | 0 | return error; |
91 | 0 | } |
92 | | |
93 | | static int create_tracking_branch( |
94 | | git_reference **branch, |
95 | | git_repository *repo, |
96 | | const git_oid *target, |
97 | | const char *branch_name, |
98 | | const char *log_message) |
99 | 0 | { |
100 | 0 | int error; |
101 | |
|
102 | 0 | if ((error = create_branch(branch, repo, target, branch_name, log_message)) < 0) |
103 | 0 | return error; |
104 | | |
105 | 0 | return setup_tracking_config( |
106 | 0 | repo, |
107 | 0 | branch_name, |
108 | 0 | GIT_REMOTE_ORIGIN, |
109 | 0 | git_reference_name(*branch)); |
110 | 0 | } |
111 | | |
112 | | static int update_head_to_new_branch( |
113 | | git_repository *repo, |
114 | | const git_oid *target, |
115 | | const char *name, |
116 | | const char *reflog_message) |
117 | 0 | { |
118 | 0 | git_reference *tracking_branch = NULL; |
119 | 0 | int error; |
120 | |
|
121 | 0 | if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) |
122 | 0 | name += strlen(GIT_REFS_HEADS_DIR); |
123 | |
|
124 | 0 | error = create_tracking_branch(&tracking_branch, repo, target, name, |
125 | 0 | reflog_message); |
126 | |
|
127 | 0 | if (!error) |
128 | 0 | error = git_repository_set_head( |
129 | 0 | repo, git_reference_name(tracking_branch)); |
130 | |
|
131 | 0 | git_reference_free(tracking_branch); |
132 | | |
133 | | /* if it already existed, then the user's refspec created it for us, ignore it' */ |
134 | 0 | if (error == GIT_EEXISTS) |
135 | 0 | error = 0; |
136 | |
|
137 | 0 | return error; |
138 | 0 | } |
139 | | |
140 | | static int update_head_to_default(git_repository *repo) |
141 | 0 | { |
142 | 0 | git_str initialbranch = GIT_STR_INIT; |
143 | 0 | const char *branch_name; |
144 | 0 | int error = 0; |
145 | |
|
146 | 0 | if ((error = git_repository_initialbranch(&initialbranch, repo)) < 0) |
147 | 0 | goto done; |
148 | | |
149 | 0 | if (git__prefixcmp(initialbranch.ptr, GIT_REFS_HEADS_DIR) != 0) { |
150 | 0 | git_error_set(GIT_ERROR_INVALID, "invalid initial branch '%s'", initialbranch.ptr); |
151 | 0 | error = -1; |
152 | 0 | goto done; |
153 | 0 | } |
154 | | |
155 | 0 | branch_name = initialbranch.ptr + strlen(GIT_REFS_HEADS_DIR); |
156 | |
|
157 | 0 | error = setup_tracking_config(repo, branch_name, GIT_REMOTE_ORIGIN, |
158 | 0 | initialbranch.ptr); |
159 | |
|
160 | 0 | done: |
161 | 0 | git_str_dispose(&initialbranch); |
162 | 0 | return error; |
163 | 0 | } |
164 | | |
165 | | static int update_remote_head( |
166 | | git_repository *repo, |
167 | | git_remote *remote, |
168 | | git_str *target, |
169 | | const char *reflog_message) |
170 | 0 | { |
171 | 0 | git_refspec *refspec; |
172 | 0 | git_reference *remote_head = NULL; |
173 | 0 | git_str remote_head_name = GIT_STR_INIT; |
174 | 0 | git_str remote_branch_name = GIT_STR_INIT; |
175 | 0 | int error; |
176 | | |
177 | | /* Determine the remote tracking ref name from the local branch */ |
178 | 0 | refspec = git_remote__matching_refspec(remote, git_str_cstr(target)); |
179 | |
|
180 | 0 | if (refspec == NULL) { |
181 | 0 | git_error_set(GIT_ERROR_NET, "the remote's default branch does not fit the refspec configuration"); |
182 | 0 | error = GIT_EINVALIDSPEC; |
183 | 0 | goto cleanup; |
184 | 0 | } |
185 | | |
186 | 0 | if ((error = git_refspec__transform( |
187 | 0 | &remote_branch_name, |
188 | 0 | refspec, |
189 | 0 | git_str_cstr(target))) < 0) |
190 | 0 | goto cleanup; |
191 | | |
192 | 0 | if ((error = git_str_printf(&remote_head_name, |
193 | 0 | "%s%s/%s", |
194 | 0 | GIT_REFS_REMOTES_DIR, |
195 | 0 | git_remote_name(remote), |
196 | 0 | GIT_HEAD_FILE)) < 0) |
197 | 0 | goto cleanup; |
198 | | |
199 | 0 | error = git_reference_symbolic_create( |
200 | 0 | &remote_head, |
201 | 0 | repo, |
202 | 0 | git_str_cstr(&remote_head_name), |
203 | 0 | git_str_cstr(&remote_branch_name), |
204 | 0 | true, |
205 | 0 | reflog_message); |
206 | |
|
207 | 0 | cleanup: |
208 | 0 | git_reference_free(remote_head); |
209 | 0 | git_str_dispose(&remote_branch_name); |
210 | 0 | git_str_dispose(&remote_head_name); |
211 | 0 | return error; |
212 | 0 | } |
213 | | |
214 | | static int update_head_to_remote( |
215 | | git_repository *repo, |
216 | | git_remote *remote, |
217 | | const char *reflog_message) |
218 | 0 | { |
219 | 0 | int error = 0; |
220 | 0 | size_t refs_len; |
221 | 0 | const git_remote_head *remote_head, **refs; |
222 | 0 | const git_oid *remote_head_id; |
223 | 0 | git_str branch = GIT_STR_INIT; |
224 | |
|
225 | 0 | if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) |
226 | 0 | return error; |
227 | | |
228 | | /* We cloned an empty repository or one with an unborn HEAD */ |
229 | 0 | if (refs_len == 0 || strcmp(refs[0]->name, GIT_HEAD_FILE)) |
230 | 0 | return update_head_to_default(repo); |
231 | | |
232 | | /* We know we have HEAD, let's see where it points */ |
233 | 0 | remote_head = refs[0]; |
234 | 0 | GIT_ASSERT(remote_head); |
235 | | |
236 | 0 | remote_head_id = &remote_head->oid; |
237 | |
|
238 | 0 | error = git_remote__default_branch(&branch, remote); |
239 | 0 | if (error == GIT_ENOTFOUND) { |
240 | 0 | error = git_repository_set_head_detached( |
241 | 0 | repo, remote_head_id); |
242 | 0 | goto cleanup; |
243 | 0 | } |
244 | | |
245 | 0 | if ((error = update_remote_head(repo, remote, &branch, reflog_message)) < 0) |
246 | 0 | goto cleanup; |
247 | | |
248 | 0 | error = update_head_to_new_branch( |
249 | 0 | repo, |
250 | 0 | remote_head_id, |
251 | 0 | git_str_cstr(&branch), |
252 | 0 | reflog_message); |
253 | |
|
254 | 0 | cleanup: |
255 | 0 | git_str_dispose(&branch); |
256 | |
|
257 | 0 | return error; |
258 | 0 | } |
259 | | |
260 | | static int update_head_to_branch( |
261 | | git_repository *repo, |
262 | | git_remote *remote, |
263 | | const char *branch, |
264 | | const char *reflog_message) |
265 | 0 | { |
266 | 0 | int retcode; |
267 | 0 | git_str remote_branch_name = GIT_STR_INIT; |
268 | 0 | git_reference *remote_ref = NULL; |
269 | 0 | git_str default_branch = GIT_STR_INIT; |
270 | |
|
271 | 0 | GIT_ASSERT_ARG(remote); |
272 | 0 | GIT_ASSERT_ARG(branch); |
273 | | |
274 | 0 | if ((retcode = git_str_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s", |
275 | 0 | git_remote_name(remote), branch)) < 0 ) |
276 | 0 | goto cleanup; |
277 | | |
278 | 0 | if ((retcode = git_reference_lookup(&remote_ref, repo, git_str_cstr(&remote_branch_name))) < 0) |
279 | 0 | goto cleanup; |
280 | | |
281 | 0 | if ((retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch, |
282 | 0 | reflog_message)) < 0) |
283 | 0 | goto cleanup; |
284 | | |
285 | 0 | retcode = git_remote__default_branch(&default_branch, remote); |
286 | |
|
287 | 0 | if (retcode == GIT_ENOTFOUND) |
288 | 0 | retcode = 0; |
289 | 0 | else if (retcode) |
290 | 0 | goto cleanup; |
291 | | |
292 | 0 | if (!git_remote__matching_refspec(remote, git_str_cstr(&default_branch))) |
293 | 0 | goto cleanup; |
294 | | |
295 | 0 | retcode = update_remote_head(repo, remote, &default_branch, reflog_message); |
296 | |
|
297 | 0 | cleanup: |
298 | 0 | git_reference_free(remote_ref); |
299 | 0 | git_str_dispose(&remote_branch_name); |
300 | 0 | git_str_dispose(&default_branch); |
301 | 0 | return retcode; |
302 | 0 | } |
303 | | |
304 | | static int default_repository_create(git_repository **out, const char *path, int bare, void *payload) |
305 | 0 | { |
306 | 0 | GIT_UNUSED(payload); |
307 | |
|
308 | 0 | return git_repository_init(out, path, bare); |
309 | 0 | } |
310 | | |
311 | | static int default_remote_create( |
312 | | git_remote **out, |
313 | | git_repository *repo, |
314 | | const char *name, |
315 | | const char *url, |
316 | | void *payload) |
317 | 0 | { |
318 | 0 | GIT_UNUSED(payload); |
319 | |
|
320 | 0 | return git_remote_create(out, repo, name, url); |
321 | 0 | } |
322 | | |
323 | | /* |
324 | | * submodules? |
325 | | */ |
326 | | |
327 | | static int create_and_configure_origin( |
328 | | git_remote **out, |
329 | | git_repository *repo, |
330 | | const char *url, |
331 | | const git_clone_options *options) |
332 | 0 | { |
333 | 0 | int error; |
334 | 0 | git_remote *origin = NULL; |
335 | 0 | char buf[GIT_PATH_MAX]; |
336 | 0 | git_remote_create_cb remote_create = options->remote_cb; |
337 | 0 | void *payload = options->remote_cb_payload; |
338 | | |
339 | | /* If the path exists and is a dir, the url should be the absolute path */ |
340 | 0 | if (git_fs_path_root(url) < 0 && git_fs_path_exists(url) && git_fs_path_isdir(url)) { |
341 | 0 | if (p_realpath(url, buf) == NULL) |
342 | 0 | return -1; |
343 | | |
344 | 0 | url = buf; |
345 | 0 | } |
346 | | |
347 | 0 | if (!remote_create) { |
348 | 0 | remote_create = default_remote_create; |
349 | 0 | payload = NULL; |
350 | 0 | } |
351 | |
|
352 | 0 | if ((error = remote_create(&origin, repo, "origin", url, payload)) < 0) |
353 | 0 | goto on_error; |
354 | | |
355 | 0 | *out = origin; |
356 | 0 | return 0; |
357 | | |
358 | 0 | on_error: |
359 | 0 | git_remote_free(origin); |
360 | 0 | return error; |
361 | 0 | } |
362 | | |
363 | | static bool should_checkout( |
364 | | git_repository *repo, |
365 | | bool is_bare, |
366 | | const git_checkout_options *opts) |
367 | 0 | { |
368 | 0 | if (is_bare) |
369 | 0 | return false; |
370 | | |
371 | 0 | if (!opts) |
372 | 0 | return false; |
373 | | |
374 | 0 | if (opts->checkout_strategy == GIT_CHECKOUT_NONE) |
375 | 0 | return false; |
376 | | |
377 | 0 | return !git_repository_head_unborn(repo); |
378 | 0 | } |
379 | | |
380 | | static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const char *reflog_message) |
381 | 0 | { |
382 | 0 | int error; |
383 | |
|
384 | 0 | if (branch) |
385 | 0 | error = update_head_to_branch(repo, remote, branch, reflog_message); |
386 | | /* Point HEAD to the same ref as the remote's head */ |
387 | 0 | else |
388 | 0 | error = update_head_to_remote(repo, remote, reflog_message); |
389 | |
|
390 | 0 | if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts)) |
391 | 0 | error = git_checkout_head(repo, co_opts); |
392 | |
|
393 | 0 | return error; |
394 | 0 | } |
395 | | |
396 | | static int clone_into( |
397 | | git_repository *repo, |
398 | | git_remote *_remote, |
399 | | const git_fetch_options *opts, |
400 | | const git_checkout_options *co_opts, |
401 | | const char *branch) |
402 | 0 | { |
403 | 0 | int error; |
404 | 0 | git_str reflog_message = GIT_STR_INIT; |
405 | 0 | git_remote_connect_options connect_opts = GIT_REMOTE_CONNECT_OPTIONS_INIT; |
406 | 0 | git_fetch_options fetch_opts; |
407 | 0 | git_remote *remote; |
408 | 0 | git_oid_t oid_type; |
409 | |
|
410 | 0 | GIT_ASSERT_ARG(repo); |
411 | 0 | GIT_ASSERT_ARG(_remote); |
412 | | |
413 | 0 | if (!git_repository_is_empty(repo)) { |
414 | 0 | git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); |
415 | 0 | return -1; |
416 | 0 | } |
417 | | |
418 | 0 | if ((error = git_remote_dup(&remote, _remote)) < 0) |
419 | 0 | return error; |
420 | | |
421 | 0 | memcpy(&fetch_opts, opts, sizeof(git_fetch_options)); |
422 | 0 | fetch_opts.update_flags = ~GIT_REMOTE_UPDATE_FETCHHEAD; |
423 | |
|
424 | 0 | if (!opts->depth) |
425 | 0 | fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_ALL; |
426 | |
|
427 | 0 | if ((error = git_remote_connect_options__from_fetch_opts(&connect_opts, remote, &fetch_opts)) < 0) |
428 | 0 | goto cleanup; |
429 | | |
430 | 0 | git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); |
431 | | |
432 | | /* |
433 | | * Connect to the server so that we can identify the remote |
434 | | * object format. |
435 | | */ |
436 | |
|
437 | 0 | if ((error = git_remote_connect_ext(remote, GIT_DIRECTION_FETCH, |
438 | 0 | &connect_opts)) < 0) |
439 | 0 | goto cleanup; |
440 | | |
441 | 0 | if ((error = git_remote_oid_type(&oid_type, remote)) < 0 || |
442 | 0 | (error = git_repository__set_objectformat(repo, oid_type)) < 0) |
443 | 0 | goto cleanup; |
444 | | |
445 | 0 | if ((error = git_remote_fetch(remote, NULL, &fetch_opts, git_str_cstr(&reflog_message))) != 0) |
446 | 0 | goto cleanup; |
447 | | |
448 | 0 | error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); |
449 | |
|
450 | 0 | cleanup: |
451 | 0 | git_remote_free(remote); |
452 | 0 | git_remote_connect_options_dispose(&connect_opts); |
453 | 0 | git_str_dispose(&reflog_message); |
454 | |
|
455 | 0 | return error; |
456 | 0 | } |
457 | | |
458 | | int git_clone__should_clone_local(const char *url_or_path, git_clone_local_t local) |
459 | 0 | { |
460 | 0 | git_str fromurl = GIT_STR_INIT; |
461 | 0 | const char *path = url_or_path; |
462 | 0 | bool is_url, is_local; |
463 | |
|
464 | 0 | if (local == GIT_CLONE_NO_LOCAL) |
465 | 0 | return 0; |
466 | | |
467 | 0 | if ((is_url = git_fs_path_is_local_file_url(url_or_path)) != 0) { |
468 | 0 | if (git_fs_path_fromurl(&fromurl, url_or_path) < 0) { |
469 | 0 | is_local = -1; |
470 | 0 | goto done; |
471 | 0 | } |
472 | | |
473 | 0 | path = fromurl.ptr; |
474 | 0 | } |
475 | | |
476 | 0 | is_local = (!is_url || local != GIT_CLONE_LOCAL_AUTO) && |
477 | 0 | git_fs_path_isdir(path); |
478 | |
|
479 | 0 | done: |
480 | 0 | git_str_dispose(&fromurl); |
481 | 0 | return is_local; |
482 | 0 | } |
483 | | |
484 | | static int git__clone( |
485 | | git_repository **out, |
486 | | const char *url, |
487 | | const char *local_path, |
488 | | const git_clone_options *_options, |
489 | | int use_existing) |
490 | 0 | { |
491 | 0 | int error = 0; |
492 | 0 | git_repository *repo = NULL; |
493 | 0 | git_remote *origin; |
494 | 0 | git_clone_options options = GIT_CLONE_OPTIONS_INIT; |
495 | 0 | uint32_t rmdir_flags = GIT_RMDIR_REMOVE_FILES; |
496 | 0 | git_repository_create_cb repository_cb; |
497 | |
|
498 | 0 | GIT_ASSERT_ARG(out); |
499 | 0 | GIT_ASSERT_ARG(url); |
500 | 0 | GIT_ASSERT_ARG(local_path); |
501 | | |
502 | 0 | if (_options) |
503 | 0 | memcpy(&options, _options, sizeof(git_clone_options)); |
504 | |
|
505 | 0 | GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options"); |
506 | | |
507 | | /* Only clone to a new directory or an empty directory */ |
508 | 0 | if (git_fs_path_exists(local_path) && !use_existing && !git_fs_path_is_empty_dir(local_path)) { |
509 | 0 | git_error_set(GIT_ERROR_INVALID, |
510 | 0 | "'%s' exists and is not an empty directory", local_path); |
511 | 0 | return GIT_EEXISTS; |
512 | 0 | } |
513 | | |
514 | | /* Only remove the root directory on failure if we create it */ |
515 | 0 | if (git_fs_path_exists(local_path)) |
516 | 0 | rmdir_flags |= GIT_RMDIR_SKIP_ROOT; |
517 | |
|
518 | 0 | if (options.repository_cb) |
519 | 0 | repository_cb = options.repository_cb; |
520 | 0 | else |
521 | 0 | repository_cb = default_repository_create; |
522 | |
|
523 | 0 | if ((error = repository_cb(&repo, local_path, options.bare, options.repository_cb_payload)) < 0) |
524 | 0 | return error; |
525 | | |
526 | 0 | if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { |
527 | 0 | int clone_local = git_clone__should_clone_local(url, options.local); |
528 | 0 | int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; |
529 | |
|
530 | 0 | if (clone_local == 1) |
531 | 0 | error = clone_local_into( |
532 | 0 | repo, origin, &options.fetch_opts, &options.checkout_opts, |
533 | 0 | options.checkout_branch, link); |
534 | 0 | else if (clone_local == 0) |
535 | 0 | error = clone_into( |
536 | 0 | repo, origin, &options.fetch_opts, &options.checkout_opts, |
537 | 0 | options.checkout_branch); |
538 | 0 | else |
539 | 0 | error = -1; |
540 | |
|
541 | 0 | git_remote_free(origin); |
542 | 0 | } |
543 | |
|
544 | 0 | if (error != 0) { |
545 | 0 | git_error *last_error; |
546 | 0 | git_error_save(&last_error); |
547 | |
|
548 | 0 | git_repository_free(repo); |
549 | 0 | repo = NULL; |
550 | |
|
551 | 0 | (void)git_futils_rmdir_r(local_path, NULL, rmdir_flags); |
552 | |
|
553 | 0 | git_error_restore(last_error); |
554 | 0 | } |
555 | |
|
556 | 0 | *out = repo; |
557 | 0 | return error; |
558 | 0 | } |
559 | | |
560 | | int git_clone( |
561 | | git_repository **out, |
562 | | const char *url, |
563 | | const char *local_path, |
564 | | const git_clone_options *_options) |
565 | 0 | { |
566 | 0 | return git__clone(out, url, local_path, _options, 0); |
567 | 0 | } |
568 | | |
569 | | int git_clone__submodule( |
570 | | git_repository **out, |
571 | | const char *url, |
572 | | const char *local_path, |
573 | | const git_clone_options *_options) |
574 | 0 | { |
575 | 0 | return git__clone(out, url, local_path, _options, 1); |
576 | 0 | } |
577 | | |
578 | | int git_clone_options_init(git_clone_options *opts, unsigned int version) |
579 | 0 | { |
580 | 0 | GIT_INIT_STRUCTURE_FROM_TEMPLATE( |
581 | 0 | opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT); |
582 | 0 | return 0; |
583 | 0 | } |
584 | | |
585 | | #ifndef GIT_DEPRECATE_HARD |
586 | | int git_clone_init_options(git_clone_options *opts, unsigned int version) |
587 | 0 | { |
588 | 0 | return git_clone_options_init(opts, version); |
589 | 0 | } |
590 | | #endif |
591 | | |
592 | | static bool can_link(const char *src, const char *dst, int link) |
593 | 0 | { |
594 | | #ifdef GIT_WIN32 |
595 | | GIT_UNUSED(src); |
596 | | GIT_UNUSED(dst); |
597 | | GIT_UNUSED(link); |
598 | | return false; |
599 | | #else |
600 | |
|
601 | 0 | struct stat st_src, st_dst; |
602 | |
|
603 | 0 | if (!link) |
604 | 0 | return false; |
605 | | |
606 | 0 | if (p_stat(src, &st_src) < 0) |
607 | 0 | return false; |
608 | | |
609 | 0 | if (p_stat(dst, &st_dst) < 0) |
610 | 0 | return false; |
611 | | |
612 | 0 | return st_src.st_dev == st_dst.st_dev; |
613 | 0 | #endif |
614 | 0 | } |
615 | | |
616 | | static int clone_local_into(git_repository *repo, git_remote *remote, const git_fetch_options *fetch_opts, const git_checkout_options *co_opts, const char *branch, int link) |
617 | 0 | { |
618 | 0 | int error, flags; |
619 | 0 | git_repository *src; |
620 | 0 | git_str src_odb = GIT_STR_INIT, dst_odb = GIT_STR_INIT, src_path = GIT_STR_INIT; |
621 | 0 | git_str reflog_message = GIT_STR_INIT; |
622 | |
|
623 | 0 | GIT_ASSERT_ARG(repo); |
624 | 0 | GIT_ASSERT_ARG(remote); |
625 | | |
626 | 0 | if (!git_repository_is_empty(repo)) { |
627 | 0 | git_error_set(GIT_ERROR_INVALID, "the repository is not empty"); |
628 | 0 | return -1; |
629 | 0 | } |
630 | | |
631 | | /* |
632 | | * Let's figure out what path we should use for the source |
633 | | * repo, if it's not rooted, the path should be relative to |
634 | | * the repository's worktree/gitdir. |
635 | | */ |
636 | 0 | if ((error = git_fs_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) |
637 | 0 | return error; |
638 | | |
639 | | /* Copy .git/objects/ from the source to the target */ |
640 | 0 | if ((error = git_repository_open(&src, git_str_cstr(&src_path))) < 0) { |
641 | 0 | git_str_dispose(&src_path); |
642 | 0 | return error; |
643 | 0 | } |
644 | | |
645 | 0 | if (git_repository__item_path(&src_odb, src, GIT_REPOSITORY_ITEM_OBJECTS) < 0 || |
646 | 0 | git_repository__item_path(&dst_odb, repo, GIT_REPOSITORY_ITEM_OBJECTS) < 0) { |
647 | 0 | error = -1; |
648 | 0 | goto cleanup; |
649 | 0 | } |
650 | | |
651 | 0 | flags = 0; |
652 | 0 | if (can_link(git_repository_path(src), git_repository_path(repo), link)) |
653 | 0 | flags |= GIT_CPDIR_LINK_FILES; |
654 | |
|
655 | 0 | error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), |
656 | 0 | flags, GIT_OBJECT_DIR_MODE); |
657 | | |
658 | | /* |
659 | | * can_link() doesn't catch all variations, so if we hit an |
660 | | * error and did want to link, let's try again without trying |
661 | | * to link. |
662 | | */ |
663 | 0 | if (error < 0 && link) { |
664 | 0 | flags &= ~GIT_CPDIR_LINK_FILES; |
665 | 0 | error = git_futils_cp_r(git_str_cstr(&src_odb), git_str_cstr(&dst_odb), |
666 | 0 | flags, GIT_OBJECT_DIR_MODE); |
667 | 0 | } |
668 | |
|
669 | 0 | if (error < 0) |
670 | 0 | goto cleanup; |
671 | | |
672 | 0 | git_str_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); |
673 | |
|
674 | 0 | if ((error = git_remote_fetch(remote, NULL, fetch_opts, git_str_cstr(&reflog_message))) != 0) |
675 | 0 | goto cleanup; |
676 | | |
677 | 0 | error = checkout_branch(repo, remote, co_opts, branch, git_str_cstr(&reflog_message)); |
678 | |
|
679 | 0 | cleanup: |
680 | 0 | git_str_dispose(&reflog_message); |
681 | 0 | git_str_dispose(&src_path); |
682 | 0 | git_str_dispose(&src_odb); |
683 | 0 | git_str_dispose(&dst_odb); |
684 | 0 | git_repository_free(src); |
685 | 0 | return error; |
686 | 0 | } |