{"thread":{"id":"13295","subject":"[PATCH 8/8] Build in clone","startedAt":"2008-04-27T17:39:30Z","lastAt":"2008-04-27T17:39:30Z","messageCount":1,"participants":["Daniel Barkalow"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"75316","messageId":"alpine.LNX.1.00.0804271311570.19665@iabervon.org","threadId":"13295","inReplyTo":null,"subject":"[PATCH 8/8] Build in clone","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2008-04-27T17:39:30Z","receivedAt":"2008-04-27T17:39:30Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"Thanks to Johannes Schindelin for various comments and improvements,\nincluding supporting cloning full bundles.\n---\nThis also pretty much requires my db/learn-HEAD series in order to have \nhttp URLs work right, since getting HEAD is more important in clone than \nfetch.\n\n Makefile                                      |    2 +-\n builtin-clone.c                               |  544 +++++++++++++++++++++++++\n builtin.h                                     |    1 +\n git-clone.sh => contrib/examples/git-clone.sh |    0 \n git.c                                         |    1 +\n 5 files changed, 547 insertions(+), 1 deletions(-)\n create mode 100644 builtin-clone.c\n rename git-clone.sh => contrib/examples/git-clone.sh (100%)\n\ndiff --git a/Makefile b/Makefile\nindex 9d84c8d..d5f763b 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -235,7 +235,6 @@ BASIC_LDFLAGS =\n \n SCRIPT_SH += git-am.sh\n SCRIPT_SH += git-bisect.sh\n-SCRIPT_SH += git-clone.sh\n SCRIPT_SH += git-filter-branch.sh\n SCRIPT_SH += git-lost-found.sh\n SCRIPT_SH += git-merge-octopus.sh\n@@ -481,6 +480,7 @@ BUILTIN_OBJS += builtin-check-ref-format.o\n BUILTIN_OBJS += builtin-checkout-index.o\n BUILTIN_OBJS += builtin-checkout.o\n BUILTIN_OBJS += builtin-clean.o\n+BUILTIN_OBJS += builtin-clone.o\n BUILTIN_OBJS += builtin-commit-tree.o\n BUILTIN_OBJS += builtin-commit.o\n BUILTIN_OBJS += builtin-config.o\ndiff --git a/builtin-clone.c b/builtin-clone.c\nnew file mode 100644\nindex 0000000..a7c075d\n--- /dev/null\n+++ b/builtin-clone.c\n@@ -0,0 +1,544 @@\n+/*\n+ * Builtin \"git clone\"\n+ *\n+ * Copyright (c) 2007 Kristian Høgsberg <krh@redhat.com>,\n+ *\t\t 2008 Daniel Barkalow <barkalow@iabervon.org>\n+ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds\n+ *\n+ * Clone a repository into a different directory that does not yet exist.\n+ */\n+\n+#include \"cache.h\"\n+#include \"parse-options.h\"\n+#include \"fetch-pack.h\"\n+#include \"refs.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"unpack-trees.h\"\n+#include \"transport.h\"\n+#include \"strbuf.h\"\n+#include \"dir.h\"\n+\n+/*\n+ * Overall FIXMEs:\n+ *  - respect DB_ENVIRONMENT for .git/objects.\n+ *\n+ * Implementation notes:\n+ *  - dropping use-separate-remote and no-separate-remote compatibility\n+ *\n+ */\n+static const char * const builtin_clone_usage[] = {\n+\t\"git-clone [options] [--] <repo> [<dir>]\",\n+\tNULL\n+};\n+\n+static int option_quiet, option_no_checkout, option_bare;\n+static int option_local, option_no_hardlinks, option_shared;\n+static char *option_template, *option_reference, *option_depth;\n+static char *option_origin = NULL;\n+static char *option_upload_pack = \"git-upload-pack\";\n+\n+static struct option builtin_clone_options[] = {\n+\tOPT__QUIET(&option_quiet),\n+\tOPT_BOOLEAN('n', \"no-checkout\", &option_no_checkout,\n+\t\t    \"don't create a checkout\"),\n+\tOPT_BOOLEAN(0, \"bare\", &option_bare, \"create a bare repository\"),\n+\tOPT_BOOLEAN(0, \"naked\", &option_bare, \"create a bare repository\"),\n+\tOPT_BOOLEAN('l', \"local\", &option_local,\n+\t\t    \"to clone from a local repository\"),\n+\tOPT_BOOLEAN(0, \"no-hardlinks\", &option_no_hardlinks,\n+\t\t    \"don't use local hardlinks, always copy\"),\n+\tOPT_BOOLEAN('s', \"shared\", &option_shared,\n+\t\t    \"setup as shared repository\"),\n+\tOPT_STRING(0, \"template\", &option_template, \"path\",\n+\t\t   \"path the template repository\"),\n+\tOPT_STRING(0, \"reference\", &option_reference, \"repo\",\n+\t\t   \"reference repository\"),\n+\tOPT_STRING('o', \"origin\", &option_origin, \"branch\",\n+\t\t   \"use <branch> instead or 'origin' to track upstream\"),\n+\tOPT_STRING('u', \"upload-pack\", &option_upload_pack, \"path\",\n+\t\t   \"path to git-upload-pack on the remote\"),\n+\tOPT_STRING(0, \"depth\", &option_depth, \"depth\",\n+\t\t    \"create a shallow clone of that depth\"),\n+\n+\tOPT_END()\n+};\n+\n+static char *get_repo_path(const char *repo, int *is_bundle)\n+{\n+\tstatic char *suffix[] = { \"/.git\", \".git\", \"\" };\n+\tstatic char *bundle_suffix[] = { \".bundle\", \"\" };\n+\tstruct stat st;\n+\tint i;\n+\n+\tfor (i = 0; i < ARRAY_SIZE(suffix); i++) {\n+\t\tconst char *path;\n+\t\tpath = mkpath(\"%s%s\", repo, suffix[i]);\n+\t\tif (!stat(path, &st) && S_ISDIR(st.st_mode)) {\n+\t\t\t*is_bundle = 0;\n+\t\t\treturn xstrdup(make_absolute_path(path));\n+\t\t}\n+\t}\n+\n+\tfor (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {\n+\t\tconst char *path;\n+\t\tpath = mkpath(\"%s%s\", repo, bundle_suffix[i]);\n+\t\tif (!stat(path, &st) && S_ISREG(st.st_mode)) {\n+\t\t\t*is_bundle = 1;\n+\t\t\treturn xstrdup(make_absolute_path(path));\n+\t\t}\n+\t}\n+\n+\treturn NULL;\n+}\n+\n+static char *guess_dir_name(const char *repo, int is_bundle)\n+{\n+\tconst char *p, *start, *end, *limit;\n+\tint after_slash_or_colon;\n+\n+\t/* Guess dir name from repository: strip trailing '/',\n+\t * strip trailing '[:/]*.{git,bundle}', strip leading '.*[/:]'. */\n+\n+\tafter_slash_or_colon = 1;\n+\tlimit = repo + strlen(repo);\n+\tstart = repo;\n+\tend = limit;\n+\tfor (p = repo; p < limit; p++) {\n+\t\tconst char *prefix = is_bundle ? \".bundle\" : \".git\";\n+\t\tif (!prefixcmp(p, prefix)) {\n+\t\t\tif (!after_slash_or_colon)\n+\t\t\t\tend = p;\n+\t\t\tp += strlen(prefix) - 1;\n+\t\t} else if (!prefixcmp(p, \".bundle\")) {\n+\t\t\tif (!after_slash_or_colon)\n+\t\t\t\tend = p;\n+\t\t\tp += 7;\n+\t\t} else if (*p == '/' || *p == ':') {\n+\t\t\tif (end == limit)\n+\t\t\t\tend = p;\n+\t\t\tafter_slash_or_colon = 1;\n+\t\t} else if (after_slash_or_colon) {\n+\t\t\tstart = p;\n+\t\t\tend = limit;\n+\t\t\tafter_slash_or_colon = 0;\n+\t\t}\n+\t}\n+\n+\treturn xstrndup(start, end - start);\n+}\n+\n+static int is_directory(const char *path)\n+{\n+\tstruct stat buf;\n+\n+\treturn !stat(path, &buf) && S_ISDIR(buf.st_mode);\n+}\n+\n+static void setup_reference(const char *repo)\n+{\n+\tconst char *ref_git;\n+\tchar *ref_git_copy;\n+\n+\tstruct remote *remote;\n+\tstruct transport *transport;\n+\tconst struct ref *extra;\n+\n+\tref_git = make_absolute_path(option_reference);\n+\n+\tif (is_directory(mkpath(\"%s/.git/objects\", ref_git)))\n+\t\tref_git = mkpath(\"%s/.git\", ref_git);\n+\telse if (!is_directory(mkpath(\"%s/objects\", ref_git)))\n+\t\tdie(\"reference repository '%s' is not a local directory.\",\n+\t\t    option_reference);\n+\n+\tref_git_copy = xstrdup(ref_git);\n+\n+\tadd_to_alternates_file(ref_git_copy);\n+\n+\tremote = remote_get(ref_git_copy);\n+\ttransport = transport_get(remote, ref_git_copy);\n+\tfor (extra = transport_get_remote_refs(transport); extra;\n+\t     extra = extra->next)\n+\t\tadd_extra_ref(extra->name, extra->old_sha1, 0);\n+\n+\ttransport_disconnect(transport);\n+\n+\tfree(ref_git_copy);\n+}\n+\n+static void copy_or_link_directory(char *src, char *dest)\n+{\n+\tstruct dirent *de;\n+\tstruct stat buf;\n+\tint src_len, dest_len;\n+\tDIR *dir;\n+\n+\tdir = opendir(src);\n+\tif (!dir)\n+\t\tdie(\"failed to open %s\\n\", src);\n+\n+\tif (mkdir(dest, 0777)) {\n+\t\tif (errno != EEXIST)\n+\t\t\tdie(\"failed to create directory %s\\n\", dest);\n+\t\telse if (stat(dest, &buf))\n+\t\t\tdie(\"failed to stat %s\\n\", dest);\n+\t\telse if (!S_ISDIR(buf.st_mode))\n+\t\t\tdie(\"%s exists and is not a directory\\n\", dest);\n+\t}\n+\n+\tsrc_len = strlen(src);\n+\tsrc[src_len] = '/';\n+\tdest_len = strlen(dest);\n+\tdest[dest_len] = '/';\n+\n+\twhile ((de = readdir(dir)) != NULL) {\n+\t\tstrcpy(src + src_len + 1, de->d_name);\n+\t\tstrcpy(dest + dest_len + 1, de->d_name);\n+\t\tif (stat(src, &buf)) {\n+\t\t\twarning (\"failed to stat %s\\n\", src);\n+\t\t\tcontinue;\n+\t\t}\n+\t\tif (S_ISDIR(buf.st_mode)) {\n+\t\t\tif (de->d_name[0] != '.')\n+\t\t\t\tcopy_or_link_directory(src, dest);\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tif (unlink(dest) && errno != ENOENT)\n+\t\t\tdie(\"failed to unlink %s\\n\", dest);\n+\t\tif (option_no_hardlinks) {\n+\t\t\tif (copy_file(dest, src, 0666))\n+\t\t\t\tdie(\"failed to copy file to %s\\n\", dest);\n+\t\t} else {\n+\t\t\tif (link(src, dest))\n+\t\t\t\tdie(\"failed to create link %s\\n\", dest);\n+\t\t}\n+\t}\n+}\n+\n+static const struct ref *clone_local(const char *src_repo,\n+\t\t\t\t     const char *dest_repo)\n+{\n+\tconst struct ref *ret;\n+\tchar src[PATH_MAX];\n+\tchar dest[PATH_MAX];\n+\tstruct remote *remote;\n+\tstruct transport *transport;\n+\n+\tif (option_shared)\n+\t\tadd_to_alternates_file(src_repo);\n+\telse {\n+\t\tsnprintf(src, PATH_MAX, \"%s/objects\", src_repo);\n+\t\tsnprintf(dest, PATH_MAX, \"%s/objects\", dest_repo);\n+\t\tcopy_or_link_directory(src, dest);\n+\t}\n+\n+\tremote = remote_get(src_repo);\n+\ttransport = transport_get(remote, src_repo);\n+\tret = transport_get_remote_refs(transport);\n+\ttransport_disconnect(transport);\n+\treturn ret;\n+}\n+\n+static const char *junk_work_tree;\n+static const char *junk_git_dir;\n+pid_t junk_pid;\n+\n+static void remove_junk(void)\n+{\n+\tstruct strbuf sb;\n+\tif (getpid() != junk_pid)\n+\t\treturn;\n+\tstrbuf_init(&sb, 0);\n+\tif (junk_git_dir) {\n+\t\tstrbuf_addstr(&sb, junk_git_dir);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+\tif (junk_work_tree) {\n+\t\tstrbuf_addstr(&sb, junk_work_tree);\n+\t\tremove_dir_recursively(&sb, 0);\n+\t\tstrbuf_reset(&sb);\n+\t}\n+}\n+\n+static void remove_junk_on_signal(int signo)\n+{\n+\tremove_junk();\n+\tsignal(SIGINT, SIG_DFL);\n+\traise(signo);\n+}\n+\n+static const struct ref *locate_head(const struct ref *refs,\n+\t\t\t\t     const struct ref *mapped_refs,\n+\t\t\t\t     const struct ref **remote_head_p)\n+{\n+\tconst struct ref *remote_head = NULL;\n+\tconst struct ref *remote_master = NULL;\n+\tconst struct ref *r;\n+\tfor (r = refs; r; r = r->next)\n+\t\tif (!strcmp(r->name, \"HEAD\"))\n+\t\t\tremote_head = r;\n+\n+\tfor (r = mapped_refs; r; r = r->next)\n+\t\tif (!strcmp(r->name, \"refs/heads/master\"))\n+\t\t\tremote_master = r;\n+\n+\tif (remote_head_p)\n+\t\t*remote_head_p = remote_head;\n+\n+\t/* If there's no HEAD value at all, never mind. */\n+\tif (!remote_head)\n+\t\treturn NULL;\n+\n+\t/* If refs/heads/master could be right, it is. */\n+\tif (remote_master && !hashcmp(remote_master->old_sha1,\n+\t\t\t\t      remote_head->old_sha1))\n+\t\treturn remote_master;\n+\n+\t/* Look for another ref that points there */\n+\tfor (r = mapped_refs; r; r = r->next)\n+\t\tif (r != remote_head &&\n+\t\t    !hashcmp(r->old_sha1, remote_head->old_sha1))\n+\t\t\treturn r;\n+\n+\t/* Nothing is the same */\n+\treturn NULL;\n+}\n+\n+static struct ref *write_remote_refs(const struct ref *refs,\n+\t\tstruct refspec *refspec, const char *reflog)\n+{\n+\tstruct ref *local_refs = NULL;\n+\tstruct ref **tail = &local_refs;\n+\tstruct ref *r;\n+\n+\tget_fetch_map(refs, refspec, &tail, 0);\n+\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\n+\tfor (r = local_refs; r; r = r->next)\n+\t\tupdate_ref(reflog,\n+\t\t\t   r->peer_ref->name, r->old_sha1, NULL, 0, DIE_ON_ERR);\n+\treturn local_refs;\n+}\n+\n+int cmd_clone(int argc, const char **argv, const char *prefix)\n+{\n+\tint use_local_hardlinks = 1;\n+\tint use_separate_remote = 1;\n+\tint is_bundle = 0;\n+\tstruct stat buf;\n+\tconst char *repo_name, *repo, *work_tree, *git_dir;\n+\tchar *path, *dir;\n+\tconst struct ref *refs, *head_points_at, *remote_head, *mapped_refs;\n+\tchar branch_top[256], key[256], value[256];\n+\tstruct strbuf reflog_msg;\n+\n+\tstruct refspec refspec;\n+\n+\tjunk_pid = getpid();\n+\n+\targc = parse_options(argc, argv, builtin_clone_options,\n+\t\t\t     builtin_clone_usage, 0);\n+\n+\tif (argc == 0)\n+\t\tdie(\"You must specify a repository to clone.\");\n+\n+\tif (option_no_hardlinks)\n+\t\tuse_local_hardlinks = 0;\n+\n+\tif (option_bare) {\n+\t\tif (option_origin)\n+\t\t\tdie(\"--bare and --origin %s options are incompatible.\",\n+\t\t\t    option_origin);\n+\t\toption_no_checkout = 1;\n+\t\tuse_separate_remote = 0;\n+\t}\n+\n+\tif (!option_origin)\n+\t\toption_origin = \"origin\";\n+\n+\trepo_name = argv[0];\n+\n+\tpath = get_repo_path(repo_name, &is_bundle);\n+\tif (path)\n+\t\trepo = path;\n+\telse if (!strchr(repo_name, ':'))\n+\t\trepo = xstrdup(make_absolute_path(repo_name));\n+\telse\n+\t\trepo = repo_name;\n+\n+\tif (argc == 2)\n+\t\tdir = xstrdup(argv[1]);\n+\telse\n+\t\tdir = guess_dir_name(repo_name, is_bundle);\n+\n+\tif (!stat(dir, &buf))\n+\t\tdie(\"destination directory '%s' already exists.\", dir);\n+\n+\tstrbuf_init(&reflog_msg, 0);\n+\tstrbuf_addf(&reflog_msg, \"clone: from %s\", repo);\n+\n+\tif (option_bare)\n+\t\twork_tree = NULL;\n+\telse {\n+\t\twork_tree = getenv(\"GIT_WORK_TREE\");\n+\t\tif (work_tree && !stat(work_tree, &buf))\n+\t\t\tdie(\"working tree '%s' already exists.\", work_tree);\n+\t}\n+\n+\tif (option_bare || work_tree)\n+\t\tgit_dir = xstrdup(dir);\n+\telse {\n+\t\twork_tree = dir;\n+\t\tgit_dir = xstrdup(mkpath(\"%s/.git\", dir));\n+\t}\n+\n+\tif (!option_bare) {\n+\t\tjunk_work_tree = work_tree;\n+\t\tif (mkdir(work_tree, 0755))\n+\t\t\tdie(\"could not create work tree dir '%s'.\", work_tree);\n+\t\tset_git_work_tree(work_tree);\n+\t}\n+\tjunk_git_dir = git_dir;\n+\tatexit(remove_junk);\n+\tsignal(SIGINT, remove_junk_on_signal);\n+\n+\tsetenv(CONFIG_ENVIRONMENT, xstrdup(mkpath(\"%s/config\", git_dir)), 1);\n+\n+\tset_git_dir(make_absolute_path(git_dir));\n+\n+\tfprintf(stderr, \"Initialize %s\\n\", git_dir);\n+\tinit_db(option_template, option_quiet ? INIT_DB_QUIET : 0);\n+\n+\tif (option_reference)\n+\t\tsetup_reference(git_dir);\n+\n+\tgit_config(git_default_config);\n+\n+\tif (option_bare) {\n+\t\tstrcpy(branch_top, \"refs/heads/\");\n+\n+\t\tgit_config_set(\"core.bare\", \"true\");\n+\t} else {\n+\t\tsnprintf(branch_top, sizeof(branch_top),\n+\t\t\t \"refs/remotes/%s/\", option_origin);\n+\n+\t\t/* Configure the remote */\n+\t\tsnprintf(key, sizeof(key), \"remote.%s.url\", option_origin);\n+\t\tgit_config_set(key, repo);\n+\n+\t\tsnprintf(key, sizeof(key), \"remote.%s.fetch\", option_origin);\n+\t\tsnprintf(value, sizeof(value),\n+\t\t\t\t\"+refs/heads/*:%s*\", branch_top);\n+\t\tgit_config_set_multivar(key, value, \"^$\", 0);\n+\t}\n+\n+\trefspec.force = 0;\n+\trefspec.pattern = 1;\n+\trefspec.src = \"refs/heads/\";\n+\trefspec.dst = branch_top;\n+\n+\tif (path && !is_bundle)\n+\t\trefs = clone_local(path, git_dir);\n+\telse {\n+\t\tstruct remote *remote = remote_get(argv[0]);\n+\t\tstruct transport *transport = transport_get(remote, argv[0]);\n+\n+\t\ttransport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n+\n+\t\tif (option_depth)\n+\t\t\ttransport_set_option(transport, TRANS_OPT_DEPTH,\n+\t\t\t\t\t     option_depth);\n+\n+\t\tif (option_quiet)\n+\t\t\ttransport->verbose = -1;\n+\n+\t\trefs = transport_get_remote_refs(transport);\n+\t\ttransport_fetch_refs(transport, refs);\n+\t}\n+\n+\tclear_extra_refs();\n+\n+\tmapped_refs = write_remote_refs(refs, &refspec, reflog_msg.buf);\n+\n+\thead_points_at = locate_head(refs, mapped_refs, &remote_head);\n+\n+\tif (head_points_at) {\n+\t\t/* Local default branch link */\n+\t\tcreate_symref(\"HEAD\", head_points_at->name, NULL);\n+\n+\t\tif (!option_bare) {\n+\t\t\tstruct strbuf head_ref;\n+\t\t\tconst char *head = head_points_at->name;\n+\n+\t\t\tif (!prefixcmp(head, \"refs/heads/\"))\n+\t\t\t\thead += 11;\n+\n+\t\t\t/* Set up the initial local branch */\n+\n+\t\t\t/* Local branch initial value */\n+\t\t\tupdate_ref(reflog_msg.buf, \"HEAD\",\n+\t\t\t\t   head_points_at->old_sha1,\n+\t\t\t\t   NULL, 0, DIE_ON_ERR);\n+\n+\t\t\tstrbuf_init(&head_ref, 0);\n+\t\t\tstrbuf_addstr(&head_ref, branch_top);\n+\t\t\tstrbuf_addstr(&head_ref, \"HEAD\");\n+\n+\t\t\t/* Remote branch link */\n+\t\t\tcreate_symref(head_ref.buf,\n+\t\t\t\t      head_points_at->peer_ref->name,\n+\t\t\t\t      reflog_msg.buf);\n+\n+\t\t\tsnprintf(key, sizeof(key), \"branch.%s.remote\", head);\n+\t\t\tgit_config_set(key, option_origin);\n+\t\t\tsnprintf(key, sizeof(key), \"branch.%s.merge\", head);\n+\t\t\tgit_config_set(key, head_points_at->name);\n+\t\t}\n+\t} else if (remote_head) {\n+\t\t/* Source had detached HEAD pointing somewhere. */\n+\t\tif (!option_bare)\n+\t\t\tupdate_ref(reflog_msg.buf, \"HEAD\",\n+\t\t\t\t   remote_head->old_sha1,\n+\t\t\t\t   NULL, REF_NODEREF, DIE_ON_ERR);\n+\t} else {\n+\t\t/* Nothing to checkout out */\n+\t\tif (!option_no_checkout)\n+\t\t\twarning(\"remote HEAD refers to nonexistent ref, \"\n+\t\t\t\t\"unable to checkout.\\n\");\n+\t\toption_no_checkout = 1;\n+\t}\n+\n+\tif (!option_no_checkout) {\n+\t\tstruct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));\n+\t\tstruct unpack_trees_options opts;\n+\t\tstruct tree *tree;\n+\t\tstruct tree_desc t;\n+\t\tint fd;\n+\n+\t\t/* We need to be in the new work tree for the checkout */\n+\t\tsetup_work_tree();\n+\n+\t\tfd = hold_locked_index(lock_file, 1);\n+\n+\t\tmemset(&opts, 0, sizeof opts);\n+\t\topts.update = 1;\n+\t\topts.verbose_update = !option_quiet;\n+\t\topts.dst_index = &the_index;\n+\n+\t\ttree = parse_tree_indirect(remote_head->old_sha1);\n+\t\tparse_tree(tree);\n+\t\tinit_tree_desc(&t, tree->buffer, tree->size);\n+\t\tunpack_trees(1, &t, &opts);\n+\n+\t\tif (write_cache(fd, active_cache, active_nr) ||\n+\t\t    commit_locked_index(lock_file))\n+\t\t\tdie(\"unable to write new index file\");\n+\t}\n+\n+\tstrbuf_release(&reflog_msg);\n+\tjunk_pid = 0;\n+\treturn 0;\n+}\ndiff --git a/builtin.h b/builtin.h\nindex 95126fd..23a90de 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -24,6 +24,7 @@ extern int cmd_check_attr(int argc, const char **argv, const char *prefix);\n extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry(int argc, const char **argv, const char *prefix);\n extern int cmd_cherry_pick(int argc, const char **argv, const char *prefix);\n+extern int cmd_clone(int argc, const char **argv, const char *prefix);\n extern int cmd_clean(int argc, const char **argv, const char *prefix);\n extern int cmd_commit(int argc, const char **argv, const char *prefix);\n extern int cmd_commit_tree(int argc, const char **argv, const char *prefix);\ndiff --git a/git-clone.sh b/contrib/examples/git-clone.sh\nsimilarity index 100%\nrename from git-clone.sh\nrename to contrib/examples/git-clone.sh\ndiff --git a/git.c b/git.c\nindex c4e4644..4e7845a 100644\n--- a/git.c\n+++ b/git.c\n@@ -286,6 +286,7 @@ static void handle_internal_command(int argc, const char **argv)\n \t\t{ \"check-attr\", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"cherry\", cmd_cherry, RUN_SETUP },\n \t\t{ \"cherry-pick\", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE },\n+\t\t{ \"clone\", cmd_clone },\n \t\t{ \"clean\", cmd_clean, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit\", cmd_commit, RUN_SETUP | NEED_WORK_TREE },\n \t\t{ \"commit-tree\", cmd_commit_tree, RUN_SETUP },\n-- \n1.5.4.3.610.gea6cd"}]}