2008-04-27 19:39:30 +02:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2019-01-24 09:29:12 +01:00
|
|
|
#define USE_THE_INDEX_COMPATIBILITY_MACROS
|
Fix sparse warnings
Fix warnings from 'make check'.
- These files don't include 'builtin.h' causing sparse to complain that
cmd_* isn't declared:
builtin/clone.c:364, builtin/fetch-pack.c:797,
builtin/fmt-merge-msg.c:34, builtin/hash-object.c:78,
builtin/merge-index.c:69, builtin/merge-recursive.c:22
builtin/merge-tree.c:341, builtin/mktag.c:156, builtin/notes.c:426
builtin/notes.c:822, builtin/pack-redundant.c:596,
builtin/pack-refs.c:10, builtin/patch-id.c:60, builtin/patch-id.c:149,
builtin/remote.c:1512, builtin/remote-ext.c:240,
builtin/remote-fd.c:53, builtin/reset.c:236, builtin/send-pack.c:384,
builtin/unpack-file.c:25, builtin/var.c:75
- These files have symbols which should be marked static since they're
only file scope:
submodule.c:12, diff.c:631, replace_object.c:92, submodule.c:13,
submodule.c:14, trace.c:78, transport.c:195, transport-helper.c:79,
unpack-trees.c:19, url.c:3, url.c:18, url.c:104, url.c:117, url.c:123,
url.c:129, url.c:136, thread-utils.c:21, thread-utils.c:48
- These files redeclare symbols to be different types:
builtin/index-pack.c:210, parse-options.c:564, parse-options.c:571,
usage.c:49, usage.c:58, usage.c:63, usage.c:72
- These files use a literal integer 0 when they really should use a NULL
pointer:
daemon.c:663, fast-import.c:2942, imap-send.c:1072, notes-merge.c:362
While we're in the area, clean up some unused #includes in builtin files
(mostly exec_cmd.h).
Signed-off-by: Stephen Boyd <bebarino@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-03-22 08:51:05 +01:00
|
|
|
#include "builtin.h"
|
2017-06-14 20:07:36 +02:00
|
|
|
#include "config.h"
|
2014-10-01 12:28:42 +02:00
|
|
|
#include "lockfile.h"
|
2008-04-27 19:39:30 +02:00
|
|
|
#include "parse-options.h"
|
|
|
|
#include "fetch-pack.h"
|
|
|
|
#include "refs.h"
|
2018-05-17 00:57:48 +02:00
|
|
|
#include "refspec.h"
|
2018-05-16 01:42:15 +02:00
|
|
|
#include "object-store.h"
|
2008-04-27 19:39:30 +02:00
|
|
|
#include "tree.h"
|
|
|
|
#include "tree-walk.h"
|
|
|
|
#include "unpack-trees.h"
|
|
|
|
#include "transport.h"
|
|
|
|
#include "strbuf.h"
|
|
|
|
#include "dir.h"
|
2019-07-11 01:59:03 +02:00
|
|
|
#include "dir-iterator.h"
|
|
|
|
#include "iterator.h"
|
chain kill signals for cleanup functions
If a piece of code wanted to do some cleanup before exiting
(e.g., cleaning up a lockfile or a tempfile), our usual
strategy was to install a signal handler that did something
like this:
do_cleanup(); /* actual work */
signal(signo, SIG_DFL); /* restore previous behavior */
raise(signo); /* deliver signal, killing ourselves */
For a single handler, this works fine. However, if we want
to clean up two _different_ things, we run into a problem.
The most recently installed handler will run, but when it
removes itself as a handler, it doesn't put back the first
handler.
This patch introduces sigchain, a tiny library for handling
a stack of signal handlers. You sigchain_push each handler,
and use sigchain_pop to restore whoever was before you in
the stack.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-22 07:02:35 +01:00
|
|
|
#include "sigchain.h"
|
2009-03-04 07:29:55 +01:00
|
|
|
#include "branch.h"
|
2009-02-25 09:32:13 +01:00
|
|
|
#include "remote.h"
|
2009-03-03 06:37:51 +01:00
|
|
|
#include "run-command.h"
|
2013-03-25 21:26:27 +01:00
|
|
|
#include "connected.h"
|
2017-08-19 00:20:21 +02:00
|
|
|
#include "packfile.h"
|
2017-12-08 16:58:46 +01:00
|
|
|
#include "list-objects-filter-options.h"
|
2008-04-27 19:39:30 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* 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[] = {
|
2015-01-13 08:44:47 +01:00
|
|
|
N_("git clone [<options>] [--] <repo> [<dir>]"),
|
2008-04-27 19:39:30 +02:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2012-01-07 15:45:59 +01:00
|
|
|
static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;
|
2017-03-17 23:38:03 +01:00
|
|
|
static int option_local = -1, option_no_hardlinks, option_shared;
|
2017-04-27 01:12:33 +02:00
|
|
|
static int option_no_tags;
|
2016-06-19 22:51:56 +02:00
|
|
|
static int option_shallow_submodules;
|
2016-06-12 12:54:00 +02:00
|
|
|
static int deepen;
|
|
|
|
static char *option_template, *option_depth, *option_since;
|
2008-04-27 19:39:30 +02:00
|
|
|
static char *option_origin = NULL;
|
2009-08-26 21:05:08 +02:00
|
|
|
static char *option_branch = NULL;
|
2016-06-12 12:54:05 +02:00
|
|
|
static struct string_list option_not = STRING_LIST_INIT_NODUP;
|
2011-03-19 16:16:56 +01:00
|
|
|
static const char *real_git_dir;
|
2008-04-27 19:39:30 +02:00
|
|
|
static char *option_upload_pack = "git-upload-pack";
|
2010-02-24 13:50:25 +01:00
|
|
|
static int option_verbosity;
|
2012-02-13 21:17:15 +01:00
|
|
|
static int option_progress = -1;
|
2019-11-21 23:04:35 +01:00
|
|
|
static int option_sparse_checkout;
|
2016-02-03 05:09:14 +01:00
|
|
|
static enum transport_family family;
|
2016-06-13 12:04:20 +02:00
|
|
|
static struct string_list option_config = STRING_LIST_INIT_NODUP;
|
2016-08-15 23:53:25 +02:00
|
|
|
static struct string_list option_required_reference = STRING_LIST_INIT_NODUP;
|
2016-08-15 23:53:26 +02:00
|
|
|
static struct string_list option_optional_reference = STRING_LIST_INIT_NODUP;
|
2014-10-14 21:38:52 +02:00
|
|
|
static int option_dissociate;
|
2016-03-01 03:07:20 +01:00
|
|
|
static int max_jobs = -1;
|
2017-03-17 23:38:03 +01:00
|
|
|
static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP;
|
2017-12-08 16:58:46 +01:00
|
|
|
static struct list_objects_filter_options filter_options;
|
2019-04-12 21:51:22 +02:00
|
|
|
static struct string_list server_options = STRING_LIST_INIT_NODUP;
|
2019-05-19 16:26:49 +02:00
|
|
|
static int option_remote_submodules;
|
2017-03-17 23:38:03 +01:00
|
|
|
|
|
|
|
static int recurse_submodules_cb(const struct option *opt,
|
|
|
|
const char *arg, int unset)
|
|
|
|
{
|
|
|
|
if (unset)
|
|
|
|
string_list_clear((struct string_list *)opt->value, 0);
|
|
|
|
else if (arg)
|
|
|
|
string_list_append((struct string_list *)opt->value, arg);
|
|
|
|
else
|
|
|
|
string_list_append((struct string_list *)opt->value,
|
|
|
|
(const char *)opt->defval);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2011-08-23 03:05:15 +02:00
|
|
|
|
2008-04-27 19:39:30 +02:00
|
|
|
static struct option builtin_clone_options[] = {
|
2010-02-24 13:50:25 +01:00
|
|
|
OPT__VERBOSITY(&option_verbosity),
|
2012-02-13 21:17:15 +01:00
|
|
|
OPT_BOOL(0, "progress", &option_progress,
|
2012-08-20 14:32:02 +02:00
|
|
|
N_("force progress reporting")),
|
2013-08-03 13:51:19 +02:00
|
|
|
OPT_BOOL('n', "no-checkout", &option_no_checkout,
|
|
|
|
N_("don't create a checkout")),
|
2013-08-03 13:51:18 +02:00
|
|
|
OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")),
|
|
|
|
OPT_HIDDEN_BOOL(0, "naked", &option_bare,
|
|
|
|
N_("create a bare repository")),
|
2013-08-03 13:51:19 +02:00
|
|
|
OPT_BOOL(0, "mirror", &option_mirror,
|
|
|
|
N_("create a mirror repository (implies bare)")),
|
2012-05-30 13:10:16 +02:00
|
|
|
OPT_BOOL('l', "local", &option_local,
|
2012-08-20 14:32:02 +02:00
|
|
|
N_("to clone from a local repository")),
|
2013-08-03 13:51:19 +02:00
|
|
|
OPT_BOOL(0, "no-hardlinks", &option_no_hardlinks,
|
2012-08-20 14:32:02 +02:00
|
|
|
N_("don't use local hardlinks, always copy")),
|
2013-08-03 13:51:19 +02:00
|
|
|
OPT_BOOL('s', "shared", &option_shared,
|
2012-08-20 14:32:02 +02:00
|
|
|
N_("setup as shared repository")),
|
2017-03-17 23:38:03 +01:00
|
|
|
{ OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules,
|
|
|
|
N_("pathspec"), N_("initialize submodules in the clone"),
|
|
|
|
PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." },
|
2020-03-16 21:27:43 +01:00
|
|
|
OPT_ALIAS(0, "recursive", "recurse-submodules"),
|
2016-03-01 03:07:20 +01:00
|
|
|
OPT_INTEGER('j', "jobs", &max_jobs,
|
|
|
|
N_("number of submodules cloned in parallel")),
|
2012-08-20 14:32:02 +02:00
|
|
|
OPT_STRING(0, "template", &option_template, N_("template-directory"),
|
|
|
|
N_("directory from which templates will be used")),
|
2016-08-15 23:53:25 +02:00
|
|
|
OPT_STRING_LIST(0, "reference", &option_required_reference, N_("repo"),
|
2015-05-21 06:15:19 +02:00
|
|
|
N_("reference repository")),
|
2016-08-15 23:53:26 +02:00
|
|
|
OPT_STRING_LIST(0, "reference-if-able", &option_optional_reference,
|
|
|
|
N_("repo"), N_("reference repository")),
|
2015-05-21 06:16:04 +02:00
|
|
|
OPT_BOOL(0, "dissociate", &option_dissociate,
|
|
|
|
N_("use --reference only while cloning")),
|
2012-08-20 14:32:02 +02:00
|
|
|
OPT_STRING('o', "origin", &option_origin, N_("name"),
|
|
|
|
N_("use <name> instead of 'origin' to track upstream")),
|
|
|
|
OPT_STRING('b', "branch", &option_branch, N_("branch"),
|
|
|
|
N_("checkout <branch> instead of the remote's HEAD")),
|
|
|
|
OPT_STRING('u', "upload-pack", &option_upload_pack, N_("path"),
|
|
|
|
N_("path to git-upload-pack on the remote")),
|
|
|
|
OPT_STRING(0, "depth", &option_depth, N_("depth"),
|
|
|
|
N_("create a shallow clone of that depth")),
|
2016-06-12 12:54:00 +02:00
|
|
|
OPT_STRING(0, "shallow-since", &option_since, N_("time"),
|
|
|
|
N_("create a shallow clone since a specific time")),
|
2016-06-12 12:54:05 +02:00
|
|
|
OPT_STRING_LIST(0, "shallow-exclude", &option_not, N_("revision"),
|
2016-12-04 23:03:59 +01:00
|
|
|
N_("deepen history of shallow clone, excluding rev")),
|
2012-01-07 15:45:59 +01:00
|
|
|
OPT_BOOL(0, "single-branch", &option_single_branch,
|
2012-08-20 14:32:02 +02:00
|
|
|
N_("clone only one branch, HEAD or --branch")),
|
2017-04-27 01:12:33 +02:00
|
|
|
OPT_BOOL(0, "no-tags", &option_no_tags,
|
|
|
|
N_("don't clone any tags, and make later fetches not to follow them")),
|
2016-04-26 03:12:27 +02:00
|
|
|
OPT_BOOL(0, "shallow-submodules", &option_shallow_submodules,
|
|
|
|
N_("any cloned submodules will be shallow")),
|
2012-08-20 14:32:02 +02:00
|
|
|
OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
|
|
|
|
N_("separate git dir from working tree")),
|
|
|
|
OPT_STRING_LIST('c', "config", &option_config, N_("key=value"),
|
|
|
|
N_("set config inside the new repository")),
|
2019-04-12 21:51:22 +02:00
|
|
|
OPT_STRING_LIST(0, "server-option", &server_options,
|
|
|
|
N_("server-specific"), N_("option to transmit")),
|
2016-02-03 05:09:14 +01:00
|
|
|
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
|
|
|
|
TRANSPORT_FAMILY_IPV4),
|
|
|
|
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
|
|
|
|
TRANSPORT_FAMILY_IPV6),
|
2017-12-08 16:58:46 +01:00
|
|
|
OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options),
|
2019-05-19 16:26:49 +02:00
|
|
|
OPT_BOOL(0, "remote-submodules", &option_remote_submodules,
|
|
|
|
N_("any cloned submodules will use their remote-tracking branch")),
|
2019-11-21 23:04:35 +01:00
|
|
|
OPT_BOOL(0, "sparse", &option_sparse_checkout,
|
|
|
|
N_("initialize sparse-checkout file to include only files at root")),
|
2008-04-27 19:39:30 +02:00
|
|
|
OPT_END()
|
|
|
|
};
|
|
|
|
|
2015-08-10 11:37:55 +02:00
|
|
|
static const char *get_repo_path_1(struct strbuf *path, int *is_bundle)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
standardize and improve lookup rules for external local repos
When you specify a local repository on the command line of
clone, ls-remote, upload-pack, receive-pack, or upload-archive,
or in a request to git-daemon, we perform a little bit of
lookup magic, doing things like looking in working trees for
.git directories and appending ".git" for bare repos.
For clone, this magic happens in get_repo_path. For
everything else, it happens in enter_repo. In both cases,
there are some ambiguous or confusing cases that aren't
handled well, and there is one case that is not handled the
same by both methods.
This patch tries to provide (and test!) standard, sensible
lookup rules for both code paths. The intended changes are:
1. When looking up "foo", we have always preferred
a working tree "foo" (containing "foo/.git" over the
bare "foo.git". But we did not prefer a bare "foo" over
"foo.git". With this patch, we do so.
2. We would select directories that existed but didn't
actually look like git repositories. With this patch,
we make sure a selected directory looks like a git
repo. Not only is this more sensible in general, but it
will help anybody who is negatively affected by change
(1) negatively (e.g., if they had "foo.git" next to its
separate work tree "foo", and expect to keep finding
"foo.git" when they reference "foo").
3. The enter_repo code path would, given "foo", look for
"foo.git/.git" (i.e., do the ".git" append magic even
for a repo with working tree). The clone code path did
not; with this patch, they now behave the same.
In the unlikely case of a working tree overlaying a bare
repo (i.e., a ".git" directory _inside_ a bare repo), we
continue to treat it as a working tree (prefering the
"inner" .git over the bare repo). This is mainly because the
combination seems nonsensical, and I'd rather stick with
existing behavior on the off chance that somebody is relying
on it.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2012-02-02 22:59:13 +01:00
|
|
|
static char *suffix[] = { "/.git", "", ".git/.git", ".git" };
|
2008-04-27 19:39:30 +02:00
|
|
|
static char *bundle_suffix[] = { ".bundle", "" };
|
2015-08-10 11:37:55 +02:00
|
|
|
size_t baselen = path->len;
|
2008-04-27 19:39:30 +02:00
|
|
|
struct stat st;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(suffix); i++) {
|
2015-08-10 11:37:55 +02:00
|
|
|
strbuf_setlen(path, baselen);
|
|
|
|
strbuf_addstr(path, suffix[i]);
|
|
|
|
if (stat(path->buf, &st))
|
2011-08-21 13:58:09 +02:00
|
|
|
continue;
|
2015-08-10 11:37:55 +02:00
|
|
|
if (S_ISDIR(st.st_mode) && is_git_directory(path->buf)) {
|
2008-04-27 19:39:30 +02:00
|
|
|
*is_bundle = 0;
|
2015-08-10 11:37:55 +02:00
|
|
|
return path->buf;
|
2011-08-21 13:58:09 +02:00
|
|
|
} else if (S_ISREG(st.st_mode) && st.st_size > 8) {
|
|
|
|
/* Is it a "gitfile"? */
|
|
|
|
char signature[8];
|
2015-08-10 11:37:55 +02:00
|
|
|
const char *dst;
|
|
|
|
int len, fd = open(path->buf, O_RDONLY);
|
2011-08-21 13:58:09 +02:00
|
|
|
if (fd < 0)
|
|
|
|
continue;
|
|
|
|
len = read_in_full(fd, signature, 8);
|
|
|
|
close(fd);
|
|
|
|
if (len != 8 || strncmp(signature, "gitdir: ", 8))
|
|
|
|
continue;
|
2015-08-10 11:37:55 +02:00
|
|
|
dst = read_gitfile(path->buf);
|
|
|
|
if (dst) {
|
2011-08-21 13:58:09 +02:00
|
|
|
*is_bundle = 0;
|
2015-08-10 11:37:55 +02:00
|
|
|
return dst;
|
2011-08-21 13:58:09 +02:00
|
|
|
}
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < ARRAY_SIZE(bundle_suffix); i++) {
|
2015-08-10 11:37:55 +02:00
|
|
|
strbuf_setlen(path, baselen);
|
|
|
|
strbuf_addstr(path, bundle_suffix[i]);
|
|
|
|
if (!stat(path->buf, &st) && S_ISREG(st.st_mode)) {
|
2008-04-27 19:39:30 +02:00
|
|
|
*is_bundle = 1;
|
2015-08-10 11:37:55 +02:00
|
|
|
return path->buf;
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2015-08-10 11:37:55 +02:00
|
|
|
static char *get_repo_path(const char *repo, int *is_bundle)
|
|
|
|
{
|
|
|
|
struct strbuf path = STRBUF_INIT;
|
|
|
|
const char *raw;
|
|
|
|
char *canon;
|
|
|
|
|
|
|
|
strbuf_addstr(&path, repo);
|
|
|
|
raw = get_repo_path_1(&path, is_bundle);
|
2017-01-26 18:54:23 +01:00
|
|
|
canon = raw ? absolute_pathdup(raw) : NULL;
|
2015-08-10 11:37:55 +02:00
|
|
|
strbuf_release(&path);
|
|
|
|
return canon;
|
|
|
|
}
|
|
|
|
|
2008-08-01 16:01:36 +02:00
|
|
|
static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
2015-08-10 17:48:23 +02:00
|
|
|
const char *end = repo + strlen(repo), *start, *ptr;
|
2015-07-09 20:24:08 +02:00
|
|
|
size_t len;
|
2009-05-13 18:32:06 +02:00
|
|
|
char *dir;
|
2008-07-19 11:32:45 +02:00
|
|
|
|
2015-08-10 17:48:23 +02:00
|
|
|
/*
|
|
|
|
* Skip scheme.
|
|
|
|
*/
|
|
|
|
start = strstr(repo, "://");
|
|
|
|
if (start == NULL)
|
|
|
|
start = repo;
|
|
|
|
else
|
|
|
|
start += 3;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Skip authentication data. The stripping does happen
|
|
|
|
* greedily, such that we strip up to the last '@' inside
|
|
|
|
* the host part.
|
|
|
|
*/
|
|
|
|
for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) {
|
|
|
|
if (*ptr == '@')
|
|
|
|
start = ptr + 1;
|
|
|
|
}
|
|
|
|
|
2008-07-19 11:32:45 +02:00
|
|
|
/*
|
2009-05-13 18:32:06 +02:00
|
|
|
* Strip trailing spaces, slashes and /.git
|
2008-07-19 11:32:45 +02:00
|
|
|
*/
|
2015-08-10 17:48:23 +02:00
|
|
|
while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1])))
|
2008-07-19 11:32:45 +02:00
|
|
|
end--;
|
2015-08-10 17:48:23 +02:00
|
|
|
if (end - start > 5 && is_dir_sep(end[-5]) &&
|
2008-07-19 11:32:45 +02:00
|
|
|
!strncmp(end - 4, ".git", 4)) {
|
|
|
|
end -= 5;
|
2015-08-10 17:48:23 +02:00
|
|
|
while (start < end && is_dir_sep(end[-1]))
|
2008-07-19 11:32:45 +02:00
|
|
|
end--;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2015-08-10 17:48:24 +02:00
|
|
|
* Strip trailing port number if we've got only a
|
|
|
|
* hostname (that is, there is no dir separator but a
|
|
|
|
* colon). This check is required such that we do not
|
|
|
|
* strip URI's like '/foo/bar:2222.git', which should
|
|
|
|
* result in a dir '2222' being guessed due to backwards
|
|
|
|
* compatibility.
|
|
|
|
*/
|
|
|
|
if (memchr(start, '/', end - start) == NULL
|
|
|
|
&& memchr(start, ':', end - start) != NULL) {
|
|
|
|
ptr = end;
|
|
|
|
while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':')
|
|
|
|
ptr--;
|
|
|
|
if (start < ptr && ptr[-1] == ':')
|
|
|
|
end = ptr - 1;
|
|
|
|
}
|
|
|
|
|
2008-07-19 11:32:45 +02:00
|
|
|
/*
|
2015-08-10 17:48:23 +02:00
|
|
|
* Find last component. To remain backwards compatible we
|
|
|
|
* also regard colons as path separators, such that
|
|
|
|
* cloning a repository 'foo:bar.git' would result in a
|
|
|
|
* directory 'bar' being guessed.
|
2008-07-19 11:32:45 +02:00
|
|
|
*/
|
2015-08-10 17:48:23 +02:00
|
|
|
ptr = end;
|
|
|
|
while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':')
|
|
|
|
ptr--;
|
|
|
|
start = ptr;
|
2008-07-19 11:32:45 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Strip .{bundle,git}.
|
|
|
|
*/
|
clone: use computed length in guess_dir_name
Commit 7e837c6 (clone: simplify string handling in
guess_dir_name(), 2015-07-09) changed clone to use
strip_suffix instead of hand-rolled pointer manipulation.
However, strip_suffix will strip from the end of a
NUL-terminated string, and we may have already stripped some
characters (like directory separators, or "/.git"). This
leads to commands like:
git clone host:foo.git/
failing to strip the ".git".
We must instead convert our pointer arithmetic into a
computed length and feed that to strip_suffix_mem, which will
then reduce the length further for us.
It would be nicer if we could drop the pointer manipulation
entirely, and just continually strip using strip_suffix. But
that doesn't quite work for two reasons:
1. The early suffixes we're stripping are not constant; we
need to look for is_dir_sep, which could be one of
several characters.
2. Mid-way through the stripping we compute the pointer
"start", which shows us the beginning of the pathname.
Which really give us two lengths to work with: the
offset from the start of the string, and from the start
of the path. By using pointers for the early part, we
can just compute the length from "start" when we need
it.
Signed-off-by: Jeff King <peff@peff.net>
Acked-by: Sebastian Schuberth <sschuberth@gmail.com>
Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2015-08-10 17:48:22 +02:00
|
|
|
len = end - start;
|
|
|
|
strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git");
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2015-08-10 17:48:25 +02:00
|
|
|
if (!len || (len == 1 && *start == '/'))
|
2016-02-27 07:41:55 +01:00
|
|
|
die(_("No directory name could be guessed.\n"
|
|
|
|
"Please specify a directory on the command line"));
|
2015-08-10 17:48:25 +02:00
|
|
|
|
2015-07-09 20:24:08 +02:00
|
|
|
if (is_bare)
|
|
|
|
dir = xstrfmt("%.*s.git", (int)len, start);
|
|
|
|
else
|
|
|
|
dir = xstrndup(start, len);
|
2009-05-13 18:32:06 +02:00
|
|
|
/*
|
|
|
|
* Replace sequences of 'control' characters and whitespace
|
|
|
|
* with one ascii space, remove leading and trailing spaces.
|
|
|
|
*/
|
|
|
|
if (*dir) {
|
|
|
|
char *out = dir;
|
|
|
|
int prev_space = 1 /* strip leading whitespace */;
|
|
|
|
for (end = dir; *end; ++end) {
|
|
|
|
char ch = *end;
|
|
|
|
if ((unsigned char)ch < '\x20')
|
|
|
|
ch = '\x20';
|
|
|
|
if (isspace(ch)) {
|
|
|
|
if (prev_space)
|
|
|
|
continue;
|
|
|
|
prev_space = 1;
|
|
|
|
} else
|
|
|
|
prev_space = 0;
|
|
|
|
*out++ = ch;
|
|
|
|
}
|
|
|
|
*out = '\0';
|
|
|
|
if (out > dir && prev_space)
|
|
|
|
out[-1] = '\0';
|
2008-08-01 16:01:36 +02:00
|
|
|
}
|
2009-05-13 18:32:06 +02:00
|
|
|
return dir;
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
2008-09-03 20:55:55 +02:00
|
|
|
static void strip_trailing_slashes(char *dir)
|
|
|
|
{
|
|
|
|
char *end = dir + strlen(dir);
|
|
|
|
|
|
|
|
while (dir < end - 1 && is_dir_sep(end[-1]))
|
|
|
|
end--;
|
|
|
|
*end = '\0';
|
|
|
|
}
|
|
|
|
|
2011-08-23 03:05:15 +02:00
|
|
|
static int add_one_reference(struct string_list_item *item, void *cb_data)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
2016-08-15 23:53:24 +02:00
|
|
|
struct strbuf err = STRBUF_INIT;
|
2016-08-15 23:53:26 +02:00
|
|
|
int *required = cb_data;
|
2016-08-15 23:53:24 +02:00
|
|
|
char *ref_git = compute_alternate_path(item->string, &err);
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2016-08-15 23:53:26 +02:00
|
|
|
if (!ref_git) {
|
|
|
|
if (*required)
|
|
|
|
die("%s", err.buf);
|
|
|
|
else
|
|
|
|
fprintf(stderr,
|
|
|
|
_("info: Could not add alternate for '%s': %s\n"),
|
|
|
|
item->string, err.buf);
|
|
|
|
} else {
|
|
|
|
struct strbuf sb = STRBUF_INIT;
|
|
|
|
strbuf_addf(&sb, "%s/objects", ref_git);
|
|
|
|
add_to_alternates_file(sb.buf);
|
|
|
|
strbuf_release(&sb);
|
|
|
|
}
|
2013-12-05 14:02:31 +01:00
|
|
|
|
2016-08-15 23:53:24 +02:00
|
|
|
strbuf_release(&err);
|
2016-08-15 23:53:26 +02:00
|
|
|
free(ref_git);
|
2011-08-23 03:05:15 +02:00
|
|
|
return 0;
|
|
|
|
}
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2011-08-23 03:05:15 +02:00
|
|
|
static void setup_reference(void)
|
|
|
|
{
|
2016-08-15 23:53:26 +02:00
|
|
|
int required = 1;
|
|
|
|
for_each_string_list(&option_required_reference,
|
|
|
|
add_one_reference, &required);
|
|
|
|
required = 0;
|
|
|
|
for_each_string_list(&option_optional_reference,
|
|
|
|
add_one_reference, &required);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
2019-05-09 23:29:22 +02:00
|
|
|
static void copy_alternates(struct strbuf *src, const char *src_repo)
|
2011-08-23 03:05:16 +02:00
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Read from the source objects/info/alternates file
|
|
|
|
* and copy the entries to corresponding file in the
|
|
|
|
* destination repository with add_to_alternates_file().
|
|
|
|
* Both src and dst have "$path/objects/info/alternates".
|
|
|
|
*
|
|
|
|
* Instead of copying bit-for-bit from the original,
|
|
|
|
* we need to append to existing one so that the already
|
|
|
|
* created entry via "clone -s" is not lost, and also
|
|
|
|
* to turn entries with paths relative to the original
|
|
|
|
* absolute, so that they can be used in the new repository.
|
|
|
|
*/
|
2017-05-03 12:16:47 +02:00
|
|
|
FILE *in = xfopen(src->buf, "r");
|
2011-08-23 03:05:16 +02:00
|
|
|
struct strbuf line = STRBUF_INIT;
|
|
|
|
|
2015-10-28 21:29:24 +01:00
|
|
|
while (strbuf_getline(&line, in) != EOF) {
|
2014-11-30 09:24:27 +01:00
|
|
|
char *abs_path;
|
2011-08-23 03:05:16 +02:00
|
|
|
if (!line.len || line.buf[0] == '#')
|
|
|
|
continue;
|
|
|
|
if (is_absolute_path(line.buf)) {
|
|
|
|
add_to_alternates_file(line.buf);
|
|
|
|
continue;
|
|
|
|
}
|
2014-11-30 09:24:27 +01:00
|
|
|
abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf);
|
clone: detect errors in normalize_path_copy
When we are copying the alternates from the source
repository, if we find a relative path that is too deep for
the source (e.g., "../../../objects" from "/repo.git/objects"),
then normalize_path_copy will report an error and leave
trash in the buffer, which we will add to our new alternates
file. Instead, let's detect the error, print a warning, and
skip copying that alternate.
There's no need to die. The relative path is probably just
broken cruft in the source repo. If it turns out to have
been important for accessing some objects, we rely on other
parts of the clone to detect that, just as they would with a
missing object in the source repo itself (though note that
clones with "-s" are inherently local, which may do fewer
object-quality checks in the first place).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-10-05 16:29:29 +02:00
|
|
|
if (!normalize_path_copy(abs_path, abs_path))
|
|
|
|
add_to_alternates_file(abs_path);
|
|
|
|
else
|
|
|
|
warning("skipping invalid relative alternate: %s/%s",
|
|
|
|
src_repo, line.buf);
|
2014-11-30 09:24:27 +01:00
|
|
|
free(abs_path);
|
2011-08-23 03:05:16 +02:00
|
|
|
}
|
|
|
|
strbuf_release(&line);
|
|
|
|
fclose(in);
|
|
|
|
}
|
|
|
|
|
2019-07-11 01:59:02 +02:00
|
|
|
static void mkdir_if_missing(const char *pathname, mode_t mode)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
if (!mkdir(pathname, mode))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (errno != EEXIST)
|
|
|
|
die_errno(_("failed to create directory '%s'"), pathname);
|
|
|
|
else if (stat(pathname, &st))
|
|
|
|
die_errno(_("failed to stat '%s'"), pathname);
|
|
|
|
else if (!S_ISDIR(st.st_mode))
|
|
|
|
die(_("%s exists and is not a directory"), pathname);
|
|
|
|
}
|
|
|
|
|
2011-08-23 03:05:16 +02:00
|
|
|
static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
|
2019-07-11 01:59:03 +02:00
|
|
|
const char *src_repo)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
|
|
|
int src_len, dest_len;
|
2019-07-11 01:59:03 +02:00
|
|
|
struct dir_iterator *iter;
|
|
|
|
int iter_status;
|
|
|
|
unsigned int flags;
|
2020-03-10 14:11:22 +01:00
|
|
|
struct strbuf realpath = STRBUF_INIT;
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2019-07-11 01:59:02 +02:00
|
|
|
mkdir_if_missing(dest->buf, 0777);
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2019-07-11 01:59:03 +02:00
|
|
|
flags = DIR_ITERATOR_PEDANTIC | DIR_ITERATOR_FOLLOW_SYMLINKS;
|
|
|
|
iter = dir_iterator_begin(src->buf, flags);
|
|
|
|
|
|
|
|
if (!iter)
|
|
|
|
die_errno(_("failed to start iterator over '%s'"), src->buf);
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2008-11-21 01:45:00 +01:00
|
|
|
strbuf_addch(src, '/');
|
|
|
|
src_len = src->len;
|
|
|
|
strbuf_addch(dest, '/');
|
|
|
|
dest_len = dest->len;
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2019-07-11 01:59:03 +02:00
|
|
|
while ((iter_status = dir_iterator_advance(iter)) == ITER_OK) {
|
2008-11-21 01:45:00 +01:00
|
|
|
strbuf_setlen(src, src_len);
|
2019-07-11 01:59:03 +02:00
|
|
|
strbuf_addstr(src, iter->relative_path);
|
2008-11-21 01:45:00 +01:00
|
|
|
strbuf_setlen(dest, dest_len);
|
2019-07-11 01:59:03 +02:00
|
|
|
strbuf_addstr(dest, iter->relative_path);
|
|
|
|
|
|
|
|
if (S_ISDIR(iter->st.st_mode)) {
|
|
|
|
mkdir_if_missing(dest->buf, 0777);
|
2011-08-23 03:05:16 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Files that cannot be copied bit-for-bit... */
|
2019-07-11 01:59:04 +02:00
|
|
|
if (!fspathcmp(iter->relative_path, "info/alternates")) {
|
2019-05-09 23:29:22 +02:00
|
|
|
copy_alternates(src, src_repo);
|
2008-04-27 19:39:30 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-11-21 01:45:00 +01:00
|
|
|
if (unlink(dest->buf) && errno != ENOENT)
|
2011-02-23 00:41:26 +01:00
|
|
|
die_errno(_("failed to unlink '%s'"), dest->buf);
|
2008-05-20 20:15:14 +02:00
|
|
|
if (!option_no_hardlinks) {
|
2020-03-10 14:11:22 +01:00
|
|
|
strbuf_realpath(&realpath, src->buf, 1);
|
|
|
|
if (!link(realpath.buf, dest->buf))
|
2008-05-20 20:15:14 +02:00
|
|
|
continue;
|
2012-05-30 13:10:16 +02:00
|
|
|
if (option_local > 0)
|
2011-02-23 00:41:26 +01:00
|
|
|
die_errno(_("failed to create link '%s'"), dest->buf);
|
2008-05-20 20:15:14 +02:00
|
|
|
option_no_hardlinks = 1;
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
2009-09-12 11:03:48 +02:00
|
|
|
if (copy_file_with_time(dest->buf, src->buf, 0666))
|
2011-02-23 00:41:26 +01:00
|
|
|
die_errno(_("failed to copy file to '%s'"), dest->buf);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
2019-07-11 01:59:03 +02:00
|
|
|
|
|
|
|
if (iter_status != ITER_DONE) {
|
|
|
|
strbuf_setlen(src, src_len);
|
|
|
|
die(_("failed to iterate over '%s'"), src->buf);
|
|
|
|
}
|
2020-03-10 14:11:22 +01:00
|
|
|
|
|
|
|
strbuf_release(&realpath);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
2012-01-16 10:46:12 +01:00
|
|
|
static void clone_local(const char *src_repo, const char *dest_repo)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
2011-08-23 03:05:16 +02:00
|
|
|
if (option_shared) {
|
|
|
|
struct strbuf alt = STRBUF_INIT;
|
2017-12-12 00:16:12 +01:00
|
|
|
get_common_dir(&alt, src_repo);
|
|
|
|
strbuf_addstr(&alt, "/objects");
|
2011-08-23 03:05:16 +02:00
|
|
|
add_to_alternates_file(alt.buf);
|
|
|
|
strbuf_release(&alt);
|
|
|
|
} else {
|
|
|
|
struct strbuf src = STRBUF_INIT;
|
|
|
|
struct strbuf dest = STRBUF_INIT;
|
2015-09-28 15:06:15 +02:00
|
|
|
get_common_dir(&src, src_repo);
|
|
|
|
get_common_dir(&dest, dest_repo);
|
|
|
|
strbuf_addstr(&src, "/objects");
|
|
|
|
strbuf_addstr(&dest, "/objects");
|
2019-07-11 01:59:03 +02:00
|
|
|
copy_or_link_directory(&src, &dest, src_repo);
|
2008-11-21 01:45:00 +01:00
|
|
|
strbuf_release(&src);
|
|
|
|
strbuf_release(&dest);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
2010-04-23 14:37:22 +02:00
|
|
|
if (0 <= option_verbosity)
|
2013-09-18 22:05:13 +02:00
|
|
|
fprintf(stderr, _("done.\n"));
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static const char *junk_work_tree;
|
2018-01-02 22:11:39 +01:00
|
|
|
static int junk_work_tree_flags;
|
2008-04-27 19:39:30 +02:00
|
|
|
static const char *junk_git_dir;
|
2018-01-02 22:11:39 +01:00
|
|
|
static int junk_git_dir_flags;
|
2013-04-27 20:39:04 +02:00
|
|
|
static enum {
|
2013-03-26 23:22:09 +01:00
|
|
|
JUNK_LEAVE_NONE,
|
|
|
|
JUNK_LEAVE_REPO,
|
|
|
|
JUNK_LEAVE_ALL
|
|
|
|
} junk_mode = JUNK_LEAVE_NONE;
|
|
|
|
|
|
|
|
static const char junk_leave_repo_msg[] =
|
|
|
|
N_("Clone succeeded, but checkout failed.\n"
|
|
|
|
"You can inspect what was checked out with 'git status'\n"
|
2019-04-25 11:45:58 +02:00
|
|
|
"and retry with 'git restore --source=HEAD :/'\n");
|
2008-04-27 19:39:30 +02:00
|
|
|
|
|
|
|
static void remove_junk(void)
|
|
|
|
{
|
2008-10-09 21:12:12 +02:00
|
|
|
struct strbuf sb = STRBUF_INIT;
|
2013-03-26 23:22:09 +01:00
|
|
|
|
|
|
|
switch (junk_mode) {
|
|
|
|
case JUNK_LEAVE_REPO:
|
|
|
|
warning("%s", _(junk_leave_repo_msg));
|
|
|
|
/* fall-through */
|
|
|
|
case JUNK_LEAVE_ALL:
|
|
|
|
return;
|
|
|
|
default:
|
|
|
|
/* proceed to removal */
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2008-04-27 19:39:30 +02:00
|
|
|
if (junk_git_dir) {
|
|
|
|
strbuf_addstr(&sb, junk_git_dir);
|
2018-01-02 22:11:39 +01:00
|
|
|
remove_dir_recursively(&sb, junk_git_dir_flags);
|
2008-04-27 19:39:30 +02:00
|
|
|
strbuf_reset(&sb);
|
|
|
|
}
|
|
|
|
if (junk_work_tree) {
|
|
|
|
strbuf_addstr(&sb, junk_work_tree);
|
2018-01-02 22:11:39 +01:00
|
|
|
remove_dir_recursively(&sb, junk_work_tree_flags);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
2017-08-30 19:49:37 +02:00
|
|
|
strbuf_release(&sb);
|
2008-04-27 19:39:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static void remove_junk_on_signal(int signo)
|
|
|
|
{
|
|
|
|
remove_junk();
|
chain kill signals for cleanup functions
If a piece of code wanted to do some cleanup before exiting
(e.g., cleaning up a lockfile or a tempfile), our usual
strategy was to install a signal handler that did something
like this:
do_cleanup(); /* actual work */
signal(signo, SIG_DFL); /* restore previous behavior */
raise(signo); /* deliver signal, killing ourselves */
For a single handler, this works fine. However, if we want
to clean up two _different_ things, we run into a problem.
The most recently installed handler will run, but when it
removes itself as a handler, it doesn't put back the first
handler.
This patch introduces sigchain, a tiny library for handling
a stack of signal handlers. You sigchain_push each handler,
and use sigchain_pop to restore whoever was before you in
the stack.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2009-01-22 07:02:35 +01:00
|
|
|
sigchain_pop(signo);
|
2008-04-27 19:39:30 +02:00
|
|
|
raise(signo);
|
|
|
|
}
|
|
|
|
|
2012-01-16 10:46:13 +01:00
|
|
|
static struct ref *find_remote_branch(const struct ref *refs, const char *branch)
|
|
|
|
{
|
|
|
|
struct ref *ref;
|
|
|
|
struct strbuf head = STRBUF_INIT;
|
|
|
|
strbuf_addstr(&head, "refs/heads/");
|
|
|
|
strbuf_addstr(&head, branch);
|
|
|
|
ref = find_ref_by_name(refs, head.buf);
|
|
|
|
strbuf_release(&head);
|
2012-01-16 10:46:15 +01:00
|
|
|
|
|
|
|
if (ref)
|
|
|
|
return ref;
|
|
|
|
|
|
|
|
strbuf_addstr(&head, "refs/tags/");
|
|
|
|
strbuf_addstr(&head, branch);
|
|
|
|
ref = find_ref_by_name(refs, head.buf);
|
|
|
|
strbuf_release(&head);
|
|
|
|
|
2012-01-16 10:46:13 +01:00
|
|
|
return ref;
|
|
|
|
}
|
|
|
|
|
2009-09-26 05:54:42 +02:00
|
|
|
static struct ref *wanted_peer_refs(const struct ref *refs,
|
clone: respect additional configured fetch refspecs during initial fetch
The initial fetch during a clone doesn't transfer refs matching
additional fetch refspecs given on the command line as configuration
variables, e.g. '-c remote.origin.fetch=<refspec>'. This contradicts
the documentation stating that configuration variables specified via
'git clone -c <key>=<value> ...' "take effect immediately after the
repository is initialized, but before the remote history is fetched"
and the given example specifically mentions "adding additional fetch
refspecs to the origin remote". Furthermore, one-shot configuration
variables specified via 'git -c <key>=<value> clone ...', though not
written to the newly created repository's config file, live during the
lifetime of the 'clone' command, including the initial fetch. All
this implies that any fetch refspecs specified this way should already
be taken into account during the initial fetch.
The reason for this is that the initial fetch is not a fully fledged
'git fetch' but a bunch of direct calls into the fetch/transport
machinery with clone's own refs-to-refspec matching logic, which
bypasses parts of 'git fetch' processing configured fetch refspecs.
This logic only considers a single default refspec, potentially
influenced by options like '--single-branch' and '--mirror'. The
configured refspecs are, however, already read and parsed properly
when clone calls remote.c:remote_get(), but it never looks at the
parsed refspecs in the resulting 'struct remote'.
Modify clone to take the remote's configured fetch refspecs into
account to retrieve all matching refs during the initial fetch. Note
that we have to explicitly add the default fetch refspec to the
remote's refspecs, because at that point the remote only includes the
fetch refspecs specified on the command line.
Add tests to check that refspecs given both via 'git clone -c ...' and
'git -c ... clone' retrieve all refs matching either the default or
the additional refspecs, and that it works even when the user
specifies an alternative remote name via '--origin=<name>'.
Signed-off-by: SZEDER Gábor <szeder.dev@gmail.com>
Reviewed-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2018-11-14 11:46:19 +01:00
|
|
|
struct refspec *refspec)
|
2008-04-27 19:39:30 +02:00
|
|
|
{
|
clone: always fetch remote HEAD
In most cases, fetching the remote HEAD explicitly is
unnecessary. It's just a symref pointing to a branch which
we are already fetching, so we will already ask for its sha1.
However, if the remote has a detached HEAD, things are less
certain. We do not ask for HEAD's sha1, but we do try to
write it into a local detached HEAD. In most cases this is
fine, as the remote HEAD is pointing to some part of the
history graph that we will fetch via the refs.
But if the remote HEAD points to an "orphan" commit (one
which was is not an ancestor of any refs), then we will not
have the object, and update_ref will complain when we try to
write the detached HEAD, aborting the whole clone.
This patch makes clone always explicitly ask the remote for
the sha1 of its HEAD commit. In the non-detached case, this
is a no-op, as we were going to ask for that sha1 anyway. In
the regular detached case, this will add an extra "want" to
the protocol negotiation, but will not change the history
that gets sent. And in the detached orphan case, we will
fetch the orphaned history so that we can write it into our
local detached HEAD.
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2011-06-08 01:03:22 +02:00
|
|
|
struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD"));
|
|
|
|
struct ref *local_refs = head;
|
|
|
|
struct ref **tail = head ? &head->next : &local_refs;
|
2008-04-27 19:39:30 +02:00
|
|
|
|
2012-01-07 15:45:59 +01:00
|
|
|
if (option_single_branch) {
|
|
|
|
struct ref *remote_head = NULL;
|
|
|
|
|
|
|
|
if (! |