@matheusbsbs / matheus-bagliano / commits / 8434c2f1af
Fork 0

Build in clone

Thanks to Johannes Schindelin for various comments and improvements, including supporting cloning full bundles. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Daniel Barkalow committed Apr 27, 2008 at 13:39 UTC 8434c2f1afedb936e0ea8c07ce25733013c2f743
5 files changed +547 -1
Makefile
+1 -1
@@ -235,7 +235,6 @@ BASIC_LDFLAGS =
235
236 SCRIPT_SH += git-am.sh
237 SCRIPT_SH += git-bisect.sh
238 -SCRIPT_SH += git-clone.sh
238 SCRIPT_SH += git-filter-branch.sh
239 SCRIPT_SH += git-lost-found.sh
240 SCRIPT_SH += git-merge-octopus.sh
@@ -481,6 +480,7 @@ BUILTIN_OBJS += builtin-check-ref-format.o
480 BUILTIN_OBJS += builtin-checkout-index.o
481 BUILTIN_OBJS += builtin-checkout.o
482 BUILTIN_OBJS += builtin-clean.o
483 +BUILTIN_OBJS += builtin-clone.o
484 BUILTIN_OBJS += builtin-commit-tree.o
485 BUILTIN_OBJS += builtin-commit.o
486 BUILTIN_OBJS += builtin-config.o
builtin-clone.c new
+544
@@ -0,0 +1,544 @@
1 +/*
2 + * Builtin "git clone"
3 + *
4 + * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
5 + * 2008 Daniel Barkalow <barkalow@iabervon.org>
6 + * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
7 + *
8 + * Clone a repository into a different directory that does not yet exist.
9 + */
10 +
11 +#include "cache.h"
12 +#include "parse-options.h"
13 +#include "fetch-pack.h"
14 +#include "refs.h"
15 +#include "tree.h"
16 +#include "tree-walk.h"
17 +#include "unpack-trees.h"
18 +#include "transport.h"
19 +#include "strbuf.h"
20 +#include "dir.h"
21 +
22 +/*
23 + * Overall FIXMEs:
24 + * - respect DB_ENVIRONMENT for .git/objects.
25 + *
26 + * Implementation notes:
27 + * - dropping use-separate-remote and no-separate-remote compatibility
28 + *
29 + */
30 +static const char * const builtin_clone_usage[] = {
31 + "git-clone [options] [--] <repo> [<dir>]",
32 + NULL
33 +};
34 +
35 +static int option_quiet, option_no_checkout, option_bare;
36 +static int option_local, option_no_hardlinks, option_shared;
37 +static char *option_template, *option_reference, *option_depth;
38 +static char *option_origin = NULL;
39 +static char *option_upload_pack = "git-upload-pack";
40 +
41 +static struct option builtin_clone_options[] = {
42 + OPT__QUIET(&option_quiet),
43 + OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
44 + "don't create a checkout"),
45 + OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
46 + OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
47 + OPT_BOOLEAN('l', "local", &option_local,
48 + "to clone from a local repository"),
49 + OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
50 + "don't use local hardlinks, always copy"),
51 + OPT_BOOLEAN('s', "shared", &option_shared,
52 + "setup as shared repository"),
53 + OPT_STRING(0, "template", &option_template, "path",
54 + "path the template repository"),
55 + OPT_STRING(0, "reference", &option_reference, "repo",
56 + "reference repository"),
57 + OPT_STRING('o', "origin", &option_origin, "branch",
58 + "use <branch> instead or 'origin' to track upstream"),
59 + OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
60 + "path to git-upload-pack on the remote"),
61 + OPT_STRING(0, "depth", &option_depth, "depth",
62 + "create a shallow clone of that depth"),
63 +
64 + OPT_END()
65 +};
66 +
67 +static char *get_repo_path(const char *repo, int *is_bundle)
68 +{
69 + static char *suffix[] = { "/.git", ".git", "" };
70 + static char *bundle_suffix[] = { ".bundle", "" };
71 + struct stat st;
72 + int i;
73 +
74 + for (i = 0; i < ARRAY_SIZE(suffix); i++) {
75 + const char *path;
76 + path = mkpath("%s%s", repo, suffix[i]);
77 + if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
78 + *is_bundle = 0;
79 + return xstrdup(make_absolute_path(path));
80 + }
81 + }
82 +
83 + for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
84 + const char *path;
85 + path = mkpath("%s%s", repo, bundle_suffix[i]);
86 + if (!stat(path, &st) && S_ISREG(st.st_mode)) {
87 + *is_bundle = 1;
88 + return xstrdup(make_absolute_path(path));
89 + }
90 + }
91 +
92 + return NULL;
93 +}
94 +
95 +static char *guess_dir_name(const char *repo, int is_bundle)
96 +{
97 + const char *p, *start, *end, *limit;
98 + int after_slash_or_colon;
99 +
100 + /* Guess dir name from repository: strip trailing '/',
101 + * strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */
102 +
103 + after_slash_or_colon = 1;
104 + limit = repo + strlen(repo);
105 + start = repo;
106 + end = limit;
107 + for (p = repo; p < limit; p++) {
108 + const char *prefix = is_bundle ? ".bundle" : ".git";
109 + if (!prefixcmp(p, prefix)) {
110 + if (!after_slash_or_colon)
111 + end = p;
112 + p += strlen(prefix) - 1;
113 + } else if (!prefixcmp(p, ".bundle")) {
114 + if (!after_slash_or_colon)
115 + end = p;
116 + p += 7;
117 + } else if (*p == '/' || *p == ':') {
118 + if (end == limit)
119 + end = p;
120 + after_slash_or_colon = 1;
121 + } else if (after_slash_or_colon) {
122 + start = p;
123 + end = limit;
124 + after_slash_or_colon = 0;
125 + }
126 + }
127 +
128 + return xstrndup(start, end - start);
129 +}
130 +
131 +static int is_directory(const char *path)
132 +{
133 + struct stat buf;
134 +
135 + return !stat(path, &buf) && S_ISDIR(buf.st_mode);
136 +}
137 +
138 +static void setup_reference(const char *repo)
139 +{
140 + const char *ref_git;
141 + char *ref_git_copy;
142 +
143 + struct remote *remote;
144 + struct transport *transport;
145 + const struct ref *extra;
146 +
147 + ref_git = make_absolute_path(option_reference);
148 +
149 + if (is_directory(mkpath("%s/.git/objects", ref_git)))
150 + ref_git = mkpath("%s/.git", ref_git);
151 + else if (!is_directory(mkpath("%s/objects", ref_git)))
152 + die("reference repository '%s' is not a local directory.",
153 + option_reference);
154 +
155 + ref_git_copy = xstrdup(ref_git);
156 +
157 + add_to_alternates_file(ref_git_copy);
158 +
159 + remote = remote_get(ref_git_copy);
160 + transport = transport_get(remote, ref_git_copy);
161 + for (extra = transport_get_remote_refs(transport); extra;
162 + extra = extra->next)
163 + add_extra_ref(extra->name, extra->old_sha1, 0);
164 +
165 + transport_disconnect(transport);
166 +
167 + free(ref_git_copy);
168 +}
169 +
170 +static void copy_or_link_directory(char *src, char *dest)
171 +{
172 + struct dirent *de;
173 + struct stat buf;
174 + int src_len, dest_len;
175 + DIR *dir;
176 +
177 + dir = opendir(src);
178 + if (!dir)
179 + die("failed to open %s\n", src);
180 +
181 + if (mkdir(dest, 0777)) {
182 + if (errno != EEXIST)
183 + die("failed to create directory %s\n", dest);
184 + else if (stat(dest, &buf))
185 + die("failed to stat %s\n", dest);
186 + else if (!S_ISDIR(buf.st_mode))
187 + die("%s exists and is not a directory\n", dest);
188 + }
189 +
190 + src_len = strlen(src);
191 + src[src_len] = '/';
192 + dest_len = strlen(dest);
193 + dest[dest_len] = '/';
194 +
195 + while ((de = readdir(dir)) != NULL) {
196 + strcpy(src + src_len + 1, de->d_name);
197 + strcpy(dest + dest_len + 1, de->d_name);
198 + if (stat(src, &buf)) {
199 + warning ("failed to stat %s\n", src);
200 + continue;
201 + }
202 + if (S_ISDIR(buf.st_mode)) {
203 + if (de->d_name[0] != '.')
204 + copy_or_link_directory(src, dest);
205 + continue;
206 + }
207 +
208 + if (unlink(dest) && errno != ENOENT)
209 + die("failed to unlink %s\n", dest);
210 + if (option_no_hardlinks) {
211 + if (copy_file(dest, src, 0666))
212 + die("failed to copy file to %s\n", dest);
213 + } else {
214 + if (link(src, dest))
215 + die("failed to create link %s\n", dest);
216 + }
217 + }
218 +}
219 +
220 +static const struct ref *clone_local(const char *src_repo,
221 + const char *dest_repo)
222 +{
223 + const struct ref *ret;
224 + char src[PATH_MAX];
225 + char dest[PATH_MAX];
226 + struct remote *remote;
227 + struct transport *transport;
228 +
229 + if (option_shared)
230 + add_to_alternates_file(src_repo);
231 + else {
232 + snprintf(src, PATH_MAX, "%s/objects", src_repo);
233 + snprintf(dest, PATH_MAX, "%s/objects", dest_repo);
234 + copy_or_link_directory(src, dest);
235 + }
236 +
237 + remote = remote_get(src_repo);
238 + transport = transport_get(remote, src_repo);
239 + ret = transport_get_remote_refs(transport);
240 + transport_disconnect(transport);
241 + return ret;
242 +}
243 +
244 +static const char *junk_work_tree;
245 +static const char *junk_git_dir;
246 +pid_t junk_pid;
247 +
248 +static void remove_junk(void)
249 +{
250 + struct strbuf sb;
251 + if (getpid() != junk_pid)
252 + return;
253 + strbuf_init(&sb, 0);
254 + if (junk_git_dir) {
255 + strbuf_addstr(&sb, junk_git_dir);
256 + remove_dir_recursively(&sb, 0);
257 + strbuf_reset(&sb);
258 + }
259 + if (junk_work_tree) {
260 + strbuf_addstr(&sb, junk_work_tree);
261 + remove_dir_recursively(&sb, 0);
262 + strbuf_reset(&sb);
263 + }
264 +}
265 +
266 +static void remove_junk_on_signal(int signo)
267 +{
268 + remove_junk();
269 + signal(SIGINT, SIG_DFL);
270 + raise(signo);
271 +}
272 +
273 +static const struct ref *locate_head(const struct ref *refs,
274 + const struct ref *mapped_refs,
275 + const struct ref **remote_head_p)
276 +{
277 + const struct ref *remote_head = NULL;
278 + const struct ref *remote_master = NULL;
279 + const struct ref *r;
280 + for (r = refs; r; r = r->next)
281 + if (!strcmp(r->name, "HEAD"))
282 + remote_head = r;
283 +
284 + for (r = mapped_refs; r; r = r->next)
285 + if (!strcmp(r->name, "refs/heads/master"))
286 + remote_master = r;
287 +
288 + if (remote_head_p)
289 + *remote_head_p = remote_head;
290 +
291 + /* If there's no HEAD value at all, never mind. */
292 + if (!remote_head)
293 + return NULL;
294 +
295 + /* If refs/heads/master could be right, it is. */
296 + if (remote_master && !hashcmp(remote_master->old_sha1,
297 + remote_head->old_sha1))
298 + return remote_master;
299 +
300 + /* Look for another ref that points there */
301 + for (r = mapped_refs; r; r = r->next)
302 + if (r != remote_head &&
303 + !hashcmp(r->old_sha1, remote_head->old_sha1))
304 + return r;
305 +
306 + /* Nothing is the same */
307 + return NULL;
308 +}
309 +
310 +static struct ref *write_remote_refs(const struct ref *refs,
311 + struct refspec *refspec, const char *reflog)
312 +{
313 + struct ref *local_refs = NULL;
314 + struct ref **tail = &local_refs;
315 + struct ref *r;
316 +
317 + get_fetch_map(refs, refspec, &tail, 0);
318 + get_fetch_map(refs, tag_refspec, &tail, 0);
319 +
320 + for (r = local_refs; r; r = r->next)
321 + update_ref(reflog,
322 + r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR);
323 + return local_refs;
324 +}
325 +
326 +int cmd_clone(int argc, const char **argv, const char *prefix)
327 +{
328 + int use_local_hardlinks = 1;
329 + int use_separate_remote = 1;
330 + int is_bundle = 0;
331 + struct stat buf;
332 + const char *repo_name, *repo, *work_tree, *git_dir;
333 + char *path, *dir;
334 + const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
335 + char branch_top[256], key[256], value[256];
336 + struct strbuf reflog_msg;
337 +
338 + struct refspec refspec;
339 +
340 + junk_pid = getpid();
341 +
342 + argc = parse_options(argc, argv, builtin_clone_options,
343 + builtin_clone_usage, 0);
344 +
345 + if (argc == 0)
346 + die("You must specify a repository to clone.");
347 +
348 + if (option_no_hardlinks)
349 + use_local_hardlinks = 0;
350 +
351 + if (option_bare) {
352 + if (option_origin)
353 + die("--bare and --origin %s options are incompatible.",
354 + option_origin);
355 + option_no_checkout = 1;
356 + use_separate_remote = 0;
357 + }
358 +
359 + if (!option_origin)
360 + option_origin = "origin";
361 +
362 + repo_name = argv[0];
363 +
364 + path = get_repo_path(repo_name, &is_bundle);
365 + if (path)
366 + repo = path;
367 + else if (!strchr(repo_name, ':'))
368 + repo = xstrdup(make_absolute_path(repo_name));
369 + else
370 + repo = repo_name;
371 +
372 + if (argc == 2)
373 + dir = xstrdup(argv[1]);
374 + else
375 + dir = guess_dir_name(repo_name, is_bundle);
376 +
377 + if (!stat(dir, &buf))
378 + die("destination directory '%s' already exists.", dir);
379 +
380 + strbuf_init(&reflog_msg, 0);
381 + strbuf_addf(&reflog_msg, "clone: from %s", repo);
382 +
383 + if (option_bare)
384 + work_tree = NULL;
385 + else {
386 + work_tree = getenv("GIT_WORK_TREE");
387 + if (work_tree && !stat(work_tree, &buf))
388 + die("working tree '%s' already exists.", work_tree);
389 + }
390 +
391 + if (option_bare || work_tree)
392 + git_dir = xstrdup(dir);
393 + else {
394 + work_tree = dir;
395 + git_dir = xstrdup(mkpath("%s/.git", dir));
396 + }
397 +
398 + if (!option_bare) {
399 + junk_work_tree = work_tree;
400 + if (mkdir(work_tree, 0755))
401 + die("could not create work tree dir '%s'.", work_tree);
402 + set_git_work_tree(work_tree);
403 + }
404 + junk_git_dir = git_dir;
405 + atexit(remove_junk);
406 + signal(SIGINT, remove_junk_on_signal);
407 +
408 + setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
409 +
410 + set_git_dir(make_absolute_path(git_dir));
411 +
412 + fprintf(stderr, "Initialize %s\n", git_dir);
413 + init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
414 +
415 + if (option_reference)
416 + setup_reference(git_dir);
417 +
418 + git_config(git_default_config);
419 +
420 + if (option_bare) {
421 + strcpy(branch_top, "refs/heads/");
422 +
423 + git_config_set("core.bare", "true");
424 + } else {
425 + snprintf(branch_top, sizeof(branch_top),
426 + "refs/remotes/%s/", option_origin);
427 +
428 + /* Configure the remote */
429 + snprintf(key, sizeof(key), "remote.%s.url", option_origin);
430 + git_config_set(key, repo);
431 +
432 + snprintf(key, sizeof(key), "remote.%s.fetch", option_origin);
433 + snprintf(value, sizeof(value),
434 + "+refs/heads/*:%s*", branch_top);
435 + git_config_set_multivar(key, value, "^$", 0);
436 + }
437 +
438 + refspec.force = 0;
439 + refspec.pattern = 1;
440 + refspec.src = "refs/heads/";
441 + refspec.dst = branch_top;
442 +
443 + if (path && !is_bundle)
444 + refs = clone_local(path, git_dir);
445 + else {
446 + struct remote *remote = remote_get(argv[0]);
447 + struct transport *transport = transport_get(remote, argv[0]);
448 +
449 + transport_set_option(transport, TRANS_OPT_KEEP, "yes");
450 +
451 + if (option_depth)
452 + transport_set_option(transport, TRANS_OPT_DEPTH,
453 + option_depth);
454 +
455 + if (option_quiet)
456 + transport->verbose = -1;
457 +
458 + refs = transport_get_remote_refs(transport);
459 + transport_fetch_refs(transport, refs);
460 + }
461 +
462 + clear_extra_refs();
463 +
464 + mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
465 +
466 + head_points_at = locate_head(refs, mapped_refs, &remote_head);
467 +
468 + if (head_points_at) {
469 + /* Local default branch link */
470 + create_symref("HEAD", head_points_at->name, NULL);
471 +
472 + if (!option_bare) {
473 + struct strbuf head_ref;
474 + const char *head = head_points_at->name;
475 +
476 + if (!prefixcmp(head, "refs/heads/"))
477 + head += 11;
478 +
479 + /* Set up the initial local branch */
480 +
481 + /* Local branch initial value */
482 + update_ref(reflog_msg.buf, "HEAD",
483 + head_points_at->old_sha1,
484 + NULL, 0, DIE_ON_ERR);
485 +
486 + strbuf_init(&head_ref, 0);
487 + strbuf_addstr(&head_ref, branch_top);
488 + strbuf_addstr(&head_ref, "HEAD");
489 +
490 + /* Remote branch link */
491 + create_symref(head_ref.buf,
492 + head_points_at->peer_ref->name,
493 + reflog_msg.buf);
494 +
495 + snprintf(key, sizeof(key), "branch.%s.remote", head);
496 + git_config_set(key, option_origin);
497 + snprintf(key, sizeof(key), "branch.%s.merge", head);
498 + git_config_set(key, head_points_at->name);
499 + }
500 + } else if (remote_head) {
501 + /* Source had detached HEAD pointing somewhere. */
502 + if (!option_bare)
503 + update_ref(reflog_msg.buf, "HEAD",
504 + remote_head->old_sha1,
505 + NULL, REF_NODEREF, DIE_ON_ERR);
506 + } else {
507 + /* Nothing to checkout out */
508 + if (!option_no_checkout)
509 + warning("remote HEAD refers to nonexistent ref, "
510 + "unable to checkout.\n");
511 + option_no_checkout = 1;
512 + }
513 +
514 + if (!option_no_checkout) {
515 + struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
516 + struct unpack_trees_options opts;
517 + struct tree *tree;
518 + struct tree_desc t;
519 + int fd;
520 +
521 + /* We need to be in the new work tree for the checkout */
522 + setup_work_tree();
523 +
524 + fd = hold_locked_index(lock_file, 1);
525 +
526 + memset(&opts, 0, sizeof opts);
527 + opts.update = 1;
528 + opts.verbose_update = !option_quiet;
529 + opts.dst_index = &the_index;
530 +
531 + tree = parse_tree_indirect(remote_head->old_sha1);
532 + parse_tree(tree);
533 + init_tree_desc(&t, tree->buffer, tree->size);
534 + unpack_trees(1, &t, &opts);
535 +
536 + if (write_cache(fd, active_cache, active_nr) ||
537 + commit_locked_index(lock_file))
538 + die("unable to write new index file");
539 + }
540 +
541 + strbuf_release(&reflog_msg);
542 + junk_pid = 0;
543 + return 0;
544 +}
builtin.h
+1
@@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
24 extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
25 extern int cmd_cherry(int argc, const char **argv, const char *prefix);
26 extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
27 +extern int cmd_clone(int argc, const char **argv, const char *prefix);
28 extern int cmd_clean(int argc, const char **argv, const char *prefix);
29 extern int cmd_commit(int argc, const char **argv, const char *prefix);
30 extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
contrib/examples/git-clone.sh renamed
git.c
+1
@@ -286,6 +286,7 @@ static void handle_internal_command(int argc, const char **argv)
286 { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
287 { "cherry", cmd_cherry, RUN_SETUP },
288 { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
289 + { "clone", cmd_clone },
290 { "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
291 { "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
292 { "commit-tree", cmd_commit_tree, RUN_SETUP },