{"thread":{"id":"29292","subject":"[PATCH] Do not fetch tags on new shallow clones","startedAt":"2012-01-04T11:35:22Z","lastAt":"2012-01-07T14:45:59Z","messageCount":12,"participants":["Nguyễn Thái Ngọc Duy","Shawn Pearce","Junio C Hamano","Nguyen Thai Ngoc Duy"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"181917","messageId":"1325676922-6995-1-git-send-email-pclouds@gmail.com","threadId":"29292","inReplyTo":null,"subject":"[PATCH] Do not fetch tags on new shallow clones","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-01-04T11:35:22Z","receivedAt":"2012-01-04T11:35:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The main purpose of shallow clones is to reduce download. Fetching\ntags likely defeats this purpose because old-enough repos tend to have\na lot of tags, spreading across history, which may increase the number\nof objects to download significantly.\n\nFor example, \"git clone --depth=10 git://.../git.git\" without changes\nfetches ~16M (50k objects). The same command with changes fetches\n~6.5M (10k objects).\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n This could also be applied for normal clones. But I don't think\n there are many use cases for it, enough to deserve new --no-tags\n option.\n\n We should also fetch a single branch, but because branches are\n usually less crowded and stay close the tip, they do not produce too\n many extra objects. Let's leave it until somebody yells up.\n\n We should also fetch tags that reference to downloaded objects. But I\n don't know how fetch does that magic, so for now users have to do\n \"git fetch\" after cloning for tags. I have only gone as far as\n fetching tags along by setting TRANS_OPT_FOLLOWTAGS? Help?\n\n builtin/clone.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 86db954..abd8578 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -428,7 +428,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref **tail = head ? &head->next : &local_refs;\n \n \tget_fetch_map(refs, refspec, &tail, 0);\n-\tif (!option_mirror)\n+\tif (!option_mirror && !option_depth)\n \t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n \n \treturn local_refs;\n-- \n1.7.8.36.g69ee2\n"},{"id":"181920","messageId":"CAJo=hJsVvEuUp8AbkCrvGQCHb29EPBP5amKA8Hp2EmykRjynjw@mail.gmail.com","threadId":"29292","inReplyTo":"1325676922-6995-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] Do not fetch tags on new shallow clones","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2012-01-04T15:16:07Z","receivedAt":"2012-01-04T15:16:07Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"2012/1/4 Nguyễn Thái Ngọc Duy <pclouds@gmail.com>:\n> The main purpose of shallow clones is to reduce download. Fetching\n> tags likely defeats this purpose because old-enough repos tend to have\n> a lot of tags, spreading across history, which may increase the number\n> of objects to download significantly.\n\nThank you for looking at this. I complained about it to Junio many\nweeks ago, but never took the time myself to fix it. :-)\n\n>  We should also fetch a single branch, but because branches are\n>  usually less crowded and stay close the tip, they do not produce too\n>  many extra objects. Let's leave it until somebody yells up.\n\nDepends on the project. In git.git maint stays relatively close to\nmaster, but its still not really that close. In other projects, there\nare certainly huge differences between two active branches, sometimes\nspanning years. Consider any product with a multiple year support\ncontract on an older version, where the support contract demands\npatches for the older version to fix bugs.  :-)\n\nI agree this can be looked at later with a different change, but there\nshould be a way to specify exactly which branches you want to clone,\nespecially in the shallow case.\n\n>  We should also fetch tags that reference to downloaded objects. But I\n>  don't know how fetch does that magic,\n\nIf the remote advertises the capability \"include-tag\", and the client\nwants tags, it asks for that include-tag capability in its request.\nThis is handled by the fetch_pack args field include_tag being set to\n1. When the remote side sees the client requesting include-tag and it\npacks the thing a tag points at, the tag is also packed, even though\nit wasn't explicitly requested by the client.\n\n> so for now users have to do\n>  \"git fetch\" after cloning for tags. I have only gone as far as\n>  fetching tags along by setting TRANS_OPT_FOLLOWTAGS? Help?\n\nRight. Set TRANS_OPT_FOLLOWTAGS in the transport structure to fetch\nonly tags that are pointing at things already being sent. The delta\nincrease in transfer is 1 object (the tag) and whatever that tag takes\nup on disk.\n\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 86db954..abd8578 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -428,7 +428,7 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n>        struct ref **tail = head ? &head->next : &local_refs;\n>\n>        get_fetch_map(refs, refspec, &tail, 0);\n> -       if (!option_mirror)\n> +       if (!option_mirror && !option_depth)\n>                get_fetch_map(refs, tag_refspec, &tail, 0);\n>\n>        return local_refs;\n\nI think if you just add this into your patch, you get the auto follow\ntag feature enabled:\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex efe8b6c..ecaafdb 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -641,6 +641,7 @@ int cmd_clone(int argc, const char **argv, const char *prefi\n                        die(_(\"Don't know how to clone %s\"), transport->url);\n\n                transport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n+               transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, \"1\");\n\n                if (option_depth)\n                        transport_set_option(transport, TRANS_OPT_DEPTH,\n\ntotally untested (didn't even compile). This only works for the\n\"remote\" cases where the native Git protocol is used. A local clone\nusing the hardlink or copy objects path, or a dumb HTTP or rsync clone\nwill ignore the option and not supply you the tags.\n\nAnnnddddd..... it doesn't appear to work.\n\nYou need to copy a block of code from fetch. The problem is the object\nwas copied locally by the transport, but the transport doesn't tell\nyou what extra objects came along. Clone has to loop back through the\nadvertised reference map from the transport, checking each tag to see\nif has_sha1_file() says the object exists locally. If it does, then\nclone needs to add that reference update to the set of things it will\nstore (and print to the terminal).\n\nI think this loop is the find_non_local_tags() in builtin/fetch.c. Its\nbeen a long time since I hacked on this code. The JGit version of\nlooking for these extra objects post transfer is more clearly\ndocumented. *sigh*\n"},{"id":"181951","messageId":"7vwr97nho7.fsf@alter.siamese.dyndns.org","threadId":"29292","inReplyTo":"1325676922-6995-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH] Do not fetch tags on new shallow clones","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-04T23:59:04Z","receivedAt":"2012-01-04T23:59:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n>  This could also be applied for normal clones. But I don't think\n>  there are many use cases for it, enough to deserve new --no-tags\n>  option.\n\nCorrect.\n\n>  We should also fetch a single branch, but because branches are\n>  usually less crowded and stay close the tip, they do not produce too\n>  many extra objects. Let's leave it until somebody yells up.\n\nProbably this is needed.\n\n>  We should also fetch tags that reference to downloaded objects.\n\nI do not think this has much merit. The usual tag-following rules AFAIK\nassumes that if you have the commit C then you ought to have all the\nancestors of C, which does not apply at all for the shallow hack to begin\nwith, and if you make the rule apply for the shallow hack, you would end\nup slurping the objects that are needed only for ancient versions, which\nwould defeat the objective of this patch, no?\n\nIt also is my understanding that the shallow hack is almost always used\nwith a depth of one, not a range, like \"git archive | tar xf -\", so if\nanything, I would say a single-branch cloning has much higher priority\nthan following tags.\n\nWill queue on 'pu'. Thanks.\n"},{"id":"181961","messageId":"CAJo=hJvL2ebziPw=0q-e=G3WFEvEKtFR2xMFMiquSyrHJDradA@mail.gmail.com","threadId":"29292","inReplyTo":"7vwr97nho7.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Do not fetch tags on new shallow clones","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2012-01-05T03:05:07Z","receivedAt":"2012-01-05T03:05:07Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"2012/1/4 Junio C Hamano <gitster@pobox.com>:\n> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>  We should also fetch tags that reference to downloaded objects.\n>\n> I do not think this has much merit.\n\nI disagree. Its useful because cloning a branch immediately after it\nhas been tagged for a release should have `git describe` provide back\nthe name of the release from the tag (assuming of course no new\ncommits were made since the tag).\n\n> The usual tag-following rules AFAIK\n> assumes that if you have the commit C then you ought to have all the\n> ancestors of C, which does not apply at all for the shallow hack to begin\n> with, and if you make the rule apply for the shallow hack, you would end\n> up slurping the objects that are needed only for ancient versions, which\n> would defeat the objective of this patch, no?\n\nWe aren't talking about fetching the ancient history tags. We are\ntalking about fetching a tag that *directly* points at one of the\ncommits we did download the complete tree of.\n\n> It also is my understanding that the shallow hack is almost always used\n> with a depth of one, not a range, like \"git archive | tar xf -\", so if\n> anything, I would say a single-branch cloning has much higher priority\n> than following tags.\n\nI think I agree with you on priority of work effort. But I lack time\nto make good on that by writing the code myself.  :-)\n"},{"id":"181962","messageId":"7vfwfuofnk.fsf@alter.siamese.dyndns.org","threadId":"29292","inReplyTo":"CAJo=hJvL2ebziPw=0q-e=G3WFEvEKtFR2xMFMiquSyrHJDradA@mail.gmail.com","subject":"Re: [PATCH] Do not fetch tags on new shallow clones","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-05T05:57:19Z","receivedAt":"2012-01-05T05:57:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Pearce <spearce@spearce.org> writes:\n\n> 2012/1/4 Junio C Hamano <gitster@pobox.com>:\n>> Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n>>>  We should also fetch tags that reference to downloaded objects.\n>>\n>> I do not think this has much merit.\n>\n> I disagree. ...\n\nNo, you don't.  Because...\n\n> ... Its useful because cloning a branch immediately after it\n> has been tagged for a release should have `git describe` provide back\n> the name of the release from the tag (assuming of course no new\n> commits were made since the tag).\n\n... this is a natural extension of ...\n\n>> It also is my understanding that the shallow hack is almost always used\n>> with a depth of one, not a range, like \"git archive | tar xf -\", so if\n>> anything, I would say a single-branch cloning has much higher priority\n>> than following tags.\n>\n> I think I agree with you on priority of work effort. ...\n\n... this thing, once you have a \"single ref only\" stuff working.  After\nLinus announces that he released 3.2, you would do the poor emulation of\n\"git archive | tar xf -\" with something like:\n\n    git clone --single=v3.2 --shallow $there linux-3.2\n\nand your \"git describe\" should fall out as a natural consequence out of\neverything else, without the usual \"tag following\" semantics, no?\n"},{"id":"181963","messageId":"1325743516-14940-1-git-send-email-pclouds@gmail.com","threadId":"29292","inReplyTo":"1325676922-6995-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v2] Limit refs to fetch to minimum in shallow clones","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-01-05T06:05:16Z","receivedAt":"2012-01-05T06:05:16Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The main purpose of shallow clones is to reduce download by only\nfetching objects up to a certain depth from the given refs. The number\nof objects depends on how many refs to follow. So:\n\n - Only fetch HEAD or the ref specified by --branch\n - Only fetch tags that point to downloaded objects\n\nMore tags/branches can be fetched later using git-fetch as usual.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n Only lightly tested, but seems to work. I'll check later if --branch\n works with a tag like Junio's example --single=v3.2\n\n Documentation/git-clone.txt |   14 ++++++----\n builtin/clone.c             |   54 +++++++++++++++++++++++++++++++++++++++----\n 2 files changed, 57 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 4b8b26b..d69f440 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -172,12 +172,14 @@ objects from the source repository into a pack in the cloned repository.\n \n --depth <depth>::\n \tCreate a 'shallow' clone with a history truncated to the\n-\tspecified number of revisions.  A shallow repository has a\n-\tnumber of limitations (you cannot clone or fetch from\n-\tit, nor push from nor into it), but is adequate if you\n-\tare only interested in the recent history of a large project\n-\twith a long history, and would want to send in fixes\n-\tas patches.\n+\tspecified number of revisions. Only one branch (either HEAD\n+\tor specified by --branch) is fetched. Tags that point\n+\toutside truncated history are not fetched.\n++\n+A shallow repository has a number of limitations (you cannot clone or\n+fetch from it, nor push from nor into it), but is adequate if you are\n+only interested in the recent history of a large project with a long\n+history, and would want to send in fixes as patches.\n \n --recursive::\n --recurse-submodules::\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex efe8b6c..8de9248 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -48,6 +48,7 @@ static int option_verbosity;\n static int option_progress;\n static struct string_list option_config;\n static struct string_list option_reference;\n+static char *src_ref_prefix = \"refs/heads/\";\n \n static int opt_parse_reference(const struct option *opt, const char *arg, int unset)\n {\n@@ -427,9 +428,27 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n \n-\tget_fetch_map(refs, refspec, &tail, 0);\n-\tif (!option_mirror)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\tif (option_depth) {\n+\t\tstruct ref *remote_head = NULL;\n+\n+\t\tif (!option_branch)\n+\t\t\tremote_head = guess_remote_head(head, refs, 0);\n+\t\telse {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&sb, src_ref_prefix);\n+\t\t\tstrbuf_addstr(&sb, option_branch);\n+\t\t\tremote_head = find_ref_by_name(refs, sb.buf);\n+\t\t\tstrbuf_release(&sb);\n+\t\t}\n+\n+\t\tif (remote_head)\n+\t\t\tget_fetch_map(remote_head, refspec, &tail, 0);\n+\t}\n+\telse {\n+\t\tget_fetch_map(refs, refspec, &tail, 0);\n+\t\tif (!option_mirror)\n+\t\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t}\n \n \treturn local_refs;\n }\n@@ -448,6 +467,27 @@ static void write_remote_refs(const struct ref *local_refs)\n \tclear_extra_refs();\n }\n \n+static void write_followtags(const struct ref *refs)\n+{\n+\tstruct ref_lock *lock;\n+\tconst struct ref *ref;\n+\n+\tfor (ref = refs; ref; ref = ref->next) {\n+\t\tif (prefixcmp(ref->name, \"refs/tags\"))\n+\t\t\tcontinue;\n+\t\tif (!suffixcmp(ref->name, \"^{}\"))\n+\t\t\tcontinue;\n+\t\tif (!has_sha1_file(ref->old_sha1))\n+\t\t\tcontinue;\n+\n+\t\tlock = lock_any_ref_for_update(ref->name, NULL, 0);\n+\t\tif (!lock)\n+\t\t\tdie_errno(_(\"unable to lock %s for writing\"), ref->name);\n+\t\tif (write_ref_sha1(lock, ref->old_sha1, \"storing tag\") < 0)\n+\t\t\tdie_errno(_(\"unable to write %s\"), ref->name);\n+\t}\n+}\n+\n static int write_one_config(const char *key, const char *value, void *data)\n {\n \treturn git_config_set_multivar(key, value ? value : \"true\", \"^$\", 0);\n@@ -478,7 +518,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\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-\tchar *src_ref_prefix = \"refs/heads/\";\n \tint err = 0;\n \n \tstruct refspec *refspec;\n@@ -642,9 +681,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \n \t\ttransport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n \n-\t\tif (option_depth)\n+\t\tif (option_depth) {\n \t\t\ttransport_set_option(transport, TRANS_OPT_DEPTH,\n \t\t\t\t\t     option_depth);\n+\t\t\ttransport_set_option(transport, TRANS_OPT_FOLLOWTAGS,\n+\t\t\t\t\t     \"1\");\n+\t\t}\n \n \t\ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \n@@ -663,6 +705,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tclear_extra_refs();\n \n \t\twrite_remote_refs(mapped_refs);\n+\t\tif (option_depth)\n+\t\t\twrite_followtags(refs);\n \n \t\tremote_head = find_ref_by_name(refs, \"HEAD\");\n \t\tremote_head_points_at =\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"181976","messageId":"CAJo=hJse=6+9DdDgwJOEas1kdbx946SSKeTjEMZrYUA6V+h=1Q@mail.gmail.com","threadId":"29292","inReplyTo":"7vfwfuofnk.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] Do not fetch tags on new shallow clones","fromName":"Shawn Pearce","fromEmail":"spearce@spearce.org","sentAt":"2012-01-05T15:16:14Z","receivedAt":"2012-01-05T15:16:14Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"2012/1/4 Junio C Hamano <gitster@pobox.com>:\n> Shawn Pearce <spearce@spearce.org> writes:\n>> ... Its useful because cloning a branch immediately after it\n>> has been tagged for a release should have `git describe` provide back\n>> the name of the release from the tag (assuming of course no new\n>> commits were made since the tag).\n...\n> ... this thing, once you have a \"single ref only\" stuff working.  After\n> Linus announces that he released 3.2, you would do the poor emulation of\n> \"git archive | tar xf -\" with something like:\n>\n>    git clone --single=v3.2 --shallow $there linux-3.2\n>\n> and your \"git describe\" should fall out as a natural consequence out of\n> everything else, without the usual \"tag following\" semantics, no?\n\nI said \"branch\" not \"tag\". Of course a single ref clone might be able\nto be used on a tag.\n\nIf my project maintainer tags a release from \"maint\" and announces\nthat, I should also be able to shallow clone maint and pick up the tag\nautomatically if it is within the depth I have asked for from the\nserver.\n\nConsider this case, a client clones shallow with a depth of 1. Then\ndoes normal `git fetch` to keep up-to-date with the project. When the\nproject places a new tag on a branch, our shallow follower will\nautomatically get that tag during her next `git fetch`, because auto\nfollowing tags is enabled in fetch and the tag's referent was included\nin the pack. Why is this case permitted to get a tag, and shallow\nclone is not?\n\nActually, I think you might find that a shallow client with depth of 1\nwill automatically pick up a missing tag at the branch head on its\nnext `git fetch`. It will see the tag's ^{} line advertise an object\nit has, and ask for the tag.\n\nWe really should support auto-following tags within the history space\nthe client already has. Its mostly done for us with the include-tag\ncapability, the client just needs to make sure it asks for it from the\nserver, and check to see if any tag reference points to an object it\nhas.\n\n> you would do the poor emulation of\n> \"git archive | tar xf -\" with something like:\n>\n>    git clone --single=v3.2 --shallow $there linux-3.2\n\nIs it really that poor of an emulation? Like tar, we get only one copy\nof each file (assuming depth 1). Assuming --format=tar.gz, both are\ncompressed. I wonder how much better or worse the Git cross-object\ndelta compression is than the libz rolling window. I could see how Git\nmight be able to compress something like C source code smaller than\ntar | gzip by using delta compression on related files (e.g. Makefile\nin every directory, or *.h and *.c files pairing by type). An added\nadvantage of the shallow clone is you can incrementally update that\nstream, as its easy to fetch a v3.2.1 patch release, or apply a patch\nand record it on top.\n"},{"id":"181991","messageId":"7vmxa1n8oe.fsf@alter.siamese.dyndns.org","threadId":"29292","inReplyTo":"1325743516-14940-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v2] Limit refs to fetch to minimum in shallow clones","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-05T21:25:37Z","receivedAt":"2012-01-05T21:25:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:\n\n> The main purpose of shallow clones is to reduce download by only\n> fetching objects up to a certain depth from the given refs. The number\n> of objects depends on how many refs to follow. So:\n>\n>  - Only fetch HEAD or the ref specified by --branch\n>  - Only fetch tags that point to downloaded objects\n>\n> More tags/branches can be fetched later using git-fetch as usual.\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n>  Only lightly tested, but seems to work.\n\nThanks.\n\nPerhaps you would want to add tests so that you do not have to say\n\"lightly tested\"?\n\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index efe8b6c..8de9248 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -48,6 +48,7 @@ static int option_verbosity;\n>  static int option_progress;\n>  static struct string_list option_config;\n>  static struct string_list option_reference;\n> +static char *src_ref_prefix = \"refs/heads/\";\n\nWould this be const?\n\n>  static int opt_parse_reference(const struct option *opt, const char *arg, int unset)\n>  {\n> @@ -427,9 +428,27 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n>  \tstruct ref *local_refs = head;\n>  \tstruct ref **tail = head ? &head->next : &local_refs;\n>  \n> -\tget_fetch_map(refs, refspec, &tail, 0);\n> -\tif (!option_mirror)\n> -\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n> +\tif (option_depth) {\n> +\t\tstruct ref *remote_head = NULL;\n> +\n> +\t\tif (!option_branch)\n> +\t\t\tremote_head = guess_remote_head(head, refs, 0);\n> +\t\telse {\n> +\t\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\t\tstrbuf_addstr(&sb, src_ref_prefix);\n> +\t\t\tstrbuf_addstr(&sb, option_branch);\n> +\t\t\tremote_head = find_ref_by_name(refs, sb.buf);\n> +\t\t\tstrbuf_release(&sb);\n> +\t\t}\n> +\n> +\t\tif (remote_head)\n> +\t\t\tget_fetch_map(remote_head, refspec, &tail, 0);\n\nWhat happens when we fail to find any remote_head and make no call to\nget_fetch_map() here?  I am wondering if that should trigger an error\nhere.\n\nAlso this breaks 5500 for rather obvious reasons, as the point of this\npatch is to reduce the object transferred when a shallow clone is made.\n\nPerhaps there should be an option to give users the historical \"all\nbranches equally shallow\" behaviour?\n"},{"id":"182007","messageId":"1325833869-20078-1-git-send-email-pclouds@gmail.com","threadId":"29292","inReplyTo":"1325743516-14940-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v3] Limit refs to fetch to minimum in shallow clones","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-01-06T07:11:09Z","receivedAt":"2012-01-06T07:11:09Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"The main purpose of shallow clones is to reduce download by only\nfetching objects up to a certain depth from the given refs. The number\nof objects depend on how many refs to follow. So:\n\n - Only fetch HEAD or the ref specified by --branch\n - Only fetch tags that reference to downloaded objects\n\nMore tags/branches can be fetched later using git-fetch as usual.\n\nThe old behaviour can still be called with --no-single-branch\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n  - add --no-single-branch so t5500 works without big changes.\n  - die() if we cannot find suitable branch to fetch (and suggest --no-single-branch)\n  - and a bit more tests to exercise new code\n\n  --branch=<tag> (or something similar) has to wait until my other\n  patch gets in a good shape (or gets dropped)\n\n There may be something slightly wrong with shallow code. I expect it\n to fetch only 3 objects (1 commit, 1 tree, 1 blob) with --depth=1\n in my new test but it fetches 6 (one more commit).\n\n Documentation/git-clone.txt |   12 ++++++++-\n builtin/clone.c             |   54 ++++++++++++++++++++++++++++++++++++++----\n t/t5500-fetch-pack.sh       |   34 ++++++++++++++++++++++++++-\n 3 files changed, 92 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 4b8b26b..58f21d6 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -13,7 +13,8 @@ SYNOPSIS\n \t  [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]\n \t  [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]\n \t  [--separate-git-dir <git dir>]\n-\t  [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>\n+\t  [--depth <depth> [--[no-]single-branch]]\n+\t  [--recursive|--recurse-submodules] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -179,6 +180,15 @@ objects from the source repository into a pack in the cloned repository.\n \twith a long history, and would want to send in fixes\n \tas patches.\n \n+--single-branch::\n+--no-single-branch::\n+\tThese options are only valid when --depth is given.\n+\t `--single-branch` only fetches one branch (either HEAD or\n+\tspecified by --branch) and tags that point to the downloaded\n+\thistory. `--no-single-branch` fetches all branches and tags\n+\tlike in normal clones. `--single-branch` is implied by\n+\tdefault.\n+\n --recursive::\n --recurse-submodules::\n \tAfter the clone is created, initialize all submodules within,\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex efe8b6c..3424e1c 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {\n \tNULL\n };\n \n-static int option_no_checkout, option_bare, option_mirror;\n+static int option_no_checkout, option_bare, option_mirror, option_single_branch = 1;\n static int option_local, option_no_hardlinks, option_shared, option_recursive;\n static char *option_template, *option_depth;\n static char *option_origin = NULL;\n@@ -48,6 +48,7 @@ static int option_verbosity;\n static int option_progress;\n static struct string_list option_config;\n static struct string_list option_reference;\n+static const char *src_ref_prefix = \"refs/heads/\";\n \n static int opt_parse_reference(const struct option *opt, const char *arg, int unset)\n {\n@@ -92,6 +93,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_BOOL(0, \"single-branch\", &option_single_branch,\n+\t\t    \"do not limit fetched refs in shallow clones\"),\n \tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, \"gitdir\",\n \t\t   \"separate git dir from working tree\"),\n \tOPT_STRING_LIST('c', \"config\", &option_config, \"key=value\",\n@@ -427,9 +430,29 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n \n-\tget_fetch_map(refs, refspec, &tail, 0);\n-\tif (!option_mirror)\n-\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\tif (!(option_depth && option_single_branch)) {\n+\t\tget_fetch_map(refs, refspec, &tail, 0);\n+\t\tif (!option_mirror)\n+\t\t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n+\t} else {\n+\t\tstruct ref *remote_head = NULL;\n+\n+\t\tif (!option_branch)\n+\t\t\tremote_head = guess_remote_head(head, refs, 0);\n+\t\telse {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&sb, src_ref_prefix);\n+\t\t\tstrbuf_addstr(&sb, option_branch);\n+\t\t\tremote_head = find_ref_by_name(refs, sb.buf);\n+\t\t\tstrbuf_release(&sb);\n+\t\t}\n+\n+\t\tif (!remote_head)\n+\t\t\tdie(_(\"Remote branch \\\"%s\\\" not found. Nothing to clone.\\n\"\n+\t\t\t      \"Try --no-single-branch to fetch all refs.\"),\n+\t\t\t    option_branch ? option_branch : \"HEAD\");\n+\t\tget_fetch_map(remote_head, refspec, &tail, 0);\n+\t}\n \n \treturn local_refs;\n }\n@@ -448,6 +471,21 @@ static void write_remote_refs(const struct ref *local_refs)\n \tclear_extra_refs();\n }\n \n+static void write_followtags(const struct ref *refs, const char *msg)\n+{\n+\tconst struct ref *ref;\n+\tfor (ref = refs; ref; ref = ref->next) {\n+\t\tif (prefixcmp(ref->name, \"refs/tags/\"))\n+\t\t\tcontinue;\n+\t\tif (!suffixcmp(ref->name, \"^{}\"))\n+\t\t\tcontinue;\n+\t\tif (!has_sha1_file(ref->old_sha1))\n+\t\t\tcontinue;\n+\t\tupdate_ref(msg, ref->name, ref->old_sha1,\n+\t\t\t   NULL, 0, DIE_ON_ERR);\n+\t}\n+}\n+\n static int write_one_config(const char *key, const char *value, void *data)\n {\n \treturn git_config_set_multivar(key, value ? value : \"true\", \"^$\", 0);\n@@ -478,7 +516,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\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-\tchar *src_ref_prefix = \"refs/heads/\";\n \tint err = 0;\n \n \tstruct refspec *refspec;\n@@ -642,9 +679,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \n \t\ttransport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n \n-\t\tif (option_depth)\n+\t\tif (option_depth) {\n \t\t\ttransport_set_option(transport, TRANS_OPT_DEPTH,\n \t\t\t\t\t     option_depth);\n+\t\t\ttransport_set_option(transport, TRANS_OPT_FOLLOWTAGS,\n+\t\t\t\t\t     option_single_branch ? \"1\" : NULL);\n+\t\t}\n \n \t\ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \n@@ -663,6 +703,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tclear_extra_refs();\n \n \t\twrite_remote_refs(mapped_refs);\n+\t\tif (option_depth && option_single_branch)\n+\t\t\twrite_followtags(refs, reflog_msg.buf);\n \n \t\tremote_head = find_ref_by_name(refs, \"HEAD\");\n \t\tremote_head_points_at =\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex bafcca7..c76a53b 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -115,7 +115,7 @@ pull_to_client 2nd \"B\" $((64*3))\n pull_to_client 3rd \"A\" $((1*3))\n \n test_expect_success 'clone shallow' '\n-\tgit clone --depth 2 \"file://$(pwd)/.\" shallow\n+\tgit clone --no-single-branch --depth 2 \"file://$(pwd)/.\" shallow\n '\n \n test_expect_success 'clone shallow object count' '\n@@ -248,4 +248,36 @@ test_expect_success 'clone shallow object count' '\n \tgrep \"^count: 52\" count.shallow\n '\n \n+test_expect_success 'clone shallow without --no-single-branch' '\n+\tgit clone --depth 1 \"file://$(pwd)/.\" shallow2\n+'\n+\n+test_expect_success 'clone shallow object count' '\n+\t(\n+\t\tcd shallow2 &&\n+\t\tgit count-objects -v\n+\t) > count.shallow2 &&\n+\tgrep \"^in-pack: 6\" count.shallow2\n+'\n+\n+test_expect_success 'shallow clone pulling tags' '\n+\tgit tag -a -m A TAGA1 A &&\n+\tgit tag -a -m B TAGB1 B &&\n+\tgit tag TAGA2 A &&\n+\tgit tag TAGB2 B &&\n+\tgit clone --depth 1 \"file://$(pwd)/.\" shallow3 &&\n+\n+\tcat >taglist.expected <<\\EOF &&\n+TAGB1\n+TAGB2\n+EOF\n+\tGIT_DIR=shallow3/.git git tag -l >taglist.actual &&\n+\ttest_cmp taglist.expected taglist.actual &&\n+\n+\techo \"in-pack: 7\" > count3.expected &&\n+\tGIT_DIR=shallow3/.git git count-objects -v |\n+\t\tgrep \"^in-pack\" > count3.actual &&\n+\ttest_cmp count3.expected count3.actual\n+'\n+\n test_done\n-- \n1.7.3.1.256.g2539c.dirty\n"},{"id":"182031","messageId":"7vfwfsllny.fsf@alter.siamese.dyndns.org","threadId":"29292","inReplyTo":"1325833869-20078-1-git-send-email-pclouds@gmail.com","subject":"Re: [PATCH v3] Limit refs to fetch to minimum in shallow clones","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2012-01-06T18:40:17Z","receivedAt":"2012-01-06T18:40:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Nguyễn Thái Ngọc Duy <pclouds@gmail.com> writes:\n\n> The main purpose of shallow clones is to reduce download by only\n> fetching objects up to a certain depth from the given refs. The number\n> of objects depend on how many refs to follow. So:\n>\n>  - Only fetch HEAD or the ref specified by --branch\n>  - Only fetch tags that reference to downloaded objects\n>\n> More tags/branches can be fetched later using git-fetch as usual.\n>\n> The old behaviour can still be called with --no-single-branch\n>\n> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n> ---\n\nThanks.\n\n> @@ -179,6 +180,15 @@ objects from the source repository into a pack in the cloned repository.\n>  \twith a long history, and would want to send in fixes\n>  \tas patches.\n>  \n> +--single-branch::\n> +--no-single-branch::\n> +\tThese options are only valid when --depth is given.\n> +\t `--single-branch` only fetches one branch (either HEAD or\n> +\tspecified by --branch) and tags that point to the downloaded\n> +\thistory. `--no-single-branch` fetches all branches and tags\n> +\tlike in normal clones. `--single-branch` is implied by\n> +\tdefault.\n> +\n\nMy first reaction after reading \"is implied by default\" was \"Huh? didn't\nwe just read these kick in only when --depth is given?\" and I had to read\nit again. Here is my attempt to rephrase it.\n\n        Clone only the history leading to the tip of a single branch,\n        either specified by the `--branch` option or the primary branch\n        remote's `HEAD` points at. When creating a shallow clone with the\n        `--depth` option, this is the default, unless `--no-single-branch`\n        is given to fetch the histories near the tips of all branches.\n\n        Currently this option only works when creating a shallow clone and\n        does not have any effect without the `--depth` option.\n\nWe might want to later enhance this to work also with a full-depth clone\nthat tracks only one branch, and the above phrasing would make it clear.\n\n> +\t\tif (!option_branch)\n> +\t\t\tremote_head = guess_remote_head(head, refs, 0);\n> +\t\telse {\n> +\t\t\tstruct strbuf sb = STRBUF_INIT;\n> +\t\t\tstrbuf_addstr(&sb, src_ref_prefix);\n> +\t\t\tstrbuf_addstr(&sb, option_branch);\n> +\t\t\tremote_head = find_ref_by_name(refs, sb.buf);\n> +\t\t\tstrbuf_release(&sb);\n> +\t\t}\n> +\n> +\t\tif (!remote_head)\n> +\t\t\tdie(_(\"Remote branch \\\"%s\\\" not found. Nothing to clone.\\n\"\n> +\t\t\t      \"Try --no-single-branch to fetch all refs.\"),\n> +\t\t\t    option_branch ? option_branch : \"HEAD\");\n\nSwitching upon option_branch to tweak the message is a good idea, but\nstrictly speaking, we would hit this die() when guess_remote_head() does\nnot find where HEAD points at because it is detached, and in that case,\nthe error is not \"Nothing to clone\", but \"We couldn't tell which branch\nyou meant to limit this cloning to\".\n\n> @@ -642,9 +679,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \n>  \t\ttransport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n>  \n> -\t\tif (option_depth)\n> +\t\tif (option_depth) {\n>  \t\t\ttransport_set_option(transport, TRANS_OPT_DEPTH,\n>  \t\t\t\t\t     option_depth);\n> +\t\t\ttransport_set_option(transport, TRANS_OPT_FOLLOWTAGS,\n> +\t\t\t\t\t     option_single_branch ? \"1\" : NULL);\n\nCurious. Does anybody set FOLLOWTAGS to the transport by default becore we\ncome here (just asking)?\n"},{"id":"182070","messageId":"CACsJy8DrabLzoxpmQqU13-81k=7Ni8kbg4HX3V67e3AL=L39wQ@mail.gmail.com","threadId":"29292","inReplyTo":"7vfwfsllny.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH v3] Limit refs to fetch to minimum in shallow clones","fromName":"Nguyen Thai Ngoc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-01-07T14:16:22Z","receivedAt":"2012-01-07T14:16:22Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"2012/1/7 Junio C Hamano <gitster@pobox.com>:\n> My first reaction after reading \"is implied by default\" was \"Huh? didn't\n> we just read these kick in only when --depth is given?\" and I had to read\n> it again. Here is my attempt to rephrase it.\n>\n>        Clone only the history leading to the tip of a single branch,\n>        either specified by the `--branch` option or the primary branch\n>        remote's `HEAD` points at. When creating a shallow clone with the\n>        `--depth` option, this is the default, unless `--no-single-branch`\n>        is given to fetch the histories near the tips of all branches.\n>\n>        Currently this option only works when creating a shallow clone and\n>        does not have any effect without the `--depth` option.\n>\n> We might want to later enhance this to work also with a full-depth clone\n> that tracks only one branch, and the above phrasing would make it clear.\n\nInteresting. Yes that's another possibility.\n\n>> +             if (!option_branch)\n>> +                     remote_head = guess_remote_head(head, refs, 0);\n>> +             else {\n>> +                     struct strbuf sb = STRBUF_INIT;\n>> +                     strbuf_addstr(&sb, src_ref_prefix);\n>> +                     strbuf_addstr(&sb, option_branch);\n>> +                     remote_head = find_ref_by_name(refs, sb.buf);\n>> +                     strbuf_release(&sb);\n>> +             }\n>> +\n>> +             if (!remote_head)\n>> +                     die(_(\"Remote branch \\\"%s\\\" not found. Nothing to clone.\\n\"\n>> +                           \"Try --no-single-branch to fetch all refs.\"),\n>> +                         option_branch ? option_branch : \"HEAD\");\n>\n> Switching upon option_branch to tweak the message is a good idea, but\n> strictly speaking, we would hit this die() when guess_remote_head() does\n> not find where HEAD points at because it is detached, and in that case,\n> the error is not \"Nothing to clone\", but \"We couldn't tell which branch\n> you meant to limit this cloning to\".\n\nYeah. And in detached case without --branch, we probably should not\nsay anything.\n\n>> @@ -642,9 +679,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>>\n>>               transport_set_option(transport, TRANS_OPT_KEEP, \"yes\");\n>>\n>> -             if (option_depth)\n>> +             if (option_depth) {\n>>                       transport_set_option(transport, TRANS_OPT_DEPTH,\n>>                                            option_depth);\n>> +                     transport_set_option(transport, TRANS_OPT_FOLLOWTAGS,\n>> +                                          option_single_branch ? \"1\" : NULL);\n>\n> Curious. Does anybody set FOLLOWTAGS to the transport by default becore we\n> come here (just asking)?\n\nNo, I just hate another \"if (option_single_branch)\", which indents the\ncall one more time. Always setting it to \"1\" in this case should\nprobably be OK too. I'm just not sure if upload-pack realizes all tags\nare requested so include-tag extension means nothing, or it does extra\nwork for no gain.\n-- \nDuy\n"},{"id":"182072","messageId":"1325947559-20080-1-git-send-email-pclouds@gmail.com","threadId":"29292","inReplyTo":"1325833869-20078-1-git-send-email-pclouds@gmail.com","subject":"[PATCH v4] clone: add --single-branch to fetch only one branch","fromName":"Nguyễn Thái Ngọc Duy","fromEmail":"pclouds@gmail.com","sentAt":"2012-01-07T14:45:59Z","receivedAt":"2012-01-07T14:45:59Z","isPatch":true,"sender":{"key":"pclouds@gmail.com","avatar":"https://avatars.githubusercontent.com/u/720?v=4"},"body":"When --single-branch is given, only one branch, either HEAD or one\nspecified by --branch, will be fetched. Also only tags that point to\nthe downloaded history are fetched.\n\nThis helps most in shallow clones, where it can reduce the download to\nminimum and that is why it is enabled by default when --depth is given.\n\nSigned-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>\n---\n The fourth round does not limit --single-branch to shallow clones any\n more. I did not see that the patch finally comes close to what Carlos\n tried to do [1], the patch that reminded me of the tag issue in shallow\n clone Shawn mentioned a while ago.\n\n [1] http://thread.gmane.org/gmane.comp.version-control.git/187639\n\n Documentation/git-clone.txt |   11 ++++++-\n builtin/clone.c             |   52 ++++++++++++++++++++++++++++--\n t/t5500-fetch-pack.sh       |   72 ++++++++++++++++++++++++++++++++++++++++++-\n 3 files changed, 129 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-clone.txt b/Documentation/git-clone.txt\nindex 4b8b26b..0931a3e 100644\n--- a/Documentation/git-clone.txt\n+++ b/Documentation/git-clone.txt\n@@ -13,7 +13,8 @@ SYNOPSIS\n \t  [-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]\n \t  [-o <name>] [-b <name>] [-u <upload-pack>] [--reference <repository>]\n \t  [--separate-git-dir <git dir>]\n-\t  [--depth <depth>] [--recursive|--recurse-submodules] [--] <repository>\n+\t  [--depth <depth>] [--[no-]single-branch]\n+\t  [--recursive|--recurse-submodules] [--] <repository>\n \t  [<directory>]\n \n DESCRIPTION\n@@ -179,6 +180,14 @@ objects from the source repository into a pack in the cloned repository.\n \twith a long history, and would want to send in fixes\n \tas patches.\n \n+--single-branch::\n+\tClone only the history leading to the tip of a single branch,\n+\teither specified by the `--branch` option or the primary\n+\tbranch remote's `HEAD` points at. When creating a shallow\n+\tclone with the `--depth` option, this is the default, unless\n+\t`--no-single-branch` is given to fetch the histories near the\n+\ttips of all branches.\n+\n --recursive::\n --recurse-submodules::\n \tAfter the clone is created, initialize all submodules within,\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 86db954..9dcc5fe 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -37,7 +37,7 @@ static const char * const builtin_clone_usage[] = {\n \tNULL\n };\n \n-static int option_no_checkout, option_bare, option_mirror;\n+static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1;\n static int option_local, option_no_hardlinks, option_shared, option_recursive;\n static char *option_template, *option_depth;\n static char *option_origin = NULL;\n@@ -48,6 +48,7 @@ static int option_verbosity;\n static int option_progress;\n static struct string_list option_config;\n static struct string_list option_reference;\n+static const char *src_ref_prefix = \"refs/heads/\";\n \n static int opt_parse_reference(const struct option *opt, const char *arg, int unset)\n {\n@@ -92,6 +93,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_BOOL(0, \"single-branch\", &option_single_branch,\n+\t\t    \"clone only one branch, HEAD or --branch\"),\n \tOPT_STRING(0, \"separate-git-dir\", &real_git_dir, \"gitdir\",\n \t\t   \"separate git dir from working tree\"),\n \tOPT_STRING_LIST('c', \"config\", &option_config, \"key=value\",\n@@ -427,8 +430,28 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \tstruct ref *local_refs = head;\n \tstruct ref **tail = head ? &head->next : &local_refs;\n \n-\tget_fetch_map(refs, refspec, &tail, 0);\n-\tif (!option_mirror)\n+\tif (option_single_branch) {\n+\t\tstruct ref *remote_head = NULL;\n+\n+\t\tif (!option_branch)\n+\t\t\tremote_head = guess_remote_head(head, refs, 0);\n+\t\telse {\n+\t\t\tstruct strbuf sb = STRBUF_INIT;\n+\t\t\tstrbuf_addstr(&sb, src_ref_prefix);\n+\t\t\tstrbuf_addstr(&sb, option_branch);\n+\t\t\tremote_head = find_ref_by_name(refs, sb.buf);\n+\t\t\tstrbuf_release(&sb);\n+\t\t}\n+\n+\t\tif (!remote_head && option_branch)\n+\t\t\twarning(_(\"Could not find remote branch %s to clone.\"),\n+\t\t\t\toption_branch);\n+\t\telse\n+\t\t\tget_fetch_map(remote_head, refspec, &tail, 0);\n+\t} else\n+\t\tget_fetch_map(refs, refspec, &tail, 0);\n+\n+\tif (!option_mirror && !option_single_branch)\n \t\tget_fetch_map(refs, tag_refspec, &tail, 0);\n \n \treturn local_refs;\n@@ -448,6 +471,21 @@ static void write_remote_refs(const struct ref *local_refs)\n \tclear_extra_refs();\n }\n \n+static void write_followtags(const struct ref *refs, const char *msg)\n+{\n+\tconst struct ref *ref;\n+\tfor (ref = refs; ref; ref = ref->next) {\n+\t\tif (prefixcmp(ref->name, \"refs/tags/\"))\n+\t\t\tcontinue;\n+\t\tif (!suffixcmp(ref->name, \"^{}\"))\n+\t\t\tcontinue;\n+\t\tif (!has_sha1_file(ref->old_sha1))\n+\t\t\tcontinue;\n+\t\tupdate_ref(msg, ref->name, ref->old_sha1,\n+\t\t\t   NULL, 0, DIE_ON_ERR);\n+\t}\n+}\n+\n static int write_one_config(const char *key, const char *value, void *data)\n {\n \treturn git_config_set_multivar(key, value ? value : \"true\", \"^$\", 0);\n@@ -478,7 +516,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\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-\tchar *src_ref_prefix = \"refs/heads/\";\n \tint err = 0;\n \n \tstruct refspec *refspec;\n@@ -498,6 +535,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tusage_msg_opt(_(\"You must specify a repository to clone.\"),\n \t\t\tbuiltin_clone_usage, builtin_clone_options);\n \n+\tif (option_single_branch == -1)\n+\t\toption_single_branch = option_depth ? 1 : 0;\n+\n \tif (option_mirror)\n \t\toption_bare = 1;\n \n@@ -645,6 +685,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tif (option_depth)\n \t\t\ttransport_set_option(transport, TRANS_OPT_DEPTH,\n \t\t\t\t\t     option_depth);\n+\t\tif (option_single_branch)\n+\t\t\ttransport_set_option(transport, TRANS_OPT_FOLLOWTAGS, \"1\");\n \n \t\ttransport_set_verbosity(transport, option_verbosity, option_progress);\n \n@@ -663,6 +705,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tclear_extra_refs();\n \n \t\twrite_remote_refs(mapped_refs);\n+\t\tif (option_single_branch)\n+\t\t\twrite_followtags(refs, reflog_msg.buf);\n \n \t\tremote_head = find_ref_by_name(refs, \"HEAD\");\n \t\tremote_head_points_at =\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex 9bf69e9..7e85c71 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -114,8 +114,19 @@ pull_to_client 2nd \"refs/heads/B\" $((64*3))\n \n pull_to_client 3rd \"refs/heads/A\" $((1*3))\n \n+test_expect_success 'single branch clone' '\n+\tgit clone --single-branch \"file://$(pwd)/.\" singlebranch\n+'\n+\n+test_expect_success 'single branch object count' '\n+\tGIT_DIR=singlebranch/.git git count-objects -v |\n+\t\tgrep \"^in-pack:\" > count.singlebranch &&\n+\techo \"in-pack: 198\" >expected &&\n+\ttest_cmp expected count.singlebranch\n+'\n+\n test_expect_success 'clone shallow' '\n-\tgit clone --depth 2 \"file://$(pwd)/.\" shallow\n+\tgit clone --no-single-branch --depth 2 \"file://$(pwd)/.\" shallow\n '\n \n test_expect_success 'clone shallow object count' '\n@@ -248,4 +259,63 @@ test_expect_success 'clone shallow object count' '\n \tgrep \"^count: 52\" count.shallow\n '\n \n+test_expect_success 'clone shallow without --no-single-branch' '\n+\tgit clone --depth 1 \"file://$(pwd)/.\" shallow2\n+'\n+\n+test_expect_success 'clone shallow object count' '\n+\t(\n+\t\tcd shallow2 &&\n+\t\tgit count-objects -v\n+\t) > count.shallow2 &&\n+\tgrep \"^in-pack: 6\" count.shallow2\n+'\n+\n+test_expect_success 'clone shallow with --branch' '\n+\tgit clone --depth 1 --branch A \"file://$(pwd)/.\" shallow3\n+'\n+\n+test_expect_success 'clone shallow object count' '\n+\techo \"in-pack: 12\" > count3.expected &&\n+\tGIT_DIR=shallow3/.git git count-objects -v |\n+\t\tgrep \"^in-pack\" > count3.actual &&\n+\ttest_cmp count3.expected count3.actual\n+'\n+\n+test_expect_success 'clone shallow with nonexistent --branch' '\n+\tgit clone --depth 1 --branch Z \"file://$(pwd)/.\" shallow4 &&\n+\tGIT_DIR=shallow4/.git git rev-parse HEAD >actual &&\n+\tgit rev-parse HEAD >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'clone shallow with detached HEAD' '\n+\tgit checkout HEAD^ &&\n+\tgit clone --depth 1 \"file://$(pwd)/.\" shallow5 &&\n+\tgit checkout - &&\n+\tGIT_DIR=shallow5/.git git rev-parse HEAD >actual &&\n+\tgit rev-parse HEAD^ >expected &&\n+\ttest_cmp expected actual\n+'\n+\n+test_expect_success 'shallow clone pulling tags' '\n+\tgit tag -a -m A TAGA1 A &&\n+\tgit tag -a -m B TAGB1 B &&\n+\tgit tag TAGA2 A &&\n+\tgit tag TAGB2 B &&\n+\tgit clone --depth 1 \"file://$(pwd)/.\" shallow6 &&\n+\n+\tcat >taglist.expected <<\\EOF &&\n+TAGB1\n+TAGB2\n+EOF\n+\tGIT_DIR=shallow6/.git git tag -l >taglist.actual &&\n+\ttest_cmp taglist.expected taglist.actual &&\n+\n+\techo \"in-pack: 7\" > count6.expected &&\n+\tGIT_DIR=shallow6/.git git count-objects -v |\n+\t\tgrep \"^in-pack\" > count6.actual &&\n+\ttest_cmp count6.expected count6.actual\n+'\n+\n test_done\n-- \n1.7.8.36.g69ee2\n"}]}