[PATCH v3 2/2] git-clone: Add new option --branch to override initial branch

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



The option '--branch' (short -b) allows the user to override the initial
branch created and checked out by git-clone (normally this is the active
branch of the remote repository).

If the selected branch is not found the operation aborts.

The rationale for this option is to allow new users to Git, who might
not be familiar with the concepts of tracking branches versus normal
branches (or even branches at all), to easily clone a repository and
start working on a branch other than the remote's active branch.

Signed-off-by: Tor Arne Vestbø <tor.arne.vestbo@xxxxxxxxx>
---

The above situation can be found in projects which keep a master branch
for active development, as well as one or more maintenance branches for
released versions of the project. If a user would like to fix a bug for
a released version, he could use the proposed new option and simply do:

  $ git clone git://git.foo.com/project.git --branch 1.6

The manual steps to achieve the same thing would be something like:

  $ git clone -n git://git.foo.com/project.git        # (1)
  $ cd project                                        # (2)
  $ git checkout -t origin/1.6                        # (3)
  $ git branch -D master                              # (4)

A new user to Git might find this list of commands a bit daunting. It
is also somewhat fragile, as running 'git status' after step (2) will
create an index and hence prevent a checkout due to merge conflicts.

(The reason for deleting the master branch in step (4) is to prevent
errors when the user tries to push his changes to the (rebased) 1.6
branch without also rebasing his master branch against origin/master)


 Documentation/git-clone.txt |    5 +++
 builtin-clone.c             |   71 +++++++++++++++++++++++++-----------------
 t/t5702-clone-options.sh    |   70 ++++++++++++++++++++++++++++++++++++++++--
 3 files changed, 114 insertions(+), 32 deletions(-)

diff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt
index 95f08b9..e7feb4d 100644
--- a/Documentation/git-clone.txt
+++ b/Documentation/git-clone.txt
@@ -119,6 +119,11 @@ then the cloned repository will become corrupt.
 	Instead of using the remote name 'origin' to keep track
 	of the upstream repository, use <name> instead.
 
+--branch <name>::
+-b <name>::
+	Instead of using the remote repository's active branch as the
+	initial branch, use <name> instead.
+
 --upload-pack <upload-pack>::
 -u <upload-pack>::
 	When given, and the repository to clone from is accessed
diff --git a/builtin-clone.c b/builtin-clone.c
index 0031b5f..0d03c87 100644
--- a/builtin-clone.c
+++ b/builtin-clone.c
@@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror;
 static int option_local, option_no_hardlinks, option_shared;
 static char *option_template, *option_reference, *option_depth;
 static char *option_origin = NULL;
+static char *option_branch;
 static char *option_upload_pack = "git-upload-pack";
 static int option_verbose;
 
@@ -69,6 +70,8 @@ static struct option builtin_clone_options[] = {
 		   "path to git-upload-pack on the remote"),
 	OPT_STRING(0, "depth", &option_depth, "depth",
 		    "create a shallow clone of that depth"),
+	OPT_STRING('b', "branch", &option_branch, "branch",
+		    "initial remote branch to check out"),
 
 	OPT_END()
 };
@@ -323,7 +326,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 	const char *repo_name, *repo, *work_tree, *git_dir;
 	char *path, *dir;
 	int dest_exists;
-	const struct ref *refs, *head_points_at, *remote_head, *mapped_refs;
+	const struct ref *refs, *mapped_refs;
+	const struct ref *remote_head = NULL, *remote_head_points_at = NULL;
+	const struct ref *initial_branch = NULL;
 	struct strbuf key = STRBUF_INIT, value = STRBUF_INIT;
 	struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;
 	struct transport *transport = NULL;
@@ -495,46 +500,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		mapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);
 
 		remote_head = find_ref_by_name(refs, "HEAD");
-		head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
-	}
-	else {
+		remote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0);
+
+		if (option_branch) {
+			initial_branch = find_ref_by_name_abbrev(refs, option_branch);
+			if (!initial_branch)
+				die("remote has no branch named '%s'.", option_branch);
+		} else {
+			initial_branch = remote_head_points_at;
+		}
+	} else {
 		warning("You appear to have cloned an empty repository.");
-		head_points_at = NULL;
-		remote_head = NULL;
 		option_no_checkout = 1;
 		if (!option_bare)
 			install_branch_config(0, "master", option_origin,
 					      "refs/heads/master");
 	}
 
-	if (head_points_at) {
-		/* Local default branch link */
-		create_symref("HEAD", head_points_at->name, NULL);
-
+	/* Set up remote HEAD */
+	if (remote_head_points_at) {
 		if (!option_bare) {
 			struct strbuf head_ref = STRBUF_INIT;
-			const char *head = head_points_at->name;
-
-			if (!prefixcmp(head, "refs/heads/"))
-				head += 11;
-
-			/* Set up the initial local branch */
-
-			/* Local branch initial value */
-			update_ref(reflog_msg.buf, "HEAD",
-				   head_points_at->old_sha1,
-				   NULL, 0, DIE_ON_ERR);
 
 			strbuf_addstr(&head_ref, branch_top.buf);
 			strbuf_addstr(&head_ref, "HEAD");
 
 			/* Remote branch link */
 			create_symref(head_ref.buf,
-				      head_points_at->peer_ref->name,
+				      remote_head_points_at->peer_ref->name,
 				      reflog_msg.buf);
-
-			install_branch_config(0, head, option_origin,
-					      head_points_at->name);
 		}
 	} else if (remote_head) {
 		/* Source had detached HEAD pointing somewhere. */
@@ -542,7 +536,26 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			update_ref(reflog_msg.buf, "HEAD",
 				   remote_head->old_sha1,
 				   NULL, REF_NODEREF, DIE_ON_ERR);
-	} else {
+	}
+
+	/* Set up intitial branch and HEAD */
+	if (initial_branch) {
+		create_symref("HEAD", initial_branch->name, NULL);
+
+		if (!option_bare) {
+			const char *head = initial_branch->name;
+
+			if (!prefixcmp(head, "refs/heads/"))
+				head += 11;
+
+			update_ref(reflog_msg.buf, "HEAD",
+				   initial_branch->old_sha1,
+				   NULL, 0, DIE_ON_ERR);
+
+			install_branch_config(0, head, option_origin,
+					      initial_branch->name);
+		}
+	} else if (!remote_head) {
 		/* Nothing to checkout out */
 		if (!option_no_checkout)
 			warning("remote HEAD refers to nonexistent ref, "
@@ -554,6 +567,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		transport_unlock_pack(transport);
 
 	if (!option_no_checkout) {
+		const struct ref *checkout_ref = initial_branch ? initial_branch : remote_head;
 		struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
 		struct unpack_trees_options opts;
 		struct tree *tree;
@@ -572,8 +586,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 		opts.verbose_update = !option_quiet;
 		opts.src_index = &the_index;
 		opts.dst_index = &the_index;
-
-		tree = parse_tree_indirect(remote_head->old_sha1);
+		tree = parse_tree_indirect(checkout_ref->old_sha1);
 		parse_tree(tree);
 		init_tree_desc(&t, tree->buffer, tree->size);
 		unpack_trees(1, &t, &opts);
@@ -583,7 +596,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)
 			die("unable to write new index file");
 
 		err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1),
-				sha1_to_hex(remote_head->old_sha1), "1", NULL);
+				sha1_to_hex(checkout_ref->old_sha1), "1", NULL);
 	}
 
 	strbuf_release(&reflog_msg);
diff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh
index 27825f5..08cd8bd 100755
--- a/t/t5702-clone-options.sh
+++ b/t/t5702-clone-options.sh
@@ -6,9 +6,14 @@ test_description='basic clone options'
 test_expect_success 'setup' '
 
 	mkdir parent &&
-	(cd parent && git init &&
-	 echo one >file && git add file &&
-	 git commit -m one)
+	(
+		cd parent && git init &&
+		echo one >file && git add file && git commit -m one &&
+		echo two >file && git add file && git commit -m two &&
+		git checkout -b topic &&
+		echo thee >topic-file && git add topic-file && git commit -m three &&
+		git checkout master
+	)
 
 '
 
@@ -32,4 +37,63 @@ test_expect_success 'redirected clone -v' '
 
 '
 
+test_expect_success 'clone --branch sets up the correct remote HEAD' '
+
+	git clone --branch=refs/heads/topic parent clone-topic-remote-head &&
+	(cd clone-topic-remote-head && grep master .git/refs/remotes/origin/HEAD)
+
+'
+
+test_expect_success 'clone --branch sets up the correct local branch' '
+
+	git clone --branch=refs/heads/topic parent clone-topic-name &&
+	(cd clone-topic-name && git rev-parse --verify refs/heads/topic)
+
+'
+
+test_expect_success 'clone --branch sets up the correct local HEAD' '
+
+	git clone --branch=refs/heads/topic parent clone-topic-head &&
+	(cd clone-topic-head && grep topic .git/HEAD)
+
+'
+
+test_expect_success 'clone --branch checks out the correct branch' '
+
+	git clone --branch=refs/heads/topic parent clone-topic-checkout &&
+	test -f clone-topic-checkout/topic-file
+
+'
+
+test_expect_success 'clone --branch works with -n' '
+
+	git clone -n --branch=refs/heads/topic parent clone-topic-checkout-n &&
+	! test -f clone-topic-checkout-n/topic-file
+
+'
+
+test_expect_success 'clone --branch works with detatched remote HEAD' '
+
+	(cd parent && git checkout master~1) &&
+	git clone --branch=refs/heads/topic parent clone-topic-checkout-detatched &&
+	test -f clone-topic-checkout-detatched/topic-file &&
+	(cd parent && git checkout master)
+
+'
+
+test_expect_success 'clone --branch works with invalid remote HEAD' '
+
+	(cd parent && echo "ref: refs/heads/nonexistent" > .git/HEAD) &&
+	git clone --branch=refs/heads/topic parent clone-topic-checkout-nonexistent &&
+	test -f clone-topic-checkout-nonexistent/topic-file  &&
+	(cd parent && git checkout master)
+'
+
+test_expect_success 'clone --branch works with abbreviated ref name' '
+
+	git clone --branch=topic parent clone-topic-abbreviated-name &&
+	(cd clone-topic-abbreviated-name && git rev-parse --verify refs/heads/topic)
+
+'
+
 test_done
-- 
1.6.2.GIT

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html

[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux