SCRIPT_SH += git-am.sh
SCRIPT_SH += git-bisect.sh
-SCRIPT_SH += git-clone.sh
SCRIPT_SH += git-filter-branch.sh
SCRIPT_SH += git-lost-found.sh
SCRIPT_SH += git-merge-octopus.sh
BUILTIN_OBJS += builtin-checkout-index.o
BUILTIN_OBJS += builtin-checkout.o
BUILTIN_OBJS += builtin-clean.o
+BUILTIN_OBJS += builtin-clone.o
BUILTIN_OBJS += builtin-commit-tree.o
BUILTIN_OBJS += builtin-commit.o
BUILTIN_OBJS += builtin-config.o
--- /dev/null
+/*
+ * 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"
+
+/*
+ * 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_absolute_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_absolute_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)
+ update_ref(reflog,
+ r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR);
+ 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 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 (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);
+
+ set_git_dir(make_absolute_path(git_dir));
+
+ fprintf(stderr, "Initialize %s\n", git_dir);
+ init_db(option_template, option_quiet ? INIT_DB_QUIET : 0);
+
+ if (option_reference)
+ setup_reference(git_dir);
+
+ git_config(git_default_config);
+
+ 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]);
+ struct transport *transport = transport_get(remote, argv[0]);
+
+ 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 (!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;
+}
/* Merge everything on the command line, but not --tags */
for (rm = ref_map; rm; rm = rm->next)
rm->merge = 1;
- if (tags == TAGS_SET) {
- struct refspec refspec;
- refspec.src = "refs/tags/";
- refspec.dst = "refs/tags/";
- refspec.pattern = 1;
- refspec.force = 0;
- get_fetch_map(remote_refs, &refspec, &tail, 0);
- }
+ if (tags == TAGS_SET)
+ get_fetch_map(remote_refs, tag_refspec, &tail, 0);
} else {
/* Use the defaults */
struct remote *remote = transport->remote;
}
}
-static void copy_templates(const char *git_dir, int len, const char *template_dir)
+static void copy_templates(const char *template_dir)
{
char path[PATH_MAX];
char template_path[PATH_MAX];
int template_len;
DIR *dir;
+ const char *git_dir = get_git_dir();
+ int len = strlen(git_dir);
if (!template_dir)
template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
}
memcpy(path, git_dir, len);
+ if (len && path[len - 1] != '/')
+ path[len++] = '/';
path[len] = 0;
copy_templates_1(path, len,
template_path, template_len,
closedir(dir);
}
-static int create_default_files(const char *git_dir, const char *template_path)
+static int create_default_files(const char *template_path)
{
+ const char *git_dir = get_git_dir();
unsigned len = strlen(git_dir);
static char path[PATH_MAX];
struct stat st1;
/*
* Create .git/refs/{heads,tags}
*/
- strcpy(path + len, "refs");
- safe_create_dir(path, 1);
- strcpy(path + len, "refs/heads");
- safe_create_dir(path, 1);
- strcpy(path + len, "refs/tags");
- safe_create_dir(path, 1);
+ safe_create_dir(git_path("refs"), 1);
+ safe_create_dir(git_path("refs/heads"), 1);
+ safe_create_dir(git_path("refs/tags"), 1);
/* First copy the templates -- we might have the default
* config file there, in which case we would want to read
* from it after installing.
*/
- path[len] = 0;
- copy_templates(path, len, template_path);
+ copy_templates(template_path);
git_config(git_default_config);
* shared-repository settings, we would need to fix them up.
*/
if (shared_repository) {
- path[len] = 0;
- adjust_shared_perm(path);
- strcpy(path + len, "refs");
- adjust_shared_perm(path);
- strcpy(path + len, "refs/heads");
- adjust_shared_perm(path);
- strcpy(path + len, "refs/tags");
- adjust_shared_perm(path);
+ adjust_shared_perm(get_git_dir());
+ adjust_shared_perm(git_path("refs"));
+ adjust_shared_perm(git_path("refs/heads"));
+ adjust_shared_perm(git_path("refs/tags"));
}
/*
/* allow template config file to override the default */
if (log_all_ref_updates == -1)
git_config_set("core.logallrefupdates", "true");
- if (work_tree != git_work_tree_cfg)
+ if (prefixcmp(git_dir, work_tree) ||
+ strcmp(git_dir + strlen(work_tree), "/.git")) {
git_config_set("core.worktree", work_tree);
+ }
}
if (!reinit) {
return reinit;
}
-static void guess_repository_type(const char *git_dir)
+int init_db(const char *template_dir, unsigned int flags)
+{
+ const char *sha1_dir;
+ char *path;
+ int len, reinit;
+
+ safe_create_dir(get_git_dir(), 0);
+
+ /* Check to see if the repository version is right.
+ * Note that a newly created repository does not have
+ * config file, so this will not fail. What we are catching
+ * is an attempt to reinitialize new repository with an old tool.
+ */
+ check_repository_format();
+
+ reinit = create_default_files(template_dir);
+
+ sha1_dir = get_object_directory();
+ len = strlen(sha1_dir);
+ path = xmalloc(len + 40);
+ memcpy(path, sha1_dir, len);
+
+ safe_create_dir(sha1_dir, 1);
+ strcpy(path+len, "/pack");
+ safe_create_dir(path, 1);
+ strcpy(path+len, "/info");
+ safe_create_dir(path, 1);
+
+ if (shared_repository) {
+ char buf[10];
+ /* We do not spell "group" and such, so that
+ * the configuration can be read by older version
+ * of git. Note, we use octal numbers for new share modes,
+ * and compatibility values for PERM_GROUP and
+ * PERM_EVERYBODY.
+ */
+ if (shared_repository == PERM_GROUP)
+ sprintf(buf, "%d", OLD_PERM_GROUP);
+ else if (shared_repository == PERM_EVERYBODY)
+ sprintf(buf, "%d", OLD_PERM_EVERYBODY);
+ else
+ sprintf(buf, "0%o", shared_repository);
+ git_config_set("core.sharedrepository", buf);
+ git_config_set("receive.denyNonFastforwards", "true");
+ }
+
+ if (!(flags & INIT_DB_QUIET))
+ printf("%s%s Git repository in %s/\n",
+ reinit ? "Reinitialized existing" : "Initialized empty",
+ shared_repository ? " shared" : "",
+ get_git_dir());
+
+ return 0;
+}
+
+static int guess_repository_type(const char *git_dir)
{
char cwd[PATH_MAX];
const char *slash;
- if (0 <= is_bare_repository_cfg)
- return;
- if (!git_dir)
- return;
-
/*
* "GIT_DIR=. git init" is always bare.
* "GIT_DIR=`pwd` git init" too.
*/
if (!strcmp(".", git_dir))
- goto force_bare;
+ return 1;
if (!getcwd(cwd, sizeof(cwd)))
die("cannot tell cwd");
if (!strcmp(git_dir, cwd))
- goto force_bare;
+ return 1;
/*
* "GIT_DIR=.git or GIT_DIR=something/.git is usually not.
*/
if (!strcmp(git_dir, ".git"))
- return;
+ return 0;
slash = strrchr(git_dir, '/');
if (slash && !strcmp(slash, "/.git"))
- return;
+ return 0;
/*
* Otherwise it is often bare. At this point
* we are just guessing.
*/
- force_bare:
- is_bare_repository_cfg = 1;
- return;
+ return 1;
}
static const char init_db_usage[] =
int cmd_init_db(int argc, const char **argv, const char *prefix)
{
const char *git_dir;
- const char *sha1_dir;
const char *template_dir = NULL;
- char *path;
- int len, i, reinit;
- int quiet = 0;
+ unsigned int flags = 0;
+ int i;
for (i = 1; i < argc; i++, argv++) {
const char *arg = argv[1];
else if (!prefixcmp(arg, "--shared="))
shared_repository = git_config_perm("arg", arg+9);
else if (!strcmp(arg, "-q") || !strcmp(arg, "--quiet"))
- quiet = 1;
+ flags |= INIT_DB_QUIET;
else
usage(init_db_usage);
}
GIT_WORK_TREE_ENVIRONMENT,
GIT_DIR_ENVIRONMENT);
- guess_repository_type(git_dir);
-
- if (is_bare_repository_cfg <= 0) {
- git_work_tree_cfg = xcalloc(PATH_MAX, 1);
- if (!getcwd(git_work_tree_cfg, PATH_MAX))
- die ("Cannot access current working directory.");
- if (access(get_git_work_tree(), X_OK))
- die ("Cannot access work tree '%s'",
- get_git_work_tree());
- }
-
/*
* Set up the default .git directory contents
*/
- git_dir = getenv(GIT_DIR_ENVIRONMENT);
if (!git_dir)
git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
- safe_create_dir(git_dir, 0);
-
- /* Check to see if the repository version is right.
- * Note that a newly created repository does not have
- * config file, so this will not fail. What we are catching
- * is an attempt to reinitialize new repository with an old tool.
- */
- check_repository_format();
-
- reinit = create_default_files(git_dir, template_dir);
-
- /*
- * And set up the object store.
- */
- sha1_dir = get_object_directory();
- len = strlen(sha1_dir);
- path = xmalloc(len + 40);
- memcpy(path, sha1_dir, len);
-
- safe_create_dir(sha1_dir, 1);
- strcpy(path+len, "/pack");
- safe_create_dir(path, 1);
- strcpy(path+len, "/info");
- safe_create_dir(path, 1);
- if (shared_repository) {
- char buf[10];
- /* We do not spell "group" and such, so that
- * the configuration can be read by older version
- * of git. Note, we use octal numbers for new share modes,
- * and compatibility values for PERM_GROUP and
- * PERM_EVERYBODY.
- */
- if (shared_repository == PERM_GROUP)
- sprintf(buf, "%d", OLD_PERM_GROUP);
- else if (shared_repository == PERM_EVERYBODY)
- sprintf(buf, "%d", OLD_PERM_EVERYBODY);
- else
- sprintf(buf, "0%o", shared_repository);
- git_config_set("core.sharedrepository", buf);
- git_config_set("receive.denyNonFastforwards", "true");
+ if (is_bare_repository_cfg < 0)
+ is_bare_repository_cfg = guess_repository_type(git_dir);
+
+ if (!is_bare_repository_cfg) {
+ if (git_dir) {
+ const char *git_dir_parent = strrchr(git_dir, '/');
+ if (git_dir_parent) {
+ char *rel = xstrndup(git_dir, git_dir_parent - git_dir);
+ git_work_tree_cfg = xstrdup(make_absolute_path(rel));
+ free(rel);
+ }
+ }
+ if (!git_work_tree_cfg) {
+ git_work_tree_cfg = xcalloc(PATH_MAX, 1);
+ if (!getcwd(git_work_tree_cfg, PATH_MAX))
+ die ("Cannot access current working directory.");
+ }
+ if (access(get_git_work_tree(), X_OK))
+ die ("Cannot access work tree '%s'",
+ get_git_work_tree());
}
- if (!quiet)
- printf("%s%s Git repository in %s/\n",
- reinit ? "Reinitialized existing" : "Initialized empty",
- shared_repository ? " shared" : "",
- git_dir);
+ set_git_dir(make_absolute_path(git_dir));
- return 0;
+ return init_db(template_dir, flags);
}
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
extern int cmd_cherry(int argc, const char **argv, const char *prefix);
extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);
+extern int cmd_clone(int argc, const char **argv, const char *prefix);
extern int cmd_clean(int argc, const char **argv, const char *prefix);
extern int cmd_commit(int argc, const char **argv, const char *prefix);
extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);
extern int set_git_dir(const char *path);
extern const char *get_git_work_tree(void);
extern const char *read_gitfile_gently(const char *path);
+extern void set_git_work_tree(const char *tree);
#define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
extern void verify_filename(const char *prefix, const char *name);
extern void verify_non_filename(const char *prefix, const char *name);
+#define INIT_DB_QUIET 0x0001
+
+extern int init_db(const char *template_dir, unsigned int flags);
+
#define alloc_nr(x) (((x)+16)*3/2)
/*
char filename[PATH_MAX];
};
extern int hold_lock_file_for_update(struct lock_file *, const char *path, int);
+extern int hold_lock_file_for_append(struct lock_file *, const char *path, int);
extern int commit_lock_file(struct lock_file *);
extern int hold_locked_index(struct lock_file *, int);
char base[FLEX_ARRAY]; /* more */
} *alt_odb_list;
extern void prepare_alt_odb(void);
+extern void add_to_alternates_file(const char *reference);
struct pack_window {
struct pack_window *next;
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2005, Linus Torvalds
+# Copyright (c) 2005, Junio C Hamano
+#
+# Clone a repository into a different directory that does not yet exist.
+
+# See git-sh-setup why.
+unset CDPATH
+
+OPTIONS_SPEC="\
+git-clone [options] [--] <repo> [<dir>]
+--
+n,no-checkout don't create a checkout
+bare create a bare repository
+naked create a bare repository
+l,local to clone from a local repository
+no-hardlinks don't use local hardlinks, always copy
+s,shared setup as a shared repository
+template= path to the template directory
+q,quiet be quiet
+reference= reference repository
+o,origin= use <name> instead of 'origin' to track upstream
+u,upload-pack= path to git-upload-pack on the remote
+depth= create a shallow clone of that depth
+
+use-separate-remote compatibility, do not use
+no-separate-remote compatibility, do not use"
+
+die() {
+ echo >&2 "$@"
+ exit 1
+}
+
+usage() {
+ exec "$0" -h
+}
+
+eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
+
+get_repo_base() {
+ (
+ cd "`/bin/pwd`" &&
+ cd "$1" || cd "$1.git" &&
+ {
+ cd .git
+ pwd
+ }
+ ) 2>/dev/null
+}
+
+if [ -n "$GIT_SSL_NO_VERIFY" -o \
+ "`git config --bool http.sslVerify`" = false ]; then
+ curl_extra_args="-k"
+fi
+
+http_fetch () {
+ # $1 = Remote, $2 = Local
+ curl -nsfL $curl_extra_args "$1" >"$2"
+ curl_exit_status=$?
+ case $curl_exit_status in
+ 126|127) exit ;;
+ *) return $curl_exit_status ;;
+ esac
+}
+
+clone_dumb_http () {
+ # $1 - remote, $2 - local
+ cd "$2" &&
+ clone_tmp="$GIT_DIR/clone-tmp" &&
+ mkdir -p "$clone_tmp" || exit 1
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git config --bool http.noEPSV`" = true ]; then
+ curl_extra_args="${curl_extra_args} --disable-epsv"
+ fi
+ http_fetch "$1/info/refs" "$clone_tmp/refs" ||
+ die "Cannot get remote repository information.
+Perhaps git-update-server-info needs to be run there?"
+ test "z$quiet" = z && v=-v || v=
+ while read sha1 refname
+ do
+ name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
+ case "$name" in
+ *^*) continue;;
+ esac
+ case "$bare,$name" in
+ yes,* | ,heads/* | ,tags/*) ;;
+ *) continue ;;
+ esac
+ if test -n "$use_separate_remote" &&
+ branch_name=`expr "z$name" : 'zheads/\(.*\)'`
+ then
+ tname="remotes/$origin/$branch_name"
+ else
+ tname=$name
+ fi
+ git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
+ done <"$clone_tmp/refs"
+ rm -fr "$clone_tmp"
+ http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
+ rm -f "$GIT_DIR/REMOTE_HEAD"
+ if test -f "$GIT_DIR/REMOTE_HEAD"; then
+ head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+ case "$head_sha1" in
+ 'ref: refs/'*)
+ ;;
+ *)
+ git-http-fetch $v -a "$head_sha1" "$1" ||
+ rm -f "$GIT_DIR/REMOTE_HEAD"
+ ;;
+ esac
+ fi
+}
+
+quiet=
+local=no
+use_local_hardlink=yes
+local_shared=no
+unset template
+no_checkout=
+upload_pack=
+bare=
+reference=
+origin=
+origin_override=
+use_separate_remote=t
+depth=
+no_progress=
+local_explicitly_asked_for=
+test -t 1 || no_progress=--no-progress
+
+while test $# != 0
+do
+ case "$1" in
+ -n|--no-checkout)
+ no_checkout=yes ;;
+ --naked|--bare)
+ bare=yes ;;
+ -l|--local)
+ local_explicitly_asked_for=yes
+ use_local_hardlink=yes
+ ;;
+ --no-hardlinks)
+ use_local_hardlink=no ;;
+ -s|--shared)
+ local_shared=yes ;;
+ --template)
+ shift; template="--template=$1" ;;
+ -q|--quiet)
+ quiet=-q ;;
+ --use-separate-remote|--no-separate-remote)
+ die "clones are always made with separate-remote layout" ;;
+ --reference)
+ shift; reference="$1" ;;
+ -o|--origin)
+ shift;
+ case "$1" in
+ '')
+ usage ;;
+ */*)
+ die "'$1' is not suitable for an origin name"
+ esac
+ git check-ref-format "heads/$1" ||
+ die "'$1' is not suitable for a branch name"
+ test -z "$origin_override" ||
+ die "Do not give more than one --origin options."
+ origin_override=yes
+ origin="$1"
+ ;;
+ -u|--upload-pack)
+ shift
+ upload_pack="--upload-pack=$1" ;;
+ --depth)
+ shift
+ depth="--depth=$1" ;;
+ --)
+ shift
+ break ;;
+ *)
+ usage ;;
+ esac
+ shift
+done
+
+repo="$1"
+test -n "$repo" ||
+ die 'you must specify a repository to clone.'
+
+# --bare implies --no-checkout and --no-separate-remote
+if test yes = "$bare"
+then
+ if test yes = "$origin_override"
+ then
+ die '--bare and --origin $origin options are incompatible.'
+ fi
+ no_checkout=yes
+ use_separate_remote=
+fi
+
+if test -z "$origin"
+then
+ origin=origin
+fi
+
+# Turn the source into an absolute path if
+# it is local
+if base=$(get_repo_base "$repo"); then
+ repo="$base"
+ if test -z "$depth"
+ then
+ local=yes
+ fi
+elif test -f "$repo"
+then
+ case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
+fi
+
+# Decide the directory name of the new repository
+if test -n "$2"
+then
+ dir="$2"
+ test $# = 2 || die "excess parameter to git-clone"
+else
+ # Derive one from the repository name
+ # Try using "humanish" part of source repo if user didn't specify one
+ if test -f "$repo"
+ then
+ # Cloning from a bundle
+ dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
+ else
+ dir=$(echo "$repo" |
+ sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
+ fi
+fi
+
+[ -e "$dir" ] && die "destination directory '$dir' already exists."
+[ yes = "$bare" ] && unset GIT_WORK_TREE
+[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
+die "working tree '$GIT_WORK_TREE' already exists."
+D=
+W=
+cleanup() {
+ test -z "$D" && rm -rf "$dir"
+ test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
+ cd ..
+ test -n "$D" && rm -rf "$D"
+ test -n "$W" && rm -rf "$W"
+ exit $err
+}
+trap 'err=$?; cleanup' 0
+mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
+test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
+W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
+if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
+ GIT_DIR="$D"
+else
+ GIT_DIR="$D/.git"
+fi &&
+export GIT_DIR &&
+GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
+
+if test -n "$bare"
+then
+ GIT_CONFIG="$GIT_DIR/config" git config core.bare true
+fi
+
+if test -n "$reference"
+then
+ ref_git=
+ if test -d "$reference"
+ then
+ if test -d "$reference/.git/objects"
+ then
+ ref_git="$reference/.git"
+ elif test -d "$reference/objects"
+ then
+ ref_git="$reference"
+ fi
+ fi
+ if test -n "$ref_git"
+ then
+ ref_git=$(cd "$ref_git" && pwd)
+ echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
+ (
+ GIT_DIR="$ref_git" git for-each-ref \
+ --format='%(objectname) %(*objectname)'
+ ) |
+ while read a b
+ do
+ test -z "$a" ||
+ git update-ref "refs/reference-tmp/$a" "$a"
+ test -z "$b" ||
+ git update-ref "refs/reference-tmp/$b" "$b"
+ done
+ else
+ die "reference repository '$reference' is not a local directory."
+ fi
+fi
+
+rm -f "$GIT_DIR/CLONE_HEAD"
+
+# We do local magic only when the user tells us to.
+case "$local" in
+yes)
+ ( cd "$repo/objects" ) ||
+ die "cannot chdir to local '$repo/objects'."
+
+ if test "$local_shared" = yes
+ then
+ mkdir -p "$GIT_DIR/objects/info"
+ echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
+ else
+ cpio_quiet_flag=""
+ cpio --help 2>&1 | grep -- --quiet >/dev/null && \
+ cpio_quiet_flag=--quiet
+ l= &&
+ if test "$use_local_hardlink" = yes
+ then
+ # See if we can hardlink and drop "l" if not.
+ sample_file=$(cd "$repo" && \
+ find objects -type f -print | sed -e 1q)
+ # objects directory should not be empty because
+ # we are cloning!
+ test -f "$repo/$sample_file" ||
+ die "fatal: cannot clone empty repository"
+ if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
+ then
+ rm -f "$GIT_DIR/objects/sample"
+ l=l
+ elif test -n "$local_explicitly_asked_for"
+ then
+ echo >&2 "Warning: -l asked but cannot hardlink to $repo"
+ fi
+ fi &&
+ cd "$repo" &&
+ # Create dirs using umask and permissions and destination
+ find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
+ # Copy existing 0444 permissions on content
+ find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
+ exit 1
+ fi
+ git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+ ;;
+*)
+ case "$repo" in
+ rsync://*)
+ case "$depth" in
+ "") ;;
+ *) die "shallow over rsync not supported" ;;
+ esac
+ rsync $quiet -av --ignore-existing \
+ --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
+ exit
+ # Look at objects/info/alternates for rsync -- http will
+ # support it natively and git native ones will do it on the
+ # remote end. Not having that file is not a crime.
+ rsync -q "$repo/objects/info/alternates" \
+ "$GIT_DIR/TMP_ALT" 2>/dev/null ||
+ rm -f "$GIT_DIR/TMP_ALT"
+ if test -f "$GIT_DIR/TMP_ALT"
+ then
+ ( cd "$D" &&
+ . git-parse-remote &&
+ resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
+ while read alt
+ do
+ case "$alt" in 'bad alternate: '*) die "$alt";; esac
+ case "$quiet" in
+ '') echo >&2 "Getting alternate: $alt" ;;
+ esac
+ rsync $quiet -av --ignore-existing \
+ --exclude info "$alt" "$GIT_DIR/objects" || exit
+ done
+ rm -f "$GIT_DIR/TMP_ALT"
+ fi
+ git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
+ ;;
+ https://*|http://*|ftp://*)
+ case "$depth" in
+ "") ;;
+ *) die "shallow over http or ftp not supported" ;;
+ esac
+ if test -z "@@NO_CURL@@"
+ then
+ clone_dumb_http "$repo" "$D"
+ else
+ die "http transport not supported, rebuild Git with curl support"
+ fi
+ ;;
+ *)
+ if [ -f "$repo" ] ; then
+ git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
+ die "unbundle from '$repo' failed."
+ else
+ case "$upload_pack" in
+ '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
+ *) git-fetch-pack --all -k \
+ $quiet "$upload_pack" $depth $no_progress "$repo" ;;
+ esac >"$GIT_DIR/CLONE_HEAD" ||
+ die "fetch-pack from '$repo' failed."
+ fi
+ ;;
+ esac
+ ;;
+esac
+test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
+
+if test -f "$GIT_DIR/CLONE_HEAD"
+then
+ # Read git-fetch-pack -k output and store the remote branches.
+ if [ -n "$use_separate_remote" ]
+ then
+ branch_top="remotes/$origin"
+ else
+ branch_top="heads"
+ fi
+ tag_top="tags"
+ while read sha1 name
+ do
+ case "$name" in
+ *'^{}')
+ continue ;;
+ HEAD)
+ destname="REMOTE_HEAD" ;;
+ refs/heads/*)
+ destname="refs/$branch_top/${name#refs/heads/}" ;;
+ refs/tags/*)
+ destname="refs/$tag_top/${name#refs/tags/}" ;;
+ *)
+ continue ;;
+ esac
+ git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
+ done < "$GIT_DIR/CLONE_HEAD"
+fi
+
+if test -n "$W"; then
+ cd "$W" || exit
+else
+ cd "$D" || exit
+fi
+
+if test -z "$bare"
+then
+ # a non-bare repository is always in separate-remote layout
+ remote_top="refs/remotes/$origin"
+ head_sha1=
+ test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
+ case "$head_sha1" in
+ 'ref: refs/'*)
+ # Uh-oh, the remote told us (http transport done against
+ # new style repository with a symref HEAD).
+ # Ideally we should skip the guesswork but for now
+ # opt for minimum change.
+ head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
+ head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
+ ;;
+ esac
+
+ # The name under $remote_top the remote HEAD seems to point at.
+ head_points_at=$(
+ (
+ test -f "$GIT_DIR/$remote_top/master" && echo "master"
+ cd "$GIT_DIR/$remote_top" &&
+ find . -type f -print | sed -e 's/^\.\///'
+ ) | (
+ done=f
+ while read name
+ do
+ test t = $done && continue
+ branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
+ if test "$head_sha1" = "$branch_tip"
+ then
+ echo "$name"
+ done=t
+ fi
+ done
+ )
+ )
+
+ # Upstream URL
+ git config remote."$origin".url "$repo" &&
+
+ # Set up the mappings to track the remote branches.
+ git config remote."$origin".fetch \
+ "+refs/heads/*:$remote_top/*" '^$' &&
+
+ # Write out remote.$origin config, and update our "$head_points_at".
+ case "$head_points_at" in
+ ?*)
+ # Local default branch
+ git symbolic-ref HEAD "refs/heads/$head_points_at" &&
+
+ # Tracking branch for the primary branch at the remote.
+ git update-ref HEAD "$head_sha1" &&
+
+ rm -f "refs/remotes/$origin/HEAD"
+ git symbolic-ref "refs/remotes/$origin/HEAD" \
+ "refs/remotes/$origin/$head_points_at" &&
+
+ git config branch."$head_points_at".remote "$origin" &&
+ git config branch."$head_points_at".merge "refs/heads/$head_points_at"
+ ;;
+ '')
+ if test -z "$head_sha1"
+ then
+ # Source had nonexistent ref in HEAD
+ echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
+ no_checkout=t
+ else
+ # Source had detached HEAD pointing nowhere
+ git update-ref --no-deref HEAD "$head_sha1" &&
+ rm -f "refs/remotes/$origin/HEAD"
+ fi
+ ;;
+ esac
+
+ case "$no_checkout" in
+ '')
+ test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
+ git read-tree -m -u $v HEAD HEAD
+ esac
+fi
+rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
+
+trap - 0
/* This is set by setup_git_dir_gently() and/or git_default_config() */
char *git_work_tree_cfg;
-static const char *work_tree;
+static char *work_tree;
static const char *git_dir;
static char *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file;
return git_dir;
}
+static int git_work_tree_initialized;
+
+/*
+ * Note. This works only before you used a work tree. This was added
+ * primarily to support git-clone to work in a new repository it just
+ * created, and is not meant to flip between different work trees.
+ */
+void set_git_work_tree(const char *new_work_tree)
+{
+ if (is_bare_repository_cfg >= 0)
+ die("cannot set work tree after initialization");
+ git_work_tree_initialized = 1;
+ free(work_tree);
+ work_tree = xstrdup(make_absolute_path(new_work_tree));
+ is_bare_repository_cfg = 0;
+}
+
const char *get_git_work_tree(void)
{
- static int initialized = 0;
- if (!initialized) {
+ if (!git_work_tree_initialized) {
work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
/* core.bare = true overrides implicit and config work tree */
if (!work_tree && is_bare_repository_cfg < 1) {
work_tree = xstrdup(make_absolute_path(git_path(work_tree)));
} else if (work_tree)
work_tree = xstrdup(make_absolute_path(work_tree));
- initialized = 1;
+ git_work_tree_initialized = 1;
if (work_tree)
is_bare_repository_cfg = 0;
}
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (c) 2005, Linus Torvalds
-# Copyright (c) 2005, Junio C Hamano
-#
-# Clone a repository into a different directory that does not yet exist.
-
-# See git-sh-setup why.
-unset CDPATH
-
-OPTIONS_SPEC="\
-git-clone [options] [--] <repo> [<dir>]
---
-n,no-checkout don't create a checkout
-bare create a bare repository
-naked create a bare repository
-l,local to clone from a local repository
-no-hardlinks don't use local hardlinks, always copy
-s,shared setup as a shared repository
-template= path to the template directory
-q,quiet be quiet
-reference= reference repository
-o,origin= use <name> instead of 'origin' to track upstream
-u,upload-pack= path to git-upload-pack on the remote
-depth= create a shallow clone of that depth
-
-use-separate-remote compatibility, do not use
-no-separate-remote compatibility, do not use"
-
-die() {
- echo >&2 "$@"
- exit 1
-}
-
-usage() {
- exec "$0" -h
-}
-
-eval "$(echo "$OPTIONS_SPEC" | git rev-parse --parseopt -- "$@" || echo exit $?)"
-
-get_repo_base() {
- (
- cd "`/bin/pwd`" &&
- cd "$1" || cd "$1.git" &&
- {
- cd .git
- pwd
- }
- ) 2>/dev/null
-}
-
-if [ -n "$GIT_SSL_NO_VERIFY" -o \
- "`git config --bool http.sslVerify`" = false ]; then
- curl_extra_args="-k"
-fi
-
-http_fetch () {
- # $1 = Remote, $2 = Local
- curl -nsfL $curl_extra_args "$1" >"$2"
- curl_exit_status=$?
- case $curl_exit_status in
- 126|127) exit ;;
- *) return $curl_exit_status ;;
- esac
-}
-
-clone_dumb_http () {
- # $1 - remote, $2 - local
- cd "$2" &&
- clone_tmp="$GIT_DIR/clone-tmp" &&
- mkdir -p "$clone_tmp" || exit 1
- if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
- "`git config --bool http.noEPSV`" = true ]; then
- curl_extra_args="${curl_extra_args} --disable-epsv"
- fi
- http_fetch "$1/info/refs" "$clone_tmp/refs" ||
- die "Cannot get remote repository information.
-Perhaps git-update-server-info needs to be run there?"
- test "z$quiet" = z && v=-v || v=
- while read sha1 refname
- do
- name=`expr "z$refname" : 'zrefs/\(.*\)'` &&
- case "$name" in
- *^*) continue;;
- esac
- case "$bare,$name" in
- yes,* | ,heads/* | ,tags/*) ;;
- *) continue ;;
- esac
- if test -n "$use_separate_remote" &&
- branch_name=`expr "z$name" : 'zheads/\(.*\)'`
- then
- tname="remotes/$origin/$branch_name"
- else
- tname=$name
- fi
- git-http-fetch $v -a -w "$tname" "$sha1" "$1" || exit 1
- done <"$clone_tmp/refs"
- rm -fr "$clone_tmp"
- http_fetch "$1/HEAD" "$GIT_DIR/REMOTE_HEAD" ||
- rm -f "$GIT_DIR/REMOTE_HEAD"
- if test -f "$GIT_DIR/REMOTE_HEAD"; then
- head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
- case "$head_sha1" in
- 'ref: refs/'*)
- ;;
- *)
- git-http-fetch $v -a "$head_sha1" "$1" ||
- rm -f "$GIT_DIR/REMOTE_HEAD"
- ;;
- esac
- fi
-}
-
-quiet=
-local=no
-use_local_hardlink=yes
-local_shared=no
-unset template
-no_checkout=
-upload_pack=
-bare=
-reference=
-origin=
-origin_override=
-use_separate_remote=t
-depth=
-no_progress=
-local_explicitly_asked_for=
-test -t 1 || no_progress=--no-progress
-
-while test $# != 0
-do
- case "$1" in
- -n|--no-checkout)
- no_checkout=yes ;;
- --naked|--bare)
- bare=yes ;;
- -l|--local)
- local_explicitly_asked_for=yes
- use_local_hardlink=yes
- ;;
- --no-hardlinks)
- use_local_hardlink=no ;;
- -s|--shared)
- local_shared=yes ;;
- --template)
- shift; template="--template=$1" ;;
- -q|--quiet)
- quiet=-q ;;
- --use-separate-remote|--no-separate-remote)
- die "clones are always made with separate-remote layout" ;;
- --reference)
- shift; reference="$1" ;;
- -o|--origin)
- shift;
- case "$1" in
- '')
- usage ;;
- */*)
- die "'$1' is not suitable for an origin name"
- esac
- git check-ref-format "heads/$1" ||
- die "'$1' is not suitable for a branch name"
- test -z "$origin_override" ||
- die "Do not give more than one --origin options."
- origin_override=yes
- origin="$1"
- ;;
- -u|--upload-pack)
- shift
- upload_pack="--upload-pack=$1" ;;
- --depth)
- shift
- depth="--depth=$1" ;;
- --)
- shift
- break ;;
- *)
- usage ;;
- esac
- shift
-done
-
-repo="$1"
-test -n "$repo" ||
- die 'you must specify a repository to clone.'
-
-# --bare implies --no-checkout and --no-separate-remote
-if test yes = "$bare"
-then
- if test yes = "$origin_override"
- then
- die '--bare and --origin $origin options are incompatible.'
- fi
- no_checkout=yes
- use_separate_remote=
-fi
-
-if test -z "$origin"
-then
- origin=origin
-fi
-
-# Turn the source into an absolute path if
-# it is local
-if base=$(get_repo_base "$repo"); then
- repo="$base"
- if test -z "$depth"
- then
- local=yes
- fi
-elif test -f "$repo"
-then
- case "$repo" in /*) ;; *) repo="$PWD/$repo" ;; esac
-fi
-
-# Decide the directory name of the new repository
-if test -n "$2"
-then
- dir="$2"
- test $# = 2 || die "excess parameter to git-clone"
-else
- # Derive one from the repository name
- # Try using "humanish" part of source repo if user didn't specify one
- if test -f "$repo"
- then
- # Cloning from a bundle
- dir=$(echo "$repo" | sed -e 's|/*\.bundle$||' -e 's|.*/||g')
- else
- dir=$(echo "$repo" |
- sed -e 's|/$||' -e 's|:*/*\.git$||' -e 's|.*[/:]||g')
- fi
-fi
-
-[ -e "$dir" ] && die "destination directory '$dir' already exists."
-[ yes = "$bare" ] && unset GIT_WORK_TREE
-[ -n "$GIT_WORK_TREE" ] && [ -e "$GIT_WORK_TREE" ] &&
-die "working tree '$GIT_WORK_TREE' already exists."
-D=
-W=
-cleanup() {
- test -z "$D" && rm -rf "$dir"
- test -z "$W" && test -n "$GIT_WORK_TREE" && rm -rf "$GIT_WORK_TREE"
- cd ..
- test -n "$D" && rm -rf "$D"
- test -n "$W" && rm -rf "$W"
- exit $err
-}
-trap 'err=$?; cleanup' 0
-mkdir -p "$dir" && D=$(cd "$dir" && pwd) || usage
-test -n "$GIT_WORK_TREE" && mkdir -p "$GIT_WORK_TREE" &&
-W=$(cd "$GIT_WORK_TREE" && pwd) && GIT_WORK_TREE="$W" && export GIT_WORK_TREE
-if test yes = "$bare" || test -n "$GIT_WORK_TREE"; then
- GIT_DIR="$D"
-else
- GIT_DIR="$D/.git"
-fi &&
-export GIT_DIR &&
-GIT_CONFIG="$GIT_DIR/config" git-init $quiet ${template+"$template"} || usage
-
-if test -n "$bare"
-then
- GIT_CONFIG="$GIT_DIR/config" git config core.bare true
-fi
-
-if test -n "$reference"
-then
- ref_git=
- if test -d "$reference"
- then
- if test -d "$reference/.git/objects"
- then
- ref_git="$reference/.git"
- elif test -d "$reference/objects"
- then
- ref_git="$reference"
- fi
- fi
- if test -n "$ref_git"
- then
- ref_git=$(cd "$ref_git" && pwd)
- echo "$ref_git/objects" >"$GIT_DIR/objects/info/alternates"
- (
- GIT_DIR="$ref_git" git for-each-ref \
- --format='%(objectname) %(*objectname)'
- ) |
- while read a b
- do
- test -z "$a" ||
- git update-ref "refs/reference-tmp/$a" "$a"
- test -z "$b" ||
- git update-ref "refs/reference-tmp/$b" "$b"
- done
- else
- die "reference repository '$reference' is not a local directory."
- fi
-fi
-
-rm -f "$GIT_DIR/CLONE_HEAD"
-
-# We do local magic only when the user tells us to.
-case "$local" in
-yes)
- ( cd "$repo/objects" ) ||
- die "cannot chdir to local '$repo/objects'."
-
- if test "$local_shared" = yes
- then
- mkdir -p "$GIT_DIR/objects/info"
- echo "$repo/objects" >>"$GIT_DIR/objects/info/alternates"
- else
- cpio_quiet_flag=""
- cpio --help 2>&1 | grep -- --quiet >/dev/null && \
- cpio_quiet_flag=--quiet
- l= &&
- if test "$use_local_hardlink" = yes
- then
- # See if we can hardlink and drop "l" if not.
- sample_file=$(cd "$repo" && \
- find objects -type f -print | sed -e 1q)
- # objects directory should not be empty because
- # we are cloning!
- test -f "$repo/$sample_file" ||
- die "fatal: cannot clone empty repository"
- if ln "$repo/$sample_file" "$GIT_DIR/objects/sample" 2>/dev/null
- then
- rm -f "$GIT_DIR/objects/sample"
- l=l
- elif test -n "$local_explicitly_asked_for"
- then
- echo >&2 "Warning: -l asked but cannot hardlink to $repo"
- fi
- fi &&
- cd "$repo" &&
- # Create dirs using umask and permissions and destination
- find objects -type d -print | (cd "$GIT_DIR" && xargs mkdir -p) &&
- # Copy existing 0444 permissions on content
- find objects ! -type d -print | cpio $cpio_quiet_flag -pumd$l "$GIT_DIR/" || \
- exit 1
- fi
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
- ;;
-*)
- case "$repo" in
- rsync://*)
- case "$depth" in
- "") ;;
- *) die "shallow over rsync not supported" ;;
- esac
- rsync $quiet -av --ignore-existing \
- --exclude info "$repo/objects/" "$GIT_DIR/objects/" ||
- exit
- # Look at objects/info/alternates for rsync -- http will
- # support it natively and git native ones will do it on the
- # remote end. Not having that file is not a crime.
- rsync -q "$repo/objects/info/alternates" \
- "$GIT_DIR/TMP_ALT" 2>/dev/null ||
- rm -f "$GIT_DIR/TMP_ALT"
- if test -f "$GIT_DIR/TMP_ALT"
- then
- ( cd "$D" &&
- . git-parse-remote &&
- resolve_alternates "$repo" <"$GIT_DIR/TMP_ALT" ) |
- while read alt
- do
- case "$alt" in 'bad alternate: '*) die "$alt";; esac
- case "$quiet" in
- '') echo >&2 "Getting alternate: $alt" ;;
- esac
- rsync $quiet -av --ignore-existing \
- --exclude info "$alt" "$GIT_DIR/objects" || exit
- done
- rm -f "$GIT_DIR/TMP_ALT"
- fi
- git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1
- ;;
- https://*|http://*|ftp://*)
- case "$depth" in
- "") ;;
- *) die "shallow over http or ftp not supported" ;;
- esac
- if test -z "@@NO_CURL@@"
- then
- clone_dumb_http "$repo" "$D"
- else
- die "http transport not supported, rebuild Git with curl support"
- fi
- ;;
- *)
- if [ -f "$repo" ] ; then
- git bundle unbundle "$repo" > "$GIT_DIR/CLONE_HEAD" ||
- die "unbundle from '$repo' failed."
- else
- case "$upload_pack" in
- '') git-fetch-pack --all -k $quiet $depth $no_progress "$repo";;
- *) git-fetch-pack --all -k \
- $quiet "$upload_pack" $depth $no_progress "$repo" ;;
- esac >"$GIT_DIR/CLONE_HEAD" ||
- die "fetch-pack from '$repo' failed."
- fi
- ;;
- esac
- ;;
-esac
-test -d "$GIT_DIR/refs/reference-tmp" && rm -fr "$GIT_DIR/refs/reference-tmp"
-
-if test -f "$GIT_DIR/CLONE_HEAD"
-then
- # Read git-fetch-pack -k output and store the remote branches.
- if [ -n "$use_separate_remote" ]
- then
- branch_top="remotes/$origin"
- else
- branch_top="heads"
- fi
- tag_top="tags"
- while read sha1 name
- do
- case "$name" in
- *'^{}')
- continue ;;
- HEAD)
- destname="REMOTE_HEAD" ;;
- refs/heads/*)
- destname="refs/$branch_top/${name#refs/heads/}" ;;
- refs/tags/*)
- destname="refs/$tag_top/${name#refs/tags/}" ;;
- *)
- continue ;;
- esac
- git update-ref -m "clone: from $repo" "$destname" "$sha1" ""
- done < "$GIT_DIR/CLONE_HEAD"
-fi
-
-if test -n "$W"; then
- cd "$W" || exit
-else
- cd "$D" || exit
-fi
-
-if test -z "$bare"
-then
- # a non-bare repository is always in separate-remote layout
- remote_top="refs/remotes/$origin"
- head_sha1=
- test ! -r "$GIT_DIR/REMOTE_HEAD" || head_sha1=`cat "$GIT_DIR/REMOTE_HEAD"`
- case "$head_sha1" in
- 'ref: refs/'*)
- # Uh-oh, the remote told us (http transport done against
- # new style repository with a symref HEAD).
- # Ideally we should skip the guesswork but for now
- # opt for minimum change.
- head_sha1=`expr "z$head_sha1" : 'zref: refs/heads/\(.*\)'`
- head_sha1=`cat "$GIT_DIR/$remote_top/$head_sha1"`
- ;;
- esac
-
- # The name under $remote_top the remote HEAD seems to point at.
- head_points_at=$(
- (
- test -f "$GIT_DIR/$remote_top/master" && echo "master"
- cd "$GIT_DIR/$remote_top" &&
- find . -type f -print | sed -e 's/^\.\///'
- ) | (
- done=f
- while read name
- do
- test t = $done && continue
- branch_tip=`cat "$GIT_DIR/$remote_top/$name"`
- if test "$head_sha1" = "$branch_tip"
- then
- echo "$name"
- done=t
- fi
- done
- )
- )
-
- # Upstream URL
- git config remote."$origin".url "$repo" &&
-
- # Set up the mappings to track the remote branches.
- git config remote."$origin".fetch \
- "+refs/heads/*:$remote_top/*" '^$' &&
-
- # Write out remote.$origin config, and update our "$head_points_at".
- case "$head_points_at" in
- ?*)
- # Local default branch
- git symbolic-ref HEAD "refs/heads/$head_points_at" &&
-
- # Tracking branch for the primary branch at the remote.
- git update-ref HEAD "$head_sha1" &&
-
- rm -f "refs/remotes/$origin/HEAD"
- git symbolic-ref "refs/remotes/$origin/HEAD" \
- "refs/remotes/$origin/$head_points_at" &&
-
- git config branch."$head_points_at".remote "$origin" &&
- git config branch."$head_points_at".merge "refs/heads/$head_points_at"
- ;;
- '')
- if test -z "$head_sha1"
- then
- # Source had nonexistent ref in HEAD
- echo >&2 "Warning: Remote HEAD refers to nonexistent ref, unable to checkout."
- no_checkout=t
- else
- # Source had detached HEAD pointing nowhere
- git update-ref --no-deref HEAD "$head_sha1" &&
- rm -f "refs/remotes/$origin/HEAD"
- fi
- ;;
- esac
-
- case "$no_checkout" in
- '')
- test "z$quiet" = z -a "z$no_progress" = z && v=-v || v=
- git read-tree -m -u $v HEAD HEAD
- esac
-fi
-rm -f "$GIT_DIR/CLONE_HEAD" "$GIT_DIR/REMOTE_HEAD"
-
-trap - 0
{ "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },
{ "cherry", cmd_cherry, RUN_SETUP },
{ "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },
+ { "clone", cmd_clone },
{ "clean", cmd_clean, RUN_SETUP | NEED_WORK_TREE },
{ "commit", cmd_commit, RUN_SETUP | NEED_WORK_TREE },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
return fd;
}
+int hold_lock_file_for_append(struct lock_file *lk, const char *path, int die_on_error)
+{
+ int fd, orig_fd;
+
+ fd = lock_file(lk, path);
+ if (fd < 0) {
+ if (die_on_error)
+ die("unable to create '%s.lock': %s", path, strerror(errno));
+ return fd;
+ }
+
+ orig_fd = open(path, O_RDONLY);
+ if (orig_fd < 0) {
+ if (errno != ENOENT) {
+ if (die_on_error)
+ die("cannot open '%s' for copying", path);
+ close(fd);
+ return error("cannot open '%s' for copying", path);
+ }
+ } else if (copy_fd(orig_fd, fd)) {
+ if (die_on_error)
+ exit(128);
+ close(fd);
+ return -1;
+ }
+ return fd;
+}
+
int close_lock_file(struct lock_file *lk)
{
int fd = lk->fd;
} cached_refs;
static struct ref_list *current_ref;
+static struct ref_list *extra_refs;
+
static void free_ref_list(struct ref_list *list)
{
struct ref_list *next;
cached_refs->packed = sort_ref_list(list);
}
+void add_extra_ref(const char *name, const unsigned char *sha1, int flag)
+{
+ extra_refs = add_ref(name, sha1, flag, extra_refs, NULL);
+}
+
+void clear_extra_refs(void)
+{
+ free_ref_list(extra_refs);
+ extra_refs = NULL;
+}
+
static struct ref_list *get_packed_refs(void)
{
if (!cached_refs.did_packed) {
struct ref_list *packed = get_packed_refs();
struct ref_list *loose = get_loose_refs();
+ struct ref_list *extra;
+
+ for (extra = extra_refs; extra; extra = extra->next)
+ retval = do_one_ref(base, fn, trim, cb_data, extra);
+
while (packed && loose) {
struct ref_list *entry;
int cmp = strcmp(packed->name, loose->name);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
+/*
+ * Extra refs will be listed by for_each_ref() before any actual refs
+ * for the duration of this process or until clear_extra_refs() is
+ * called. Only extra refs added before for_each_ref() is called will
+ * be listed on a given call of for_each_ref().
+ */
+extern void add_extra_ref(const char *refname, const unsigned char *sha1, int flags);
+extern void clear_extra_refs(void);
+
extern int peel_ref(const char *, unsigned char *);
/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
#include "remote.h"
#include "refs.h"
+static struct refspec s_tag_refspec = {
+ 0,
+ 1,
+ 0,
+ "refs/tags/",
+ "refs/tags/"
+};
+
+const struct refspec *tag_refspec = &s_tag_refspec;
+
struct counted_string {
size_t len;
const char *s;
char *dst;
};
+extern const struct refspec *tag_refspec;
+
struct ref *alloc_ref(unsigned namelen);
struct ref *alloc_ref_from_str(const char* str);
munmap(map, mapsz);
}
+void add_to_alternates_file(const char *reference)
+{
+ struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+ int fd = hold_lock_file_for_append(lock, git_path("objects/info/alternates"), 1);
+ char *alt = mkpath("%s/objects\n", reference);
+ write_or_die(fd, alt, strlen(alt));
+ if (commit_lock_file(lock))
+ die("could not close alternates file");
+ if (alt_odb_tail)
+ link_alt_odb_entries(alt, alt + strlen(alt), '\n', NULL, 0);
+}
+
void prepare_alt_odb(void)
{
const char *alt;
'
+test_expect_success 'clone checks out files' '
+
+ git clone src dst &&
+ test -f dst/file
+
+'
+
test_done
base_dir=`pwd`
+U=$base_dir/UPLOAD_LOG
+
test_expect_success 'preparing first repository' \
'test_create_repo A && cd A &&
echo first > file1 &&
cd "$base_dir"
+rm -f $U
+
test_expect_success 'cloning with reference (no -l -s)' \
-'git clone --reference B "file://$(pwd)/A" D'
+'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>$U'
+
+test_expect_success 'fetched no objects' \
+'! grep "^want" $U'
cd "$base_dir"
cd "$base_dir"
+test_expect_success 'preparing alternate repository #1' \
+'test_create_repo F && cd F &&
+echo first > file1 &&
+git add file1 &&
+git commit -m initial'
+
+cd "$base_dir"
+
+test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' \
+'git clone F G && cd F &&
+echo second > file2 &&
+git add file2 &&
+git commit -m addition'
+
+cd "$base_dir"
+
+test_expect_success 'cloning alternate repo #1, using #2 as reference' \
+'git clone --reference G F H'
+
+cd "$base_dir"
+
+test_expect_success 'cloning with reference being subset of source (-l -s)' \
+'git clone -l -s --reference A B E'
+
+cd "$base_dir"
+
test_done
}
static int fetch_objs_via_rsync(struct transport *transport,
- int nr_objs, struct ref **to_fetch)
+ int nr_objs, const struct ref **to_fetch)
{
struct strbuf buf = STRBUF_INIT;
struct child_process rsync;
#ifndef NO_CURL /* http fetch is the only user */
static int fetch_objs_via_walker(struct transport *transport,
- int nr_objs, struct ref **to_fetch)
+ int nr_objs, const struct ref **to_fetch)
{
char *dest = xstrdup(transport->url);
struct walker *walker = transport->data;
}
static int fetch_objs_via_curl(struct transport *transport,
- int nr_objs, struct ref **to_fetch)
+ int nr_objs, const struct ref **to_fetch)
{
if (!transport->data)
transport->data = get_http_walker(transport->url,
}
static int fetch_refs_from_bundle(struct transport *transport,
- int nr_heads, struct ref **to_fetch)
+ int nr_heads, const struct ref **to_fetch)
{
struct bundle_transport_data *data = transport->data;
return unbundle(&data->header, data->fd);
}
static int fetch_refs_via_pack(struct transport *transport,
- int nr_heads, struct ref **to_fetch)
+ int nr_heads, const struct ref **to_fetch)
{
struct git_transport_data *data = transport->data;
char **heads = xmalloc(nr_heads * sizeof(*heads));
return transport->remote_refs;
}
-int transport_fetch_refs(struct transport *transport, struct ref *refs)
+int transport_fetch_refs(struct transport *transport, const struct ref *refs)
{
int rc;
int nr_heads = 0, nr_alloc = 0;
- struct ref **heads = NULL;
- struct ref *rm;
+ const struct ref **heads = NULL;
+ const struct ref *rm;
for (rm = refs; rm; rm = rm->next) {
if (rm->peer_ref &&
const char *value);
struct ref *(*get_refs_list)(struct transport *transport);
- int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
+ int (*fetch)(struct transport *transport, int refs_nr, const struct ref **refs);
int (*push)(struct transport *connection, int refspec_nr, const char **refspec, int flags);
int (*disconnect)(struct transport *connection);
const struct ref *transport_get_remote_refs(struct transport *transport);
-int transport_fetch_refs(struct transport *transport, struct ref *refs);
+int transport_fetch_refs(struct transport *transport, const struct ref *refs);
void transport_unlock_pack(struct transport *transport);
int transport_disconnect(struct transport *transport);