@@ -215,6 +215,13 @@ objects from the source repository into a pack in the cloned repository.
`--branch` can also take tags and detaches the HEAD at that commit
in the resulting repository.
+`--ref` _<name>_::
+ This detaches HEAD and makes it point to the commit where the _<name>_
+ reference is pointing to. In a non-bare repository, this is the ref that
+ will be checked out.
+ Can be used in conjunction with `--single-branch` and `--no-tags` to
+ clone only the given ref. Cannot be combined with `--branch`.
+
`-u` _<upload-pack>_::
`--upload-pack` _<upload-pack>_::
When given, and the repository to clone from is accessed
@@ -259,7 +266,7 @@ corresponding `--mirror` and `--no-tags` options instead.
`--`[`no-`]`single-branch`::
Clone only the history leading to the tip of a single branch,
- either specified by the `--branch` option or the primary
+ either specified by the `--branch` or `--ref` option or the primary
branch remote's `HEAD` points at.
Further fetches into the resulting repository will only update the
remote-tracking branch for the branch this option was used for the
@@ -69,6 +69,7 @@ static char *option_template, *option_depth, *option_since;
static char *option_origin = NULL;
static char *remote_name = NULL;
static char *option_branch = NULL;
+static char *option_ref = NULL;
static struct string_list option_not = STRING_LIST_INIT_NODUP;
static const char *real_git_dir;
static const char *ref_format;
@@ -141,6 +142,8 @@ static struct option builtin_clone_options[] = {
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(0, "ref", &option_ref, N_("ref"),
+ N_("checkout <ref> (detached) 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"),
@@ -531,32 +534,64 @@ static struct ref *wanted_peer_refs(const struct ref *refs,
if (option_single_branch) {
struct ref *remote_head = NULL;
- if (!option_branch)
+ if (!option_branch && !option_ref)
remote_head = guess_remote_head(head, refs, 0);
else {
free_one_ref(head);
local_refs = head = NULL;
tail = &local_refs;
- remote_head = copy_ref(find_remote_branch(refs, option_branch));
+ if (option_branch)
+ remote_head = copy_ref(find_remote_branch(refs, option_branch));
+ else
+ remote_head = copy_ref(find_ref_by_name(refs, option_ref));
}
if (!remote_head && option_branch)
warning(_("Could not find remote branch %s to clone."),
option_branch);
+ else if (!remote_head && option_ref)
+ warning(_("Could not find remote ref %s to clone."),
+ option_ref);
else {
int i;
for (i = 0; i < refspec->nr; i++)
get_fetch_map(remote_head, &refspec->items[i],
&tail, 0);
- /* if --branch=tag, pull the requested tag explicitly */
- get_fetch_map(remote_head, &tag_refspec, &tail, 0);
+ if (option_ref) {
+ struct strbuf spec = STRBUF_INIT;
+ struct refspec_item ref_refspec;
+
+ strbuf_addf(&spec, "%s:%s", option_ref, option_ref);
+ refspec_item_init(&ref_refspec, spec.buf, 0);
+
+ get_fetch_map(remote_head, &ref_refspec, &tail, 0);
+
+ refspec_item_clear(&ref_refspec);
+ strbuf_release(&spec);
+ } else {
+ /* if --branch=tag, pull the requested tag explicitly */
+ get_fetch_map(remote_head, &tag_refspec, &tail, 0);
+ }
}
free_refs(remote_head);
} else {
int i;
for (i = 0; i < refspec->nr; i++)
get_fetch_map(refs, &refspec->items[i], &tail, 0);
+
+ if (option_ref) {
+ struct strbuf spec = STRBUF_INIT;
+ struct refspec_item ref_refspec;
+
+ strbuf_addf(&spec, "%s:%s", option_ref, option_ref);
+ refspec_item_init(&ref_refspec, spec.buf, 0);
+
+ get_fetch_map(refs, &ref_refspec, &tail, 0);
+
+ refspec_item_clear(&ref_refspec);
+ strbuf_release(&spec);
+ }
}
if (!option_mirror && !option_single_branch && !option_no_tags)
@@ -684,10 +719,15 @@ static void update_head(const struct ref *our, const struct ref *remote,
} else if (our) {
struct commit *c = lookup_commit_reference(the_repository,
&our->old_oid);
- /* --branch specifies a non-branch (i.e. tags), detach HEAD */
- refs_update_ref(get_main_ref_store(the_repository), msg,
- "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
- UPDATE_REFS_DIE_ON_ERR);
+ if (c)
+ /* --branch specifies a non-branch (i.e. tags), detach HEAD */
+ refs_update_ref(get_main_ref_store(the_repository), msg,
+ "HEAD", &c->object.oid, NULL, REF_NO_DEREF,
+ UPDATE_REFS_DIE_ON_ERR);
+ else
+ refs_update_ref(get_main_ref_store(the_repository), msg,
+ "HEAD", &our->old_oid, NULL, REF_NO_DEREF,
+ UPDATE_REFS_DIE_ON_ERR);
} else if (remote) {
/*
* We know remote HEAD points to a non-branch, or
@@ -898,6 +938,9 @@ static void write_refspec_config(const char *src_ref_prefix,
else
strbuf_addf(&value, "+%s:%s%s", our_head_points_at->name,
branch_top->buf, option_branch);
+ } else if (option_ref) {
+ strbuf_addf(&value, "+%s:%s", our_head_points_at->name,
+ our_head_points_at->name);
} else if (remote_head_points_at) {
const char *head = remote_head_points_at->name;
if (!skip_prefix(head, "refs/heads/", &head))
@@ -1383,6 +1426,9 @@ int cmd_clone(int argc,
if (option_branch)
expand_ref_prefix(&transport_ls_refs_options.ref_prefixes,
option_branch);
+ if (option_ref)
+ strvec_push(&transport_ls_refs_options.ref_prefixes,
+ option_ref);
if (!option_no_tags)
strvec_push(&transport_ls_refs_options.ref_prefixes,
"refs/tags/");
@@ -1468,6 +1514,11 @@ int cmd_clone(int argc,
if (!our_head_points_at)
die(_("Remote branch %s not found in upstream %s"),
option_branch, remote_name);
+ } else if (option_ref) {
+ our_head_points_at = find_ref_by_name(mapped_refs, option_ref);
+ if (!our_head_points_at)
+ die(_("Remote ref %s not found in upstream %s"),
+ option_ref, remote_name);
} else if (remote_head_points_at) {
our_head_points_at = remote_head_points_at;
} else if (remote_head) {
@@ -17,6 +17,7 @@ test_expect_success 'setup' '
git tag two &&
echo three >file &&
git commit -a -m three &&
+ git update-ref refs/some/three HEAD &&
git checkout -b side &&
echo four >file &&
git commit -a -m four &&
@@ -236,4 +237,38 @@ test_expect_success '--single-branch with detached' '
test_must_be_empty actual
'
+test_expect_success 'with --ref' '
+ git clone --ref=refs/some/three . dir_ref &&
+ git -C dir_ref for-each-ref refs > refs &&
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" refs >actual &&
+ git for-each-ref refs >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'with --ref and --no-tags' '
+ git clone --ref=refs/some/three --no-tags . dir_ref_notags &&
+ git -C dir_ref_notags for-each-ref refs > refs &&
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" refs >actual &&
+ git for-each-ref refs/heads >expect &&
+ git for-each-ref refs/some >>expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with --ref' '
+ git clone --single-branch --ref=refs/some/three . dir_single_ref &&
+ git -C dir_single_ref for-each-ref refs > actual &&
+ git for-each-ref refs/some >expect &&
+ git for-each-ref refs/tags >>expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with --ref and --no-tags' '
+ git clone --single-branch --ref=refs/some/three --no-tags . dir_single_ref_notags &&
+ git -C dir_single_ref_notags for-each-ref refs > actual &&
+ git for-each-ref refs/some >expect &&
+ test_cmp expect actual
+'
+
test_done