1b1dd23f2d
When you misuse a git command, you are shown the usage string. But this is currently shown in the dashed form. So if you just copy what you see, it will not work, when the dashed form is no longer supported. This patch makes git commands show the dash-less version. For shell scripts that do not specify OPTIONS_SPEC, git-sh-setup.sh generates a dash-less usage string now. Signed-off-by: Stephan Beyer <s-beyer@gmx.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>
573 lines
14 KiB
C
573 lines
14 KiB
C
/*
|
|
* Builtin "git clone"
|
|
*
|
|
* Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,
|
|
* 2008 Daniel Barkalow <barkalow@iabervon.org>
|
|
* Based on git-commit.sh by Junio C Hamano and Linus Torvalds
|
|
*
|
|
* Clone a repository into a different directory that does not yet exist.
|
|
*/
|
|
|
|
#include "cache.h"
|
|
#include "parse-options.h"
|
|
#include "fetch-pack.h"
|
|
#include "refs.h"
|
|
#include "tree.h"
|
|
#include "tree-walk.h"
|
|
#include "unpack-trees.h"
|
|
#include "transport.h"
|
|
#include "strbuf.h"
|
|
#include "dir.h"
|
|
#include "pack-refs.h"
|
|
|
|
/*
|
|
* Overall FIXMEs:
|
|
* - respect DB_ENVIRONMENT for .git/objects.
|
|
*
|
|
* Implementation notes:
|
|
* - dropping use-separate-remote and no-separate-remote compatibility
|
|
*
|
|
*/
|
|
static const char * const builtin_clone_usage[] = {
|
|
"git clone [options] [--] <repo> [<dir>]",
|
|
NULL
|
|
};
|
|
|
|
static int option_quiet, option_no_checkout, option_bare;
|
|
static int option_local, option_no_hardlinks, option_shared;
|
|
static char *option_template, *option_reference, *option_depth;
|
|
static char *option_origin = NULL;
|
|
static char *option_upload_pack = "git-upload-pack";
|
|
|
|
static struct option builtin_clone_options[] = {
|
|
OPT__QUIET(&option_quiet),
|
|
OPT_BOOLEAN('n', "no-checkout", &option_no_checkout,
|
|
"don't create a checkout"),
|
|
OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"),
|
|
OPT_BOOLEAN(0, "naked", &option_bare, "create a bare repository"),
|
|
OPT_BOOLEAN('l', "local", &option_local,
|
|
"to clone from a local repository"),
|
|
OPT_BOOLEAN(0, "no-hardlinks", &option_no_hardlinks,
|
|
"don't use local hardlinks, always copy"),
|
|
OPT_BOOLEAN('s', "shared", &option_shared,
|
|
"setup as shared repository"),
|
|
OPT_STRING(0, "template", &option_template, "path",
|
|
"path the template repository"),
|
|
OPT_STRING(0, "reference", &option_reference, "repo",
|
|
"reference repository"),
|
|
OPT_STRING('o', "origin", &option_origin, "branch",
|
|
"use <branch> instead or 'origin' to track upstream"),
|
|
OPT_STRING('u', "upload-pack", &option_upload_pack, "path",
|
|
"path to git-upload-pack on the remote"),
|
|
OPT_STRING(0, "depth", &option_depth, "depth",
|
|
"create a shallow clone of that depth"),
|
|
|
|
OPT_END()
|
|
};
|
|
|
|
static char *get_repo_path(const char *repo, int *is_bundle)
|
|
{
|
|
static char *suffix[] = { "/.git", ".git", "" };
|
|
static char *bundle_suffix[] = { ".bundle", "" };
|
|
struct stat st;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
|
|
const char *path;
|
|
path = mkpath("%s%s", repo, suffix[i]);
|
|
if (!stat(path, &st) && S_ISDIR(st.st_mode)) {
|
|
*is_bundle = 0;
|
|
return xstrdup(make_nonrelative_path(path));
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
|
|
const char *path;
|
|
path = mkpath("%s%s", repo, bundle_suffix[i]);
|
|
if (!stat(path, &st) && S_ISREG(st.st_mode)) {
|
|
*is_bundle = 1;
|
|
return xstrdup(make_nonrelative_path(path));
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static char *guess_dir_name(const char *repo, int is_bundle)
|
|
{
|
|
const char *p, *start, *end, *limit;
|
|
int after_slash_or_colon;
|
|
|
|
/* Guess dir name from repository: strip trailing '/',
|
|
* strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */
|
|
|
|
after_slash_or_colon = 1;
|
|
limit = repo + strlen(repo);
|
|
start = repo;
|
|
end = limit;
|
|
for (p = repo; p < limit; p++) {
|
|
const char *prefix = is_bundle ? ".bundle" : ".git";
|
|
if (!prefixcmp(p, prefix)) {
|
|
if (!after_slash_or_colon)
|
|
end = p;
|
|
p += strlen(prefix) - 1;
|
|
} else if (!prefixcmp(p, ".bundle")) {
|
|
if (!after_slash_or_colon)
|
|
end = p;
|
|
p += 7;
|
|
} else if (*p == '/' || *p == ':') {
|
|
if (end == limit)
|
|
end = p;
|
|
after_slash_or_colon = 1;
|
|
} else if (after_slash_or_colon) {
|
|
start = p;
|
|
end = limit;
|
|
after_slash_or_colon = 0;
|
|
}
|
|
}
|
|
|
|
return xstrndup(start, end - start);
|
|
}
|
|
|
|
static int is_directory(const char *path)
|
|
{
|
|
struct stat buf;
|
|
|
|
return !stat(path, &buf) && S_ISDIR(buf.st_mode);
|
|
}
|
|
|
|
static void setup_reference(const char *repo)
|
|
{
|
|
const char *ref_git;
|
|
char *ref_git_copy;
|
|
|
|
struct remote *remote;
|
|
struct transport *transport;
|
|
const struct ref *extra;
|
|
|
|
ref_git = make_absolute_path(option_reference);
|
|
|
|
if (is_directory(mkpath("%s/.git/objects", ref_git)))
|
|
ref_git = mkpath("%s/.git", ref_git);
|
|
else if (!is_directory(mkpath("%s/objects", ref_git)))
|
|
die("reference repository '%s' is not a local directory.",
|
|
option_reference);
|
|
|
|
ref_git_copy = xstrdup(ref_git);
|
|
|
|
add_to_alternates_file(ref_git_copy);
|
|
|
|
remote = remote_get(ref_git_copy);
|
|
transport = transport_get(remote, ref_git_copy);
|
|
for (extra = transport_get_remote_refs(transport); extra;
|
|
extra = extra->next)
|
|
add_extra_ref(extra->name, extra->old_sha1, 0);
|
|
|
|
transport_disconnect(transport);
|
|
|
|
free(ref_git_copy);
|
|
}
|
|
|
|
static void copy_or_link_directory(char *src, char *dest)
|
|
{
|
|
struct dirent *de;
|
|
struct stat buf;
|
|
int src_len, dest_len;
|
|
DIR *dir;
|
|
|
|
dir = opendir(src);
|
|
if (!dir)
|
|
die("failed to open %s\n", src);
|
|
|
|
if (mkdir(dest, 0777)) {
|
|
if (errno != EEXIST)
|
|
die("failed to create directory %s\n", dest);
|
|
else if (stat(dest, &buf))
|
|
die("failed to stat %s\n", dest);
|
|
else if (!S_ISDIR(buf.st_mode))
|
|
die("%s exists and is not a directory\n", dest);
|
|
}
|
|
|
|
src_len = strlen(src);
|
|
src[src_len] = '/';
|
|
dest_len = strlen(dest);
|
|
dest[dest_len] = '/';
|
|
|
|
while ((de = readdir(dir)) != NULL) {
|
|
strcpy(src + src_len + 1, de->d_name);
|
|
strcpy(dest + dest_len + 1, de->d_name);
|
|
if (stat(src, &buf)) {
|
|
warning ("failed to stat %s\n", src);
|
|
continue;
|
|
}
|
|
if (S_ISDIR(buf.st_mode)) {
|
|
if (de->d_name[0] != '.')
|
|
copy_or_link_directory(src, dest);
|
|
continue;
|
|
}
|
|
|
|
if (unlink(dest) && errno != ENOENT)
|
|
die("failed to unlink %s\n", dest);
|
|
if (!option_no_hardlinks) {
|
|
if (!link(src, dest))
|
|
continue;
|
|
if (option_local)
|
|
die("failed to create link %s\n", dest);
|
|
option_no_hardlinks = 1;
|
|
}
|
|
if (copy_file(dest, src, 0666))
|
|
die("failed to copy file to %s\n", dest);
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
static const struct ref *clone_local(const char *src_repo,
|
|
const char *dest_repo)
|
|
{
|
|
const struct ref *ret;
|
|
char src[PATH_MAX];
|
|
char dest[PATH_MAX];
|
|
struct remote *remote;
|
|
struct transport *transport;
|
|
|
|
if (option_shared)
|
|
add_to_alternates_file(src_repo);
|
|
else {
|
|
snprintf(src, PATH_MAX, "%s/objects", src_repo);
|
|
snprintf(dest, PATH_MAX, "%s/objects", dest_repo);
|
|
copy_or_link_directory(src, dest);
|
|
}
|
|
|
|
remote = remote_get(src_repo);
|
|
transport = transport_get(remote, src_repo);
|
|
ret = transport_get_remote_refs(transport);
|
|
transport_disconnect(transport);
|
|
return ret;
|
|
}
|
|
|
|
static const char *junk_work_tree;
|
|
static const char *junk_git_dir;
|
|
pid_t junk_pid;
|
|
|
|
static void remove_junk(void)
|
|
{
|
|
struct strbuf sb;
|
|
if (getpid() != junk_pid)
|
|
return;
|
|
strbuf_init(&sb, 0);
|
|
if (junk_git_dir) {
|
|
strbuf_addstr(&sb, junk_git_dir);
|
|
remove_dir_recursively(&sb, 0);
|
|
strbuf_reset(&sb);
|
|
}
|
|
if (junk_work_tree) {
|
|
strbuf_addstr(&sb, junk_work_tree);
|
|
remove_dir_recursively(&sb, 0);
|
|
strbuf_reset(&sb);
|
|
}
|
|
}
|
|
|
|
static void remove_junk_on_signal(int signo)
|
|
{
|
|
remove_junk();
|
|
signal(SIGINT, SIG_DFL);
|
|
raise(signo);
|
|
}
|
|
|
|
static const struct ref *locate_head(const struct ref *refs,
|
|
const struct ref *mapped_refs,
|
|
const struct ref **remote_head_p)
|
|
{
|
|
const struct ref *remote_head = NULL;
|
|
const struct ref *remote_master = NULL;
|
|
const struct ref *r;
|
|
for (r = refs; r; r = r->next)
|
|
if (!strcmp(r->name, "HEAD"))
|
|
remote_head = r;
|
|
|
|
for (r = mapped_refs; r; r = r->next)
|
|
if (!strcmp(r->name, "refs/heads/master"))
|
|
remote_master = r;
|
|
|
|
if (remote_head_p)
|
|
*remote_head_p = remote_head;
|
|
|
|
/* If there's no HEAD value at all, never mind. */
|
|
if (!remote_head)
|
|
return NULL;
|
|
|
|
/* If refs/heads/master could be right, it is. */
|
|
if (remote_master && !hashcmp(remote_master->old_sha1,
|
|
remote_head->old_sha1))
|
|
return remote_master;
|
|
|
|
/* Look for another ref that points there */
|
|
for (r = mapped_refs; r; r = r->next)
|
|
if (r != remote_head &&
|
|
!hashcmp(r->old_sha1, remote_head->old_sha1))
|
|
return r;
|
|
|
|
/* Nothing is the same */
|
|
return NULL;
|
|
}
|
|
|
|
static struct ref *write_remote_refs(const struct ref *refs,
|
|
struct refspec *refspec, const char *reflog)
|
|
{
|
|
struct ref *local_refs = NULL;
|
|
struct ref **tail = &local_refs;
|
|
struct ref *r;
|
|
|
|
get_fetch_map(refs, refspec, &tail, 0);
|
|
get_fetch_map(refs, tag_refspec, &tail, 0);
|
|
|
|
for (r = local_refs; r; r = r->next)
|
|
add_extra_ref(r->peer_ref->name, r->old_sha1, 0);
|
|
|
|
pack_refs(PACK_REFS_ALL);
|
|
clear_extra_refs();
|
|
|
|
return local_refs;
|
|
}
|
|
|
|
int cmd_clone(int argc, const char **argv, const char *prefix)
|
|
{
|
|
int use_local_hardlinks = 1;
|
|
int use_separate_remote = 1;
|
|
int is_bundle = 0;
|
|
struct stat buf;
|
|
const char *repo_name, *repo, *work_tree, *git_dir;
|
|
char *path, *dir;
|
|
const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
|
|
char branch_top[256], key[256], value[256];
|
|
struct strbuf reflog_msg;
|
|
struct transport *transport = NULL;
|
|
|
|
struct refspec refspec;
|
|
|
|
junk_pid = getpid();
|
|
|
|
argc = parse_options(argc, argv, builtin_clone_options,
|
|
builtin_clone_usage, 0);
|
|
|
|
if (argc == 0)
|
|
die("You must specify a repository to clone.");
|
|
|
|
if (option_no_hardlinks)
|
|
use_local_hardlinks = 0;
|
|
|
|
if (option_bare) {
|
|
if (option_origin)
|
|
die("--bare and --origin %s options are incompatible.",
|
|
option_origin);
|
|
option_no_checkout = 1;
|
|
use_separate_remote = 0;
|
|
}
|
|
|
|
if (!option_origin)
|
|
option_origin = "origin";
|
|
|
|
repo_name = argv[0];
|
|
|
|
path = get_repo_path(repo_name, &is_bundle);
|
|
if (path)
|
|
repo = path;
|
|
else if (!strchr(repo_name, ':'))
|
|
repo = xstrdup(make_absolute_path(repo_name));
|
|
else
|
|
repo = repo_name;
|
|
|
|
if (argc == 2)
|
|
dir = xstrdup(argv[1]);
|
|
else
|
|
dir = guess_dir_name(repo_name, is_bundle);
|
|
|
|
if (!stat(dir, &buf))
|
|
die("destination directory '%s' already exists.", dir);
|
|
|
|
strbuf_init(&reflog_msg, 0);
|
|
strbuf_addf(&reflog_msg, "clone: from %s", repo);
|
|
|
|
if (option_bare)
|
|
work_tree = NULL;
|
|
else {
|
|
work_tree = getenv("GIT_WORK_TREE");
|
|
if (work_tree && !stat(work_tree, &buf))
|
|
die("working tree '%s' already exists.", work_tree);
|
|
}
|
|
|
|
if (option_bare || work_tree)
|
|
git_dir = xstrdup(dir);
|
|
else {
|
|
work_tree = dir;
|
|
git_dir = xstrdup(mkpath("%s/.git", dir));
|
|
}
|
|
|
|
if (!option_bare) {
|
|
junk_work_tree = work_tree;
|
|
if (safe_create_leading_directories_const(work_tree) < 0)
|
|
die("could not create leading directories of '%s'",
|
|
work_tree);
|
|
if (mkdir(work_tree, 0755))
|
|
die("could not create work tree dir '%s'.", work_tree);
|
|
set_git_work_tree(work_tree);
|
|
}
|
|
junk_git_dir = git_dir;
|
|
atexit(remove_junk);
|
|
signal(SIGINT, remove_junk_on_signal);
|
|
|
|
setenv(CONFIG_ENVIRONMENT, xstrdup(mkpath("%s/config", git_dir)), 1);
|
|
|
|
if (safe_create_leading_directories_const(git_dir) < 0)
|
|
die("could not create leading directories of '%s'", git_dir);
|
|
set_git_dir(make_absolute_path(git_dir));
|
|
|
|
init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
|
|
|
|
/*
|
|
* At this point, the config exists, so we do not need the
|
|
* environment variable. We actually need to unset it, too, to
|
|
* re-enable parsing of the global configs.
|
|
*/
|
|
unsetenv(CONFIG_ENVIRONMENT);
|
|
|
|
if (option_reference)
|
|
setup_reference(git_dir);
|
|
|
|
git_config(git_default_config, NULL);
|
|
|
|
if (option_bare) {
|
|
strcpy(branch_top, "refs/heads/");
|
|
|
|
git_config_set("core.bare", "true");
|
|
} else {
|
|
snprintf(branch_top, sizeof(branch_top),
|
|
"refs/remotes/%s/", option_origin);
|
|
|
|
/* Configure the remote */
|
|
snprintf(key, sizeof(key), "remote.%s.url", option_origin);
|
|
git_config_set(key, repo);
|
|
|
|
snprintf(key, sizeof(key), "remote.%s.fetch", option_origin);
|
|
snprintf(value, sizeof(value),
|
|
"+refs/heads/*:%s*", branch_top);
|
|
git_config_set_multivar(key, value, "^$", 0);
|
|
}
|
|
|
|
refspec.force = 0;
|
|
refspec.pattern = 1;
|
|
refspec.src = "refs/heads/";
|
|
refspec.dst = branch_top;
|
|
|
|
if (path && !is_bundle)
|
|
refs = clone_local(path, git_dir);
|
|
else {
|
|
struct remote *remote = remote_get(argv[0]);
|
|
transport = transport_get(remote, remote->url[0]);
|
|
|
|
if (!transport->get_refs_list || !transport->fetch)
|
|
die("Don't know how to clone %s", transport->url);
|
|
|
|
transport_set_option(transport, TRANS_OPT_KEEP, "yes");
|
|
|
|
if (option_depth)
|
|
transport_set_option(transport, TRANS_OPT_DEPTH,
|
|
option_depth);
|
|
|
|
if (option_quiet)
|
|
transport->verbose = -1;
|
|
|
|
refs = transport_get_remote_refs(transport);
|
|
transport_fetch_refs(transport, refs);
|
|
}
|
|
|
|
clear_extra_refs();
|
|
|
|
mapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);
|
|
|
|
head_points_at = locate_head(refs, mapped_refs, &remote_head);
|
|
|
|
if (head_points_at) {
|
|
/* Local default branch link */
|
|
create_symref("HEAD", head_points_at->name, NULL);
|
|
|
|
if (!option_bare) {
|
|
struct strbuf head_ref;
|
|
const char *head = head_points_at->name;
|
|
|
|
if (!prefixcmp(head, "refs/heads/"))
|
|
head += 11;
|
|
|
|
/* Set up the initial local branch */
|
|
|
|
/* Local branch initial value */
|
|
update_ref(reflog_msg.buf, "HEAD",
|
|
head_points_at->old_sha1,
|
|
NULL, 0, DIE_ON_ERR);
|
|
|
|
strbuf_init(&head_ref, 0);
|
|
strbuf_addstr(&head_ref, branch_top);
|
|
strbuf_addstr(&head_ref, "HEAD");
|
|
|
|
/* Remote branch link */
|
|
create_symref(head_ref.buf,
|
|
head_points_at->peer_ref->name,
|
|
reflog_msg.buf);
|
|
|
|
snprintf(key, sizeof(key), "branch.%s.remote", head);
|
|
git_config_set(key, option_origin);
|
|
snprintf(key, sizeof(key), "branch.%s.merge", head);
|
|
git_config_set(key, head_points_at->name);
|
|
}
|
|
} else if (remote_head) {
|
|
/* Source had detached HEAD pointing somewhere. */
|
|
if (!option_bare)
|
|
update_ref(reflog_msg.buf, "HEAD",
|
|
remote_head->old_sha1,
|
|
NULL, REF_NODEREF, DIE_ON_ERR);
|
|
} else {
|
|
/* Nothing to checkout out */
|
|
if (!option_no_checkout)
|
|
warning("remote HEAD refers to nonexistent ref, "
|
|
"unable to checkout.\n");
|
|
option_no_checkout = 1;
|
|
}
|
|
|
|
if (transport)
|
|
transport_unlock_pack(transport);
|
|
|
|
if (!option_no_checkout) {
|
|
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
|
|
struct unpack_trees_options opts;
|
|
struct tree *tree;
|
|
struct tree_desc t;
|
|
int fd;
|
|
|
|
/* We need to be in the new work tree for the checkout */
|
|
setup_work_tree();
|
|
|
|
fd = hold_locked_index(lock_file, 1);
|
|
|
|
memset(&opts, 0, sizeof opts);
|
|
opts.update = 1;
|
|
opts.merge = 1;
|
|
opts.fn = oneway_merge;
|
|
opts.verbose_update = !option_quiet;
|
|
opts.src_index = &the_index;
|
|
opts.dst_index = &the_index;
|
|
|
|
tree = parse_tree_indirect(remote_head->old_sha1);
|
|
parse_tree(tree);
|
|
init_tree_desc(&t, tree->buffer, tree->size);
|
|
unpack_trees(1, &t, &opts);
|
|
|
|
if (write_cache(fd, active_cache, active_nr) ||
|
|
commit_locked_index(lock_file))
|
|
die("unable to write new index file");
|
|
}
|
|
|
|
strbuf_release(&reflog_msg);
|
|
junk_pid = 0;
|
|
return 0;
|
|
}
|