Merge pull request #1222 from scunz/clone_branch Switch to specified branch during clone
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
diff --git a/include/git2/clone.h b/include/git2/clone.h
index e299c15..b546768 100644
--- a/include/git2/clone.h
+++ b/include/git2/clone.h
@@ -57,6 +57,8 @@ GIT_BEGIN_DECL
* the origin remote before the fetch is initiated.
* - `remote_autotag` may be used to specify the autotag setting before the
* initial fetch.
+ * - `checkout_branch` gives the name of the branch to checkout. NULL means
+ * use the remote's HEAD.
*/
typedef struct git_clone_options {
@@ -76,10 +78,11 @@ typedef struct git_clone_options {
git_transport *transport;
git_remote_callbacks *remote_callbacks;
git_remote_autotag_option_t remote_autotag;
+ const char* checkout_branch;
} git_clone_options;
#define GIT_CLONE_OPTIONS_VERSION 1
-#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE}}
+#define GIT_CLONE_OPTIONS_INIT {GIT_CLONE_OPTIONS_VERSION, {GIT_CHECKOUT_OPTS_VERSION, GIT_CHECKOUT_SAFE_CREATE}}
/**
* Clone a remote repository, and checkout the branch pointed to by the remote
diff --git a/src/clone.c b/src/clone.c
index 9012c04..d60977a 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -29,7 +29,7 @@ static int create_branch(
const char *name)
{
git_commit *head_obj = NULL;
- git_reference *branch_ref;
+ git_reference *branch_ref = NULL;
int error;
/* Find the target commit */
@@ -260,6 +260,32 @@ cleanup:
return retcode;
}
+static int update_head_to_branch(
+ git_repository *repo,
+ const git_clone_options *options)
+{
+ int retcode;
+ git_buf remote_branch_name = GIT_BUF_INIT;
+ git_reference* remote_ref = NULL;
+
+ assert(options->checkout_branch);
+
+ if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
+ options->remote_name, options->checkout_branch)) < 0 )
+ goto cleanup;
+
+ if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
+ goto cleanup;
+
+ retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref),
+ options->checkout_branch);
+
+cleanup:
+ git_reference_free(remote_ref);
+ git_buf_free(&remote_branch_name);
+ return retcode;
+}
+
/*
* submodules?
*/
@@ -331,8 +357,13 @@ static int setup_remotes_and_fetch(
options->fetch_progress_payload)) {
/* Create "origin/foo" branches for all remote branches */
if (!git_remote_update_tips(origin)) {
+ /* Point HEAD to the requested branch */
+ if (options->checkout_branch) {
+ if (!update_head_to_branch(repo, options))
+ retcode = 0;
+ }
/* Point HEAD to the same ref as the remote's head */
- if (!update_head_to_remote(repo, origin)) {
+ else if (!update_head_to_remote(repo, origin)) {
retcode = 0;
}
}
diff --git a/tests-clar/clone/nonetwork.c b/tests-clar/clone/nonetwork.c
index f241abf..953f796 100644
--- a/tests-clar/clone/nonetwork.c
+++ b/tests-clar/clone/nonetwork.c
@@ -7,6 +7,8 @@
static git_clone_options g_options;
static git_repository *g_repo;
+static git_reference* g_ref;
+static git_remote* g_remote;
void test_clone_nonetwork__initialize(void)
{
@@ -27,6 +29,16 @@ void test_clone_nonetwork__cleanup(void)
g_repo = NULL;
}
+ if (g_ref) {
+ git_reference_free(g_ref);
+ g_ref = NULL;
+ }
+
+ if (g_remote) {
+ git_remote_free(g_remote);
+ g_remote = NULL;
+ }
+
cl_fixture_cleanup("./foo");
}
@@ -73,66 +85,51 @@ void test_clone_nonetwork__fail_with_already_existing_but_non_empty_directory(vo
void test_clone_nonetwork__custom_origin_name(void)
{
- git_remote *remote;
-
g_options.remote_name = "my_origin";
cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
- cl_git_pass(git_remote_load(&remote, g_repo, "my_origin"));
-
- git_remote_free(remote);
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "my_origin"));
}
void test_clone_nonetwork__custom_push_url(void)
{
- git_remote *remote;
const char *url = "http://example.com";
g_options.pushurl = url;
cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
- cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
- cl_assert_equal_s(url, git_remote_pushurl(remote));
-
- git_remote_free(remote);
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "origin"));
+ cl_assert_equal_s(url, git_remote_pushurl(g_remote));
}
void test_clone_nonetwork__custom_fetch_spec(void)
{
- git_remote *remote;
- git_reference *master;
const git_refspec *actual_fs;
const char *spec = "+refs/heads/master:refs/heads/foo";
g_options.fetch_spec = spec;
cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
- cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
- actual_fs = git_remote_fetchspec(remote);
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "origin"));
+ actual_fs = git_remote_fetchspec(g_remote);
cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs));
cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs));
- cl_git_pass(git_reference_lookup(&master, g_repo, "refs/heads/foo"));
- git_reference_free(master);
-
- git_remote_free(remote);
+ cl_git_pass(git_reference_lookup(&g_ref, g_repo, "refs/heads/foo"));
}
void test_clone_nonetwork__custom_push_spec(void)
{
- git_remote *remote;
const git_refspec *actual_fs;
const char *spec = "+refs/heads/master:refs/heads/foo";
g_options.push_spec = spec;
cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
- cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
- actual_fs = git_remote_pushspec(remote);
+ cl_git_pass(git_remote_load(&g_remote, g_repo, "origin"));
+ actual_fs = git_remote_pushspec(g_remote);
cl_assert_equal_s("refs/heads/master", git_refspec_src(actual_fs));
cl_assert_equal_s("refs/heads/foo", git_refspec_dst(actual_fs));
-
- git_remote_free(remote);
}
void test_clone_nonetwork__custom_autotag(void)
@@ -167,3 +164,14 @@ void test_clone_nonetwork__can_prevent_the_checkout_of_a_standard_repo(void)
git_buf_free(&path);
}
+void test_clone_nonetwork__can_checkout_given_branch(void)
+{
+ g_options.checkout_branch = "test";
+ cl_git_pass(git_clone(&g_repo, cl_git_fixture_url("testrepo.git"), "./foo", &g_options));
+
+ cl_assert_equal_i(0, git_repository_head_orphan(g_repo));
+
+ cl_git_pass(git_repository_head(&g_ref, g_repo));
+ cl_assert_equal_s(git_reference_name(g_ref), "refs/heads/test");
+}
+