{"thread":{"id":"18591","subject":"[PATCH v3 2/2] git-clone: Add new option --branch to override initial branch","startedAt":"2009-03-27T17:45:11Z","lastAt":"2009-03-27T17:45:13Z","messageCount":3,"participants":["Tor Arne Vestbø"],"isPatch":true,"patchVersion":3,"patchTotal":2},"messages":[{"id":"109671","messageId":"1238175913-19175-1-git-send-email-tor.arne.vestbo@nokia.com","threadId":"18591","inReplyTo":null,"subject":"[PATCH v3 0/2] git-clone: Add new option --branch to override initial branch","fromName":"Tor Arne Vestbø","fromEmail":"tor.arne.vestbo@nokia.com","sentAt":"2009-03-27T17:45:11Z","receivedAt":"2009-03-27T17:45:11Z","isPatch":true,"sender":{"key":"tor.arne.vestbo@nokia.com","avatar":"https://gravatar.com/avatar/483668eb97b2c2076a95f0ceafe499a5167f313582bc174f1223c6b0ae7aa200?d=mp&s=160"},"body":"This mini-series is a continuation of the efforts to add add a new\noption to git clone to allow overriding the initial branch.\n\nThe previous patch (v2) and discussion can be found here:\n\n  http://thread.gmane.org/gmane.comp.version-control.git/111967/focus=111983\n\nWith Junios' comments to v2 of my patch here:\n\n  http://thread.gmane.org/gmane.comp.version-control.git/111967/focus=111983\n\nI believe I've covered all the issues in the review, but if there's\nstill issues please let me know and I'll rework the patch.\n\nBest,\n\nTor Arne\n\n\nTor Arne VestbÃ¸ (2):\n  Move find_ref_by_name_abbrev to refs.c so it can be used globally\n  git-clone: Add new option --branch to override initial branch\n\n Documentation/git-clone.txt |    5 +++\n builtin-clone.c             |   71 +++++++++++++++++++++++++-----------------\n cache.h                     |    1 +\n refs.c                      |   10 ++++++\n remote.c                    |   10 ------\n t/t5702-clone-options.sh    |   70 ++++++++++++++++++++++++++++++++++++++++--\n 6 files changed, 125 insertions(+), 42 deletions(-)\n"},{"id":"109670","messageId":"1238175913-19175-2-git-send-email-tor.arne.vestbo@nokia.com","threadId":"18591","inReplyTo":"1238175913-19175-1-git-send-email-tor.arne.vestbo@nokia.com","subject":"[PATCH v3 1/2] Move find_ref_by_name_abbrev to refs.c so it can be used globally","fromName":"Tor Arne Vestbø","fromEmail":"tor.arne.vestbo@nokia.com","sentAt":"2009-03-27T17:45:12Z","receivedAt":"2009-03-27T17:45:12Z","isPatch":true,"sender":{"key":"tor.arne.vestbo@nokia.com","avatar":"https://gravatar.com/avatar/483668eb97b2c2076a95f0ceafe499a5167f313582bc174f1223c6b0ae7aa200?d=mp&s=160"},"body":"Signed-off-by: Tor Arne Vestbø <tor.arne.vestbo@nokia.com>\n---\n cache.h  |    1 +\n refs.c   |   10 ++++++++++\n remote.c |   10 ----------\n 3 files changed, 11 insertions(+), 10 deletions(-)\n\ndiff --git a/cache.h b/cache.h\nindex f48e80b..23699a8 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -815,6 +815,7 @@ struct ref {\n #define REF_TAGS\t(1u << 2)\n \n extern struct ref *find_ref_by_name(const struct ref *list, const char *name);\n+extern struct ref *find_ref_by_name_abbrev(const struct ref *list, const char *name);\n \n #define CONNECT_VERBOSE       (1u << 0)\n extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);\ndiff --git a/refs.c b/refs.c\nindex 26b0014..161e053 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1644,3 +1644,13 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name)\n \t\t\treturn (struct ref *)list;\n \treturn NULL;\n }\n+\n+struct ref *find_ref_by_name_abbrev(const struct ref *list, const char *name)\n+{\n+\tfor ( ; list; list = list->next) {\n+\t\tif (refname_match(name, list->name, ref_fetch_rules))\n+\t\t\treturn (struct ref *)list;\n+\t}\n+\treturn NULL;\n+}\n+\ndiff --git a/remote.c b/remote.c\nindex e5d6b10..e903b39 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1220,16 +1220,6 @@ static struct ref *get_expanded_map(const struct ref *remote_refs,\n \treturn ret;\n }\n \n-static const struct ref *find_ref_by_name_abbrev(const struct ref *refs, const char *name)\n-{\n-\tconst struct ref *ref;\n-\tfor (ref = refs; ref; ref = ref->next) {\n-\t\tif (refname_match(name, ref->name, ref_fetch_rules))\n-\t\t\treturn ref;\n-\t}\n-\treturn NULL;\n-}\n-\n struct ref *get_remote_ref(const struct ref *remote_refs, const char *name)\n {\n \tconst struct ref *ref = find_ref_by_name_abbrev(remote_refs, name);\n-- \n1.6.2.GIT\n"},{"id":"109669","messageId":"1238175913-19175-3-git-send-email-tor.arne.vestbo@nokia.com","threadId":"18591","inReplyTo":"1238175913-19175-1-git-send-email-tor.arne.vestbo@nokia.com","subject":"[PATCH v3 2/2] git-clone: Add new option --branch to override initial branch","fromName":"Tor Arne Vestbø","fromEmail":"tor.arne.vestbo@nokia.com","sentAt":"2009-03-27T17:45:13Z","receivedAt":"2009-03-27T17:45:13Z","isPatch":true,"sender":{"key":"tor.arne.vestbo@nokia.com","avatar":"https://gravatar.com/avatar/483668eb97b2c2076a95f0ceafe499a5167f313582bc174f1223c6b0ae7aa200?d=mp&s=160"},"body":"The option '--branch' (short -b) allows the user to override the initial\nbranch created and checked out by git-clone (normally this is the active\nbranch of the remote repository).\n\nIf the selected branch is not found the operation aborts.\n\nThe rationale for this option is to allow new users to Git, who might\nnot be familiar with the concepts of tracking branches versus normal\nbranches (or even branches at all), to easily clone a repository and\nstart working on a branch other than the remote's active branch.\n\nSigned-off-by: Tor Arne Vestbø <tor.arne.vestbo@nokia.com>\n---\n\nThe above situation can be found in projects which keep a master branch\nfor active development, as well as one or more maintenance branches for\nreleased versions of the project. If a user would like to fix a bug for\na released version, he could use the proposed new option and simply do:\n\n  $ git clone git://git.foo.com/project.git --branch 1.6\n\nThe manual steps to achieve the same thing would be something like:\n\n  $ git clone -n git://git.foo.com/project.git        # (1)\n  $ cd project                                        # (2)\n  $ git checkout -t origin/1.6                        # (3)\n  $ git branch -D master                              # (4)\n\nA new user to Git might find this list of commands a bit daunting. It\nis also somewhat fragile, as running 'git status' after step (2) will\ncreate an index and hence prevent a checkout due to merge conflicts.\n\n(The reason for deleting the master branch in step (4) is to prevent\nerrors when the user tries to push his changes to the (rebased) 1.6\nbranch without also rebasing his master branch against origin/master)\n\n\n Documentation/git-clone.txt |    5 +++\n builtin-clone.c             |   71 +++++++++++++++++++++++++-----------------\n t/t5702-clone-options.sh    |   70 ++++++++++++++++++++++++++++++++++++++++--\n 3 files changed, 114 insertions(+), 32 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 95f08b9..e7feb4d 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -119,6 +119,11 @@ then the cloned repository will become corrupt.\n \tInstead of using the remote name 'origin' to keep track\n \tof the upstream repository, use <name> instead.\n \n+--branch <name>::\n+-b <name>::\n+\tInstead of using the remote repository's active branch as the\n+\tinitial branch, use <name> instead.\n+\n --upload-pack <upload-pack>::\n -u <upload-pack>::\n \tWhen given, and the repository to clone from is accessed\ndiff --git a/builtin-clone.c b/builtin-clone.c\nindex 0031b5f..0d03c87 100644\n--- a/builtin-clone.c\n+++ b/builtin-clone.c\n@@ -41,6 +41,7 @@ static int option_quiet, option_no_checkout, option_bare, option_mirror;\n static int option_local, option_no_hardlinks, option_shared;\n static char *option_template, *option_reference, *option_depth;\n static char *option_origin = NULL;\n+static char *option_branch;\n static char *option_upload_pack = \"git-upload-pack\";\n static int option_verbose;\n \n@@ -69,6 +70,8 @@ static struct option builtin_clone_options[] = {\n \t\t   \"path to git-upload-pack on the remote\"),\n \tOPT_STRING(0, \"depth\", &option_depth, \"depth\",\n \t\t    \"create a shallow clone of that depth\"),\n+\tOPT_STRING('b', \"branch\", &option_branch, \"branch\",\n+\t\t    \"initial remote branch to check out\"),\n \n \tOPT_END()\n };\n@@ -323,7 +326,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tconst char *repo_name, *repo, *work_tree, *git_dir;\n \tchar *path, *dir;\n \tint dest_exists;\n-\tconst struct ref *refs, *head_points_at, *remote_head, *mapped_refs;\n+\tconst struct ref *refs, *mapped_refs;\n+\tconst struct ref *remote_head = NULL, *remote_head_points_at = NULL;\n+\tconst struct ref *initial_branch = NULL;\n \tstruct strbuf key = STRBUF_INIT, value = STRBUF_INIT;\n \tstruct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT;\n \tstruct transport *transport = NULL;\n@@ -495,46 +500,35 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tmapped_refs = write_remote_refs(refs, refspec, reflog_msg.buf);\n \n \t\tremote_head = find_ref_by_name(refs, \"HEAD\");\n-\t\thead_points_at = guess_remote_head(remote_head, mapped_refs, 0);\n-\t}\n-\telse {\n+\t\tremote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0);\n+\n+\t\tif (option_branch) {\n+\t\t\tinitial_branch = find_ref_by_name_abbrev(refs, option_branch);\n+\t\t\tif (!initial_branch)\n+\t\t\t\tdie(\"remote has no branch named '%s'.\", option_branch);\n+\t\t} else {\n+\t\t\tinitial_branch = remote_head_points_at;\n+\t\t}\n+\t} else {\n \t\twarning(\"You appear to have cloned an empty repository.\");\n-\t\thead_points_at = NULL;\n-\t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare)\n \t\t\tinstall_branch_config(0, \"master\", option_origin,\n \t\t\t\t\t      \"refs/heads/master\");\n \t}\n \n-\tif (head_points_at) {\n-\t\t/* Local default branch link */\n-\t\tcreate_symref(\"HEAD\", head_points_at->name, NULL);\n-\n+\t/* Set up remote HEAD */\n+\tif (remote_head_points_at) {\n \t\tif (!option_bare) {\n \t\t\tstruct strbuf head_ref = STRBUF_INIT;\n-\t\t\tconst char *head = head_points_at->name;\n-\n-\t\t\tif (!prefixcmp(head, \"refs/heads/\"))\n-\t\t\t\thead += 11;\n-\n-\t\t\t/* Set up the initial local branch */\n-\n-\t\t\t/* Local branch initial value */\n-\t\t\tupdate_ref(reflog_msg.buf, \"HEAD\",\n-\t\t\t\t   head_points_at->old_sha1,\n-\t\t\t\t   NULL, 0, DIE_ON_ERR);\n \n \t\t\tstrbuf_addstr(&head_ref, branch_top.buf);\n \t\t\tstrbuf_addstr(&head_ref, \"HEAD\");\n \n \t\t\t/* Remote branch link */\n \t\t\tcreate_symref(head_ref.buf,\n-\t\t\t\t      head_points_at->peer_ref->name,\n+\t\t\t\t      remote_head_points_at->peer_ref->name,\n \t\t\t\t      reflog_msg.buf);\n-\n-\t\t\tinstall_branch_config(0, head, option_origin,\n-\t\t\t\t\t      head_points_at->name);\n \t\t}\n \t} else if (remote_head) {\n \t\t/* Source had detached HEAD pointing somewhere. */\n@@ -542,7 +536,26 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\tupdate_ref(reflog_msg.buf, \"HEAD\",\n \t\t\t\t   remote_head->old_sha1,\n \t\t\t\t   NULL, REF_NODEREF, DIE_ON_ERR);\n-\t} else {\n+\t}\n+\n+\t/* Set up intitial branch and HEAD */\n+\tif (initial_branch) {\n+\t\tcreate_symref(\"HEAD\", initial_branch->name, NULL);\n+\n+\t\tif (!option_bare) {\n+\t\t\tconst char *head = initial_branch->name;\n+\n+\t\t\tif (!prefixcmp(head, \"refs/heads/\"))\n+\t\t\t\thead += 11;\n+\n+\t\t\tupdate_ref(reflog_msg.buf, \"HEAD\",\n+\t\t\t\t   initial_branch->old_sha1,\n+\t\t\t\t   NULL, 0, DIE_ON_ERR);\n+\n+\t\t\tinstall_branch_config(0, head, option_origin,\n+\t\t\t\t\t      initial_branch->name);\n+\t\t}\n+\t} else if (!remote_head) {\n \t\t/* Nothing to checkout out */\n \t\tif (!option_no_checkout)\n \t\t\twarning(\"remote HEAD refers to nonexistent ref, \"\n@@ -554,6 +567,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport_unlock_pack(transport);\n \n \tif (!option_no_checkout) {\n+\t\tconst struct ref *checkout_ref = initial_branch ? initial_branch : remote_head;\n \t\tstruct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));\n \t\tstruct unpack_trees_options opts;\n \t\tstruct tree *tree;\n@@ -572,8 +586,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\topts.verbose_update = !option_quiet;\n \t\topts.src_index = &the_index;\n \t\topts.dst_index = &the_index;\n-\n-\t\ttree = parse_tree_indirect(remote_head->old_sha1);\n+\t\ttree = parse_tree_indirect(checkout_ref->old_sha1);\n \t\tparse_tree(tree);\n \t\tinit_tree_desc(&t, tree->buffer, tree->size);\n \t\tunpack_trees(1, &t, &opts);\n@@ -583,7 +596,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\t\tdie(\"unable to write new index file\");\n \n \t\terr |= run_hook(NULL, \"post-checkout\", sha1_to_hex(null_sha1),\n-\t\t\t\tsha1_to_hex(remote_head->old_sha1), \"1\", NULL);\n+\t\t\t\tsha1_to_hex(checkout_ref->old_sha1), \"1\", NULL);\n \t}\n \n \tstrbuf_release(&reflog_msg);\ndiff --git a/t/t5702-clone-options.sh b/t/t5702-clone-options.sh\nindex 27825f5..08cd8bd 100755\n--- a/t/t5702-clone-options.sh\n+++ b/t/t5702-clone-options.sh\n@@ -6,9 +6,14 @@ test_description='basic clone options'\n test_expect_success 'setup' '\n \n \tmkdir parent &&\n-\t(cd parent && git init &&\n-\t echo one >file && git add file &&\n-\t git commit -m one)\n+\t(\n+\t\tcd parent && git init &&\n+\t\techo one >file && git add file && git commit -m one &&\n+\t\techo two >file && git add file && git commit -m two &&\n+\t\tgit checkout -b topic &&\n+\t\techo thee >topic-file && git add topic-file && git commit -m three &&\n+\t\tgit checkout master\n+\t)\n \n '\n \n@@ -32,4 +37,63 @@ test_expect_success 'redirected clone -v' '\n \n '\n \n+test_expect_success 'clone --branch sets up the correct remote HEAD' '\n+\n+\tgit clone --branch=refs/heads/topic parent clone-topic-remote-head &&\n+\t(cd clone-topic-remote-head && grep master .git/refs/remotes/origin/HEAD)\n+\n+'\n+\n+test_expect_success 'clone --branch sets up the correct local branch' '\n+\n+\tgit clone --branch=refs/heads/topic parent clone-topic-name &&\n+\t(cd clone-topic-name && git rev-parse --verify refs/heads/topic)\n+\n+'\n+\n+test_expect_success 'clone --branch sets up the correct local HEAD' '\n+\n+\tgit clone --branch=refs/heads/topic parent clone-topic-head &&\n+\t(cd clone-topic-head && grep topic .git/HEAD)\n+\n+'\n+\n+test_expect_success 'clone --branch checks out the correct branch' '\n+\n+\tgit clone --branch=refs/heads/topic parent clone-topic-checkout &&\n+\ttest -f clone-topic-checkout/topic-file\n+\n+'\n+\n+test_expect_success 'clone --branch works with -n' '\n+\n+\tgit clone -n --branch=refs/heads/topic parent clone-topic-checkout-n &&\n+\t! test -f clone-topic-checkout-n/topic-file\n+\n+'\n+\n+test_expect_success 'clone --branch works with detatched remote HEAD' '\n+\n+\t(cd parent && git checkout master~1) &&\n+\tgit clone --branch=refs/heads/topic parent clone-topic-checkout-detatched &&\n+\ttest -f clone-topic-checkout-detatched/topic-file &&\n+\t(cd parent && git checkout master)\n+\n+'\n+\n+test_expect_success 'clone --branch works with invalid remote HEAD' '\n+\n+\t(cd parent && echo \"ref: refs/heads/nonexistent\" > .git/HEAD) &&\n+\tgit clone --branch=refs/heads/topic parent clone-topic-checkout-nonexistent &&\n+\ttest -f clone-topic-checkout-nonexistent/topic-file  &&\n+\t(cd parent && git checkout master)\n+'\n+\n+test_expect_success 'clone --branch works with abbreviated ref name' '\n+\n+\tgit clone --branch=topic parent clone-topic-abbreviated-name &&\n+\t(cd clone-topic-abbreviated-name && git rev-parse --verify refs/heads/topic)\n+\n+'\n+\n test_done\n-- \n1.6.2.GIT\n"}]}