]>
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 BS |
9 | |
10 | #ifndef GIT_WIN32 | |
acdd3d95 | 11 | #include <dirent.h> |
830388a7 | 12 | #endif |
764df57e BS |
13 | |
14 | #include "git2/clone.h" | |
15 | #include "git2/remote.h" | |
bb1f6087 | 16 | #include "git2/revparse.h" |
4fbc899a BS |
17 | #include "git2/branch.h" |
18 | #include "git2/config.h" | |
14741d62 | 19 | #include "git2/checkout.h" |
2b63db4c BS |
20 | #include "git2/commit.h" |
21 | #include "git2/tree.h" | |
764df57e BS |
22 | |
23 | #include "common.h" | |
24 | #include "remote.h" | |
25 | #include "fileops.h" | |
4fbc899a | 26 | #include "refs.h" |
764df57e BS |
27 | |
28 | GIT_BEGIN_DECL | |
29 | ||
4fbc899a | 30 | struct HeadInfo { |
ea817863 BS |
31 | git_repository *repo; |
32 | git_oid remote_head_oid; | |
33 | git_buf branchname; | |
4fbc899a | 34 | }; |
bb1f6087 | 35 | |
2b63db4c | 36 | static int create_tracking_branch(git_repository *repo, const git_oid *target, const char *name) |
4fbc899a | 37 | { |
ea817863 BS |
38 | git_object *head_obj = NULL; |
39 | git_oid branch_oid; | |
40 | int retcode = GIT_ERROR; | |
41 | ||
42 | /* Find the target commit */ | |
43 | if (git_object_lookup(&head_obj, repo, target, GIT_OBJ_ANY) < 0) | |
44 | return GIT_ERROR; | |
45 | ||
46 | /* Create the new branch */ | |
47 | if (!git_branch_create(&branch_oid, repo, name, head_obj, 0)) { | |
48 | /* Set up tracking */ | |
49 | git_config *cfg; | |
50 | if (!git_repository_config(&cfg, repo)) { | |
51 | git_buf remote = GIT_BUF_INIT; | |
52 | git_buf merge = GIT_BUF_INIT; | |
53 | git_buf merge_target = GIT_BUF_INIT; | |
54 | if (!git_buf_printf(&remote, "branch.%s.remote", name) && | |
55 | !git_buf_printf(&merge, "branch.%s.merge", name) && | |
56 | !git_buf_printf(&merge_target, "refs/heads/%s", name) && | |
57 | !git_config_set_string(cfg, git_buf_cstr(&remote), "origin") && | |
58 | !git_config_set_string(cfg, git_buf_cstr(&merge), git_buf_cstr(&merge_target))) { | |
59 | retcode = 0; | |
60 | } | |
61 | git_buf_free(&remote); | |
62 | git_buf_free(&merge); | |
63 | git_buf_free(&merge_target); | |
64 | git_config_free(cfg); | |
65 | } | |
66 | } | |
67 | ||
68 | git_object_free(head_obj); | |
69 | return retcode; | |
4fbc899a BS |
70 | } |
71 | ||
72 | static int reference_matches_remote_head(const char *head_name, void *payload) | |
73 | { | |
ea817863 BS |
74 | struct HeadInfo *head_info = (struct HeadInfo *)payload; |
75 | git_oid oid; | |
76 | ||
77 | /* Stop looking if we've already found a match */ | |
78 | if (git_buf_len(&head_info->branchname) > 0) return 0; | |
79 | ||
80 | if (!git_reference_name_to_oid(&oid, head_info->repo, head_name) && | |
81 | !git_oid_cmp(&head_info->remote_head_oid, &oid)) { | |
82 | git_buf_puts(&head_info->branchname, | |
83 | head_name+strlen("refs/remotes/origin/")); | |
84 | } | |
85 | return 0; | |
8340dd5d BS |
86 | } |
87 | ||
2b63db4c | 88 | static int update_head_to_new_branch(git_repository *repo, const git_oid *target, const char *name) |
af58ec9e | 89 | { |
ea817863 BS |
90 | int retcode = GIT_ERROR; |
91 | ||
92 | if (!create_tracking_branch(repo, target, name)) { | |
93 | git_reference *head; | |
94 | if (!git_reference_lookup(&head, repo, GIT_HEAD_FILE)) { | |
95 | git_buf targetbuf = GIT_BUF_INIT; | |
96 | if (!git_buf_printf(&targetbuf, "refs/heads/%s", name) && | |
97 | !git_reference_set_target(head, git_buf_cstr(&targetbuf))) { | |
98 | /* Read the tree into the index */ | |
99 | git_commit *commit; | |
100 | if (!git_commit_lookup(&commit, repo, target)) { | |
101 | git_tree *tree; | |
102 | if (!git_commit_tree(&tree, commit)) { | |
103 | git_index *index; | |
104 | if (!git_repository_index(&index, repo)) { | |
105 | if (!git_index_read_tree(index, tree)) { | |
106 | git_index_write(index); | |
107 | retcode = 0; | |
108 | } | |
109 | git_index_free(index); | |
110 | } | |
111 | git_tree_free(tree); | |
112 | } | |
113 | git_commit_free(commit); | |
114 | } | |
115 | } | |
116 | git_buf_free(&targetbuf); | |
117 | git_reference_free(head); | |
118 | } | |
119 | } | |
120 | ||
121 | return retcode; | |
af58ec9e BS |
122 | } |
123 | ||
8340dd5d BS |
124 | static int update_head_to_remote(git_repository *repo, git_remote *remote) |
125 | { | |
ea817863 BS |
126 | int retcode = GIT_ERROR; |
127 | git_remote_head *remote_head; | |
128 | git_oid oid; | |
129 | struct HeadInfo head_info; | |
130 | ||
131 | /* Get the remote's HEAD. This is always the first ref in remote->refs. */ | |
132 | remote_head = remote->refs.contents[0]; | |
133 | git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); | |
134 | git_buf_init(&head_info.branchname, 16); | |
135 | head_info.repo = repo; | |
136 | ||
137 | /* Check to see if "master" matches the remote head */ | |
138 | if (!git_reference_name_to_oid(&oid, repo, "refs/remotes/origin/master") && | |
139 | !git_oid_cmp(&remote_head->oid, &oid)) { | |
140 | retcode = update_head_to_new_branch(repo, &oid, "master"); | |
141 | } | |
142 | /* Not master. Check all the other refs. */ | |
143 | else if (!git_reference_foreach(repo, GIT_REF_LISTALL, | |
144 | reference_matches_remote_head, | |
145 | &head_info) && | |
146 | git_buf_len(&head_info.branchname) > 0) { | |
147 | retcode = update_head_to_new_branch(repo, &head_info.remote_head_oid, | |
148 | git_buf_cstr(&head_info.branchname)); | |
149 | } | |
150 | ||
151 | git_buf_free(&head_info.branchname); | |
152 | return retcode; | |
bb1f6087 BS |
153 | } |
154 | ||
764df57e BS |
155 | /* |
156 | * submodules? | |
764df57e BS |
157 | */ |
158 | ||
bb1f6087 BS |
159 | |
160 | ||
f2a855d5 | 161 | static int setup_remotes_and_fetch(git_repository *repo, |
ea817863 BS |
162 | const char *origin_url, |
163 | git_indexer_stats *stats) | |
764df57e | 164 | { |
ea817863 BS |
165 | int retcode = GIT_ERROR; |
166 | git_remote *origin = NULL; | |
167 | git_off_t bytes = 0; | |
168 | git_indexer_stats dummy_stats; | |
169 | ||
170 | if (!stats) stats = &dummy_stats; | |
171 | ||
172 | /* Create the "origin" remote */ | |
173 | if (!git_remote_add(&origin, repo, "origin", origin_url)) { | |
174 | /* Connect and download everything */ | |
175 | if (!git_remote_connect(origin, GIT_DIR_FETCH)) { | |
176 | if (!git_remote_download(origin, &bytes, stats)) { | |
177 | /* Create "origin/foo" branches for all remote branches */ | |
178 | if (!git_remote_update_tips(origin, NULL)) { | |
179 | /* Point HEAD to the same ref as the remote's head */ | |
180 | if (!update_head_to_remote(repo, origin)) { | |
181 | retcode = 0; | |
182 | } | |
183 | } | |
184 | } | |
185 | git_remote_disconnect(origin); | |
186 | } | |
187 | git_remote_free(origin); | |
188 | } | |
189 | ||
190 | return retcode; | |
764df57e BS |
191 | } |
192 | ||
acdd3d95 BS |
193 | |
194 | static bool is_dot_or_dotdot(const char *name) | |
195 | { | |
ea817863 BS |
196 | return (name[0] == '.' && |
197 | (name[1] == '\0' || | |
198 | (name[1] == '.' && name[2] == '\0'))); | |
acdd3d95 BS |
199 | } |
200 | ||
201 | /* TODO: p_opendir, p_closedir */ | |
202 | static bool path_is_okay(const char *path) | |
203 | { | |
830388a7 | 204 | #ifdef GIT_WIN32 |
ea817863 BS |
205 | HANDLE hFind = INVALID_HANDLE_VALUE; |
206 | wchar_t *wbuf; | |
207 | WIN32_FIND_DATAW ffd; | |
830388a7 | 208 | #else |
ea817863 BS |
209 | DIR *dir = NULL; |
210 | struct dirent *e; | |
830388a7 BS |
211 | #endif |
212 | ||
ea817863 | 213 | bool retval = true; |
acdd3d95 | 214 | |
ea817863 BS |
215 | /* The path must either not exist, or be an empty directory */ |
216 | if (!git_path_exists(path)) return true; | |
acdd3d95 | 217 | |
ea817863 BS |
218 | if (!git_path_isdir(path)) { |
219 | giterr_set(GITERR_INVALID, | |
220 | "'%s' exists and is not an empty directory", path); | |
221 | return false; | |
222 | } | |
acdd3d95 | 223 | |
830388a7 | 224 | #ifdef GIT_WIN32 |
ea817863 BS |
225 | wbuf = gitwin_to_utf16(path); |
226 | gitwin_append_utf16(wbuf, "\\*", 2); | |
227 | hFind = FindFirstFileW(wbuf, &ffd); | |
228 | if (INVALID_HANDLE_VALUE != hFind) { | |
229 | retval = false; | |
230 | FindClose(hFind); | |
231 | } | |
232 | git__free(wbuf); | |
830388a7 | 233 | #else |
ea817863 BS |
234 | dir = opendir(path); |
235 | if (!dir) { | |
236 | giterr_set(GITERR_OS, "Couldn't open '%s'", path); | |
237 | return false; | |
238 | } | |
239 | ||
240 | while ((e = readdir(dir)) != NULL) { | |
241 | if (!is_dot_or_dotdot(e->d_name)) { | |
242 | giterr_set(GITERR_INVALID, | |
243 | "'%s' exists and is not an empty directory", path); | |
244 | retval = false; | |
245 | break; | |
246 | } | |
247 | } | |
248 | closedir(dir); | |
830388a7 BS |
249 | #endif |
250 | ||
ea817863 | 251 | return retval; |
acdd3d95 BS |
252 | } |
253 | ||
254 | ||
f2a855d5 | 255 | static int clone_internal(git_repository **out, |
ea817863 BS |
256 | const char *origin_url, |
257 | const char *path, | |
258 | git_indexer_stats *stats, | |
259 | int is_bare) | |
764df57e | 260 | { |
ea817863 BS |
261 | int retcode = GIT_ERROR; |
262 | git_repository *repo = NULL; | |
263 | ||
264 | if (!path_is_okay(path)) { | |
265 | return GIT_ERROR; | |
266 | } | |
267 | ||
268 | if (!(retcode = git_repository_init(&repo, path, is_bare))) { | |
269 | if ((retcode = setup_remotes_and_fetch(repo, origin_url, stats)) < 0) { | |
270 | /* Failed to fetch; clean up */ | |
271 | git_repository_free(repo); | |
272 | git_futils_rmdir_r(path, GIT_DIRREMOVAL_FILES_AND_DIRS); | |
273 | } else { | |
274 | *out = repo; | |
275 | retcode = 0; | |
276 | } | |
277 | } | |
278 | ||
279 | return retcode; | |
764df57e BS |
280 | } |
281 | ||
f2a855d5 | 282 | int git_clone_bare(git_repository **out, |
ea817863 BS |
283 | const char *origin_url, |
284 | const char *dest_path, | |
285 | git_indexer_stats *stats) | |
bb1f6087 | 286 | { |
ea817863 BS |
287 | assert(out && origin_url && dest_path); |
288 | return clone_internal(out, origin_url, dest_path, stats, 1); | |
bb1f6087 | 289 | } |
764df57e | 290 | |
bb1f6087 | 291 | |
f2a855d5 | 292 | int git_clone(git_repository **out, |
ea817863 BS |
293 | const char *origin_url, |
294 | const char *workdir_path, | |
295 | git_indexer_stats *stats) | |
764df57e | 296 | { |
ea817863 | 297 | int retcode = GIT_ERROR; |
8340dd5d | 298 | |
ea817863 | 299 | assert(out && origin_url && workdir_path); |
14741d62 | 300 | |
ea817863 BS |
301 | if (!(retcode = clone_internal(out, origin_url, workdir_path, stats, 0))) { |
302 | git_indexer_stats checkout_stats; | |
303 | retcode = git_checkout_force(*out, &checkout_stats); | |
304 | } | |
8340dd5d | 305 | |
ea817863 | 306 | return retcode; |
764df57e BS |
307 | } |
308 | ||
309 | ||
310 | ||
bb1f6087 | 311 | |
764df57e | 312 | GIT_END_DECL |