diff options
| author | Toon Claes <toon@iotcl.com> | 2025-02-06 07:33:35 +0100 |
|---|---|---|
| committer | Junio C Hamano <gitster@pobox.com> | 2025-02-06 12:26:42 -0800 |
| commit | 337855629f59a3f435dabef900e22202ce8e00e1 (patch) | |
| tree | daf39e968e1cbcf45307853d5702da0f17f56fde /builtin/clone.c | |
| parent | 9144b9362b2ed972f5886dcc7beee6a2acce2708 (diff) | |
| download | git-337855629f59a3f435dabef900e22202ce8e00e1.tar.xz | |
builtin/clone: teach git-clone(1) the --revision= option
The git-clone(1) command has the option `--branch` that allows the user
to select the branch they want HEAD to point to. In a non-bare
repository this also checks out that branch.
Option `--branch` also accepts a tag. When a tag name is provided, the
commit this tag points to is checked out and HEAD is detached. Thus
`--branch` can be used to clone a repository and check out a ref kept
under `refs/heads` or `refs/tags`. But some other refs might be in use
as well. For example Git forges might use refs like `refs/pull/<id>` and
`refs/merge-requests/<id>` to track pull/merge requests. These refs
cannot be selected upon git-clone(1).
Add option `--revision` to git-clone(1). This option accepts a fully
qualified reference, or a hexadecimal commit ID. This enables the user
to clone and check out any revision they want. `--revision` can be used
in conjunction with `--depth` to do a minimal clone that only contains
the blob and tree for a single revision. This can be useful for
automated tests running in CI systems.
Using option `--branch` and `--single-branch` together is a similar
scenario, but serves a different purpose. Using these two options, a
singlet remote tracking branch is created and the fetch refspec is set
up so git-fetch(1) will receive updates on that branch from the remote.
This allows the user work on that single branch.
Option `--revision` on contrary detaches HEAD, creates no tracking
branches, and writes no fetch refspec.
Signed-off-by: Toon Claes <toon@iotcl.com>
Acked-by: Patrick Steinhardt <ps@pks.im>
[jc: removed unnecessary TEST_PASSES_SANITIZE_LEAK from the test]
Signed-off-by: Junio C Hamano <gitster@pobox.com>
Diffstat (limited to 'builtin/clone.c')
| -rw-r--r-- | builtin/clone.c | 57 |
1 files changed, 46 insertions, 11 deletions
diff --git a/builtin/clone.c b/builtin/clone.c index 1d421c8f75..f9a2ecbe9c 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -59,6 +59,7 @@ struct clone_opts { int wants_head; + int detach; }; #define CLONE_OPTS_INIT { \ .wants_head = 1 /* default enabled */ \ @@ -565,11 +566,11 @@ static void update_remote_refs(const struct ref *refs, } } -static void update_head(const struct ref *our, const struct ref *remote, +static void update_head(struct clone_opts *opts, const struct ref *our, const struct ref *remote, const char *unborn, const char *msg) { const char *head; - if (our && skip_prefix(our->name, "refs/heads/", &head)) { + if (our && !opts->detach && skip_prefix(our->name, "refs/heads/", &head)) { /* Local default branch link */ if (refs_update_symref(get_main_ref_store(the_repository), "HEAD", our->name, NULL) < 0) die(_("unable to update HEAD")); @@ -580,8 +581,9 @@ static void update_head(const struct ref *our, const struct ref *remote, install_branch_config(0, head, remote_name, our->name); } } else if (our) { - struct commit *c = lookup_commit_reference(the_repository, - &our->old_oid); + struct commit *c = lookup_commit_or_die(&our->old_oid, + our->name); + /* --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, @@ -900,6 +902,7 @@ int cmd_clone(int argc, int option_filter_submodules = -1; /* unspecified */ struct string_list server_options = STRING_LIST_INIT_NODUP; const char *bundle_uri = NULL; + char *option_rev = NULL; struct clone_opts opts = CLONE_OPTS_INIT; @@ -943,6 +946,8 @@ int cmd_clone(int argc, 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, "revision", &option_rev, N_("rev"), + N_("clone single revision <rev> and check out")), 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"), @@ -1279,7 +1284,7 @@ int cmd_clone(int argc, strbuf_addstr(&branch_top, src_ref_prefix); git_config_set("core.bare", "true"); - } else { + } else if (!option_rev) { strbuf_addf(&branch_top, "refs/remotes/%s/", remote_name); } @@ -1298,8 +1303,9 @@ int cmd_clone(int argc, remote = remote_get_early(remote_name); - refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, - branch_top.buf); + if (!option_rev) + refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, + branch_top.buf); path = get_repo_path(remote->url.v[0], &is_bundle); is_local = option_local != 0 && path && !is_bundle; @@ -1342,6 +1348,11 @@ int cmd_clone(int argc, transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + die_for_incompatible_opt2(!!option_rev, "--revision", + !!option_branch, "--branch"); + die_for_incompatible_opt2(!!option_rev, "--revision", + option_mirror, "--mirror"); + if (reject_shallow) transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1"); if (option_depth) @@ -1378,7 +1389,14 @@ int cmd_clone(int argc, if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; - strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + if (option_rev) { + option_tags = 0; + option_single_branch = 0; + opts.wants_head = 0; + opts.detach = 1; + + refspec_append(&remote->fetch, option_rev); + } if (option_tags || option_branch) /* @@ -1393,6 +1411,17 @@ int cmd_clone(int argc, expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, option_branch); + /* + * As part of transport_get_remote_refs() the server tells us the hash + * algorithm, which we require to initialize the repo. But calling that + * function without any ref prefix, will cause the server to announce + * all known refs. If the argument passed to --revision was a hex oid, + * ref_prefixes will be empty so we fall back to asking about HEAD to + * reduce traffic from the server. + */ + if (opts.wants_head || transport_ls_refs_options.ref_prefixes.nr == 0) + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); /* @@ -1501,6 +1530,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_rev) { + our_head_points_at = mapped_refs; + if (!our_head_points_at) + die(_("Remote revision %s not found in upstream %s"), + option_rev, remote_name); } else if (remote_head_points_at) { our_head_points_at = remote_head_points_at; } else if (remote_head) { @@ -1539,8 +1573,9 @@ int cmd_clone(int argc, free(to_free); } - write_refspec_config(src_ref_prefix, our_head_points_at, - remote_head_points_at, &branch_top); + if (!option_rev) + write_refspec_config(src_ref_prefix, our_head_points_at, + remote_head_points_at, &branch_top); if (filter_options.choice) partial_clone_register(remote_name, &filter_options); @@ -1556,7 +1591,7 @@ int cmd_clone(int argc, branch_top.buf, reflog_msg.buf, transport, !is_local); - update_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf); + update_head(&opts, our_head_points_at, remote_head, unborn_head, reflog_msg.buf); /* * We want to show progress for recursive submodule clones iff |
