]>
Commit | Line | Data |
---|---|---|
764df57e BS |
1 | /* |
2 | * Copyright (C) 2009-2012 the libgit2 contributors | |
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 <assert.h> | |
830388a7 | 9 | |
764df57e BS |
10 | #include "git2/clone.h" |
11 | #include "git2/remote.h" | |
bb1f6087 | 12 | #include "git2/revparse.h" |
4fbc899a BS |
13 | #include "git2/branch.h" |
14 | #include "git2/config.h" | |
14741d62 | 15 | #include "git2/checkout.h" |
2b63db4c BS |
16 | #include "git2/commit.h" |
17 | #include "git2/tree.h" | |
764df57e BS |
18 | |
19 | #include "common.h" | |
20 | #include "remote.h" | |
c4f68b32 | 21 | #include "pkt.h" |
764df57e | 22 | #include "fileops.h" |
4fbc899a | 23 | #include "refs.h" |
c3b5099f | 24 | #include "path.h" |
764df57e | 25 | |
bf0e62a2 | 26 | static int create_branch( |
7eca3c56 | 27 | git_reference **branch, |
28 | git_repository *repo, | |
29 | const git_oid *target, | |
30 | const char *name) | |
4fbc899a | 31 | { |
ea817863 | 32 | git_object *head_obj = NULL; |
8a155a04 | 33 | git_reference *branch_ref; |
bf0e62a2 | 34 | int error; |
ea817863 BS |
35 | |
36 | /* Find the target commit */ | |
bf0e62a2 | 37 | if ((error = git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY)) < 0) |
38 | return error; | |
ea817863 BS |
39 | |
40 | /* Create the new branch */ | |
bf0e62a2 | 41 | error = git_branch_create(&branch_ref, repo, name, head_obj, 0); |
ea817863 BS |
42 | |
43 | git_object_free(head_obj); | |
7eca3c56 | 44 | |
bf0e62a2 | 45 | if (!error) |
7eca3c56 | 46 | *branch = branch_ref; |
47 | else | |
48 | git_reference_free(branch_ref); | |
49 | ||
bf0e62a2 | 50 | return error; |
51 | } | |
52 | ||
53 | static int setup_tracking_config( | |
54 | git_repository *repo, | |
55 | const char *branch_name, | |
56 | const char *remote_name, | |
57 | const char *merge_target) | |
58 | { | |
59 | git_config *cfg; | |
60 | git_buf remote_key = GIT_BUF_INIT, merge_key = GIT_BUF_INIT; | |
61 | int error = -1; | |
62 | ||
63 | if (git_repository_config__weakptr(&cfg, repo) < 0) | |
64 | return -1; | |
65 | ||
66 | if (git_buf_printf(&remote_key, "branch.%s.remote", branch_name) < 0) | |
67 | goto cleanup; | |
68 | ||
69 | if (git_buf_printf(&merge_key, "branch.%s.merge", branch_name) < 0) | |
70 | goto cleanup; | |
71 | ||
72 | if (git_config_set_string(cfg, git_buf_cstr(&remote_key), remote_name) < 0) | |
73 | goto cleanup; | |
74 | ||
75 | if (git_config_set_string(cfg, git_buf_cstr(&merge_key), merge_target) < 0) | |
76 | goto cleanup; | |
77 | ||
78 | error = 0; | |
79 | ||
80 | cleanup: | |
81 | git_buf_free(&remote_key); | |
82 | git_buf_free(&merge_key); | |
83 | return error; | |
84 | } | |
85 | ||
86 | static int create_tracking_branch( | |
87 | git_reference **branch, | |
88 | git_repository *repo, | |
89 | const git_oid *target, | |
90 | const char *branch_name) | |
91 | { | |
92 | int error; | |
93 | ||
94 | if ((error = create_branch(branch, repo, target, branch_name)) < 0) | |
95 | return error; | |
96 | ||
97 | return setup_tracking_config( | |
98 | repo, | |
99 | branch_name, | |
100 | GIT_REMOTE_ORIGIN, | |
101 | git_reference_name(*branch)); | |
4fbc899a BS |
102 | } |
103 | ||
70edc1b0 | 104 | struct head_info { |
105 | git_repository *repo; | |
106 | git_oid remote_head_oid; | |
107 | git_buf branchname; | |
d280c71b | 108 | const git_refspec *refspec; |
70edc1b0 | 109 | }; |
110 | ||
d280c71b | 111 | static int reference_matches_remote_head( |
112 | const char *reference_name, | |
113 | void *payload) | |
4fbc899a | 114 | { |
70edc1b0 | 115 | struct head_info *head_info = (struct head_info *)payload; |
ea817863 BS |
116 | git_oid oid; |
117 | ||
d280c71b | 118 | /* TODO: Should we guard against references |
119 | * which name doesn't start with refs/heads/ ? | |
120 | */ | |
121 | ||
ea817863 | 122 | /* Stop looking if we've already found a match */ |
d280c71b | 123 | if (git_buf_len(&head_info->branchname) > 0) |
124 | return 0; | |
125 | ||
126 | if (git_reference_name_to_oid( | |
127 | &oid, | |
128 | head_info->repo, | |
129 | reference_name) < 0) { | |
130 | /* TODO: How to handle not found references? | |
131 | */ | |
132 | return -1; | |
133 | } | |
ea817863 | 134 | |
d280c71b | 135 | if (git_oid_cmp(&head_info->remote_head_oid, &oid) == 0) { |
136 | /* Determine the local reference name from the remote tracking one */ | |
137 | if (git_refspec_transform_l( | |
138 | &head_info->branchname, | |
139 | head_info->refspec, | |
140 | reference_name) < 0) | |
141 | return -1; | |
142 | ||
143 | if (git_buf_sets( | |
144 | &head_info->branchname, | |
145 | git_buf_cstr(&head_info->branchname) + strlen(GIT_REFS_HEADS_DIR)) < 0) | |
146 | return -1; | |
ea817863 | 147 | } |
d280c71b | 148 | |
ea817863 | 149 | return 0; |
8340dd5d BS |
150 | } |
151 | ||
bf0e62a2 | 152 | static int update_head_to_new_branch( |
153 | git_repository *repo, | |
154 | const git_oid *target, | |
155 | const char *name) | |
af58ec9e | 156 | { |
aa4437f6 | 157 | git_reference *tracking_branch = NULL; |
7eca3c56 | 158 | int error; |
ea817863 | 159 | |
bf0e62a2 | 160 | if ((error = create_tracking_branch( |
161 | &tracking_branch, | |
162 | repo, | |
163 | target, | |
164 | name)) < 0) | |
165 | return error; | |
ea817863 | 166 | |
7eca3c56 | 167 | error = git_repository_set_head(repo, git_reference_name(tracking_branch)); |
168 | ||
169 | git_reference_free(tracking_branch); | |
170 | ||
171 | return error; | |
af58ec9e BS |
172 | } |
173 | ||
8340dd5d BS |
174 | static int update_head_to_remote(git_repository *repo, git_remote *remote) |
175 | { | |
d280c71b | 176 | int retcode = -1; |
ea817863 | 177 | git_remote_head *remote_head; |
c4f68b32 | 178 | git_pkt_ref *pkt; |
70edc1b0 | 179 | struct head_info head_info; |
d280c71b | 180 | git_buf remote_master_name = GIT_BUF_INIT; |
ea817863 | 181 | |
bf0e62a2 | 182 | /* Did we just clone an empty repository? */ |
183 | if (remote->refs.length == 0) { | |
184 | return setup_tracking_config( | |
185 | repo, | |
186 | "master", | |
187 | GIT_REMOTE_ORIGIN, | |
188 | GIT_REFS_HEADS_MASTER_FILE); | |
189 | } | |
190 | ||
ea817863 | 191 | /* Get the remote's HEAD. This is always the first ref in remote->refs. */ |
c4f68b32 | 192 | pkt = remote->transport->refs.contents[0]; |
193 | remote_head = &pkt->head; | |
ea817863 BS |
194 | git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); |
195 | git_buf_init(&head_info.branchname, 16); | |
196 | head_info.repo = repo; | |
d280c71b | 197 | head_info.refspec = git_remote_fetchspec(remote); |
198 | ||
199 | /* Determine the remote tracking reference name from the local master */ | |
200 | if (git_refspec_transform_r( | |
201 | &remote_master_name, | |
202 | head_info.refspec, | |
203 | GIT_REFS_HEADS_MASTER_FILE) < 0) | |
204 | return -1; | |
205 | ||
206 | /* Check to see if the remote HEAD points to the remote master */ | |
207 | if (reference_matches_remote_head(git_buf_cstr(&remote_master_name), &head_info) < 0) | |
208 | goto cleanup; | |
209 | ||
210 | if (git_buf_len(&head_info.branchname) > 0) { | |
211 | retcode = update_head_to_new_branch( | |
212 | repo, | |
213 | &head_info.remote_head_oid, | |
214 | git_buf_cstr(&head_info.branchname)); | |
215 | ||
216 | goto cleanup; | |
ea817863 | 217 | } |
d280c71b | 218 | |
ea817863 | 219 | /* Not master. Check all the other refs. */ |
d280c71b | 220 | if (git_reference_foreach( |
221 | repo, | |
222 | GIT_REF_LISTALL, | |
223 | reference_matches_remote_head, | |
224 | &head_info) < 0) | |
225 | goto cleanup; | |
226 | ||
227 | if (git_buf_len(&head_info.branchname) > 0) { | |
228 | retcode = update_head_to_new_branch( | |
229 | repo, | |
230 | &head_info.remote_head_oid, | |
231 | git_buf_cstr(&head_info.branchname)); | |
232 | ||
233 | goto cleanup; | |
234 | } else { | |
235 | /* TODO: What should we do if nothing has been found? | |
236 | */ | |
ea817863 BS |
237 | } |
238 | ||
d280c71b | 239 | cleanup: |
240 | git_buf_free(&remote_master_name); | |
ea817863 BS |
241 | git_buf_free(&head_info.branchname); |
242 | return retcode; | |
bb1f6087 BS |
243 | } |
244 | ||
764df57e BS |
245 | /* |
246 | * submodules? | |
764df57e BS |
247 | */ |
248 | ||
bb1f6087 BS |
249 | |
250 | ||
aa1e8674 BS |
251 | static int setup_remotes_and_fetch( |
252 | git_repository *repo, | |
253 | const char *origin_url, | |
7d222e13 | 254 | git_transfer_progress_callback progress_cb, |
aa1e8674 | 255 | void *progress_payload) |
764df57e | 256 | { |
ea817863 BS |
257 | int retcode = GIT_ERROR; |
258 | git_remote *origin = NULL; | |
ea817863 BS |
259 | |
260 | /* Create the "origin" remote */ | |
096d9e94 | 261 | if (!git_remote_add(&origin, repo, GIT_REMOTE_ORIGIN, origin_url)) { |
ea817863 BS |
262 | /* Connect and download everything */ |
263 | if (!git_remote_connect(origin, GIT_DIR_FETCH)) { | |
1e3b8ed5 | 264 | if (!git_remote_download(origin, progress_cb, progress_payload)) { |
ea817863 | 265 | /* Create "origin/foo" branches for all remote branches */ |
7affe23d | 266 | if (!git_remote_update_tips(origin)) { |
ea817863 BS |
267 | /* Point HEAD to the same ref as the remote's head */ |
268 | if (!update_head_to_remote(repo, origin)) { | |
269 | retcode = 0; | |
270 | } | |
271 | } | |
272 | } | |
273 | git_remote_disconnect(origin); | |
274 | } | |
275 | git_remote_free(origin); | |
276 | } | |
277 | ||
278 | return retcode; | |
764df57e BS |
279 | } |
280 | ||
acdd3d95 | 281 | |
acdd3d95 BS |
282 | static bool path_is_okay(const char *path) |
283 | { | |
ea817863 BS |
284 | /* The path must either not exist, or be an empty directory */ |
285 | if (!git_path_exists(path)) return true; | |
d024419f | 286 | if (!git_path_is_empty_dir(path)) { |
ea817863 BS |
287 | giterr_set(GITERR_INVALID, |
288 | "'%s' exists and is not an empty directory", path); | |
289 | return false; | |
290 | } | |
d024419f | 291 | return true; |
acdd3d95 BS |
292 | } |
293 | ||
4d968f13 | 294 | static bool should_checkout( |
295 | git_repository *repo, | |
296 | bool is_bare, | |
297 | git_checkout_opts *opts) | |
298 | { | |
299 | if (is_bare) | |
300 | return false; | |
301 | ||
302 | if (!opts) | |
303 | return false; | |
304 | ||
305 | return !git_repository_head_orphan(repo); | |
306 | } | |
acdd3d95 | 307 | |
bf0e62a2 | 308 | static int clone_internal( |
309 | git_repository **out, | |
310 | const char *origin_url, | |
311 | const char *path, | |
7d222e13 | 312 | git_transfer_progress_callback fetch_progress_cb, |
aa1e8674 | 313 | void *fetch_progress_payload, |
bf0e62a2 | 314 | git_checkout_opts *checkout_opts, |
4d968f13 | 315 | bool is_bare) |
764df57e | 316 | { |
ea817863 BS |
317 | int retcode = GIT_ERROR; |
318 | git_repository *repo = NULL; | |
319 | ||
320 | if (!path_is_okay(path)) { | |
321 | return GIT_ERROR; | |
322 | } | |
323 | ||
324 | if (!(retcode = git_repository_init(&repo, path, is_bare))) { | |
aa1e8674 BS |
325 | if ((retcode = setup_remotes_and_fetch(repo, origin_url, |
326 | fetch_progress_cb, fetch_progress_payload)) < 0) { | |
ea817863 BS |
327 | /* Failed to fetch; clean up */ |
328 | git_repository_free(repo); | |
0d64bef9 | 329 | git_futils_rmdir_r(path, NULL, GIT_DIRREMOVAL_FILES_AND_DIRS); |
ea817863 BS |
330 | } else { |
331 | *out = repo; | |
332 | retcode = 0; | |
333 | } | |
334 | } | |
335 | ||
4d968f13 | 336 | if (!retcode && should_checkout(repo, is_bare, checkout_opts)) |
80642656 | 337 | retcode = git_checkout_head(*out, checkout_opts); |
bf0e62a2 | 338 | |
ea817863 | 339 | return retcode; |
764df57e BS |
340 | } |
341 | ||
9c3a98f1 BS |
342 | int git_clone_bare( |
343 | git_repository **out, | |
344 | const char *origin_url, | |
345 | const char *dest_path, | |
7d222e13 | 346 | git_transfer_progress_callback fetch_progress_cb, |
aa1e8674 | 347 | void *fetch_progress_payload) |
bb1f6087 | 348 | { |
ea817863 | 349 | assert(out && origin_url && dest_path); |
bf0e62a2 | 350 | |
351 | return clone_internal( | |
352 | out, | |
353 | origin_url, | |
354 | dest_path, | |
aa1e8674 BS |
355 | fetch_progress_cb, |
356 | fetch_progress_payload, | |
bf0e62a2 | 357 | NULL, |
bf0e62a2 | 358 | 1); |
bb1f6087 | 359 | } |
764df57e | 360 | |
bb1f6087 | 361 | |
9c3a98f1 BS |
362 | int git_clone( |
363 | git_repository **out, | |
364 | const char *origin_url, | |
365 | const char *workdir_path, | |
7d222e13 | 366 | git_transfer_progress_callback fetch_progress_cb, |
aa1e8674 | 367 | void *fetch_progress_payload, |
9c3a98f1 | 368 | git_checkout_opts *checkout_opts) |
764df57e | 369 | { |
ea817863 | 370 | assert(out && origin_url && workdir_path); |
14741d62 | 371 | |
bf0e62a2 | 372 | return clone_internal( |
373 | out, | |
374 | origin_url, | |
375 | workdir_path, | |
aa1e8674 BS |
376 | fetch_progress_cb, |
377 | fetch_progress_payload, | |
bf0e62a2 | 378 | checkout_opts, |
379 | 0); | |
764df57e | 380 | } |