{"thread":{"id":"54790","subject":"Cloning empty repository uses locally configured default branch name","startedAt":"2020-12-08T01:32:28Z","lastAt":"2021-02-08T22:29:50Z","messageCount":109,"participants":["Jonathan Tan","Junio C Hamano","brian m. carlson","Jeff King","Ævar Arnfjörð Bjarmason","Felipe Contreras"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"411707","messageId":"20201208013121.677494-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":null,"subject":"Cloning empty repository uses locally configured default branch name","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-08T01:31:20Z","receivedAt":"2020-12-08T01:32:28Z","isPatch":false,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning an empty repository, a local branch is created. But its\nname is not the name of the branch that the remote HEAD points to - it\nis the locally configured default branch name. This issue arose at\n$DAYJOB and, from my memory, it is also not an uncommon workflow to\nconfigure things online on a repo host and then use \"git clone\" so that\nthings like remotes are automatically configured.\n\nHas anyone looked into solutions for this? Both protocol v0 and v2 do\nnot send symref information about unborn branches (v0 because, as\nprotocol-capabilities.txt says, \"servers SHOULD include this capability\nfor the HEAD symref if it is one of the refs being sent\"; v2 because\na symref is included only if it refers to one of the refs being sent).\nIn protocol v2, this could be done by adding a capability to ls-remote\n(maybe, \"unborn\"), and in protocol v0, this could be done either by\nupdating the existing \"symref\" capability to be written even when the\ntarget branch is unborn (which is potentially backwards incompatible) or\nintroducing a new capability which is like \"symref\".\n\nA small issue is that upload-pack protocol v0 doesn't even write the\nblank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\nbranch, but that can be fixed as in the patch below.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n upload-pack.c | 40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1006bebd50..d2359a8560 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1179,18 +1179,15 @@ static void format_symref_info(struct strbuf *buf, struct string_list *symref)\n \t\tstrbuf_addf(buf, \" symref=%s:%s\", item->string, (char *)item->util);\n }\n \n-static int send_ref(const char *refname, const struct object_id *oid,\n-\t\t    int flag, void *cb_data)\n+static const char *capabilities = \"multi_ack thin-pack side-band\"\n+\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n+\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n+\n+static void write_ref_lines(const char *refname_nons,\n+\t\t\t    const struct object_id *oid,\n+\t\t\t    const struct object_id *peeled,\n+\t\t\t    struct upload_pack_data *data)\n {\n-\tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n-\t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n-\t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n-\tconst char *refname_nons = strip_namespace(refname);\n-\tstruct object_id peeled;\n-\tstruct upload_pack_data *data = cb_data;\n-\n-\tif (mark_our_ref(refname_nons, refname, oid))\n-\t\treturn 0;\n \n \tif (capabilities) {\n \t\tstruct strbuf symref_info = STRBUF_INIT;\n@@ -1213,8 +1210,23 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\tpacket_write_fmt(1, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n \t}\n \tcapabilities = NULL;\n-\tif (!peel_ref(refname, &peeled))\n-\t\tpacket_write_fmt(1, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n+\tif (peeled)\n+\t\tpacket_write_fmt(1, \"%s %s^{}\\n\", oid_to_hex(peeled), refname_nons);\n+}\n+\n+static int send_ref(const char *refname, const struct object_id *oid,\n+\t\t    int flag, void *cb_data)\n+{\n+\tconst char *refname_nons = strip_namespace(refname);\n+\tstruct object_id peeled;\n+\tstruct upload_pack_data *data = cb_data;\n+\n+\tif (mark_our_ref(refname_nons, refname, oid))\n+\t\treturn 0;\n+\twrite_ref_lines(refname_nons,\n+\t\t\toid,\n+\t\t\tpeel_ref(refname, &peeled) ? NULL : &peeled,\n+\t\t\tdata);\n \treturn 0;\n }\n \n@@ -1332,6 +1344,8 @@ void upload_pack(struct upload_pack_options *options)\n \t\treset_timeout(data.timeout);\n \t\thead_ref_namespaced(send_ref, &data);\n \t\tfor_each_namespaced_ref(send_ref, &data);\n+\t\tif (capabilities)\n+\t\t\twrite_ref_lines(\"capabilities^{}\", &null_oid, NULL, &data);\n \t\tadvertise_shallow_grafts(1);\n \t\tpacket_flush(1);\n \t} else {\n-- \n2.29.2.576.ga3fc446d84-goog\n\n"},{"id":"411708","messageId":"xmqq7dpt82l4.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-08T02:16:07Z","receivedAt":"2020-12-08T02:16:53Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Has anyone looked into solutions for this? Both protocol v0 and v2 do\n> not send symref information about unborn branches (v0 because, as\n> protocol-capabilities.txt says, \"servers SHOULD include this capability\n> for the HEAD symref if it is one of the refs being sent\"; v2 because\n> a symref is included only if it refers to one of the refs being sent).\n> In protocol v2, this could be done by adding a capability to ls-remote\n> (maybe, \"unborn\"), and in protocol v0, this could be done either by\n> updating the existing \"symref\" capability to be written even when the\n> target branch is unborn (which is potentially backwards incompatible) or\n> introducing a new capability which is like \"symref\".\n\nThanks for looking into this (I think this came up again today\nduring my reviews of some topic).\n\nIt would be a backward incompatible change to add to v0, but at this\npoint shouldn't we be leaving v0 as-is and move everybody to v2?\n\nIf it is a simple and safe enough change, though, saying \"why not\"\nis very tempting, though.\n\n> A small issue is that upload-pack protocol v0 doesn't even write the\n> blank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\n> branch, but that can be fixed as in the patch below.\n\nI think the codepaths we have today in process_capabilities() and\nprocess_dummy_ref() (both in connect.c) would do the right thing\nwhen it sees a blank ref line even when nothing gets transported,\nbut I smell that the rewrite of this state machine is fairly recent\n(say in the past few years) and I do not offhand know if clients\nbefore the rewrite of the state machine (say in v2.18.0) would be OK\nwith the change.  It should be easy to check, though.\n\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> ---\n>  upload-pack.c | 40 +++++++++++++++++++++++++++-------------\n>  1 file changed, 27 insertions(+), 13 deletions(-)\n>\n> diff --git a/upload-pack.c b/upload-pack.c\n> index 1006bebd50..d2359a8560 100644\n> --- a/upload-pack.c\n> +++ b/upload-pack.c\n> @@ -1179,18 +1179,15 @@ static void format_symref_info(struct strbuf *buf, struct string_list *symref)\n>  \t\tstrbuf_addf(buf, \" symref=%s:%s\", item->string, (char *)item->util);\n>  }\n>  \n> -static int send_ref(const char *refname, const struct object_id *oid,\n> -\t\t    int flag, void *cb_data)\n> +static const char *capabilities = \"multi_ack thin-pack side-band\"\n> +\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n> +\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n> +\n> +static void write_ref_lines(const char *refname_nons,\n> +\t\t\t    const struct object_id *oid,\n> +\t\t\t    const struct object_id *peeled,\n> +\t\t\t    struct upload_pack_data *data)\n>  {\n> -\tstatic const char *capabilities = \"multi_ack thin-pack side-band\"\n> -\t\t\" side-band-64k ofs-delta shallow deepen-since deepen-not\"\n> -\t\t\" deepen-relative no-progress include-tag multi_ack_detailed\";\n> -\tconst char *refname_nons = strip_namespace(refname);\n> -\tstruct object_id peeled;\n> -\tstruct upload_pack_data *data = cb_data;\n> -\n> -\tif (mark_our_ref(refname_nons, refname, oid))\n> -\t\treturn 0;\n>  \n>  \tif (capabilities) {\n>  \t\tstruct strbuf symref_info = STRBUF_INIT;\n> @@ -1213,8 +1210,23 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \t\tpacket_write_fmt(1, \"%s %s\\n\", oid_to_hex(oid), refname_nons);\n>  \t}\n>  \tcapabilities = NULL;\n> -\tif (!peel_ref(refname, &peeled))\n> -\t\tpacket_write_fmt(1, \"%s %s^{}\\n\", oid_to_hex(&peeled), refname_nons);\n> +\tif (peeled)\n> +\t\tpacket_write_fmt(1, \"%s %s^{}\\n\", oid_to_hex(peeled), refname_nons);\n> +}\n> +\n> +static int send_ref(const char *refname, const struct object_id *oid,\n> +\t\t    int flag, void *cb_data)\n> +{\n> +\tconst char *refname_nons = strip_namespace(refname);\n> +\tstruct object_id peeled;\n> +\tstruct upload_pack_data *data = cb_data;\n> +\n> +\tif (mark_our_ref(refname_nons, refname, oid))\n> +\t\treturn 0;\n> +\twrite_ref_lines(refname_nons,\n> +\t\t\toid,\n> +\t\t\tpeel_ref(refname, &peeled) ? NULL : &peeled,\n> +\t\t\tdata);\n>  \treturn 0;\n>  }\n>  \n> @@ -1332,6 +1344,8 @@ void upload_pack(struct upload_pack_options *options)\n>  \t\treset_timeout(data.timeout);\n>  \t\thead_ref_namespaced(send_ref, &data);\n>  \t\tfor_each_namespaced_ref(send_ref, &data);\n> +\t\tif (capabilities)\n> +\t\t\twrite_ref_lines(\"capabilities^{}\", &null_oid, NULL, &data);\n>  \t\tadvertise_shallow_grafts(1);\n>  \t\tpacket_flush(1);\n>  \t} else {\n"},{"id":"411710","messageId":"X87lobsQmgncoIpF@camp.crustytoothpaste.net","threadId":"54790","inReplyTo":"xmqq7dpt82l4.fsf@gitster.c.googlers.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2020-12-08T02:32:01Z","receivedAt":"2020-12-08T02:33:19Z","isPatch":false,"sender":{"key":"sandals@crustytoothpaste.net","avatar":"https://avatars.githubusercontent.com/u/497054?v=4"},"body":"On 2020-12-08 at 02:16:07, Junio C Hamano wrote:\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > Has anyone looked into solutions for this? Both protocol v0 and v2 do\n> > not send symref information about unborn branches (v0 because, as\n> > protocol-capabilities.txt says, \"servers SHOULD include this capability\n> > for the HEAD symref if it is one of the refs being sent\"; v2 because\n> > a symref is included only if it refers to one of the refs being sent).\n> > In protocol v2, this could be done by adding a capability to ls-remote\n> > (maybe, \"unborn\"), and in protocol v0, this could be done either by\n> > updating the existing \"symref\" capability to be written even when the\n> > target branch is unborn (which is potentially backwards incompatible) or\n> > introducing a new capability which is like \"symref\".\n> \n> Thanks for looking into this (I think this came up again today\n> during my reviews of some topic).\n> \n> It would be a backward incompatible change to add to v0, but at this\n> point shouldn't we be leaving v0 as-is and move everybody to v2?\n> \n> If it is a simple and safe enough change, though, saying \"why not\"\n> is very tempting, though.\n\nYeah, I think this would be a nice thing to add to v2.  I've considered\nadding a way to push symrefs (that is, update the head on the remote\nside), but that would be a bit trickier.  Still, there's no reason the\nfetch side couldn't learn a \"symref\" capability in the meantime.\n\nI don't see a need for this in v0, since all versions of Git that\nsupport this will also support v2.  I think it's okay if other clients\nhave to add support for v2 before they get the cool new features.\n-- \nbrian m. carlson (he/him or they/them)\nHouston, Texas, US\n"},{"id":"411723","messageId":"X8+iu/0nPfd0lrSn@coredump.intra.peff.net","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-08T15:58:51Z","receivedAt":"2020-12-08T15:59:34Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 07, 2020 at 05:31:20PM -0800, Jonathan Tan wrote:\n\n> When cloning an empty repository, a local branch is created. But its\n> name is not the name of the branch that the remote HEAD points to - it\n> is the locally configured default branch name. This issue arose at\n> $DAYJOB and, from my memory, it is also not an uncommon workflow to\n> configure things online on a repo host and then use \"git clone\" so that\n> things like remotes are automatically configured.\n> \n> Has anyone looked into solutions for this? Both protocol v0 and v2 do\n> not send symref information about unborn branches (v0 because, as\n> protocol-capabilities.txt says, \"servers SHOULD include this capability\n> for the HEAD symref if it is one of the refs being sent\"; v2 because\n> a symref is included only if it refers to one of the refs being sent).\n> In protocol v2, this could be done by adding a capability to ls-remote\n> (maybe, \"unborn\"), and in protocol v0, this could be done either by\n> updating the existing \"symref\" capability to be written even when the\n> target branch is unborn (which is potentially backwards incompatible) or\n> introducing a new capability which is like \"symref\".\n\nWe discussed this a few years ago, and I even wrote a small patch (for\nv0 at the time, of course):\n\n  https://lore.kernel.org/git/20170525155924.hk5jskennph6tta3@sigill.intra.peff.net/\n\nA rebased version of that patch is below (it needed updating to handle\nsome namespacing stuff). Coupled with your patch here for the truly\nempty repo case, it makes the server side of v0 do what you'd want.\n\nBut the client side needs to handle it, too. See the linked thread for\nsome discussion.\n\nI wouldn't be too worried about the backwards incompatibility of sending\na symref line in the capabilities that doesn't point to a ref we're\nsending. Old clients are quite likely to ignore it. But...\n\n> A small issue is that upload-pack protocol v0 doesn't even write the\n> blank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\n> branch, but that can be fixed as in the patch below.\n\nI would worry how clients handle this bogus entry in the ref\nadvertisement. It looks like the actual Git client is OK, but what about\njgit, libgit2, etc? That's not necessarily a deal-breaker, but it would\nbe nice to know how they react.\n\nIt also only helps with v0 (and I agree with the sentiment that it would\nbe OK to ignore v0 at this point). For v2, we'd have to issue a HEAD\nline like:\n\n  0000000000000000000000000000000000000000 HEAD symref=refs/heads/foo\n\nThat probably would break clients, but the unborn capability should take\ncare of that.\n\nPatch below (I think it only helps v0, but it could serve as a model for\ndoing the same thing in v2).\n\n---\n upload-pack.c | 19 ++++++++++---------\n 1 file changed, 10 insertions(+), 9 deletions(-)\n\ndiff --git a/upload-pack.c b/upload-pack.c\nindex 1006bebd50..b0cc337dcb 100644\n--- a/upload-pack.c\n+++ b/upload-pack.c\n@@ -1218,20 +1218,21 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int find_symref(const char *refname, const struct object_id *oid,\n-\t\t       int flag, void *cb_data)\n+static void find_symref(const char *refname, struct string_list *out)\n {\n \tconst char *symref_target;\n \tstruct string_list_item *item;\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tint flag;\n+\n+\tstrbuf_addf(&namespaced, \"%s%s\", get_git_namespace(), refname);\n+\tsymref_target = resolve_ref_unsafe(namespaced.buf, 0, NULL, &flag);\n+\tstrbuf_release(&namespaced);\n \n-\tif ((flag & REF_ISSYMREF) == 0)\n-\t\treturn 0;\n-\tsymref_target = resolve_ref_unsafe(refname, 0, NULL, &flag);\n \tif (!symref_target || (flag & REF_ISSYMREF) == 0)\n-\t\tdie(\"'%s' is a symref but it is not?\", refname);\n-\titem = string_list_append(cb_data, strip_namespace(refname));\n+\t\treturn;\n+\titem = string_list_append(out, refname);\n \titem->util = xstrdup(strip_namespace(symref_target));\n-\treturn 0;\n }\n \n static int parse_object_filter_config(const char *var, const char *value,\n@@ -1326,7 +1327,7 @@ void upload_pack(struct upload_pack_options *options)\n \tdata.daemon_mode = options->daemon_mode;\n \tdata.timeout = options->timeout;\n \n-\thead_ref_namespaced(find_symref, &data.symref);\n+\tfind_symref(\"HEAD\", &data.symref);\n \n \tif (options->advertise_refs || !data.stateless_rpc) {\n \t\treset_timeout(data.timeout);\n-- \n2.29.2.980.g00fe049108\n\n"},{"id":"411729","messageId":"20201208200649.998740-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"X8+iu/0nPfd0lrSn@coredump.intra.peff.net","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-08T20:06:49Z","receivedAt":"2020-12-08T20:17:55Z","isPatch":false,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Mon, Dec 07, 2020 at 05:31:20PM -0800, Jonathan Tan wrote:\n> \n> > When cloning an empty repository, a local branch is created. But its\n> > name is not the name of the branch that the remote HEAD points to - it\n> > is the locally configured default branch name. This issue arose at\n> > $DAYJOB and, from my memory, it is also not an uncommon workflow to\n> > configure things online on a repo host and then use \"git clone\" so that\n> > things like remotes are automatically configured.\n> > \n> > Has anyone looked into solutions for this? Both protocol v0 and v2 do\n> > not send symref information about unborn branches (v0 because, as\n> > protocol-capabilities.txt says, \"servers SHOULD include this capability\n> > for the HEAD symref if it is one of the refs being sent\"; v2 because\n> > a symref is included only if it refers to one of the refs being sent).\n> > In protocol v2, this could be done by adding a capability to ls-remote\n> > (maybe, \"unborn\"), and in protocol v0, this could be done either by\n> > updating the existing \"symref\" capability to be written even when the\n> > target branch is unborn (which is potentially backwards incompatible) or\n> > introducing a new capability which is like \"symref\".\n> \n> We discussed this a few years ago, and I even wrote a small patch (for\n> v0 at the time, of course):\n> \n>   https://lore.kernel.org/git/20170525155924.hk5jskennph6tta3@sigill.intra.peff.net/\n> \n> A rebased version of that patch is below (it needed updating to handle\n> some namespacing stuff). Coupled with your patch here for the truly\n> empty repo case, it makes the server side of v0 do what you'd want.\n> \n> But the client side needs to handle it, too. See the linked thread for\n> some discussion.\n\nThanks for the pointer.\n\n> I wouldn't be too worried about the backwards incompatibility of sending\n> a symref line in the capabilities that doesn't point to a ref we're\n> sending. Old clients are quite likely to ignore it. But...\n> \n> > A small issue is that upload-pack protocol v0 doesn't even write the\n> > blank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\n> > branch, but that can be fixed as in the patch below.\n> \n> I would worry how clients handle this bogus entry in the ref\n> advertisement. It looks like the actual Git client is OK, but what about\n> jgit, libgit2, etc? That's not necessarily a deal-breaker, but it would\n> be nice to know how they react.\n\nThat bogus entry is defined in the protocol and JGit both produces and\nconsumes that line. Consumption was verified by patching Git with my\npatch and running the following commands in separate terminals:\n\n~/git/bin-wrappers/git daemon --port=9425 --base-path=. .\nsudo tcpdump -i any port 9425 -w -\n~/jgit/bazel-bin/org.eclipse.jgit.pgm/jgit ls-remote git://localhost:9425/empty\n\nAnd production:\n\n~/jgit/bazel-bin/org.eclipse.jgit.pgm/jgit daemon --port=9426 .\nGIT_TRACE_PACKET=1 git ls-remote git://localhost:9426/empty\n\n(Note that the JGit CLI does not have a separate --base-path parameter.)\n\nI have not checked libgit2, but quite a few servers use JGit out there,\nso it presumably should be able to interoperate with them and hence\nsupport the bogus entry.\n\n> It also only helps with v0 (and I agree with the sentiment that it would\n> be OK to ignore v0 at this point). For v2, we'd have to issue a HEAD\n> line like:\n> \n>   0000000000000000000000000000000000000000 HEAD symref=refs/heads/foo\n> \n> That probably would break clients, but the unborn capability should take\n> care of that.\n\nYes - or a special string like \"unborn\" in place of the 000.000.\n"},{"id":"411733","messageId":"20201208185538.973044-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq7dpt82l4.fsf@gitster.c.googlers.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-08T18:55:38Z","receivedAt":"2020-12-08T20:23:58Z","isPatch":false,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > Has anyone looked into solutions for this? Both protocol v0 and v2 do\n> > not send symref information about unborn branches (v0 because, as\n> > protocol-capabilities.txt says, \"servers SHOULD include this capability\n> > for the HEAD symref if it is one of the refs being sent\"; v2 because\n> > a symref is included only if it refers to one of the refs being sent).\n> > In protocol v2, this could be done by adding a capability to ls-remote\n> > (maybe, \"unborn\"), and in protocol v0, this could be done either by\n> > updating the existing \"symref\" capability to be written even when the\n> > target branch is unborn (which is potentially backwards incompatible) or\n> > introducing a new capability which is like \"symref\".\n> \n> Thanks for looking into this (I think this came up again today\n> during my reviews of some topic).\n> \n> It would be a backward incompatible change to add to v0, but at this\n> point shouldn't we be leaving v0 as-is and move everybody to v2?\n\nThat makes sense.\n\n> If it is a simple and safe enough change, though, saying \"why not\"\n> is very tempting, though.\n\nI'll look into how simple and safe it is.\n\n> > A small issue is that upload-pack protocol v0 doesn't even write the\n> > blank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\n> > branch, but that can be fixed as in the patch below.\n> \n> I think the codepaths we have today in process_capabilities() and\n> process_dummy_ref() (both in connect.c) would do the right thing\n> when it sees a blank ref line even when nothing gets transported,\n> but I smell that the rewrite of this state machine is fairly recent\n> (say in the past few years) and I do not offhand know if clients\n> before the rewrite of the state machine (say in v2.18.0) would be OK\n> with the change.  It should be easy to check, though.\n\nYes - I backported my patch to v2.17.0 and it works:\n\n  $ GIT_TRACE_PACKET=1 ~/git/bin-wrappers/git ls-remote \"file://$(pwd)/empty\"\n  10:49:33.474111 pkt-line.c:80           packet:  upload-pack> 0000000000000000000000000000000000000000 capabilities^{}\\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed agent=git/2.17.0.dirty\n  10:49:33.474182 pkt-line.c:80           packet:  upload-pack> 0000\n  10:49:33.474243 pkt-line.c:80           packet:          git< 0000000000000000000000000000000000000000 capabilities^{}\\0multi_ack thin-pack side-band side-band-64k ofs-delta shallow deepen-since deepen-not deepen-relative no-progress include-tag multi_ack_detailed agent=git/2.17.0.dirty\n  10:49:33.474315 pkt-line.c:80           packet:          git< 0000\n  10:49:33.474320 pkt-line.c:80           packet:          git> 0000\n  10:49:33.474358 pkt-line.c:80           packet:  upload-pack< 0000\n"},{"id":"411744","messageId":"xmqqim9c6mj4.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20201208185538.973044-1-jonathantanmy@google.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-08T21:00:31Z","receivedAt":"2020-12-08T21:01:35Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> > A small issue is that upload-pack protocol v0 doesn't even write the\n>> > blank ref line (\"000...000 capabilities^{}\") if HEAD points to an unborn\n>> > branch, but that can be fixed as in the patch below.\n>> \n>> I think the codepaths we have today in process_capabilities() and\n>> process_dummy_ref() (both in connect.c) would do the right thing\n>> when it sees a blank ref line even when nothing gets transported,\n>> but I smell that the rewrite of this state machine is fairly recent\n>> (say in the past few years) and I do not offhand know if clients\n>> before the rewrite of the state machine (say in v2.18.0) would be OK\n>> with the change.  It should be easy to check, though.\n>\n> Yes - I backported my patch to v2.17.0 and it works:\n\nI wouldn't be surprised if other reimplementations of Git (like\njgit, libgit2 and Go or Python or whatever your favorite language)\nbarfs, though.\n"},{"id":"411749","messageId":"X8/tCgWcJXHW7VRZ@coredump.intra.peff.net","threadId":"54790","inReplyTo":"20201208200649.998740-1-jonathantanmy@google.com","subject":"Re: Cloning empty repository uses locally configured default branch name","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-08T21:15:54Z","receivedAt":"2020-12-08T21:16:36Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 08, 2020 at 12:06:49PM -0800, Jonathan Tan wrote:\n\n> > I would worry how clients handle this bogus entry in the ref\n> > advertisement. It looks like the actual Git client is OK, but what about\n> > jgit, libgit2, etc? That's not necessarily a deal-breaker, but it would\n> > be nice to know how they react.\n> \n> That bogus entry is defined in the protocol and JGit both produces and\n> consumes that line. Consumption was verified by patching Git with my\n> patch and running the following commands in separate terminals:\n\nAh, indeed.\n\nI forgot that we went through all of this a few years ago for your\neb398797cd (connect: advertized capability is not a ref, 2016-09-09). I\nstand behind the \"it was probably originally an error in the protocol\ndocumentation\" from [1], but at this point I think we can say it's a\nsupported part of the protocol.\n\nAll of this is moot, of course, if we only do the v2 solution. :)\n\n-Peff\n\n[1] https://lore.kernel.org/git/20160902233547.mzgluioc7hhabalw@sigill.intra.peff.net/\n"},{"id":"412096","messageId":"20201211210508.2337494-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"[PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-11T21:05:08Z","receivedAt":"2020-12-11T21:52:59Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning an empty repository, a default branch is created. However,\nit is named after the locally configured init.defaultBranch, not the\ndefault branch of the remote repository.\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, and \"git clone\" needs to use this information.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\nThis feature indicates that \"ls-refs\" supports the \"unborn\" argument;\nwhen it is specified, \"ls-refs\" will send the HEAD symref with the name\nof its unborn target.\n\nOn the client side, Git will always send the \"unborn\" argument if it is\nsupported by the server. During \"git clone\", if cloning an empty\nrepository, Git will use the new information to determine the local\nbranch to create. In all other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 19 +++++++--\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         |  2 +-\n builtin/ls-remote.c                     |  2 +-\n builtin/remote.c                        |  2 +-\n connect.c                               | 29 +++++++++++--\n ls-refs.c                               | 54 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  3 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  7 ++--\n t/t5702-protocol-v2.sh                  | 17 ++++++++\n transport-helper.c                      |  7 +++-\n transport-internal.h                    | 13 +++---\n transport.c                             | 29 ++++++++-----\n transport.h                             |  7 +++-\n 17 files changed, 165 insertions(+), 42 deletions(-)\n\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex e597b74da3..dfe03aa114 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..217c87fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -980,6 +980,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint submodule_progress;\n \n \tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tchar *unborn_head_target = NULL;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1264,7 +1265,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n+\t\t\t\t\t &unborn_head_target);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (unborn_head_target &&\n+\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n+\t\t\t\tref = unborn_head_target;\n+\t\t\t\tunborn_head_target = NULL;\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n \t\t\tfree(ref);\n \t\t}\n \t}\n@@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n+\tfree(unborn_head_target);\n \tstrvec_clear(&ref_prefixes);\n \treturn err;\n }\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..9f921dfab4 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc, NULL);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..a7ef59acfc 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..4cf3f60b1b 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex c1b211b272..246e62f118 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n \tif (query) {\n \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n \t\t\tstates->remote->url[0] : NULL);\n-\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n+\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n \t\ttransport_disconnect(transport);\n \n \t\tstates->queried = 1;\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..3c35324b4c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -455,7 +475,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc)\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target)\n {\n \tint i;\n \tconst char *hash_name;\n@@ -488,6 +509,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -496,7 +519,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..fdb644b482 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -32,6 +32,8 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned allow_unborn : 1;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -74,8 +79,28 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int ls_refs_config(const char *var, const char *value, void *data)\n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n {\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint null_oid;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n+\tnull_oid = is_null_oid(&oid);\n+\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n+{\n+\tstruct ls_refs_data *data = cb_data;\n+\n+\tif (!strcmp(\"lsrefs.unborn\", var))\n+\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n+\t\t\t!strcmp(value, \"advertise\");\n \t/*\n \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n \t * config. This may need to eventually be expanded to \"receive\", but we\n@@ -91,7 +116,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n-\tgit_config(ls_refs_config, NULL);\n+\tgit_config(ls_refs_config, &data);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n \t\tconst char *arg = request->line;\n@@ -103,14 +128,35 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = 1;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tif (data.unborn)\n+\t\tsend_possibly_unborn_head(&data);\n+\telse\n+\t\thead_ref_namespaced(send_ref, &data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tchar *str = NULL;\n+\n+\t\tif (!repo_config_get_string(the_repository, \"lsrefs.unborn\",\n+\t\t\t\t\t    &str) &&\n+\t\t    !strcmp(\"advertise\", str)) {\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t\t\tfree(str);\n+\t\t}\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..967f2178d8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc);\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target);\n \n int resolve_remote_symref(struct ref *ref, struct ref *list);\n \ndiff --git a/serve.c b/serve.c\nindex f6341206c4..30cb56d507 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -62,7 +62,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..d3bd79987b 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,12 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.unborn advertise &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..380333b662 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,23 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\tgit -c init.defaultbranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.unborn advertise &&\n+\n+\tgit -c init.defaultbranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if it is not advertised' '\n+\ttest_config -C file_empty_parent lsrefs.unborn none &&\n+\n+\tgit -c init.defaultbranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child_2 &&\n+\tgrep \"refs/heads/main\" file_empty_child_2/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..5d97eba935 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,16 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t const struct strvec *ref_prefixes,\n+\t\t\t\t char **unborn_head_target)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\tref_prefixes,\n+\t\t\t\t\t\t\tunborn_head_target);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..5037f6197d 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -18,19 +18,16 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n+\t *\n+\t * See transport_get_remote_refs() for information on ref_prefixes and\n+\t * unborn_head_target.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t\t     char **unborn_head_target);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 47da955e4f..815e175017 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,8 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -163,7 +164,7 @@ static int fetch_refs_from_bundle(struct transport *transport,\n \tint ret;\n \n \tif (!data->get_refs_from_bundle_called)\n-\t\tget_refs_from_bundle(transport, 0, NULL);\n+\t\tget_refs_from_bundle(transport, 0, NULL, NULL);\n \tret = unbundle(the_repository, &data->header, data->fd,\n \t\t\t   transport->progress ? BUNDLE_VERBOSE : 0);\n \ttransport->hash_algo = data->header.hash_algo;\n@@ -281,7 +282,7 @@ static void die_if_server_options(struct transport *transport)\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n-\t\t\t     int must_list_refs)\n+\t\t\t     int must_list_refs, char **unborn_head_target)\n {\n \tstruct git_transport_data *data = transport->data;\n \tstruct ref *refs = NULL;\n@@ -301,7 +302,8 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n \t\t\t\t\tref_prefixes,\n \t\t\t\t\ttransport->server_options,\n-\t\t\t\t\ttransport->stateless_rpc);\n+\t\t\t\t\ttransport->stateless_rpc,\n+\t\t\t\t\tunborn_head_target);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -324,9 +326,11 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, ref_prefixes, 1,\n+\t\t\t unborn_head_target);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -370,7 +374,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n-\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs);\n+\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs, NULL);\n \t}\n \n \tif (data->version == protocol_unknown_version)\n@@ -765,7 +769,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \t\treturn -1;\n \n \tif (!data->got_remote_heads)\n-\t\tget_refs_via_connect(transport, 1, NULL);\n+\t\tget_refs_via_connect(transport, 1, NULL, NULL);\n \n \tmemset(&args, 0, sizeof(args));\n \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n@@ -1251,7 +1255,8 @@ int transport_push(struct repository *r,\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &ref_prefixes,\n+\t\t\t\t\t\t\t       NULL);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n \t\tstrvec_clear(&ref_prefixes);\n@@ -1370,12 +1375,14 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t ref_prefixes,\n+\t\t\t\t\t\t\t unborn_head_target);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..65de0c9c00 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -241,9 +241,14 @@ int transport_push(struct repository *repo,\n  * advertisement.  Since ref filtering is done on the server's end (and only\n  * when using protocol v2), this can return refs which don't match the provided\n  * ref_prefixes.\n+ *\n+ * If unborn_head_target is not NULL, and the remote reports HEAD as pointing\n+ * to an unborn branch, this function stores the unborn branch in\n+ * unborn_head_target. It should be freed by the caller.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.29.2.576.ga3fc446d84-goog\n\n"},{"id":"412100","messageId":"xmqqeejvj4hb.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-11T23:41:04Z","receivedAt":"2020-12-11T23:56:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> When cloning an empty repository, a default branch is created. However,\n> it is named after the locally configured init.defaultBranch, not the\n> default branch of the remote repository.\n\nThe default branch of the remote repository and the current branch\npointed at by their HEAD in the remote repository can be different,\nand we are interested in setting our HEAD to the latter.  So \n\n\t..., not the current branch of the remote repository.\n\n> To solve this, the remote needs to communicate the target of the HEAD\n> symref, and \"git clone\" needs to use this information.\n\nYes.  That's a good change (I am on vacation today, so I won't be\nreading the changes themselves today, but I agree with the intent\n100%).\n\nThanks.\n"},{"id":"412163","messageId":"87blewwoil.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-14T12:38:58Z","receivedAt":"2020-12-14T12:42:59Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Dec 11 2020, Jonathan Tan wrote:\n\n> When cloning an empty repository, a default branch is created. However,\n> it is named after the locally configured init.defaultBranch, not the\n> default branch of the remote repository.\n>\n> To solve this, the remote needs to communicate the target of the HEAD\n> symref, and \"git clone\" needs to use this information.\n>\n> Currently, symrefs that have unborn targets (such as in this case) are\n> not communicated by the protocol. Teach Git to advertise and support the\n> \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\n> This feature indicates that \"ls-refs\" supports the \"unborn\" argument;\n> when it is specified, \"ls-refs\" will send the HEAD symref with the name\n> of its unborn target.\n>\n> On the client side, Git will always send the \"unborn\" argument if it is\n> supported by the server. During \"git clone\", if cloning an empty\n> repository, Git will use the new information to determine the local\n> branch to create. In all other cases, Git will ignore it.\n\nI'm not a fan of this change not because of the whole s/master/whatever/\ndiscussion, but because of the magic it adds for seemingly little gain &\nwithout any documentation.\n\nSo if I have init.defaultBranch explicitly set that'll be ignored on\n\"clone\", but on \"init/git remote add/fetch\" it won't?\n\nI think so, and I swear I knew yesterday when I read this patch, but now\nI can't remember. Anyway, the point that I avoided re-reading the patch\nto find out, because even if there's an on-list answer to that it should\nreally be documented because I'll forget it next week, and our users\nwill never know :)\n\nThis patch also leaves Documentation/config/init.txt untouched, and now\nunder lsrefs.unborn it explicitly contradicts the behavior of git:\n\n    Allows overriding the default branch name e.g. when initializing\n    a new repository or when cloning an empty repository.\n\nShouldn't this at the very least be a\ninit.defaultBranchFromRemote=<bool> which if set overrides\ninit.defaultBranch? We could turn that to \"true\" by default and get the\nsame behavior as you have here, but with less inexplicable magic for the\nuser, no?\n\nIt seems if you're a user and wonder why a clone of a bare repo doesn't\ngive you \"init\" defaults the only way you'll find out is\nGIT_TRACE_PACKET and the like.\n\nAnother reason I'm not a fan of it is because it's another piece of\nmagic \"clone\" does that you can't emulate in \"init/fetch\". We have\ne.g. --single-branch as an existing case of that (although you can at\nleast do that with parse ls-remote -> init -> config -> fetch), and\nthat's a case that doesn't fit into a refspec.\n\nBut shouldn't there at least be a corresponding \"fetch\" option? On init\nwe'll create head, but \"git fetch --clobber-my-idea-of-HEAD-with-remote\n...\"?\n\nMaybe not for reasons I haven't thought of, but I'd at least be much\nhappier with an updated commit message justifying another special-case\nin clone that you can't do with \"init/fetch\".\n\nAnd on the \"litte gain\" side of things: I very much suspect that the\nonly users who'll ever use this will be some big hosting providers (but\nmaybe not, the commit doesn't suggest a use-case). Wouldn't this be even\nmore useful in those cases by just a pre-receive hook on their side\ndetecting an initial push refusing \"master\", and:\n\n    git push -o yes-use-old-init-default <...>\n\nInstead of a patch to git to do the same & which would take $SOMEYEARS\nto be rolled out, since it depends on client-side understanding.\n\nComment on the patch below (okey I did read some of it:):\n\n> diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\n> index e597b74da3..dfe03aa114 100644\n> --- a/Documentation/technical/protocol-v2.txt\n> +++ b/Documentation/technical/protocol-v2.txt\n> @@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n>  \tWhen specified, only references having a prefix matching one of\n>  \tthe provided prefixes are displayed.\n>  \n> +If the 'unborn' feature is advertised the following argument can be\n> +included in the client's request.\n> +\n> +    unborn\n> +\tThe server may send symrefs pointing to unborn branches in the form\n> +\t\"unborn <refname> symref-target:<target>\".\n> +\n>  The output of ls-refs is as follows:\n>  \n>      output = *ref\n>  \t     flush-pkt\n> -    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n> +    obj-id-or-unborn = (obj-id | \"unborn\")\n> +    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n>      ref-attribute = (symref | peeled)\n>      symref = \"symref-target:\" symref-target\n>      peeled = \"peeled:\" obj-id\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index a0841923cf..217c87fddf 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -980,6 +980,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tint submodule_progress;\n>  \n>  \tstruct strvec ref_prefixes = STRVEC_INIT;\n> +\tchar *unborn_head_target = NULL;\n>  \n>  \tpacket_trace_identity(\"clone\");\n>  \n> @@ -1264,7 +1265,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tif (!option_no_tags)\n>  \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n>  \n> -\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n> +\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n> +\t\t\t\t\t &unborn_head_target);\n>  \n>  \tif (refs) {\n>  \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n> @@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \t\tremote_head = NULL;\n>  \t\toption_no_checkout = 1;\n>  \t\tif (!option_bare) {\n> -\t\t\tconst char *branch = git_default_branch_name();\n> -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\tconst char *branch;\n> +\t\t\tchar *ref;\n> +\n> +\t\t\tif (unborn_head_target &&\n> +\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n> +\t\t\t\tref = unborn_head_target;\n> +\t\t\t\tunborn_head_target = NULL;\n> +\t\t\t} else {\n> +\t\t\t\tbranch = git_default_branch_name();\n> +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\t}\n>  \n>  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n>  \t\t\tfree(ref);\n>  \t\t}\n>  \t}\n> @@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tstrbuf_release(&key);\n>  \tjunk_mode = JUNK_LEAVE_ALL;\n>  \n> +\tfree(unborn_head_target);\n>  \tstrvec_clear(&ref_prefixes);\n>  \treturn err;\n>  }\n> diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\n> index 58b7c1fbdc..9f921dfab4 100644\n> --- a/builtin/fetch-pack.c\n> +++ b/builtin/fetch-pack.c\n> @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n>  \tversion = discover_version(&reader);\n>  \tswitch (version) {\n>  \tcase protocol_v2:\n> -\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n> +\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n> +\t\t\t\targs.stateless_rpc, NULL);\n>  \t\tbreak;\n>  \tcase protocol_v1:\n>  \tcase protocol_v0:\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index ecf8537605..a7ef59acfc 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n>  \n>  \tif (must_list_refs) {\n>  \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n> -\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n> +\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n>  \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n>  \t} else\n>  \t\tremote_refs = NULL;\n> diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\n> index 092917eca2..4cf3f60b1b 100644\n> --- a/builtin/ls-remote.c\n> +++ b/builtin/ls-remote.c\n> @@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n>  \tif (server_options.nr)\n>  \t\ttransport->server_options = &server_options;\n>  \n> -\tref = transport_get_remote_refs(transport, &ref_prefixes);\n> +\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n>  \tif (ref) {\n>  \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n>  \t\trepo_set_hash_algo(the_repository, hash_algo);\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index c1b211b272..246e62f118 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n>  \tif (query) {\n>  \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n>  \t\t\tstates->remote->url[0] : NULL);\n> -\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n> +\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n>  \t\ttransport_disconnect(transport);\n>  \n>  \t\tstates->queried = 1;\n> diff --git a/connect.c b/connect.c\n> index 8b8f56cf6d..3c35324b4c 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n>  }\n>  \n>  /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n> -static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n> +static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n> +\t\t\t  char **unborn_head_target)\n>  {\n>  \tint ret = 1;\n>  \tint i = 0;\n> @@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n>  \t\tgoto out;\n>  \t}\n>  \n> +\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n> +\t\ti++;\n> +\t\tif (unborn_head_target &&\n> +\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n> +\t\t\t/*\n> +\t\t\t * Look for the symref target (if any). If found,\n> +\t\t\t * return it to the caller.\n> +\t\t\t */\n> +\t\t\tfor (; i < line_sections.nr; i++) {\n> +\t\t\t\tconst char *arg = line_sections.items[i].string;\n> +\n> +\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n> +\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t}\n> +\t\tgoto out;\n> +\t}\n>  \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n>  \t    *end) {\n>  \t\tret = 0;\n> @@ -455,7 +475,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \t\t\t     struct ref **list, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n>  \t\t\t     const struct string_list *server_options,\n> -\t\t\t     int stateless_rpc)\n> +\t\t\t     int stateless_rpc,\n> +\t\t\t     char **unborn_head_target)\n>  {\n>  \tint i;\n>  \tconst char *hash_name;\n> @@ -488,6 +509,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \tif (!for_push)\n>  \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n>  \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n> +\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n> +\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n>  \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n>  \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n>  \t\t\t\t ref_prefixes->v[i]);\n> @@ -496,7 +519,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \n>  \t/* Process response from server */\n>  \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n> -\t\tif (!process_ref_v2(reader, &list))\n> +\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n>  \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n>  \t}\n>  \n> diff --git a/ls-refs.c b/ls-refs.c\n> index a1e0b473e4..fdb644b482 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -32,6 +32,8 @@ struct ls_refs_data {\n>  \tunsigned peel;\n>  \tunsigned symrefs;\n>  \tstruct strvec prefixes;\n> +\tunsigned allow_unborn : 1;\n> +\tunsigned unborn : 1;\n>  };\n>  \n>  static int send_ref(const char *refname, const struct object_id *oid,\n> @@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \tif (!ref_match(&data->prefixes, refname_nons))\n>  \t\treturn 0;\n>  \n> -\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\tif (oid)\n> +\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\telse\n> +\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n>  \tif (data->symrefs && flag & REF_ISSYMREF) {\n>  \t\tstruct object_id unused;\n>  \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n> @@ -74,8 +79,28 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \treturn 0;\n>  }\n>  \n> -static int ls_refs_config(const char *var, const char *value, void *data)\n> +static void send_possibly_unborn_head(struct ls_refs_data *data)\n>  {\n> +\tstruct strbuf namespaced = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tint flag;\n> +\tint null_oid;\n> +\n> +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n> +\tnull_oid = is_null_oid(&oid);\n> +\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n> +\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n> +\tstrbuf_release(&namespaced);\n> +}\n> +\n> +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> +{\n> +\tstruct ls_refs_data *data = cb_data;\n> +\n> +\tif (!strcmp(\"lsrefs.unborn\", var))\n> +\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n> +\t\t\t!strcmp(value, \"advertise\");\n>  \t/*\n>  \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n>  \t * config. This may need to eventually be expanded to \"receive\", but we\n> @@ -91,7 +116,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \n>  \tmemset(&data, 0, sizeof(data));\n>  \n> -\tgit_config(ls_refs_config, NULL);\n> +\tgit_config(ls_refs_config, &data);\n>  \n>  \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n>  \t\tconst char *arg = request->line;\n> @@ -103,14 +128,35 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \t\t\tdata.symrefs = 1;\n>  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n>  \t\t\tstrvec_push(&data.prefixes, out);\n> +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> +\t\t\tdata.unborn = 1;\n>  \t}\n>  \n>  \tif (request->status != PACKET_READ_FLUSH)\n>  \t\tdie(_(\"expected flush after ls-refs arguments\"));\n>  \n> -\thead_ref_namespaced(send_ref, &data);\n> +\tif (data.unborn)\n> +\t\tsend_possibly_unborn_head(&data);\n> +\telse\n> +\t\thead_ref_namespaced(send_ref, &data);\n>  \tfor_each_namespaced_ref(send_ref, &data);\n>  \tpacket_flush(1);\n>  \tstrvec_clear(&data.prefixes);\n>  \treturn 0;\n>  }\n> +\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n> +{\n> +\tif (value) {\n> +\t\tchar *str = NULL;\n> +\n> +\t\tif (!repo_config_get_string(the_repository, \"lsrefs.unborn\",\n> +\t\t\t\t\t    &str) &&\n> +\t\t    !strcmp(\"advertise\", str)) {\n> +\t\t\tstrbuf_addstr(value, \"unborn\");\n> +\t\t\tfree(str);\n> +\t\t}\n> +\t}\n> +\n> +\treturn 1;\n> +}\n> diff --git a/ls-refs.h b/ls-refs.h\n> index 7b33a7c6b8..a99e4be0bd 100644\n> --- a/ls-refs.h\n> +++ b/ls-refs.h\n> @@ -6,5 +6,6 @@ struct strvec;\n>  struct packet_reader;\n>  int ls_refs(struct repository *r, struct strvec *keys,\n>  \t    struct packet_reader *request);\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value);\n>  \n>  #endif /* LS_REFS_H */\n> diff --git a/remote.h b/remote.h\n> index 3211abdf05..967f2178d8 100644\n> --- a/remote.h\n> +++ b/remote.h\n> @@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \t\t\t     struct ref **list, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n>  \t\t\t     const struct string_list *server_options,\n> -\t\t\t     int stateless_rpc);\n> +\t\t\t     int stateless_rpc,\n> +\t\t\t     char **unborn_head_target);\n>  \n>  int resolve_remote_symref(struct ref *ref, struct ref *list);\n>  \n> diff --git a/serve.c b/serve.c\n> index f6341206c4..30cb56d507 100644\n> --- a/serve.c\n> +++ b/serve.c\n> @@ -62,7 +62,7 @@ struct protocol_capability {\n>  \n>  static struct protocol_capability capabilities[] = {\n>  \t{ \"agent\", agent_advertise, NULL },\n> -\t{ \"ls-refs\", always_advertise, ls_refs },\n> +\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n>  \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n>  \t{ \"server-option\", always_advertise, NULL },\n>  \t{ \"object-format\", object_format_advertise, NULL },\n\nAll of this looks good to me, and re unrelated recent questions about\npackfile-uri I had it's really nice to have a narrow example of adding a\nsimple ls-refs time verb / functionality like this to the protocol.\n\n> diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\n> index 7f082fb23b..d3bd79987b 100755\n> --- a/t/t5606-clone-options.sh\n> +++ b/t/t5606-clone-options.sh\n> @@ -102,11 +102,12 @@ test_expect_success 'redirected clone -v does show progress' '\n>  '\n>  \n>  test_expect_success 'chooses correct default initial branch name' '\n> -\tgit init --bare empty &&\n> +\tgit -c init.defaultBranch=foo init --bare empty &&\n> +\ttest_config -C empty lsrefs.unborn advertise &&\n\nIsn't this reducing test coverage? You're changing an existing\nargument-less \"init --bare\" test's behavior,\n\n>  \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n>  \tgit -c init.defaultBranch=up clone empty whats-up &&\n> -\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n> -\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n> +\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n> +\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n>  '\n\nAlso re the above point about discoverability: Right below this we test\n\"init --initial-branch=guess\". Wouldn't a way to unify bring\nfetch/init/clone functionality be to use that as a jump-off point,\ni.e. clone having --use-remote-initial-branch, init optionally leaving\nbehind a (broken) empty/nonexisting HEAD, and \"fetch\" with an argument\nalso supporting --use-remote-initial-branch or something.\n\n> +test_expect_success 'clone of empty repo propagates name of default branch' '\n> +\tgit -c init.defaultbranch=mydefaultbranch init file_empty_parent &&\n> +\ttest_config -C file_empty_parent lsrefs.unborn advertise &&\n> +\n> +\tgit -c init.defaultbranch=main -c protocol.version=2 \\\n> +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n\nNit. Let's spell config.likeThis not config.likethis when not in the C\ncode.\n"},{"id":"412170","messageId":"5fd789e99119d_d21eb208c1@natae.notmuch","threadId":"54790","inReplyTo":"87blewwoil.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-14T15:51:05Z","receivedAt":"2020-12-14T15:52:12Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> On Fri, Dec 11 2020, Jonathan Tan wrote:\n> \n> > When cloning an empty repository, a default branch is created. However,\n> > it is named after the locally configured init.defaultBranch, not the\n> > default branch of the remote repository.\n> >\n> > To solve this, the remote needs to communicate the target of the HEAD\n> > symref, and \"git clone\" needs to use this information.\n> >\n> > Currently, symrefs that have unborn targets (such as in this case) are\n> > not communicated by the protocol. Teach Git to advertise and support the\n> > \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\n> > This feature indicates that \"ls-refs\" supports the \"unborn\" argument;\n> > when it is specified, \"ls-refs\" will send the HEAD symref with the name\n> > of its unborn target.\n> >\n> > On the client side, Git will always send the \"unborn\" argument if it is\n> > supported by the server. During \"git clone\", if cloning an empty\n> > repository, Git will use the new information to determine the local\n> > branch to create. In all other cases, Git will ignore it.\n> \n> I'm not a fan of this change not because of the whole s/master/whatever/\n> discussion, but because of the magic it adds for seemingly little gain &\n> without any documentation.\n\nI am against the master rename, and yet I am in favor of this patch.\n\nI have been running git with \"init.defaultbranch=foobar\" to prepare\nmyself to a future in which the Git project chooses an objectively\ninferior default branch name.\n\nWhen I clone an empty repository, I expect the branch name to be chosen\nby the person who created that repository (not 'foobar'). If GitHub\nchooses to name the default branch \"main\", they can tell their users to\nalways clone the empty repository, and the users don't need to be\ninstructed to do anything else (like \"git init -b main\").\n\nThis way the Git project could follow a simple maxim:\n\n  He who creates the repository chooses the master branch name\n\nAnd this in addition offloads the burden on the Git project to choose a\nparticular default branch name.\n\n> So if I have init.defaultBranch explicitly set that'll be ignored on\n> \"clone\", but on \"init/git remote add/fetch\" it won't?\n\nIt is already ignored on clone... except when the repository is empty.\n\n> I think so, and I swear I knew yesterday when I read this patch, but now\n> I can't remember. Anyway, the point that I avoided re-reading the patch\n> to find out, because even if there's an on-list answer to that it should\n> really be documented because I'll forget it next week, and our users\n> will never know :)\n\nI think the patch does bring the expected behavior. The current behavior\nis the one that is unexpected, and has been unnoticed simply because\nmost repositories use the name \"master\".\n\nSome people would call that unexpected behavior a bug.\n\nBy removing the bug we don't have to document it.\n\n> This patch also leaves Documentation/config/init.txt untouched, and now\n> under lsrefs.unborn it explicitly contradicts the behavior of git:\n> \n>     Allows overriding the default branch name e.g. when initializing\n>     a new repository or when cloning an empty repository.\n\nThat should be updated.\n\n> Shouldn't this at the very least be a\n> init.defaultBranchFromRemote=<bool> which if set overrides\n> init.defaultBranch? We could turn that to \"true\" by default and get the\n> same behavior as you have here, but with less inexplicable magic for the\n> user, no?\n\nI don't think init.defaultbranch has lived long enough for people to\nrely on the \"buggy\" behavior.\n\n> It seems if you're a user and wonder why a clone of a bare repo doesn't\n> give you \"init\" defaults the only way you'll find out is\n> GIT_TRACE_PACKET and the like.\n\nYeah, but who created that repository?\n\nIf you configure Git to use \"master\", and cloning a new repository from\nGitHub fetches \"main\", you know who to blame.\n\nLet them take backlash.\n\nI suspect they will eventually be forced to provide an option.\n\n> Another reason I'm not a fan of it is because it's another piece of\n> magic \"clone\" does that you can't emulate in \"init/fetch\". We have\n> e.g. --single-branch as an existing case of that (although you can at\n> least do that with parse ls-remote -> init -> config -> fetch), and\n> that's a case that doesn't fit into a refspec.\n> \n> But shouldn't there at least be a corresponding \"fetch\" option? On init\n> we'll create head, but \"git fetch --clobber-my-idea-of-HEAD-with-remote\n> ...\"?\n\nThat would be better, yes.\n\nBut let's not let the perfect be the enemy of the good.\n\n> And on the \"litte gain\" side of things: I very much suspect that the\n> only users who'll ever use this will be some big hosting providers (but\n> maybe not, the commit doesn't suggest a use-case).\n\nYes, and that's enough reason.\n\nI say let's offload the branch name decision to them, and let *them*\ndeal with the fallback from their users.\n\n> Wouldn't this be even more useful in those cases by just a pre-receive\n> hook on their side detecting an initial push refusing \"master\", and:\n\nBut that's not what they want to do (I suspect).\n\nThey want the default branch name be \"main\", but still the user could\nrename the branch, do the initial commit, and push without problems.\n\nYes, it would take years to roll this change, but it also takes years\nfor them to update their initial repository instructions too (they\nhaven't included \"git init -b main\" yet).\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"412185","messageId":"xmqqim94e4et.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"87blewwoil.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-14T16:30:34Z","receivedAt":"2020-12-14T16:31:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n>> On the client side, Git will always send the \"unborn\" argument if it is\n>> supported by the server. During \"git clone\", if cloning an empty\n>> repository, Git will use the new information to determine the local\n>> branch to create. In all other cases, Git will ignore it.\n>\n> I'm not a fan of this change not because of the whole s/master/whatever/\n> discussion, but because of the magic it adds for seemingly little gain &\n> without any documentation.\n>\n> So if I have init.defaultBranch explicitly set that'll be ignored on\n> \"clone\", but on \"init/git remote add/fetch\" it won't?\n\nThat description is backwards.\n\nTo help interoperate with the repository you cloned from better, we\nmade it easy to use whatever your 'origin' uses. \"git clone\" does so\nby (1) in the original implementation, by inferring where HEAD\npoints at over there by comparing the objects reported for HEAD and\ntips of branches (2) later, by adding symref capability to the\nprotocl so that the sending repository can tell exactly which branch\nits HEAD points at.  What was lacking was that symref capability is\nnot sent if there is nothing in the repository.  And I think this is\nan attempt to bring that \"cloning nothing\" case in line with a clone\nof a repository with contents.\n\n> Shouldn't this at the very least be a\n> init.defaultBranchFromRemote=<bool> which if set overrides\n> init.defaultBranch? We could turn that to \"true\" by default and get the\n> same behavior as you have here, but with less inexplicable magic for the\n> user, no?\n\nI view the change in the patch being discussed a bugfix (clone ought\nto follow whatever the other side uses by default, unless you say -b,\nand the case when cloning an empty repository was buggy).  I am OK\nif we wanted to consider a _new_ feature to always use the name you\nwant locally (i.e. as if you added \"-b $(git config init.defaultBranch)\"\non your \"git clone\" command line), but that is a new feature that needs\nto be discussed in a separate topic.\n\n> Another reason I'm not a fan of it is because it's another piece of\n> magic \"clone\" does that you can't emulate in \"init/fetch\".\n\nThat ship has sailed longlonglong time ago when dfeff66e (revamp\ngit-clone., 2006-03-20) started pointing our HEAD to match theirs.\n\n> But shouldn't there at least be a corresponding \"fetch\" option? On init\n> we'll create head, but \"git fetch --clobber-my-idea-of-HEAD-with-remote\n> ...\"?\n\nIt may be nice to have a corresponding one, but again, that is a\nseparate topic on a new feature, and not relevant in the context of\nthis fix.\n\n> Maybe not for reasons I haven't thought of, but I'd at least be much\n> happier with an updated commit message justifying another special-case\n> in clone that you can't do with \"init/fetch\".\n\nThis is *not* another special-case, but is 14-year old outstanding\none, so I do not think there specifically needs such justification.\nThe log message DOES need to be clarified.  Your mistaking that this\nis a new feature and not a bugfix may be a good indication that the\nproposed log message is not doing its job.\n\n> And on the \"litte gain\" side of things: I very much suspect that the\n> only users who'll ever use this will be some big hosting providers (but\n> maybe not, the commit doesn't suggest a use-case).\n\nExplorers who learn this new GitHub or GitLab thingy, create an\nempty repository there and then clone it to their local disk, just\nto dip their toes in the water, would most benefit.  Those of us who\nare working on an already existing and populated projects won't be\nhelped or bothered.  We do sometimes create our own repositories and\npublish to hosting sites, and I expect that many experienced Git\nusers follow the \"local first and the push\", and they won't be\nhelped or bothered.\n\nBut I expect some do \"create a void at the hosting site and clone to\nget a local playpen\" for their real projects.  They would be helped,\nand because Git userbase is populous enough that their number in\nabsolute terms would not be insignificant, even if they weren't in\npercentage terms.\n"},{"id":"412192","messageId":"20201214192534.3105508-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"87blewwoil.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-14T19:25:34Z","receivedAt":"2020-12-14T19:26:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> I'm not a fan of this change not because of the whole s/master/whatever/\n> discussion, but because of the magic it adds for seemingly little gain &\n> without any documentation.\n> \n> So if I have init.defaultBranch explicitly set that'll be ignored on\n> \"clone\", but on \"init/git remote add/fetch\" it won't?\n> \n> I think so, and I swear I knew yesterday when I read this patch, but now\n> I can't remember. Anyway, the point that I avoided re-reading the patch\n> to find out, because even if there's an on-list answer to that it should\n> really be documented because I'll forget it next week, and our users\n> will never know :)\n\nThat's the plan - yes. It makes sense to me that \"git clone\" will not\nuse \"init.defaultBranch\" (especially since it has \"init\" in the name),\nbut \"git init\" will. (It also makes sense to me that \"git remote add\"\nand \"git fetch\" will not change HEAD.)\n\n> This patch also leaves Documentation/config/init.txt untouched, and now\n> under lsrefs.unborn it explicitly contradicts the behavior of git:\n> \n>     Allows overriding the default branch name e.g. when initializing\n>     a new repository or when cloning an empty repository.\n\nAh...thanks for the pointer. I'll change it.\n\n> Shouldn't this at the very least be a\n> init.defaultBranchFromRemote=<bool> which if set overrides\n> init.defaultBranch? We could turn that to \"true\" by default and get the\n> same behavior as you have here, but with less inexplicable magic for the\n> user, no?\n\nI think you're coming with the idea that it is perfectly natural for\n\"git clone\" to respect \"init.defaultBranch\", but that doesn't even\nhappen in the typical case wherein we clone a non-empty repository - so\nI don't agree with that idea.\n\n> It seems if you're a user and wonder why a clone of a bare repo doesn't\n> give you \"init\" defaults the only way you'll find out is\n> GIT_TRACE_PACKET and the like.\n\nI assume you mean empty repo instead of bare repo? For me, I would find\nit more surprising that the resulting local repo didn't have the same\nHEAD as the remote.\n\n> Another reason I'm not a fan of it is because it's another piece of\n> magic \"clone\" does that you can't emulate in \"init/fetch\". We have\n> e.g. --single-branch as an existing case of that (although you can at\n> least do that with parse ls-remote -> init -> config -> fetch), and\n> that's a case that doesn't fit into a refspec.\n\nSame answer as above.\n\n> But shouldn't there at least be a corresponding \"fetch\" option? On init\n> we'll create head, but \"git fetch --clobber-my-idea-of-HEAD-with-remote\n> ...\"?\n\nI think that it's OK for \"clone\" to create HEAD, but not OK for \"fetch\"\nto modify HEAD.\n\n> Maybe not for reasons I haven't thought of, but I'd at least be much\n> happier with an updated commit message justifying another special-case\n> in clone that you can't do with \"init/fetch\".\n\nSame answer as above - I don't think this is a special case.\n\n> And on the \"litte gain\" side of things: I very much suspect that the\n> only users who'll ever use this will be some big hosting providers (but\n> maybe not, the commit doesn't suggest a use-case). Wouldn't this be even\n> more useful in those cases by just a pre-receive hook on their side\n> detecting an initial push refusing \"master\", and:\n> \n>     git push -o yes-use-old-init-default <...>\n> \n> Instead of a patch to git to do the same & which would take $SOMEYEARS\n> to be rolled out, since it depends on client-side understanding.\n\nThis would detect the problem only upon push.\n\n> > @@ -62,7 +62,7 @@ struct protocol_capability {\n> >  \n> >  static struct protocol_capability capabilities[] = {\n> >  \t{ \"agent\", agent_advertise, NULL },\n> > -\t{ \"ls-refs\", always_advertise, ls_refs },\n> > +\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n> >  \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n> >  \t{ \"server-option\", always_advertise, NULL },\n> >  \t{ \"object-format\", object_format_advertise, NULL },\n> \n> All of this looks good to me, and re unrelated recent questions about\n> packfile-uri I had it's really nice to have a narrow example of adding a\n> simple ls-refs time verb / functionality like this to the protocol.\n\nThanks.\n\n> > diff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\n> > index 7f082fb23b..d3bd79987b 100755\n> > --- a/t/t5606-clone-options.sh\n> > +++ b/t/t5606-clone-options.sh\n> > @@ -102,11 +102,12 @@ test_expect_success 'redirected clone -v does show progress' '\n> >  '\n> >  \n> >  test_expect_success 'chooses correct default initial branch name' '\n> > -\tgit init --bare empty &&\n> > +\tgit -c init.defaultBranch=foo init --bare empty &&\n> > +\ttest_config -C empty lsrefs.unborn advertise &&\n> \n> Isn't this reducing test coverage? You're changing an existing\n> argument-less \"init --bare\" test's behavior,\n\nThe test here is regarding \"clone\", not the behavior of \"init\". I'm\ndoing some textual comparison below, so I want to insulate this test\nagainst future default branch name changes.\n\n> \n> >  \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n> >  \tgit -c init.defaultBranch=up clone empty whats-up &&\n> > -\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n> > -\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n> > +\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n> > +\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n> >  '\n> \n> Also re the above point about discoverability: Right below this we test\n> \"init --initial-branch=guess\". Wouldn't a way to unify bring\n> fetch/init/clone functionality be to use that as a jump-off point,\n> i.e. clone having --use-remote-initial-branch, \n\nOK - this is already happening for non-empty repositories, and my patch\nmakes it also happen for empty repositories.\n\n> init optionally leaving\n> behind a (broken) empty/nonexisting HEAD, \n\nI'm not sure how this is superior to just using what the remote has\n(upon \"clone\") and using init.defaultBranch when no remote is involved\n(upon \"init\").\n\n> and \"fetch\" with an argument\n> also supporting --use-remote-initial-branch or something.\n\nAgain, I don't think that \"fetch\" should update HEAD.\n\n> \n> > +test_expect_success 'clone of empty repo propagates name of default branch' '\n> > +\tgit -c init.defaultbranch=mydefaultbranch init file_empty_parent &&\n> > +\ttest_config -C file_empty_parent lsrefs.unborn advertise &&\n> > +\n> > +\tgit -c init.defaultbranch=main -c protocol.version=2 \\\n> > +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n> \n> Nit. Let's spell config.likeThis not config.likethis when not in the C\n> code.\n\nOK - will do.\n"},{"id":"412193","messageId":"5fd7c03160b1c_d3c9c20853@natae.notmuch","threadId":"54790","inReplyTo":"20201214192534.3105508-1-jonathantanmy@google.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-14T19:42:41Z","receivedAt":"2020-12-14T19:43:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Jonathan Tan wrote:\n> > But shouldn't there at least be a corresponding \"fetch\" option? On init\n> > we'll create head, but \"git fetch --clobber-my-idea-of-HEAD-with-remote\n> > ...\"?\n> \n> I think that it's OK for \"clone\" to create HEAD, but not OK for \"fetch\"\n> to modify HEAD.\n\nNot the local HEAD, the remote HEAD.\n\nSee my proposal to update the remote head in different scenarios:\n\nhttps://lore.kernel.org/git/20201118091219.3341585-1-felipe.contreras@gmail.com/\n\n-- \nFelipe Contreras\n"},{"id":"412224","messageId":"X9gREadU/cxBYREY@coredump.intra.peff.net","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-15T01:27:45Z","receivedAt":"2020-12-15T01:28:32Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Dec 11, 2020 at 01:05:08PM -0800, Jonathan Tan wrote:\n\n> Subject: Re: [PATCH] clone: in protocol v2, use remote's default branch\n> \n> When cloning an empty repository, a default branch is created. However,\n> it is named after the locally configured init.defaultBranch, not the\n> default branch of the remote repository.\n\nYour subject line puzzled me at first, because I thought we already did\nthat. And indeed we do, but this is about adding the unborn case. I\nthink this contributed to Ævar's confusion.\n\nMaybe:\n\n  Subject: clone: respect unborn remote HEAD\n\n  When cloning, we choose the default branch based on the remote HEAD.\n  But if there is no remote HEAD, we'll fall back to using our local\n  init.defaultBranch. Traditionally this hasn't been a big deal, because\n  everybody used \"master\" as the default. But these days it is likely to\n  cause confusion if the server and client implementations choose\n  different values (e.g., if the remote started with \"main\", we may\n  choose \"master\" locally, create commits there, and then the user is\n  surprised when they push to \"master\" and not \"main\").\n\n  To solve this...\n\nmakes the current state more clear, as well as motivating why we care.\n\nIt might also be worth breaking the patch up a bit. E.g., implement the\ncapability in upload-pack, then infrastructure for the client to use the\ncapability and surface the info to transport callers, and then finally\nsurface it to in the program logic of ls-refs, then clone, etc.\n\nNot strictly necessary, but it make it easier to see what is being\nchanged at each step.\n\n> Currently, symrefs that have unborn targets (such as in this case) are\n> not communicated by the protocol. Teach Git to advertise and support the\n> \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\n> This feature indicates that \"ls-refs\" supports the \"unborn\" argument;\n> when it is specified, \"ls-refs\" will send the HEAD symref with the name\n> of its unborn target.\n\nIt's probably also worth mentioning that v0 won't get any support here,\nand why.\n\n-Peff\n"},{"id":"412226","messageId":"878s9zx2ul.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"xmqqim94e4et.fsf@gitster.c.googlers.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2020-12-15T01:41:38Z","receivedAt":"2020-12-15T01:42:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Dec 14 2020, Junio C Hamano wrote:\n\n> Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n>> Maybe not for reasons I haven't thought of, but I'd at least be much\n>> happier with an updated commit message justifying another special-case\n>> in clone that you can't do with \"init/fetch\".\n>\n> This is *not* another special-case, but is 14-year old outstanding\n> one, so I do not think there specifically needs such justification.\n> The log message DOES need to be clarified.  Your mistaking that this\n> is a new feature and not a bugfix may be a good indication that the\n> proposed log message is not doing its job.\n\nFor context: This clone feature has been there since early 2009, it\nwasn't until late 2017/early 2018 that we had protocol v2 that gave us\nthe ability to fix the bug.\n\nI suppose the distinction between what's new behavior and what's a\nbugfix in something that was really meant to work a certain way all\nalong but didn't is too subtle for me to discern sometimes :)\n\n86ac7518590 (Allow cloning an empty repository, 2009-01-23) which added\nit seems to match my mental model of it being just a shortcut for some\nof the the URL config \"init\" otherwise wouldn't setup for you. At a time\nwhen git-init.txt said:\n\n    An initial `HEAD` file that references the HEAD of the master branch\n    is also created.\n\n> > Another reason I'm not a fan of it is because it's another piece of\n> > magic \"clone\" does that you can't emulate in \"init/fetch\".\n> \n> That ship has sailed longlonglong time ago when dfeff66e (revamp\n> git-clone., 2006-03-20) started pointing our HEAD to match theirs.\n\nLet me rephrase: I think it's unfortunate when we add new things things\nto porcelain commands that aren't easy or possible to emulate in\nplumbing.\n\nI.e. this feature seems like a candidate to be exposed by something like\nby a ls-remote flag if you'd like to do an init/config/fetch. AFAIK the\nonly way to do it is to mock a \"clone\" with GIT_TRACE_PACKET or get the\ninformation out-of-bounds.\n\n>> And on the \"litte gain\" side of things: I very much suspect that the\n>> only users who'll ever use this will be some big hosting providers (but\n>> maybe not, the commit doesn't suggest a use-case).\n>\n> Explorers who learn this new GitHub or GitLab thingy, create an\n> empty repository there and then clone it to their local disk, just\n> to dip their toes in the water, would most benefit.  Those of us who\n> are working on an already existing and populated projects won't be\n> helped or bothered.  We do sometimes create our own repositories and\n> publish to hosting sites, and I expect that many experienced Git\n> users follow the \"local first and the push\", and they won't be\n> helped or bothered.\n>\n> But I expect some do \"create a void at the hosting site and clone to\n> get a local playpen\" for their real projects.  They would be helped,\n> and because Git userbase is populous enough that their number in\n> absolute terms would not be insignificant, even if they weren't in\n> percentage terms.\n\nThat's how I've always used it. Seems from the above-referenced\n5cd12b85fe8 that's what it was meant for to begin with.\n\nAnyway, there's 3 replies to my E-Mail including yours insisting this\nmakes perfect sense, I'm happy to go along with the consensus. I wrote\nmy reply with the assumption that it was obvious that this was a change\nin established behavior, but apparently that's not the prevailing view.\n\nTo borrow from Felipe Contreras's reply in the side-thread \"I expect the\nbranch name to be chosen by the person who created that repository\".\n\nI suppose this comes down to a mental model of what it means to have\n\"created a repository\". When I click \"create repo\" on those popular\nhosting sites (e.g. github & gitlab) and clone it I was expecting it to\njust be a shorthand init + a URL in my config (and refspecs...).\n\nThat's also what happens with this patch if you \"git init --bare\n/tmp/my.git\", then edit the HEAD symref to point to \"foobar\" and clone\nit with file:///, it'll be \"master\" in your clone (or whatever\ninit.defaultBranch is). Isn't that discrepancy a bug then?\n\nBut of course then when you push your \"foobar\" as the first branch the\nHEAD symref won't be updated. In the olden times when everyone ran their\nown git server this was a common FAQ, \"just run 'git symbolic-ref'\".\n\nOn both of those big hosting sites (didn't test others) whatever their\npreferred default name is they'll go with your idea and update HEAD's\npointer on the first such push. So this notion that the default unborn\nsymref isn't transported & it's up to the client to set it on-push (or\nmanually afterwards) has been reinforced by in-the-wild use.\n"},{"id":"412228","messageId":"xmqqim93ajvq.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"878s9zx2ul.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-15T02:22:17Z","receivedAt":"2020-12-15T02:23:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> I.e. this feature seems like a candidate to be exposed by something like\n> by a ls-remote flag if you'd like to do an init/config/fetch. AFAIK the\n> only way to do it is to mock a \"clone\" with GIT_TRACE_PACKET or get the\n> information out-of-bounds.\n\nYes, I think the updated protocol should be able to help adding a\nnew feature usable by script writers, and I agree that ls-remote may\nbe the ideal home for such a feature.\n\n> To borrow from Felipe Contreras's reply in the side-thread \"I expect the\n> branch name to be chosen by the person who created that repository\".\n\nI expect a bit differently.  My expectation is that \"git clone\"\ntries to help you inter-operate well with the project you clone.\nYou may be creating your local repository by cloning theirs, but\nbecause I do not expect \"who created\" matters more than how well\nend-users' workflows would work in the resulting repository, I\ndo not expect local init.defaultBranch should matter here.\n\nIf the project you would eventually push back to designates one\nbranch as its primary branch everybody is expected to work off of\n(which is what it means to point it with their HEAD), it is\nconvenient if your local clone names your primary branch to match.\nThe push.default settings like 'simple' and 'current' are designed\nto work well when your local branch namespace matches what they\nhave.\n\n> I suppose this comes down to a mental model of what it means to have\n> \"created a repository\". When I click \"create repo\" on those popular\n> hosting sites (e.g. github & gitlab) and clone it I was expecting it to\n> just be a shorthand init + a URL in my config (and refspecs...).\n\nSo, no, I do not think \"who created a repository\" has much to do\nwith the objective of the patch in question.  It's really \"what's\nthe upstream's view of the primary branch\".\n\n> That's also what happens with this patch if you \"git init --bare\n> /tmp/my.git\", then edit the HEAD symref to point to \"foobar\" and clone\n> it with file:///, it'll be \"master\" in your clone (or whatever\n> init.defaultBranch is). Isn't that discrepancy a bug then?\n\nYes, I view it as the same bug to be fixed; JTan's protocol update\npatch only deals with the transport based on the git protocol and\ndoes not (yet?) address the --local short-cut.  In principle, it\nshould be a lot easier than the protocol update.  Any takers?\n\n> On both of those big hosting sites (didn't test others) whatever their\n> preferred default name is they'll go with your idea and update HEAD's\n> pointer on the first such push. So this notion that the default unborn\n> symref isn't transported & it's up to the client to set it on-push (or\n> manually afterwards) has been reinforced by in-the-wild use.\n\nI think it would be great if somebody comes up with a protocol\nupdate for that \"other\" direction to push into an unborn HEAD.  I\nhaven't thought things through, but you may be right to point out\nthat the \"clone learns and prepares local to match the other side\"\nwe are discussing may not be complete with such a corresponding fix\nin the opposite direction.\n\nThanks.\n"},{"id":"412231","messageId":"X9ghqMo5WS8FrBEz@coredump.intra.peff.net","threadId":"54790","inReplyTo":"878s9zx2ul.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-15T02:38:32Z","receivedAt":"2020-12-15T02:40:02Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 15, 2020 at 02:41:38AM +0100, Ævar Arnfjörð Bjarmason wrote:\n\n> > > Another reason I'm not a fan of it is because it's another piece of\n> > > magic \"clone\" does that you can't emulate in \"init/fetch\".\n> > \n> > That ship has sailed longlonglong time ago when dfeff66e (revamp\n> > git-clone., 2006-03-20) started pointing our HEAD to match theirs.\n> \n> Let me rephrase: I think it's unfortunate when we add new things things\n> to porcelain commands that aren't easy or possible to emulate in\n> plumbing.\n> \n> I.e. this feature seems like a candidate to be exposed by something like\n> by a ls-remote flag if you'd like to do an init/config/fetch. AFAIK the\n> only way to do it is to mock a \"clone\" with GIT_TRACE_PACKET or get the\n> information out-of-bounds.\n\nI think the situation is better than that. We are surfacing the remote\nHEAD here, and there is already a command for copying that to our local\ntracking symref: \"git remote set-head origin -a\", which will set up\nrefs/remotes/origin/HEAD.\n\nI think there are two ways we could improve that further:\n\n  - making it more natural to pick up or update the remote HEAD via\n    fetch; Felipe's patches to git-fetch look good to me\n\n  - it might be nice to be able to have some equivalent to the dwim \"git\n    checkout foo\" that creates a new \"foo\" based off of origin/foo.\n    Doing \"git checkout origin/HEAD\" will detach the HEAD. I think right\n    now you'd have to do something like:\n\n      tracking=$(git symbolic-ref refs/remotes/origin/HEAD)\n      branch=${tracking#refs/remotes/origin/}\n      git checkout -b $branch $tracking\n\n    Or maybe not. It's not something people probably need to do a lot.\n    And if the point is to have plumbing commands that can do the same,\n    then maybe those commands are sufficient.\n\n-Peff\n"},{"id":"412236","messageId":"xmqq8s9zaica.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"X9ghqMo5WS8FrBEz@coredump.intra.peff.net","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-15T02:55:33Z","receivedAt":"2020-12-15T02:56:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I think the situation is better than that. We are surfacing the remote\n> HEAD here, and there is already a command for copying that to our local\n> tracking symref: \"git remote set-head origin -a\", which will set up\n> refs/remotes/origin/HEAD.\n>\n> I think there are two ways we could improve that further:\n>\n>   - making it more natural to pick up or update the remote HEAD via\n>     fetch; Felipe's patches to git-fetch look good to me\n\nI do not mind that as an option (not the default) to the \"git fetch\"\ncommand.  But I think Ævar was driving at the lack of a scriptable\nbuilding block.\n\n>   - it might be nice to be able to have some equivalent to the dwim \"git\n>     checkout foo\" that creates a new \"foo\" based off of origin/foo.\n>     Doing \"git checkout origin/HEAD\" will detach the HEAD. I think right\n>     now you'd have to do something like:\n>\n>       tracking=$(git symbolic-ref refs/remotes/origin/HEAD)\n>       branch=${tracking#refs/remotes/origin/}\n>       git checkout -b $branch $tracking\n\nMeaning \"git checkout origin\" would look at origin/HEAD and find the\nremote-tracking branch it points at, and uses that name?  I think\nthat does make quite a lot of sense.  You are correct to point out\nthat not just \"git checkout origin/HEAD\", but \"git checkout origin\",\ncurrently detaches the HEAD at that commit, if you have origin/HEAD\npointing at one of the remote-tracking branches.\n\nBut if we were to make such a change, \"git fetch\" shouldn't\nautomatically update remotes/origin/HEAD, I would think.  It does\nnot matter too much if we are talking about a publishing repository\nwhere the HEAD rarely changes (and when it does, it is a significant\nevent that everybody in the downstream should take notice), but if\nyou clone from a live repository with active development, you do not\nwant to lose a stable reference to what you consider as the primary\nbranch at your origin repository.\n\n>     Or maybe not. It's not something people probably need to do a lot.\n>     And if the point is to have plumbing commands that can do the same,\n>     then maybe those commands are sufficient.\n\n"},{"id":"412238","messageId":"5fd82bfe20d9c_d7c48208da@natae.notmuch","threadId":"54790","inReplyTo":"878s9zx2ul.fsf@evledraar.gmail.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-15T03:22:38Z","receivedAt":"2020-12-15T03:23:47Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n\n> To borrow from Felipe Contreras's reply in the side-thread \"I expect the\n> branch name to be chosen by the person who created that repository\".\n> \n> I suppose this comes down to a mental model of what it means to have\n> \"created a repository\". When I click \"create repo\" on those popular\n> hosting sites (e.g. github & gitlab) and clone it I was expecting it to\n> just be a shorthand init + a URL in my config (and refspecs...).\n\nIndeed. But then it would be *them* taking away agency from the user (by\nnot allowing the user to choose the name of the branch), not us.\n\nIn Spanish we have a saying: \"don't give extra change\", which is similar to\nthe lawyery advice: \"don't volunteer [information]\".\n\nLet's not volunteer user complaints. Let GitHub take some of those.\n\n-- \nFelipe Contreras"},{"id":"412241","messageId":"X9g9Y9LWc0NtHlQn@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqq8s9zaica.fsf@gitster.c.googlers.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-15T04:36:51Z","receivedAt":"2020-12-15T04:37:58Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 14, 2020 at 06:55:33PM -0800, Junio C Hamano wrote:\n\n> >   - it might be nice to be able to have some equivalent to the dwim \"git\n> >     checkout foo\" that creates a new \"foo\" based off of origin/foo.\n> >     Doing \"git checkout origin/HEAD\" will detach the HEAD. I think right\n> >     now you'd have to do something like:\n> >\n> >       tracking=$(git symbolic-ref refs/remotes/origin/HEAD)\n> >       branch=${tracking#refs/remotes/origin/}\n> >       git checkout -b $branch $tracking\n> \n> Meaning \"git checkout origin\" would look at origin/HEAD and find the\n> remote-tracking branch it points at, and uses that name?  I think\n> that does make quite a lot of sense.  You are correct to point out\n> that not just \"git checkout origin/HEAD\", but \"git checkout origin\",\n> currently detaches the HEAD at that commit, if you have origin/HEAD\n> pointing at one of the remote-tracking branches.\n\nI'm not sure if it's a good idea to change \"git checkout origin\" here or\nnot. It already does something useful. I was mostly suggesting that the\nother thing might _also_ be useful, but I'm not sure if it is wise to\nchange the current behavior.\n\nI was thinking more like an explicit way to trigger the dwim-behavior,\nlike:\n\n  # same as \"git checkout foo\" magic that creates \"foo\", but we\n  # have said explicitly both that we expect to make the new branch, and\n  # also that we expect it to come from origin.\n  git checkout --make-local origin/foo\n\n  # similar, but because we are being explicit, we know it is reasonable\n  # to dereference HEAD to find the actual branch name\n  git checkout --make-local origin/HEAD\n\nI dunno. I hate the name \"--make-local\", and in the non-dereferencing\nform, it is not much different than just \"git checkout -b foo\norigin/foo\". I'm mostly just thinking aloud here. :)\n\n> But if we were to make such a change, \"git fetch\" shouldn't\n> automatically update remotes/origin/HEAD, I would think.  It does\n> not matter too much if we are talking about a publishing repository\n> where the HEAD rarely changes (and when it does, it is a significant\n> event that everybody in the downstream should take notice), but if\n> you clone from a live repository with active development, you do not\n> want to lose a stable reference to what you consider as the primary\n> branch at your origin repository.\n\nThat seems orthogonal. Whether there is checkout magic or not, changing\nwhat origin/HEAD points to would be disruptive to selecting it as a\ntracking source, or doing diffs, or whatever. But that is why the\nproposal in that series was to make the behavior configurable, and\ndefault to \"fill it in if missing\" as the default, not \"always update on\nfetch\".\n\n-Peff\n"},{"id":"412291","messageId":"20201215191035.513219-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"X9gREadU/cxBYREY@coredump.intra.peff.net","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-15T19:10:35Z","receivedAt":"2020-12-15T19:11:45Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > Subject: Re: [PATCH] clone: in protocol v2, use remote's default branch\n> > \n> > When cloning an empty repository, a default branch is created. However,\n> > it is named after the locally configured init.defaultBranch, not the\n> > default branch of the remote repository.\n> \n> Your subject line puzzled me at first, because I thought we already did\n> that. And indeed we do, but this is about adding the unborn case. I\n> think this contributed to Ævar's confusion.\n> \n> Maybe:\n> \n>   Subject: clone: respect unborn remote HEAD\n> \n>   When cloning, we choose the default branch based on the remote HEAD.\n>   But if there is no remote HEAD, we'll fall back to using our local\n>   init.defaultBranch. Traditionally this hasn't been a big deal, because\n>   everybody used \"master\" as the default. But these days it is likely to\n>   cause confusion if the server and client implementations choose\n>   different values (e.g., if the remote started with \"main\", we may\n>   choose \"master\" locally, create commits there, and then the user is\n>   surprised when they push to \"master\" and not \"main\").\n> \n>   To solve this...\n> \n> makes the current state more clear, as well as motivating why we care.\n> \n> It might also be worth breaking the patch up a bit. E.g., implement the\n> capability in upload-pack, then infrastructure for the client to use the\n> capability and surface the info to transport callers, and then finally\n> surface it to in the program logic of ls-refs, then clone, etc.\n> \n> Not strictly necessary, but it make it easier to see what is being\n> changed at each step.\n\nAll this sounds good.\n\n> > Currently, symrefs that have unborn targets (such as in this case) are\n> > not communicated by the protocol. Teach Git to advertise and support the\n> > \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\n> > This feature indicates that \"ls-refs\" supports the \"unborn\" argument;\n> > when it is specified, \"ls-refs\" will send the HEAD symref with the name\n> > of its unborn target.\n> \n> It's probably also worth mentioning that v0 won't get any support here,\n> and why.\n\nOK - thanks for your comments. I'll send out an updated version soon.\n"},{"id":"412325","messageId":"cover.1608084282.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"[PATCH v2 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T02:07:55Z","receivedAt":"2020-12-16T02:08:52Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Thanks everyone for your comments. Changes from v1:\n\n - Split into patches. Patch 1 has the server-side changes, and patch 2\n   is a preparatory patch that just updates the API, so that reviewers\n   can more clearly see the difference in logic in patch 3.\n - Updated commit messsage.\n - Updated test to use \"-c init.defaultBranch\" instead of \"-c\n   init.defaultbranch\" (capitalization).\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: add no-op arg for future patch\n  clone: respect remote unborn HEAD\n\n Documentation/config/init.txt           |  2 +-\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 19 +++++++--\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         |  2 +-\n builtin/ls-remote.c                     |  2 +-\n builtin/remote.c                        |  2 +-\n connect.c                               | 29 +++++++++++--\n ls-refs.c                               | 54 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  3 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  7 ++--\n t/t5702-protocol-v2.sh                  | 17 ++++++++\n transport-helper.c                      |  7 +++-\n transport-internal.h                    | 13 +++---\n transport.c                             | 29 ++++++++-----\n transport.h                             |  7 +++-\n 18 files changed, 166 insertions(+), 43 deletions(-)\n\n-- \n2.29.2.684.gfbc64c5ab5-goog\n\n"},{"id":"412326","messageId":"7f5b50c7b284f2630fe5be6455cc5d3e03b1eb70.1608084282.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608084282.git.jonathantanmy@google.com","subject":"[PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T02:07:56Z","receivedAt":"2020-12-16T02:08:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\nThis feature indicates that \"ls-refs\" supports the \"unborn\" argument;\nwhen it is specified, \"ls-refs\" will send the HEAD symref with the name\nof its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/technical/protocol-v2.txt | 10 ++++-\n ls-refs.c                               | 54 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n 4 files changed, 61 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex e597b74da3..dfe03aa114 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..fdb644b482 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -32,6 +32,8 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned allow_unborn : 1;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -74,8 +79,28 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int ls_refs_config(const char *var, const char *value, void *data)\n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n {\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint null_oid;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n+\tnull_oid = is_null_oid(&oid);\n+\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n+{\n+\tstruct ls_refs_data *data = cb_data;\n+\n+\tif (!strcmp(\"lsrefs.unborn\", var))\n+\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n+\t\t\t!strcmp(value, \"advertise\");\n \t/*\n \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n \t * config. This may need to eventually be expanded to \"receive\", but we\n@@ -91,7 +116,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n-\tgit_config(ls_refs_config, NULL);\n+\tgit_config(ls_refs_config, &data);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n \t\tconst char *arg = request->line;\n@@ -103,14 +128,35 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = 1;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tif (data.unborn)\n+\t\tsend_possibly_unborn_head(&data);\n+\telse\n+\t\thead_ref_namespaced(send_ref, &data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tchar *str = NULL;\n+\n+\t\tif (!repo_config_get_string(the_repository, \"lsrefs.unborn\",\n+\t\t\t\t\t    &str) &&\n+\t\t    !strcmp(\"advertise\", str)) {\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t\t\tfree(str);\n+\t\t}\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex f6341206c4..30cb56d507 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -62,7 +62,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\n-- \n2.29.2.684.gfbc64c5ab5-goog\n\n"},{"id":"412327","messageId":"e24fb6d746279a71f8d6a532790231c2a46468aa.1608084282.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608084282.git.jonathantanmy@google.com","subject":"[PATCH v2 2/3] connect, transport: add no-op arg for future patch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T02:07:57Z","receivedAt":"2020-12-16T02:08:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"A future patch will require transport_get_remote_refs() and\nget_remote_refs() to gain a new argument. Add the argument in this\npatch, with no effect on execution, so that the future patch only needs\nto concern itself with new logic.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/clone.c      |  2 +-\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      |  2 +-\n builtin/ls-remote.c  |  2 +-\n builtin/remote.c     |  2 +-\n connect.c            |  5 ++++-\n remote.h             |  3 ++-\n transport-helper.c   |  7 +++++--\n transport-internal.h | 13 +++++--------\n transport.c          | 29 ++++++++++++++++++-----------\n transport.h          |  7 ++++++-\n 11 files changed, 46 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..70f9450db4 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1264,7 +1264,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..9f921dfab4 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc, NULL);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..a7ef59acfc 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..4cf3f60b1b 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex c1b211b272..246e62f118 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n \tif (query) {\n \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n \t\t\tstates->remote->url[0] : NULL);\n-\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n+\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n \t\ttransport_disconnect(transport);\n \n \t\tstates->queried = 1;\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..99d9052365 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc)\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target)\n {\n \tint i;\n \tconst char *hash_name;\n@@ -496,6 +497,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n+\t\tif (unborn_head_target)\n+\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n \t\tif (!process_ref_v2(reader, &list))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..967f2178d8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc);\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target);\n \n int resolve_remote_symref(struct ref *ref, struct ref *list);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..5d97eba935 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,16 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t const struct strvec *ref_prefixes,\n+\t\t\t\t char **unborn_head_target)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\tref_prefixes,\n+\t\t\t\t\t\t\tunborn_head_target);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..5037f6197d 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -18,19 +18,16 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n+\t *\n+\t * See transport_get_remote_refs() for information on ref_prefixes and\n+\t * unborn_head_target.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t\t     char **unborn_head_target);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 47da955e4f..815e175017 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,8 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -163,7 +164,7 @@ static int fetch_refs_from_bundle(struct transport *transport,\n \tint ret;\n \n \tif (!data->get_refs_from_bundle_called)\n-\t\tget_refs_from_bundle(transport, 0, NULL);\n+\t\tget_refs_from_bundle(transport, 0, NULL, NULL);\n \tret = unbundle(the_repository, &data->header, data->fd,\n \t\t\t   transport->progress ? BUNDLE_VERBOSE : 0);\n \ttransport->hash_algo = data->header.hash_algo;\n@@ -281,7 +282,7 @@ static void die_if_server_options(struct transport *transport)\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n-\t\t\t     int must_list_refs)\n+\t\t\t     int must_list_refs, char **unborn_head_target)\n {\n \tstruct git_transport_data *data = transport->data;\n \tstruct ref *refs = NULL;\n@@ -301,7 +302,8 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n \t\t\t\t\tref_prefixes,\n \t\t\t\t\ttransport->server_options,\n-\t\t\t\t\ttransport->stateless_rpc);\n+\t\t\t\t\ttransport->stateless_rpc,\n+\t\t\t\t\tunborn_head_target);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -324,9 +326,11 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, ref_prefixes, 1,\n+\t\t\t unborn_head_target);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -370,7 +374,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n-\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs);\n+\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs, NULL);\n \t}\n \n \tif (data->version == protocol_unknown_version)\n@@ -765,7 +769,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \t\treturn -1;\n \n \tif (!data->got_remote_heads)\n-\t\tget_refs_via_connect(transport, 1, NULL);\n+\t\tget_refs_via_connect(transport, 1, NULL, NULL);\n \n \tmemset(&args, 0, sizeof(args));\n \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n@@ -1251,7 +1255,8 @@ int transport_push(struct repository *r,\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &ref_prefixes,\n+\t\t\t\t\t\t\t       NULL);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n \t\tstrvec_clear(&ref_prefixes);\n@@ -1370,12 +1375,14 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t ref_prefixes,\n+\t\t\t\t\t\t\t unborn_head_target);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..65de0c9c00 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -241,9 +241,14 @@ int transport_push(struct repository *repo,\n  * advertisement.  Since ref filtering is done on the server's end (and only\n  * when using protocol v2), this can return refs which don't match the provided\n  * ref_prefixes.\n+ *\n+ * If unborn_head_target is not NULL, and the remote reports HEAD as pointing\n+ * to an unborn branch, this function stores the unborn branch in\n+ * unborn_head_target. It should be freed by the caller.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.29.2.684.gfbc64c5ab5-goog\n\n"},{"id":"412328","messageId":"6fcb3b16ce9edab7b6351b317155f141c2419d8d.1608084282.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608084282.git.jonathantanmy@google.com","subject":"[PATCH v2 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T02:07:58Z","receivedAt":"2020-12-16T02:08:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 19 ++++++++++++++++---\n connect.c                     | 28 ++++++++++++++++++++++++----\n t/t5606-clone-options.sh      |  7 ++++---\n t/t5702-protocol-v2.sh        | 17 +++++++++++++++++\n 5 files changed, 62 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 70f9450db4..217c87fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -980,6 +980,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint submodule_progress;\n \n \tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tchar *unborn_head_target = NULL;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1264,7 +1265,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n+\t\t\t\t\t &unborn_head_target);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (unborn_head_target &&\n+\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n+\t\t\t\tref = unborn_head_target;\n+\t\t\t\tunborn_head_target = NULL;\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n \t\t\tfree(ref);\n \t\t}\n \t}\n@@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n+\tfree(unborn_head_target);\n \tstrvec_clear(&ref_prefixes);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 99d9052365..3c35324b4c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -489,6 +509,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -497,9 +519,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (unborn_head_target)\n-\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..d3bd79987b 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,12 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.unborn advertise &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..6afd6bb482 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,23 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.unborn advertise &&\n+\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if it is not advertised' '\n+\ttest_config -C file_empty_parent lsrefs.unborn none &&\n+\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child_2 &&\n+\tgrep \"refs/heads/main\" file_empty_child_2/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \n-- \n2.29.2.684.gfbc64c5ab5-goog\n\n"},{"id":"412330","messageId":"xmqq7dpi5tvl.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"X9g9Y9LWc0NtHlQn@coredump.intra.peff.net","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-16T03:09:50Z","receivedAt":"2020-12-16T03:10:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n>> Meaning \"git checkout origin\" would look at origin/HEAD and find the\n>> remote-tracking branch it points at, and uses that name?  I think\n>> that does make quite a lot of sense.  You are correct to point out\n>> that not just \"git checkout origin/HEAD\", but \"git checkout origin\",\n>> currently detaches the HEAD at that commit, if you have origin/HEAD\n>> pointing at one of the remote-tracking branches.\n>\n> I'm not sure if it's a good idea to change \"git checkout origin\" here or\n> not. It already does something useful. I was mostly suggesting that the\n> other thing might _also_ be useful, but I'm not sure if it is wise to\n> change the current behavior.\n\nWell, \"git checkout origin/HEAD\" would also do something useful,\nwhich happens to be identical to \"git checkout origin\", to detach\nHEAD at the commit.\n\n> I was thinking more like an explicit way to trigger the dwim-behavior,\n> like:\n>\n>   # same as \"git checkout foo\" magic that creates \"foo\", but we\n>   # have said explicitly both that we expect to make the new branch, and\n>   # also that we expect it to come from origin.\n>   git checkout --make-local origin/foo\n\nBy default I think --guess (formerly known as --dwim) is enabled, so\n\"git checkout foo\" is \"git checkout --guess foo\", which is making\nlocal 'foo' out of the uniquely found remote-tracking branch.  This\nnew one is to reduce the \"uniquely found\" part from the magic and\nlet you be a bit more explicit, but not explicit enough to say \"-t\"\nor \"-b foo\"?  I am not sure if this is all that useful.\n\nIf this were a slightly different proposal, I would see the\nconvenience value in it, though.  Currently what \"--guess\" does is:\n\n      If the name 'foo' given does not exist as a local branch,\n      and the name appears exactly once as a remote-tracking branch\n      from some remote (i.e. 'refs/remotes/origin/foo' exists, but\n      there is no other 'refs/remotes/*/foo'), create a local 'foo'\n      that builds on that remote-tracking branch and check it out.\n\nWhat would happen if we tweaked the existing \"--guess\" behaviour\nslightly?\n\n      \"git checkout --guess origin/foo\", even when there is a second\n      remote 'publish' that also has a remote-tracking branch for\n      its 'foo' (i.e. both 'refs/remotes/{origin,publish}/foo'\n      exists), can be used to disambiguate among these remotes with\n      'foo'.  You'd get local 'foo' that builds on 'foo' from the\n      remote 'origin' and check it out.\n\n>   # similar, but because we are being explicit, we know it is reasonable\n>   # to dereference HEAD to find the actual branch name\n>   git checkout --make-local origin/HEAD\n\nThe user does not need \"git symbolic-ref refs/remotes/origin/HEAD\"\nif such a feature were available.  \"git checkout --some-option origin\"\nwithout having to say /HEAD may be a better UI, though.\n\nAnd \"checkout\" being a Porcelain, and the DWIM feature that is\nalways on is subject to be improved for human use, I do not see why\nthat --some-option cannot be --guess.  If I want to get the current\nbehaviour, I can explicitly say \"git checkout --detach origin\"\nanyway, no?\n\n> That seems orthogonal. Whether there is checkout magic or not, changing\n> what origin/HEAD points to would be disruptive to selecting it as a\n> tracking source, or doing diffs, or whatever. But that is why the\n> proposal in that series was to make the behavior configurable, and\n> default to \"fill it in if missing\" as the default, not \"always update on\n> fetch\".\n\nAh, I totally forgot that the favoured variant was \"fill in if\nmissing, but don't move once it is set\".  Yes, I think that is a\nsensible default.\n\nThanks.\n"},{"id":"412337","messageId":"xmqq1rfq5l8x.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"7f5b50c7b284f2630fe5be6455cc5d3e03b1eb70.1608084282.git.jonathantanmy@google.com","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-16T06:16:14Z","receivedAt":"2020-12-16T06:17:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> diff --git a/ls-refs.c b/ls-refs.c\n> index a1e0b473e4..fdb644b482 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -32,6 +32,8 @@ struct ls_refs_data {\n>  \tunsigned peel;\n>  \tunsigned symrefs;\n>  \tstruct strvec prefixes;\n> +\tunsigned allow_unborn : 1;\n> +\tunsigned unborn : 1;\n>  };\n\nOK, so the idea is\n\n - lsrefs.unborn controls on the serving side if this new feature is\n   advertised to the incoming clients;\n\n - the client side can ask \"unborn\" and that is recorded in\n   data.unborn bit at the beginning of ls_refs();\n\n - the response may show an unborn symbolic ref when \"unborn\" was asked.\n\nwhich looks internally consistent.\n\n> @@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \tif (!ref_match(&data->prefixes, refname_nons))\n>  \t\treturn 0;\n>  \n> -\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\tif (oid)\n> +\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\telse\n> +\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n\nA line that has token \"unborn\" instead of a full object name in hex\nis something existing clients are not prepared to receive and grok.\nIt is not immediately clear how this new code makes sure that\nclients that did not ask for \"unborn\" in what was parsed at the\nbeginning of ls_refs() will not see such a line.  Presumably, no\nexisting caller of send_ref passes NULL in oid and the only one\nthat does so is the one in send_possibly_unborn_head() when the HEAD\npoints at an unborn branch.\n\nOK.\n\n> @@ -74,8 +79,28 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \treturn 0;\n>  }\n>  \n> -static int ls_refs_config(const char *var, const char *value, void *data)\n> +static void send_possibly_unborn_head(struct ls_refs_data *data)\n>  {\n> +\tstruct strbuf namespaced = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tint flag;\n> +\tint null_oid;\n\nI'd suggest renaming this one, which masks the global null_oid of\n\"const struct object_id\" type.  This code does not break only\nbecause is_null_oid() happens to be implemented as a static inline,\nand not as a C-preprocessor macro, right?\n\n> +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n> +\tnull_oid = is_null_oid(&oid);\n> +\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n> +\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n> +\tstrbuf_release(&namespaced);\n> +}\n> +\n> +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> +{\n> +\tstruct ls_refs_data *data = cb_data;\n> +\n> +\tif (!strcmp(\"lsrefs.unborn\", var))\n> +\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n> +\t\t\t!strcmp(value, \"advertise\");\n\nAre there differences between allow and advertise?  Would\nlsrefs.allowUnborn that is a boolean, thus allowing the value to be\nparsed by git_config_bool(), make more sense here, I wonder.  Or is\nthis meant as some future enhancement, e.g. you plan to have some\nservers that allow \"unborn\" request even though they do not actively\nadvertise the support of the feature?  Without documentation update\nor an in-code comment, it is rather hard to guess the intention\nhere.\n\n> @@ -91,7 +116,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \n>  \tmemset(&data, 0, sizeof(data));\n>  \n> -\tgit_config(ls_refs_config, NULL);\n> +\tgit_config(ls_refs_config, &data);\n>  \n>  \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n>  \t\tconst char *arg = request->line;\n> @@ -103,14 +128,35 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \t\t\tdata.symrefs = 1;\n>  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n>  \t\t\tstrvec_push(&data.prefixes, out);\n> +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> +\t\t\tdata.unborn = 1;\n\nSomehow, it appears to me that writing it in a way along with this\nline ...\n\n\t\telse if (!strcmp(\"unborn\", arg))\n\t\t\tdata.unborn = data.allow_unborn;\n\n... would make more sense.  Whether we allowed \"unborn\" request or\nnot, when the other side says \"unborn\", we are handling the request\nfor the unborn feature, and the condition with strcmp() alone\nsignals that better (in other words, when we acquire more request\ntypes, we do not want to pass the control to \"else if\" clauses that\nmay come after this part when we see \"unborn\" request and when we\nare configured not to accept \"unborn\" requests.\n\nIt does not make any difference in the current code, of course, and\nit is more about future-proofing the cleanness of the code.\n\n> -\thead_ref_namespaced(send_ref, &data);\n> +\tif (data.unborn)\n> +\t\tsend_possibly_unborn_head(&data);\n> +\telse\n> +\t\thead_ref_namespaced(send_ref, &data);\n\nI found the \"send_possibly 70% duplicates what the more generic\nhead_ref_namespaced() does\" a bit disturbing.\n\n>  \tfor_each_namespaced_ref(send_ref, &data);\n>  \tpacket_flush(1);\n>  \tstrvec_clear(&data.prefixes);\n>  \treturn 0;\n>  }\n> +\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n> +{\n> +\tif (value) {\n> +\t\tchar *str = NULL;\n> +\n> +\t\tif (!repo_config_get_string(the_repository, \"lsrefs.unborn\",\n> +\t\t\t\t\t    &str) &&\n> +\t\t    !strcmp(\"advertise\", str)) {\n> +\t\t\tstrbuf_addstr(value, \"unborn\");\n> +\t\t\tfree(str);\n> +\t\t}\n> +\t}\n> +\n> +\treturn 1;\n> +}\n> diff --git a/ls-refs.h b/ls-refs.h\n> index 7b33a7c6b8..a99e4be0bd 100644\n> --- a/ls-refs.h\n> +++ b/ls-refs.h\n> @@ -6,5 +6,6 @@ struct strvec;\n>  struct packet_reader;\n>  int ls_refs(struct repository *r, struct strvec *keys,\n>  \t    struct packet_reader *request);\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value);\n>  \n>  #endif /* LS_REFS_H */\n> diff --git a/serve.c b/serve.c\n> index f6341206c4..30cb56d507 100644\n> --- a/serve.c\n> +++ b/serve.c\n> @@ -62,7 +62,7 @@ struct protocol_capability {\n>  \n>  static struct protocol_capability capabilities[] = {\n>  \t{ \"agent\", agent_advertise, NULL },\n> -\t{ \"ls-refs\", always_advertise, ls_refs },\n> +\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n>  \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n>  \t{ \"server-option\", always_advertise, NULL },\n>  \t{ \"object-format\", object_format_advertise, NULL },\n\nThanks.\n"},{"id":"412338","messageId":"xmqqwnxi46gt.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"e24fb6d746279a71f8d6a532790231c2a46468aa.1608084282.git.jonathantanmy@google.com","subject":"Re: [PATCH v2 2/3] connect, transport: add no-op arg for future patch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-16T06:20:50Z","receivedAt":"2020-12-16T06:21:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> A future patch will require transport_get_remote_refs() and\n> get_remote_refs() to gain a new argument. Add the argument in this\n> patch, with no effect on execution, so that the future patch only needs\n> to concern itself with new logic.\n\nPlease give at least a hint about what this \"new argument\" will be\npassing around through the callchain.  E.g.\n\n\tIn a future patch we plan to return the name of an unborn\n\tcurrent branch from deep in the callchain to a caller via a\n\tnew pointer parameter that points at a variable in the\n\tcaller when the caller calls get_remote_refs() and\n\ttransport_get_remote_refs().  Add the parameter to functions\n\tinvolved in the callchain, but no caller passes an actual\n\targument yet in this step ...\n\nor something like that.\n\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> ---\n>  builtin/clone.c      |  2 +-\n>  builtin/fetch-pack.c |  3 ++-\n>  builtin/fetch.c      |  2 +-\n>  builtin/ls-remote.c  |  2 +-\n>  builtin/remote.c     |  2 +-\n>  connect.c            |  5 ++++-\n>  remote.h             |  3 ++-\n>  transport-helper.c   |  7 +++++--\n>  transport-internal.h | 13 +++++--------\n>  transport.c          | 29 ++++++++++++++++++-----------\n>  transport.h          |  7 ++++++-\n>  11 files changed, 46 insertions(+), 29 deletions(-)\n>\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index a0841923cf..70f9450db4 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1264,7 +1264,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tif (!option_no_tags)\n>  \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n>  \n> -\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n> +\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n>  \n>  \tif (refs) {\n>  \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n> diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\n> index 58b7c1fbdc..9f921dfab4 100644\n> --- a/builtin/fetch-pack.c\n> +++ b/builtin/fetch-pack.c\n> @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n>  \tversion = discover_version(&reader);\n>  \tswitch (version) {\n>  \tcase protocol_v2:\n> -\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n> +\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n> +\t\t\t\targs.stateless_rpc, NULL);\n>  \t\tbreak;\n>  \tcase protocol_v1:\n>  \tcase protocol_v0:\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index ecf8537605..a7ef59acfc 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n>  \n>  \tif (must_list_refs) {\n>  \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n> -\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n> +\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n>  \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n>  \t} else\n>  \t\tremote_refs = NULL;\n> diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\n> index 092917eca2..4cf3f60b1b 100644\n> --- a/builtin/ls-remote.c\n> +++ b/builtin/ls-remote.c\n> @@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n>  \tif (server_options.nr)\n>  \t\ttransport->server_options = &server_options;\n>  \n> -\tref = transport_get_remote_refs(transport, &ref_prefixes);\n> +\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n>  \tif (ref) {\n>  \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n>  \t\trepo_set_hash_algo(the_repository, hash_algo);\n> diff --git a/builtin/remote.c b/builtin/remote.c\n> index c1b211b272..246e62f118 100644\n> --- a/builtin/remote.c\n> +++ b/builtin/remote.c\n> @@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n>  \tif (query) {\n>  \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n>  \t\t\tstates->remote->url[0] : NULL);\n> -\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n> +\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n>  \t\ttransport_disconnect(transport);\n>  \n>  \t\tstates->queried = 1;\n> diff --git a/connect.c b/connect.c\n> index 8b8f56cf6d..99d9052365 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \t\t\t     struct ref **list, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n>  \t\t\t     const struct string_list *server_options,\n> -\t\t\t     int stateless_rpc)\n> +\t\t\t     int stateless_rpc,\n> +\t\t\t     char **unborn_head_target)\n>  {\n>  \tint i;\n>  \tconst char *hash_name;\n> @@ -496,6 +497,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \n>  \t/* Process response from server */\n>  \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n> +\t\tif (unborn_head_target)\n> +\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n>  \t\tif (!process_ref_v2(reader, &list))\n>  \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n>  \t}\n> diff --git a/remote.h b/remote.h\n> index 3211abdf05..967f2178d8 100644\n> --- a/remote.h\n> +++ b/remote.h\n> @@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \t\t\t     struct ref **list, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n>  \t\t\t     const struct string_list *server_options,\n> -\t\t\t     int stateless_rpc);\n> +\t\t\t     int stateless_rpc,\n> +\t\t\t     char **unborn_head_target);\n>  \n>  int resolve_remote_symref(struct ref *ref, struct ref *list);\n>  \n> diff --git a/transport-helper.c b/transport-helper.c\n> index 5f6e0b3bd8..5d97eba935 100644\n> --- a/transport-helper.c\n> +++ b/transport-helper.c\n> @@ -1162,13 +1162,16 @@ static int has_attribute(const char *attrs, const char *attr)\n>  }\n>  \n>  static struct ref *get_refs_list(struct transport *transport, int for_push,\n> -\t\t\t\t const struct strvec *ref_prefixes)\n> +\t\t\t\t const struct strvec *ref_prefixes,\n> +\t\t\t\t char **unborn_head_target)\n>  {\n>  \tget_helper(transport);\n>  \n>  \tif (process_connect(transport, for_push)) {\n>  \t\tdo_take_over(transport);\n> -\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n> +\t\treturn transport->vtable->get_refs_list(transport, for_push,\n> +\t\t\t\t\t\t\tref_prefixes,\n> +\t\t\t\t\t\t\tunborn_head_target);\n>  \t}\n>  \n>  \treturn get_refs_list_using_list(transport, for_push);\n> diff --git a/transport-internal.h b/transport-internal.h\n> index 27c9daffc4..5037f6197d 100644\n> --- a/transport-internal.h\n> +++ b/transport-internal.h\n> @@ -18,19 +18,16 @@ struct transport_vtable {\n>  \t * the transport to try to share connections, for_push is a\n>  \t * hint as to whether the ultimate operation is a push or a fetch.\n>  \t *\n> -\t * If communicating using protocol v2 a list of prefixes can be\n> -\t * provided to be sent to the server to enable it to limit the ref\n> -\t * advertisement.  Since ref filtering is done on the server's end, and\n> -\t * only when using protocol v2, this list will be ignored when not\n> -\t * using protocol v2 meaning this function can return refs which don't\n> -\t * match the provided ref_prefixes.\n> -\t *\n>  \t * If the transport is able to determine the remote hash for\n>  \t * the ref without a huge amount of effort, it should store it\n>  \t * in the ref's old_sha1 field; otherwise it should be all 0.\n> +\t *\n> +\t * See transport_get_remote_refs() for information on ref_prefixes and\n> +\t * unborn_head_target.\n>  \t **/\n>  \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n> -\t\t\t\t     const struct strvec *ref_prefixes);\n> +\t\t\t\t     const struct strvec *ref_prefixes,\n> +\t\t\t\t     char **unborn_head_target);\n>  \n>  \t/**\n>  \t * Fetch the objects for the given refs. Note that this gets\n> diff --git a/transport.c b/transport.c\n> index 47da955e4f..815e175017 100644\n> --- a/transport.c\n> +++ b/transport.c\n> @@ -127,7 +127,8 @@ struct bundle_transport_data {\n>  \n>  static struct ref *get_refs_from_bundle(struct transport *transport,\n>  \t\t\t\t\tint for_push,\n> -\t\t\t\t\tconst struct strvec *ref_prefixes)\n> +\t\t\t\t\tconst struct strvec *ref_prefixes,\n> +\t\t\t\t\tchar **unborn_head_target)\n>  {\n>  \tstruct bundle_transport_data *data = transport->data;\n>  \tstruct ref *result = NULL;\n> @@ -163,7 +164,7 @@ static int fetch_refs_from_bundle(struct transport *transport,\n>  \tint ret;\n>  \n>  \tif (!data->get_refs_from_bundle_called)\n> -\t\tget_refs_from_bundle(transport, 0, NULL);\n> +\t\tget_refs_from_bundle(transport, 0, NULL, NULL);\n>  \tret = unbundle(the_repository, &data->header, data->fd,\n>  \t\t\t   transport->progress ? BUNDLE_VERBOSE : 0);\n>  \ttransport->hash_algo = data->header.hash_algo;\n> @@ -281,7 +282,7 @@ static void die_if_server_options(struct transport *transport)\n>   */\n>  static struct ref *handshake(struct transport *transport, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n> -\t\t\t     int must_list_refs)\n> +\t\t\t     int must_list_refs, char **unborn_head_target)\n>  {\n>  \tstruct git_transport_data *data = transport->data;\n>  \tstruct ref *refs = NULL;\n> @@ -301,7 +302,8 @@ static struct ref *handshake(struct transport *transport, int for_push,\n>  \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n>  \t\t\t\t\tref_prefixes,\n>  \t\t\t\t\ttransport->server_options,\n> -\t\t\t\t\ttransport->stateless_rpc);\n> +\t\t\t\t\ttransport->stateless_rpc,\n> +\t\t\t\t\tunborn_head_target);\n>  \t\tbreak;\n>  \tcase protocol_v1:\n>  \tcase protocol_v0:\n> @@ -324,9 +326,11 @@ static struct ref *handshake(struct transport *transport, int for_push,\n>  }\n>  \n>  static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n> -\t\t\t\t\tconst struct strvec *ref_prefixes)\n> +\t\t\t\t\tconst struct strvec *ref_prefixes,\n> +\t\t\t\t\tchar **unborn_head_target)\n>  {\n> -\treturn handshake(transport, for_push, ref_prefixes, 1);\n> +\treturn handshake(transport, for_push, ref_prefixes, 1,\n> +\t\t\t unborn_head_target);\n>  }\n>  \n>  static int fetch_refs_via_pack(struct transport *transport,\n> @@ -370,7 +374,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n>  \t\t\t\tbreak;\n>  \t\t\t}\n>  \t\t}\n> -\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs);\n> +\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs, NULL);\n>  \t}\n>  \n>  \tif (data->version == protocol_unknown_version)\n> @@ -765,7 +769,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n>  \t\treturn -1;\n>  \n>  \tif (!data->got_remote_heads)\n> -\t\tget_refs_via_connect(transport, 1, NULL);\n> +\t\tget_refs_via_connect(transport, 1, NULL, NULL);\n>  \n>  \tmemset(&args, 0, sizeof(args));\n>  \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n> @@ -1251,7 +1255,8 @@ int transport_push(struct repository *r,\n>  \n>  \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n>  \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n> -\t\t\t\t\t\t\t       &ref_prefixes);\n> +\t\t\t\t\t\t\t       &ref_prefixes,\n> +\t\t\t\t\t\t\t       NULL);\n>  \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n>  \n>  \t\tstrvec_clear(&ref_prefixes);\n> @@ -1370,12 +1375,14 @@ int transport_push(struct repository *r,\n>  }\n>  \n>  const struct ref *transport_get_remote_refs(struct transport *transport,\n> -\t\t\t\t\t    const struct strvec *ref_prefixes)\n> +\t\t\t\t\t    const struct strvec *ref_prefixes,\n> +\t\t\t\t\t    char **unborn_head_target)\n>  {\n>  \tif (!transport->got_remote_refs) {\n>  \t\ttransport->remote_refs =\n>  \t\t\ttransport->vtable->get_refs_list(transport, 0,\n> -\t\t\t\t\t\t\t ref_prefixes);\n> +\t\t\t\t\t\t\t ref_prefixes,\n> +\t\t\t\t\t\t\t unborn_head_target);\n>  \t\ttransport->got_remote_refs = 1;\n>  \t}\n>  \n> diff --git a/transport.h b/transport.h\n> index 24558c027d..65de0c9c00 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -241,9 +241,14 @@ int transport_push(struct repository *repo,\n>   * advertisement.  Since ref filtering is done on the server's end (and only\n>   * when using protocol v2), this can return refs which don't match the provided\n>   * ref_prefixes.\n> + *\n> + * If unborn_head_target is not NULL, and the remote reports HEAD as pointing\n> + * to an unborn branch, this function stores the unborn branch in\n> + * unborn_head_target. It should be freed by the caller.\n>   */\n>  const struct ref *transport_get_remote_refs(struct transport *transport,\n> -\t\t\t\t\t    const struct strvec *ref_prefixes);\n> +\t\t\t\t\t    const struct strvec *ref_prefixes,\n> +\t\t\t\t\t    char **unborn_head_target);\n>  \n>  /*\n>   * Fetch the hash algorithm used by a remote.\n"},{"id":"412380","messageId":"X9pQjHrRQQqB6AFI@coredump.intra.peff.net","threadId":"54790","inReplyTo":"7f5b50c7b284f2630fe5be6455cc5d3e03b1eb70.1608084282.git.jonathantanmy@google.com","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-16T18:23:08Z","receivedAt":"2020-12-16T18:24:51Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 15, 2020 at 06:07:56PM -0800, Jonathan Tan wrote:\n\n> +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> +{\n> +\tstruct ls_refs_data *data = cb_data;\n> +\n> +\tif (!strcmp(\"lsrefs.unborn\", var))\n> +\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n> +\t\t\t!strcmp(value, \"advertise\");\n\nWhat's the reason we would want this to be configurable? I would think\nwe would just want it always on for the server, and then clients can\nchoose to make us of it or not (and probably not by omitting the\ncapability; the question is what they want to do with the information\nabout HEAD, but that is true whether it is unborn or not, and is\ncontrolled by options like \"clone -b\").\n\n-Peff\n"},{"id":"412382","messageId":"X9pUc2HXUr3+WHbR@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqq7dpi5tvl.fsf@gitster.c.googlers.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-16T18:39:47Z","receivedAt":"2020-12-16T18:40:54Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 15, 2020 at 07:09:50PM -0800, Junio C Hamano wrote:\n\n> > I'm not sure if it's a good idea to change \"git checkout origin\" here or\n> > not. It already does something useful. I was mostly suggesting that the\n> > other thing might _also_ be useful, but I'm not sure if it is wise to\n> > change the current behavior.\n> \n> Well, \"git checkout origin/HEAD\" would also do something useful,\n> which happens to be identical to \"git checkout origin\", to detach\n> HEAD at the commit.\n\nTo be clear, I meant that both of those do the useful thing, and I'm not\nsure if it would be confusing to users to change that (but see below).\n\n> > I was thinking more like an explicit way to trigger the dwim-behavior,\n> > like:\n> >\n> >   # same as \"git checkout foo\" magic that creates \"foo\", but we\n> >   # have said explicitly both that we expect to make the new branch, and\n> >   # also that we expect it to come from origin.\n> >   git checkout --make-local origin/foo\n> \n> By default I think --guess (formerly known as --dwim) is enabled, so\n> \"git checkout foo\" is \"git checkout --guess foo\", which is making\n> local 'foo' out of the uniquely found remote-tracking branch.  This\n> new one is to reduce the \"uniquely found\" part from the magic and\n> let you be a bit more explicit, but not explicit enough to say \"-t\"\n> or \"-b foo\"?  I am not sure if this is all that useful.\n\nI agree it's not all that useful in that example. What I was thinking is\nthat by giving the implicit/heuristic magic a more explicit verbose\nname, then we make it natural to extend the explicit version to more\ncases where it might be questionable to do it implicitly.\n\nSo no, I doubt anybody would normally type what I wrote above. But it\nlets us explain it as:\n\n  - there's a feature \"--make-local\" (I still hate the name) that makes\n    a local branch from a remote one if it doesn't already exist\n\n  - that feature knows how to resolve symrefs and create the branch from\n    the pointed-to name\n\n  - as a shortcut, \"git checkout foo\" is a synonym for \"--make-local\n    origin/foo\" when \"origin/foo\" exists but \"foo\" does not\n\nIt's definitely not worth it, though, if we decide that there's an\nimplicit/heuristic syntax that should trigger the symref thing.\n\n> If this were a slightly different proposal, I would see the\n> convenience value in it, though.  Currently what \"--guess\" does is:\n> \n>       If the name 'foo' given does not exist as a local branch,\n>       and the name appears exactly once as a remote-tracking branch\n>       from some remote (i.e. 'refs/remotes/origin/foo' exists, but\n>       there is no other 'refs/remotes/*/foo'), create a local 'foo'\n>       that builds on that remote-tracking branch and check it out.\n> \n> What would happen if we tweaked the existing \"--guess\" behaviour\n> slightly?\n> \n>       \"git checkout --guess origin/foo\", even when there is a second\n>       remote 'publish' that also has a remote-tracking branch for\n>       its 'foo' (i.e. both 'refs/remotes/{origin,publish}/foo'\n>       exists), can be used to disambiguate among these remotes with\n>       'foo'.  You'd get local 'foo' that builds on 'foo' from the\n>       remote 'origin' and check it out.\n\nI forgot we had --guess. Piggy-backing on that might be sensible as a\nstronger \"explicit\" signal that this is what the user wants (though\n\"--guess\" is still a funny name here, because we're no longer guessing\nat all; the user told us what they want).\n\nBut yeah, the semantics you outlined in the second paragraph match what\nI was expecting \"--make-local\" to do.\n\n> >   # similar, but because we are being explicit, we know it is reasonable\n> >   # to dereference HEAD to find the actual branch name\n> >   git checkout --make-local origin/HEAD\n> \n> The user does not need \"git symbolic-ref refs/remotes/origin/HEAD\"\n> if such a feature were available.  \"git checkout --some-option origin\"\n> without having to say /HEAD may be a better UI, though.\n\nRight. I'm assuming that \"origin/HEAD\" and \"origin\" could be used\ninterchangeably in my example.\n\n> And \"checkout\" being a Porcelain, and the DWIM feature that is\n> always on is subject to be improved for human use, I do not see why\n> that --some-option cannot be --guess.  If I want to get the current\n> behaviour, I can explicitly say \"git checkout --detach origin\"\n> anyway, no?\n\nI think:\n\n  git checkout --guess origin\n\nwould make sense to dereference origin/HEAD to \"foo\", as if we had said\n\"git checkout foo\". That's the explicit part that seems safe. My\nquestion is whether:\n\n  git checkout origin\n\nshould likewise do so. As you note, one can always use --detach to make\ntheir intention clear, and checkout is a porcelain, so we are OK to\nchange it. But would users find that annoying? I frequently use \"git\ncheckout origin\" to get a detached HEAD pointing at your master (e.g.,\nbecause I want to reproduce a bug, or do a \"something like this...\"\npatch). I'm sure I could retrain my fingers, but I wonder if I'm not the\nonly one.\n\nDoing it for only an explicit \"--guess\" turns that feature into a\ntri-state (explicitly off, explicitly on, or \"implicit, so be a little\nmore conservative\"). Which perhaps is harder to explain, but I think\ncleanly adds the new feature in a consistent way, without really\nchanging any existing behavior.\n\nRelated, I assume that:\n\n  git checkout --guess origin/foo\n  git checkout origin/foo\n\nshould behave the same as their \"origin\" or \"origin/HEAD\" counterparts\nfor consistency (obviously making \"foo\" in the former case, and either\ndetaching or making \"foo\" in the second case, depending on what you\nthink of the earlier paragraphs).\n\n-Peff\n"},{"id":"412435","messageId":"xmqqft4531xl.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"X9pUc2HXUr3+WHbR@coredump.intra.peff.net","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-16T20:56:22Z","receivedAt":"2020-12-16T20:57:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> I think:\n>\n>   git checkout --guess origin\n>\n> would make sense to dereference origin/HEAD to \"foo\", as if we had said\n> \"git checkout foo\". That's the explicit part that seems safe. My\n> question is whether:\n>\n>   git checkout origin\n>\n> should likewise do so.\n\nI see.  I think \"--guess\" is by default true, so unless you have\ncheckout.guess=false configured, my answer to the above question is\nyes.\n\n> As you note, one can always use --detach to make\n> their intention clear, and checkout is a porcelain, so we are OK to\n> change it. But would users find that annoying? I frequently use \"git\n> checkout origin\" to get a detached HEAD pointing at your master (e.g.,\n> because I want to reproduce a bug, or do a \"something like this...\"\n> patch). I'm sure I could retrain my fingers, but I wonder if I'm not the\n> only one.\n\nMy fingers say \"git checkout X^0\" instead of \"--detach\" when I want\nto detach for any value of X (e.g. \"HEAD\", \"v2.28.0\").  But I do\nunderstand people like to be implicit when they can.\n\n> Doing it for only an explicit \"--guess\" turns that feature into a\n> tri-state (explicitly off, explicitly on, or \"implicit, so be a little\n> more conservative\"). Which perhaps is harder to explain, but I think\n> cleanly adds the new feature in a consistent way, without really\n> changing any existing behavior.\n\nHmmmm...  I do not offhand know if that is a good idea or not.\n\n> Related, I assume that:\n>\n>   git checkout --guess origin/foo\n>   git checkout origin/foo\n>\n> should behave the same as their \"origin\" or \"origin/HEAD\" counterparts\n> for consistency (obviously making \"foo\" in the former case, and either\n> detaching or making \"foo\" in the second case, depending on what you\n> think of the earlier paragraphs).\n\nI think that is what I said in the \"what would happen if we tweaked\"\nparagraph about using origin/ prefix as a disambiguator?  Then yes,\nI think we are in agreement.\n\nThanks.\n"},{"id":"412454","messageId":"20201216234914.1671487-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq1rfq5l8x.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T23:49:14Z","receivedAt":"2020-12-16T23:50:16Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > @@ -74,8 +79,28 @@ static int send_ref(const char *refname, const struct object_id *oid,\n> >  \treturn 0;\n> >  }\n> >  \n> > -static int ls_refs_config(const char *var, const char *value, void *data)\n> > +static void send_possibly_unborn_head(struct ls_refs_data *data)\n> >  {\n> > +\tstruct strbuf namespaced = STRBUF_INIT;\n> > +\tstruct object_id oid;\n> > +\tint flag;\n> > +\tint null_oid;\n> \n> I'd suggest renaming this one, which masks the global null_oid of\n> \"const struct object_id\" type.  This code does not break only\n> because is_null_oid() happens to be implemented as a static inline,\n> and not as a C-preprocessor macro, right?\n\nOK - will rename.\n\n> > +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> > +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n> > +\tnull_oid = is_null_oid(&oid);\n> > +\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n> > +\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n> > +\tstrbuf_release(&namespaced);\n> > +}\n> > +\n> > +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> > +{\n> > +\tstruct ls_refs_data *data = cb_data;\n> > +\n> > +\tif (!strcmp(\"lsrefs.unborn\", var))\n> > +\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n> > +\t\t\t!strcmp(value, \"advertise\");\n> \n> Are there differences between allow and advertise?  Would\n> lsrefs.allowUnborn that is a boolean, thus allowing the value to be\n> parsed by git_config_bool(), make more sense here, I wonder.  Or is\n> this meant as some future enhancement, e.g. you plan to have some\n> servers that allow \"unborn\" request even though they do not actively\n> advertise the support of the feature?  Without documentation update\n> or an in-code comment, it is rather hard to guess the intention\n> here.\n\nI'll update the documentation. With this current patch, yes, some\nservers will allow \"unborn\" requests even though they do not actively\nadvertise it. This allows servers in load-balanced environments to first\nbe configured to support the feature, then after ensuring that the\nconfiguration for all servers is complete, to turn on advertisement.\n\n> > @@ -91,7 +116,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \n> >  \tmemset(&data, 0, sizeof(data));\n> >  \n> > -\tgit_config(ls_refs_config, NULL);\n> > +\tgit_config(ls_refs_config, &data);\n> >  \n> >  \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n> >  \t\tconst char *arg = request->line;\n> > @@ -103,14 +128,35 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \t\t\tdata.symrefs = 1;\n> >  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n> >  \t\t\tstrvec_push(&data.prefixes, out);\n> > +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> > +\t\t\tdata.unborn = 1;\n> \n> Somehow, it appears to me that writing it in a way along with this\n> line ...\n> \n> \t\telse if (!strcmp(\"unborn\", arg))\n> \t\t\tdata.unborn = data.allow_unborn;\n> \n> ... would make more sense.  Whether we allowed \"unborn\" request or\n> not, when the other side says \"unborn\", we are handling the request\n> for the unborn feature, and the condition with strcmp() alone\n> signals that better (in other words, when we acquire more request\n> types, we do not want to pass the control to \"else if\" clauses that\n> may come after this part when we see \"unborn\" request and when we\n> are configured not to accept \"unborn\" requests.\n> \n> It does not make any difference in the current code, of course, and\n> it is more about future-proofing the cleanness of the code.\n\nGood point. I'll go ahead and write it as you describe.\n\nI was following the style in upload-pack, where writing it my way versus\nyour way would make a difference because we die on invalid arguments at\nthe end. (It does raise the question whether we should die on invalid\narguments, but maybe that's for another time.)\n\n> \n> > -\thead_ref_namespaced(send_ref, &data);\n> > +\tif (data.unborn)\n> > +\t\tsend_possibly_unborn_head(&data);\n> > +\telse\n> > +\t\thead_ref_namespaced(send_ref, &data);\n> \n> I found the \"send_possibly 70% duplicates what the more generic\n> head_ref_namespaced() does\" a bit disturbing.\n\nThere's more duplication in refs.c (e.g. head_ref_namespaced() and\nrefs_head_ref()) too. I'll see if I can refactor those into something\nmore generic.\n\n"},{"id":"412455","messageId":"20201216235443.1674103-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"X9pQjHrRQQqB6AFI@coredump.intra.peff.net","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-16T23:54:43Z","receivedAt":"2020-12-16T23:55:28Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Tue, Dec 15, 2020 at 06:07:56PM -0800, Jonathan Tan wrote:\n> \n> > +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> > +{\n> > +\tstruct ls_refs_data *data = cb_data;\n> > +\n> > +\tif (!strcmp(\"lsrefs.unborn\", var))\n> > +\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n> > +\t\t\t!strcmp(value, \"advertise\");\n> \n> What's the reason we would want this to be configurable? I would think\n> we would just want it always on for the server, and then clients can\n> choose to make us of it or not (and probably not by omitting the\n> capability; the question is what they want to do with the information\n> about HEAD, but that is true whether it is unborn or not, and is\n> controlled by options like \"clone -b\").\n\nFirstly, this allows a staged rollout in load-balancing situations\nwherein we turn on \"allow\" for all servers, then \"advertise\", so that we\ndon't end up with a client that sees the advertisement but then sends\nthe follow-up request to a server that has not received the latest\nconfiguration yet.\n\nSecondly, I wonder if some people purposely set HEAD to an unborn branch\njust so that the repository would be presented as not having a HEAD.\nNow, the name of the unborn branch would be revealed. I don't know if\nthat's a problem, though - but if it is, at least this configuration\nvariable is a way to solve that.\n\nI'll include this in the documentation in a next version of this patch\nset.\n"},{"id":"412470","messageId":"xmqqzh2dz071.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20201216235443.1674103-1-jonathantanmy@google.com","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-17T01:32:50Z","receivedAt":"2020-12-17T01:33:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Firstly, this allows a staged rollout in load-balancing situations\n> wherein we turn on \"allow\" for all servers, then \"advertise\", so that we\n> don't end up with a client that sees the advertisement but then sends\n> the follow-up request to a server that has not received the latest\n> configuration yet.\n\nIf this were the _first_ capability we are adding to the system, the\nabove makes quite a lot of sense, but I do not recall any existing\ncapability that can be configured this way.  How would one deploy a\nset of servers that gradually start allowing fetching unadvertised\nbut reachable commits, for example?  I am not saying that the \"I'll\naccept if asked, but I won't actively advertise\" is a bad feature; I\njust find it disturbing that only this knob has that feature.\n"},{"id":"412539","messageId":"X9xJLWdFJfNJTn0p@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqqzh2dz071.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v2 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-18T06:16:13Z","receivedAt":"2020-12-18T06:17:12Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 16, 2020 at 05:32:50PM -0800, Junio C Hamano wrote:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > Firstly, this allows a staged rollout in load-balancing situations\n> > wherein we turn on \"allow\" for all servers, then \"advertise\", so that we\n> > don't end up with a client that sees the advertisement but then sends\n> > the follow-up request to a server that has not received the latest\n> > configuration yet.\n> \n> If this were the _first_ capability we are adding to the system, the\n> above makes quite a lot of sense, but I do not recall any existing\n> capability that can be configured this way.  How would one deploy a\n> set of servers that gradually start allowing fetching unadvertised\n> but reachable commits, for example?  I am not saying that the \"I'll\n> accept if asked, but I won't actively advertise\" is a bad feature; I\n> just find it disturbing that only this knob has that feature.\n\nYeah, that was my thought. It is a real problem, but not one we've dealt\nwith before in this way (or at all, really). Two recent examples:\n\n  - for fetching, the object-format=sha1 capability is always parroted\n    back by v2.28 and higher clients. But there's no config to disable\n    the advertisement, so during a partial deployment to a load-balanced\n    cluster, a client may say \"object-format=sha1\" to a server that\n    doesn't understand it.\n\n  - for pushing, we added report-status-v2, which is likewise always\n    repeated back by any v2.29 and higher client.\n\nAt GitHub we recently merged in v2.29, and just hacked report-status-v2\nout of the advertisement within the code (leaving it in the \"we can read\nthis but won't advertise it state\"). Temporarily modifying the code is\ndefinitely ugly, and I don't mind a cleaner solution, but:\n\n  - it would be nice if this were done in a more consistent way for all\n    new capabilities\n\n  - one nice thing about the code change is that after the rollout is\n    done, it's safe to make the code unconditional again, which makes\n    it simpler to read/reason about.\n\n    This may be oversimplifying it a bit, of course. On one platform, we\n    know when the rollout is happening. But if it's something we ship\n    upstream, then \"rollout\" may be on the jump from v2.28 to v2.29, or\n    to v2.30, or v2.31, etc. You can never say \"rollouts are done, and\n    existing server versions know about this feature\". So any upstream\n    support like config has to stay forever.\n\nSo I dunno. My biggest complaint is that the config option defaults to\n_off_.  So it's helping load-balanced rollouts, but creating complexity\nfor everyone else who might want to enable the feature.\n\n(I know there was also an indication that some people might want it off\nbecause they somehow want to have no HEAD at all. I don't find this\nparticularly compelling, but even if it were, I think we could leave it\nthe config as an escape hatch for such folks, but still default it to\n\"on\").\n\n-Peff\n"},{"id":"412540","messageId":"X9xJ6BHM9VY0/yLs@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqqft4531xl.fsf@gitster.c.googlers.com","subject":"Re: [PATCH] clone: in protocol v2, use remote's default branch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2020-12-18T06:19:20Z","receivedAt":"2020-12-18T06:20:18Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Dec 16, 2020 at 12:56:22PM -0800, Junio C Hamano wrote:\n\n> > I think:\n> >\n> >   git checkout --guess origin\n> >\n> > would make sense to dereference origin/HEAD to \"foo\", as if we had said\n> > \"git checkout foo\". That's the explicit part that seems safe. My\n> > question is whether:\n> >\n> >   git checkout origin\n> >\n> > should likewise do so.\n> \n> I see.  I think \"--guess\" is by default true, so unless you have\n> checkout.guess=false configured, my answer to the above question is\n> yes.\n\nYes, I agree with the current definition of \"--guess\", the two would be\nthe same. I'm just concerned that people will be unhappy with changing\nthe behavior of the latter, so everything else (the \"tri-state --guess\"\nthing) is an attempt to band-aid over that.\n\nIf we decide it's not a concern worth addressing, then I agree the two\nshould behave the same. I'm just not convinced it won't annoy people who\nare used to how \"git checkout\" works now with non-local branches.\n\n-Peff\n"},{"id":"412784","messageId":"cover.1608587839.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"[PATCH v3 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-21T22:30:58Z","receivedAt":"2020-12-21T22:31:47Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Thanks everyone for your comments. Changes from v2:\n\nPatch 1:\n\n - I took a look at head_ref_namespaced() was hoping that it could be\n   refactored to meet my new needs, but I don't think it's feasible.\n   head_ref_namespaced() seems to have a precise use - for when we\n   already have a callback that we're using with\n   for_each_namespaced_ref(), and we want to use it with the HEAD ref as\n   well. Instead what I did is to eliminate the use of\n   head_ref_namespaced() here, and used send_possibly_unborn_head() for\n   both the regular and the new unborn case.\n\n - Renamed null_oid to avoid shadowing, as pointed out by Junio.\n\n - Changed to a boolean lsrefs.allowUnborn, so no more advertise/allow\n   distinction. Currently it still defaults to unallowed. Peff, what did\n   you mean in [1] by the following:\n\n> So I dunno. My biggest complaint is that the config option defaults to\n> _off_.  So it's helping load-balanced rollouts, but creating complexity\n> for everyone else who might want to enable the feature.\n\n   So it seems like you're saying that it should default to \"on\", but at\n   the same time you are talking about enabling the feature (which seems\n   to imply switching it from \"off\" to \"on\"). (Also, note that this is a\n   server-side thing; on the client-side, Git will always use what the\n   server gives and there is no option to control this.)\n\n - Added documentation of lsrefs.allowUnborn, which I forgot.\n\nPatch 2:\n\n - Used Junio's suggested commit message.\n\nPatch 3:\n\n - No changes except what was necessary due to the config option change.\n\n[1] https://lore.kernel.org/git/X9xJLWdFJfNJTn0p@coredump.intra.peff.net/\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: add no-op arg for future patch\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 19 +++++++--\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         |  2 +-\n builtin/ls-remote.c                     |  2 +-\n builtin/remote.c                        |  2 +-\n connect.c                               | 29 ++++++++++++--\n ls-refs.c                               | 51 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  3 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  7 ++--\n t/t5702-protocol-v2.sh                  |  9 +++++\n transport-helper.c                      |  7 +++-\n transport-internal.h                    | 13 +++----\n transport.c                             | 29 ++++++++------\n transport.h                             |  7 +++-\n 20 files changed, 160 insertions(+), 43 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v2:\n1:  7f5b50c7b2 ! 1:  7d20ec323a ls-refs: report unborn targets of symrefs\n    @@ Commit message\n     \n         Currently, symrefs that have unborn targets (such as in this case) are\n         not communicated by the protocol. Teach Git to advertise and support the\n    -    \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.unborn config).\n    -    This feature indicates that \"ls-refs\" supports the \"unborn\" argument;\n    -    when it is specified, \"ls-refs\" will send the HEAD symref with the name\n    -    of its unborn target.\n    +    \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.allowunborn\n    +    config). This feature indicates that \"ls-refs\" supports the \"unborn\"\n    +    argument; when it is specified, \"ls-refs\" will send the HEAD symref with\n    +    the name of its unborn target.\n     \n         This change is only for protocol v2. A similar change for protocol v0\n         would require independent protocol design (there being no analogous\n    @@ Commit message\n     \n         Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n     \n    + ## Documentation/config.txt ##\n    +@@ Documentation/config.txt: include::config/interactive.txt[]\n    + \n    + include::config/log.txt[]\n    + \n    ++include::config/lsrefs.txt[]\n    ++\n    + include::config/mailinfo.txt[]\n    + \n    + include::config/mailmap.txt[]\n    +\n    + ## Documentation/config/lsrefs.txt (new) ##\n    +@@\n    ++lsrefs.allowUnborn::\n    ++\tAllow the server to send information about unborn symrefs during the\n    ++\tprotocol v2 ref advertisement.\n    +\n      ## Documentation/technical/protocol-v2.txt ##\n     @@ Documentation/technical/protocol-v2.txt: ls-refs takes in the following arguments:\n      \tWhen specified, only references having a prefix matching one of\n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n     +\tstruct strbuf namespaced = STRBUF_INIT;\n     +\tstruct object_id oid;\n     +\tint flag;\n    -+\tint null_oid;\n    ++\tint oid_is_null;\n     +\n     +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n     +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n    -+\tnull_oid = is_null_oid(&oid);\n    -+\tif (!null_oid || (data->symrefs && (flag & REF_ISSYMREF)))\n    -+\t\tsend_ref(namespaced.buf, null_oid ? NULL : &oid, flag, data);\n    ++\toid_is_null = is_null_oid(&oid);\n    ++\tif (!oid_is_null ||\n    ++\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n    ++\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n     +\tstrbuf_release(&namespaced);\n     +}\n     +\n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n     +{\n     +\tstruct ls_refs_data *data = cb_data;\n     +\n    -+\tif (!strcmp(\"lsrefs.unborn\", var))\n    -+\t\tdata->allow_unborn = !strcmp(value, \"allow\") ||\n    -+\t\t\t!strcmp(value, \"advertise\");\n    ++\tif (!strcmp(\"lsrefs.allowunborn\", var))\n    ++\t\tdata->allow_unborn = git_config_bool(var, value);\n    ++\n      \t/*\n      \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n      \t * config. This may need to eventually be expanded to \"receive\", but we\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n      \t\tdie(_(\"expected flush after ls-refs arguments\"));\n      \n     -\thead_ref_namespaced(send_ref, &data);\n    -+\tif (data.unborn)\n    -+\t\tsend_possibly_unborn_head(&data);\n    -+\telse\n    -+\t\thead_ref_namespaced(send_ref, &data);\n    ++\tsend_possibly_unborn_head(&data);\n      \tfor_each_namespaced_ref(send_ref, &data);\n      \tpacket_flush(1);\n      \tstrvec_clear(&data.prefixes);\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n     +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n     +{\n     +\tif (value) {\n    -+\t\tchar *str = NULL;\n    ++\t\tint allow_unborn_value;\n     +\n    -+\t\tif (!repo_config_get_string(the_repository, \"lsrefs.unborn\",\n    -+\t\t\t\t\t    &str) &&\n    -+\t\t    !strcmp(\"advertise\", str)) {\n    ++\t\tif (!repo_config_get_bool(the_repository,\n    ++\t\t\t\t\t \"lsrefs.allowunborn\",\n    ++\t\t\t\t\t &allow_unborn_value) &&\n    ++\t\t    allow_unborn_value)\n     +\t\t\tstrbuf_addstr(value, \"unborn\");\n    -+\t\t\tfree(str);\n    -+\t\t}\n     +\t}\n     +\n     +\treturn 1;\n2:  e24fb6d746 ! 2:  b5a78857eb connect, transport: add no-op arg for future patch\n    @@ Metadata\n      ## Commit message ##\n         connect, transport: add no-op arg for future patch\n     \n    -    A future patch will require transport_get_remote_refs() and\n    -    get_remote_refs() to gain a new argument. Add the argument in this\n    -    patch, with no effect on execution, so that the future patch only needs\n    -    to concern itself with new logic.\n    +    In a future patch we plan to return the name of an unborn current branch\n    +    from deep in the callchain to a caller via a new pointer parameter that\n    +    points at a variable in the caller when the caller calls\n    +    get_remote_refs() and transport_get_remote_refs(). Add the parameter to\n    +    functions involved in the callchain, but no caller passes an actual\n    +    argument yet in this step. Thus, the future patch only needs to concern\n    +    itself with new logic.\n     \n         Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n     \n3:  6fcb3b16ce ! 3:  c2303dc976 clone: respect remote unborn HEAD\n    @@ t/t5606-clone-options.sh: test_expect_success 'redirected clone -v does show pro\n      test_expect_success 'chooses correct default initial branch name' '\n     -\tgit init --bare empty &&\n     +\tgit -c init.defaultBranch=foo init --bare empty &&\n    -+\ttest_config -C empty lsrefs.unborn advertise &&\n    ++\ttest_config -C empty lsrefs.allowUnborn true &&\n      \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n      \tgit -c init.defaultBranch=up clone empty whats-up &&\n     -\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n    @@ t/t5702-protocol-v2.sh: test_expect_success 'clone with file:// using protocol v\n      \n     +test_expect_success 'clone of empty repo propagates name of default branch' '\n     +\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n    -+\ttest_config -C file_empty_parent lsrefs.unborn advertise &&\n    ++\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n     +\n     +\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n     +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n     +\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n     +'\n    -+\n    -+test_expect_success '...but not if it is not advertised' '\n    -+\ttest_config -C file_empty_parent lsrefs.unborn none &&\n    -+\n    -+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n    -+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child_2 &&\n    -+\tgrep \"refs/heads/main\" file_empty_child_2/.git/HEAD\n    -+'\n     +\n      test_expect_success 'fetch with file:// using protocol v2' '\n      \ttest_when_finished \"rm -f log\" &&\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412785","messageId":"7d20ec323a11e83fb8605abe4608a74a9b15373f.1608587839.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608587839.git.jonathantanmy@google.com","subject":"[PATCH v3 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-21T22:30:59Z","receivedAt":"2020-12-21T22:32:04Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.allowunborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n ls-refs.c                               | 51 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n 6 files changed, 63 insertions(+), 6 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..dcbec11aaa\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,3 @@\n+lsrefs.allowUnborn::\n+\tAllow the server to send information about unborn symrefs during the\n+\tprotocol v2 ref advertisement.\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..4707511c10 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..82c79895c3 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -32,6 +32,8 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned allow_unborn : 1;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -74,8 +79,29 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int ls_refs_config(const char *var, const char *value, void *data)\n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n {\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n+{\n+\tstruct ls_refs_data *data = cb_data;\n+\n+\tif (!strcmp(\"lsrefs.allowunborn\", var))\n+\t\tdata->allow_unborn = git_config_bool(var, value);\n+\n \t/*\n \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n \t * config. This may need to eventually be expanded to \"receive\", but we\n@@ -91,7 +117,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n-\tgit_config(ls_refs_config, NULL);\n+\tgit_config(ls_refs_config, &data);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n \t\tconst char *arg = request->line;\n@@ -103,14 +129,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = 1;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tint allow_unborn_value;\n+\n+\t\tif (!repo_config_get_bool(the_repository,\n+\t\t\t\t\t \"lsrefs.allowunborn\",\n+\t\t\t\t\t &allow_unborn_value) &&\n+\t\t    allow_unborn_value)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412786","messageId":"b5a78857ebdd0c58c1208143776a0bccbebdb438.1608587839.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608587839.git.jonathantanmy@google.com","subject":"[PATCH v3 2/3] connect, transport: add no-op arg for future patch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-21T22:31:00Z","receivedAt":"2020-12-21T22:32:05Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs(). Add the parameter to\nfunctions involved in the callchain, but no caller passes an actual\nargument yet in this step. Thus, the future patch only needs to concern\nitself with new logic.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/clone.c      |  2 +-\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      |  2 +-\n builtin/ls-remote.c  |  2 +-\n builtin/remote.c     |  2 +-\n connect.c            |  5 ++++-\n remote.h             |  3 ++-\n transport-helper.c   |  7 +++++--\n transport-internal.h | 13 +++++--------\n transport.c          | 29 ++++++++++++++++++-----------\n transport.h          |  7 ++++++-\n 11 files changed, 46 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..70f9450db4 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1264,7 +1264,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..9f921dfab4 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc, NULL);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..a7ef59acfc 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..4cf3f60b1b 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d11a5589e4..9a547240ab 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n \tif (query) {\n \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n \t\t\tstates->remote->url[0] : NULL);\n-\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n+\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n \t\ttransport_disconnect(transport);\n \n \t\tstates->queried = 1;\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..99d9052365 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc)\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target)\n {\n \tint i;\n \tconst char *hash_name;\n@@ -496,6 +497,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n+\t\tif (unborn_head_target)\n+\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n \t\tif (!process_ref_v2(reader, &list))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..967f2178d8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc);\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target);\n \n int resolve_remote_symref(struct ref *ref, struct ref *list);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..5d97eba935 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,16 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t const struct strvec *ref_prefixes,\n+\t\t\t\t char **unborn_head_target)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\tref_prefixes,\n+\t\t\t\t\t\t\tunborn_head_target);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..5037f6197d 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -18,19 +18,16 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n+\t *\n+\t * See transport_get_remote_refs() for information on ref_prefixes and\n+\t * unborn_head_target.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t\t     char **unborn_head_target);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..396a601d78 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,8 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -163,7 +164,7 @@ static int fetch_refs_from_bundle(struct transport *transport,\n \tint ret;\n \n \tif (!data->get_refs_from_bundle_called)\n-\t\tget_refs_from_bundle(transport, 0, NULL);\n+\t\tget_refs_from_bundle(transport, 0, NULL, NULL);\n \tret = unbundle(the_repository, &data->header, data->fd,\n \t\t\t   transport->progress ? BUNDLE_VERBOSE : 0);\n \ttransport->hash_algo = data->header.hash_algo;\n@@ -281,7 +282,7 @@ static void die_if_server_options(struct transport *transport)\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n-\t\t\t     int must_list_refs)\n+\t\t\t     int must_list_refs, char **unborn_head_target)\n {\n \tstruct git_transport_data *data = transport->data;\n \tstruct ref *refs = NULL;\n@@ -305,7 +306,8 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n \t\t\t\t\tref_prefixes,\n \t\t\t\t\ttransport->server_options,\n-\t\t\t\t\ttransport->stateless_rpc);\n+\t\t\t\t\ttransport->stateless_rpc,\n+\t\t\t\t\tunborn_head_target);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -334,9 +336,11 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, ref_prefixes, 1,\n+\t\t\t unborn_head_target);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -380,7 +384,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n-\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs);\n+\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs, NULL);\n \t}\n \n \tif (data->version == protocol_unknown_version)\n@@ -775,7 +779,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \t\treturn -1;\n \n \tif (!data->got_remote_heads)\n-\t\tget_refs_via_connect(transport, 1, NULL);\n+\t\tget_refs_via_connect(transport, 1, NULL, NULL);\n \n \tmemset(&args, 0, sizeof(args));\n \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n@@ -1261,7 +1265,8 @@ int transport_push(struct repository *r,\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &ref_prefixes,\n+\t\t\t\t\t\t\t       NULL);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n \t\tstrvec_clear(&ref_prefixes);\n@@ -1380,12 +1385,14 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t ref_prefixes,\n+\t\t\t\t\t\t\t unborn_head_target);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..65de0c9c00 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -241,9 +241,14 @@ int transport_push(struct repository *repo,\n  * advertisement.  Since ref filtering is done on the server's end (and only\n  * when using protocol v2), this can return refs which don't match the provided\n  * ref_prefixes.\n+ *\n+ * If unborn_head_target is not NULL, and the remote reports HEAD as pointing\n+ * to an unborn branch, this function stores the unborn branch in\n+ * unborn_head_target. It should be freed by the caller.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412787","messageId":"c2303dc9761493decd7a21a7f1e89d6dd9c646e4.1608587839.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608587839.git.jonathantanmy@google.com","subject":"[PATCH v3 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-21T22:31:01Z","receivedAt":"2020-12-21T22:32:05Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 19 ++++++++++++++++---\n connect.c                     | 28 ++++++++++++++++++++++++----\n t/t5606-clone-options.sh      |  7 ++++---\n t/t5702-protocol-v2.sh        |  9 +++++++++\n 5 files changed, 54 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 70f9450db4..217c87fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -980,6 +980,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint submodule_progress;\n \n \tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tchar *unborn_head_target = NULL;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1264,7 +1265,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n+\t\t\t\t\t &unborn_head_target);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (unborn_head_target &&\n+\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n+\t\t\t\tref = unborn_head_target;\n+\t\t\t\tunborn_head_target = NULL;\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n \t\t\tfree(ref);\n \t\t}\n \t}\n@@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n+\tfree(unborn_head_target);\n \tstrvec_clear(&ref_prefixes);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 99d9052365..3c35324b4c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -489,6 +509,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -497,9 +519,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (unborn_head_target)\n-\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..67a4c6d05b 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,12 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.allowUnborn true &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..4fbbe5aff5 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,15 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n+\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412789","messageId":"xmqq7dpalnzd.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"cover.1608587839.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-21T23:48:38Z","receivedAt":"2020-12-21T23:49:40Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Jonathan Tan (3):\n>   ls-refs: report unborn targets of symrefs\n>   connect, transport: add no-op arg for future patch\n>   clone: respect remote unborn HEAD\n\nThis passes standalone all its tests, but in 'seen', seems to break\nsome tests.\n\nhttps://travis-ci.org/github/git/git/builds/750936755 has just\nstarted, but my local tests before publishing the day's integration\nresult failed 5702, 0031, and 5606 with this topic, and all passed\nwithout the topic.\n\nThanks.\n"},{"id":"412861","messageId":"cover.1608673963.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201211210508.2337494-1-jonathantanmy@google.com","subject":"[PATCH v4 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-22T21:54:17Z","receivedAt":"2020-12-22T21:55:05Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Thanks Junio for informing me of the test failures. Turns out that it\nwas partly because I didn't memset oid (and in some code paths, it gets\nread without being written to), and partly because I didn't set\nGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME. Here's an updated patch set with\nthe fixes.\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: add no-op arg for future patch\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 19 +++++++--\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         |  2 +-\n builtin/ls-remote.c                     |  2 +-\n builtin/remote.c                        |  2 +-\n connect.c                               | 29 ++++++++++++--\n ls-refs.c                               | 52 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  3 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  8 ++--\n t/t5702-protocol-v2.sh                  | 11 ++++++\n transport-helper.c                      |  7 +++-\n transport-internal.h                    | 13 +++----\n transport.c                             | 29 ++++++++------\n transport.h                             |  7 +++-\n 20 files changed, 164 insertions(+), 43 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v3:\n1:  7d20ec323a ! 1:  a66e50626e ls-refs: report unborn targets of symrefs\n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n     +\tint flag;\n     +\tint oid_is_null;\n     +\n    ++\tmemset(&oid, 0, sizeof(oid));\n     +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n     +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n     +\toid_is_null = is_null_oid(&oid);\n2:  b5a78857eb = 2:  14f3962adc connect, transport: add no-op arg for future patch\n3:  c2303dc976 ! 3:  e770fc46eb clone: respect remote unborn HEAD\n    @@ t/t5606-clone-options.sh: test_expect_success 'redirected clone -v does show pro\n      \n      test_expect_success 'chooses correct default initial branch name' '\n     -\tgit init --bare empty &&\n    ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=foo init --bare empty &&\n     +\ttest_config -C empty lsrefs.allowUnborn true &&\n      \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n    @@ t/t5702-protocol-v2.sh: test_expect_success 'clone with file:// using protocol v\n      '\n      \n     +test_expect_success 'clone of empty repo propagates name of default branch' '\n    ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n     +\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n     +\n    ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n     +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n     +\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412862","messageId":"a66e50626ef9343e0351fc6cfe8abcf4f9eed7f3.1608673963.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608673963.git.jonathantanmy@google.com","subject":"[PATCH v4 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-22T21:54:18Z","receivedAt":"2020-12-22T21:55:22Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.allowunborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n ls-refs.c                               | 52 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n 6 files changed, 64 insertions(+), 6 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..dcbec11aaa\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,3 @@\n+lsrefs.allowUnborn::\n+\tAllow the server to send information about unborn symrefs during the\n+\tprotocol v2 ref advertisement.\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..4707511c10 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..ff61e704f1 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -32,6 +32,8 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned allow_unborn : 1;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -74,8 +79,30 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int ls_refs_config(const char *var, const char *value, void *data)\n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n {\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tmemset(&oid, 0, sizeof(oid));\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n+{\n+\tstruct ls_refs_data *data = cb_data;\n+\n+\tif (!strcmp(\"lsrefs.allowunborn\", var))\n+\t\tdata->allow_unborn = git_config_bool(var, value);\n+\n \t/*\n \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n \t * config. This may need to eventually be expanded to \"receive\", but we\n@@ -91,7 +118,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n-\tgit_config(ls_refs_config, NULL);\n+\tgit_config(ls_refs_config, &data);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n \t\tconst char *arg = request->line;\n@@ -103,14 +130,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = 1;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tint allow_unborn_value;\n+\n+\t\tif (!repo_config_get_bool(the_repository,\n+\t\t\t\t\t \"lsrefs.allowunborn\",\n+\t\t\t\t\t &allow_unborn_value) &&\n+\t\t    allow_unborn_value)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412863","messageId":"14f3962adc0ce03505fbef2c40434400c539c40d.1608673963.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608673963.git.jonathantanmy@google.com","subject":"[PATCH v4 2/3] connect, transport: add no-op arg for future patch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-22T21:54:19Z","receivedAt":"2020-12-22T21:55:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs(). Add the parameter to\nfunctions involved in the callchain, but no caller passes an actual\nargument yet in this step. Thus, the future patch only needs to concern\nitself with new logic.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n builtin/clone.c      |  2 +-\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      |  2 +-\n builtin/ls-remote.c  |  2 +-\n builtin/remote.c     |  2 +-\n connect.c            |  5 ++++-\n remote.h             |  3 ++-\n transport-helper.c   |  7 +++++--\n transport-internal.h | 13 +++++--------\n transport.c          | 29 ++++++++++++++++++-----------\n transport.h          |  7 ++++++-\n 11 files changed, 46 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a0841923cf..70f9450db4 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1264,7 +1264,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..9f921dfab4 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc, NULL);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..a7ef59acfc 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1393,7 +1393,7 @@ static int do_fetch(struct transport *transport,\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..4cf3f60b1b 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -118,7 +118,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/builtin/remote.c b/builtin/remote.c\nindex d11a5589e4..9a547240ab 100644\n--- a/builtin/remote.c\n+++ b/builtin/remote.c\n@@ -950,7 +950,7 @@ static int get_remote_ref_states(const char *name,\n \tif (query) {\n \t\ttransport = transport_get(states->remote, states->remote->url_nr > 0 ?\n \t\t\tstates->remote->url[0] : NULL);\n-\t\tremote_refs = transport_get_remote_refs(transport, NULL);\n+\t\tremote_refs = transport_get_remote_refs(transport, NULL, NULL);\n \t\ttransport_disconnect(transport);\n \n \t\tstates->queried = 1;\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..99d9052365 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc)\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target)\n {\n \tint i;\n \tconst char *hash_name;\n@@ -496,6 +497,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n+\t\tif (unborn_head_target)\n+\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n \t\tif (!process_ref_v2(reader, &list))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..967f2178d8 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -198,7 +198,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n \t\t\t     const struct string_list *server_options,\n-\t\t\t     int stateless_rpc);\n+\t\t\t     int stateless_rpc,\n+\t\t\t     char **unborn_head_target);\n \n int resolve_remote_symref(struct ref *ref, struct ref *list);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..5d97eba935 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,16 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t const struct strvec *ref_prefixes,\n+\t\t\t\t char **unborn_head_target)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\tref_prefixes,\n+\t\t\t\t\t\t\tunborn_head_target);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..5037f6197d 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -18,19 +18,16 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n+\t *\n+\t * See transport_get_remote_refs() for information on ref_prefixes and\n+\t * unborn_head_target.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t\t     char **unborn_head_target);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..396a601d78 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,8 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -163,7 +164,7 @@ static int fetch_refs_from_bundle(struct transport *transport,\n \tint ret;\n \n \tif (!data->get_refs_from_bundle_called)\n-\t\tget_refs_from_bundle(transport, 0, NULL);\n+\t\tget_refs_from_bundle(transport, 0, NULL, NULL);\n \tret = unbundle(the_repository, &data->header, data->fd,\n \t\t\t   transport->progress ? BUNDLE_VERBOSE : 0);\n \ttransport->hash_algo = data->header.hash_algo;\n@@ -281,7 +282,7 @@ static void die_if_server_options(struct transport *transport)\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\t     const struct strvec *ref_prefixes,\n-\t\t\t     int must_list_refs)\n+\t\t\t     int must_list_refs, char **unborn_head_target)\n {\n \tstruct git_transport_data *data = transport->data;\n \tstruct ref *refs = NULL;\n@@ -305,7 +306,8 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n \t\t\t\t\tref_prefixes,\n \t\t\t\t\ttransport->server_options,\n-\t\t\t\t\ttransport->stateless_rpc);\n+\t\t\t\t\ttransport->stateless_rpc,\n+\t\t\t\t\tunborn_head_target);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\n@@ -334,9 +336,11 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tconst struct strvec *ref_prefixes,\n+\t\t\t\t\tchar **unborn_head_target)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, ref_prefixes, 1,\n+\t\t\t unborn_head_target);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -380,7 +384,7 @@ static int fetch_refs_via_pack(struct transport *transport,\n \t\t\t\tbreak;\n \t\t\t}\n \t\t}\n-\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs);\n+\t\trefs_tmp = handshake(transport, 0, NULL, must_list_refs, NULL);\n \t}\n \n \tif (data->version == protocol_unknown_version)\n@@ -775,7 +779,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re\n \t\treturn -1;\n \n \tif (!data->got_remote_heads)\n-\t\tget_refs_via_connect(transport, 1, NULL);\n+\t\tget_refs_via_connect(transport, 1, NULL, NULL);\n \n \tmemset(&args, 0, sizeof(args));\n \targs.send_mirror = !!(flags & TRANSPORT_PUSH_MIRROR);\n@@ -1261,7 +1265,8 @@ int transport_push(struct repository *r,\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &ref_prefixes,\n+\t\t\t\t\t\t\t       NULL);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n \t\tstrvec_clear(&ref_prefixes);\n@@ -1380,12 +1385,14 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t ref_prefixes,\n+\t\t\t\t\t\t\t unborn_head_target);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..65de0c9c00 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -241,9 +241,14 @@ int transport_push(struct repository *repo,\n  * advertisement.  Since ref filtering is done on the server's end (and only\n  * when using protocol v2), this can return refs which don't match the provided\n  * ref_prefixes.\n+ *\n+ * If unborn_head_target is not NULL, and the remote reports HEAD as pointing\n+ * to an unborn branch, this function stores the unborn branch in\n+ * unborn_head_target. It should be freed by the caller.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    const struct strvec *ref_prefixes,\n+\t\t\t\t\t    char **unborn_head_target);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412864","messageId":"e770fc46eb3b600d8475d69964badaf2c63a7364.1608673963.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1608673963.git.jonathantanmy@google.com","subject":"[PATCH v4 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2020-12-22T21:54:20Z","receivedAt":"2020-12-22T21:55:23Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 19 ++++++++++++++++---\n connect.c                     | 28 ++++++++++++++++++++++++----\n t/t5606-clone-options.sh      |  8 +++++---\n t/t5702-protocol-v2.sh        | 11 +++++++++++\n 5 files changed, 57 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 70f9450db4..217c87fddf 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -980,6 +980,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint submodule_progress;\n \n \tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tchar *unborn_head_target = NULL;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1264,7 +1265,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tif (!option_no_tags)\n \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n+\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n+\t\t\t\t\t &unborn_head_target);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (unborn_head_target &&\n+\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n+\t\t\t\tref = unborn_head_target;\n+\t\t\t\tunborn_head_target = NULL;\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n \t\t\tfree(ref);\n \t\t}\n \t}\n@@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n+\tfree(unborn_head_target);\n \tstrvec_clear(&ref_prefixes);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 99d9052365..3c35324b4c 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -489,6 +509,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -497,9 +519,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (unborn_head_target)\n-\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..0111d4e8bd 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.allowUnborn true &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..ed8750fadd 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,17 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \n-- \n2.29.2.729.g45daf8777d-goog\n\n"},{"id":"412865","messageId":"xmqqwnx9fqbw.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"cover.1608673963.git.jonathantanmy@google.com","subject":"Re: [PATCH v4 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2020-12-22T22:06:43Z","receivedAt":"2020-12-22T22:07:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Thanks Junio for informing me of the test failures. Turns out that it\n> was partly because I didn't memset oid (and in some code paths, it gets\n> read without being written to), and partly because I didn't set\n> GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME. Here's an updated patch set with\n> the fixes.\n>\n> Jonathan Tan (3):\n>   ls-refs: report unborn targets of symrefs\n>   connect, transport: add no-op arg for future patch\n>   clone: respect remote unborn HEAD\n\nHaving to unset the GIT_TEST_* environment even when we have an\nexplicit \"git -c init.defaultBranch=<val>\" is a bit awkward and\nfrustrating, but hopefully this would futureproof the tests for\nthe current and future world ;-)\n\nWill replace.\n\n>\n>  Documentation/config.txt                |  2 +\n>  Documentation/config/init.txt           |  2 +-\n>  Documentation/config/lsrefs.txt         |  3 ++\n>  Documentation/technical/protocol-v2.txt | 10 ++++-\n>  builtin/clone.c                         | 19 +++++++--\n>  builtin/fetch-pack.c                    |  3 +-\n>  builtin/fetch.c                         |  2 +-\n>  builtin/ls-remote.c                     |  2 +-\n>  builtin/remote.c                        |  2 +-\n>  connect.c                               | 29 ++++++++++++--\n>  ls-refs.c                               | 52 +++++++++++++++++++++++--\n>  ls-refs.h                               |  1 +\n>  remote.h                                |  3 +-\n>  serve.c                                 |  2 +-\n>  t/t5606-clone-options.sh                |  8 ++--\n>  t/t5702-protocol-v2.sh                  | 11 ++++++\n>  transport-helper.c                      |  7 +++-\n>  transport-internal.h                    | 13 +++----\n>  transport.c                             | 29 ++++++++------\n>  transport.h                             |  7 +++-\n>  20 files changed, 164 insertions(+), 43 deletions(-)\n>  create mode 100644 Documentation/config/lsrefs.txt\n>\n> Range-diff against v3:\n> 1:  7d20ec323a ! 1:  a66e50626e ls-refs: report unborn targets of symrefs\n>     @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n>      +\tint flag;\n>      +\tint oid_is_null;\n>      +\n>     ++\tmemset(&oid, 0, sizeof(oid));\n>      +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n>      +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n>      +\toid_is_null = is_null_oid(&oid);\n> 2:  b5a78857eb = 2:  14f3962adc connect, transport: add no-op arg for future patch\n> 3:  c2303dc976 ! 3:  e770fc46eb clone: respect remote unborn HEAD\n>     @@ t/t5606-clone-options.sh: test_expect_success 'redirected clone -v does show pro\n>       \n>       test_expect_success 'chooses correct default initial branch name' '\n>      -\tgit init --bare empty &&\n>     ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n>      +\tgit -c init.defaultBranch=foo init --bare empty &&\n>      +\ttest_config -C empty lsrefs.allowUnborn true &&\n>       \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n>     @@ t/t5702-protocol-v2.sh: test_expect_success 'clone with file:// using protocol v\n>       '\n>       \n>      +test_expect_success 'clone of empty repo propagates name of default branch' '\n>     ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n>      +\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n>      +\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n>      +\n>     ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n>      +\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n>      +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n>      +\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n"},{"id":"414919","messageId":"YAngjWaKvEukZB0p@coredump.intra.peff.net","threadId":"54790","inReplyTo":"cover.1608587839.git.jonathantanmy@google.com","subject":"Re: [PATCH v3 0/3] Cloning with remote unborn HEAD","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-21T20:14:05Z","receivedAt":"2021-01-21T20:15:23Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Dec 21, 2020 at 02:30:58PM -0800, Jonathan Tan wrote:\n\n> > So I dunno. My biggest complaint is that the config option defaults to\n> > _off_.  So it's helping load-balanced rollouts, but creating complexity\n> > for everyone else who might want to enable the feature.\n> \n>    So it seems like you're saying that it should default to \"on\", but at\n>    the same time you are talking about enabling the feature (which seems\n>    to imply switching it from \"off\" to \"on\"). (Also, note that this is a\n>    server-side thing; on the client-side, Git will always use what the\n>    server gives and there is no option to control this.)\n\nSorry, I missed this question over the holidays. Yes, what I meant is\nthat everyone should really want this feature on, because it gives\nstrictly more information and lets the client be smarter.\n\nBut if it defaults to \"off\", server operators may well not bother to\nturn it on (or even know it exists). And the clients who would benefit\nmay have trouble convincing server operators to do so.\n\nSo I would strongly prefer it default to \"on\", and the onus be on server\noperators with non-atomic clusters, etc, to turn it off when deploying\nin their environments.\n\n-Peff\n"},{"id":"414922","messageId":"YAnotHAiuSz4Du/0@coredump.intra.peff.net","threadId":"54790","inReplyTo":"a66e50626ef9343e0351fc6cfe8abcf4f9eed7f3.1608673963.git.jonathantanmy@google.com","subject":"Re: [PATCH v4 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-21T20:48:52Z","receivedAt":"2021-01-21T20:50:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 22, 2020 at 01:54:18PM -0800, Jonathan Tan wrote:\n\n> -static int ls_refs_config(const char *var, const char *value, void *data)\n> +static void send_possibly_unborn_head(struct ls_refs_data *data)\n>  {\n> +\tstruct strbuf namespaced = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tint flag;\n> +\tint oid_is_null;\n> +\n> +\tmemset(&oid, 0, sizeof(oid));\n> +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n\nIt feels weird to call resolve_ref_unsafe() without checking the return\nvalue. How do we detect errors?\n\nI think the logic is that we make assumptions about which fields it will\ntouch (i.e., zeroing the flags, and not touching our zero'd oid), and\nthen check those. That feels a bit non-obvious and intimate with the\nimplementation, though (and was presumably the source of the \"oops, we\nneed to clear the oid bug between v3 and v4).\n\nI feel like that deserves a comment, but I also wonder if:\n\n  refname = resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n  if (!refname)\n\treturn; /* broken, bad name, not even a symref, etc */\n\n  /*\n   * now we can look at oid even if we didn't memset() it, because\n   * a successful return from resolve_ref_unsafe() means that it\n   * has cleared it if appropriate\n   */\n  oid_is_null = is_null_oid(&oid);\n  ...etc...\n\n> +\tif (!oid_is_null ||\n> +\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n> +\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n\nIt likewise feels a bit funny that we determine the symref name in the\nearlier call to resolve_ref_unsafe(), but we do not pass it here (and in\nfact, we'll end up looking it up again!).\n\nBut that is not much different than what we do for normal refs passed to\nthe send_ref() callback. It would be nice if the iteration could pass in\n\"by the way, here is the symref value\" to avoid that. But in practice it\nisn't a big deal, since we only do the lookup when we see the ISSYMREF\nflag set. So typically it is only one or two extra ref resolutions.\n\n> @@ -91,7 +118,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \n>  \tmemset(&data, 0, sizeof(data));\n>  \n> -\tgit_config(ls_refs_config, NULL);\n> +\tgit_config(ls_refs_config, &data);\n\nYou will probably not be surprised that I would suggest defaulting\ndata->allow_unborn to 1 before this config call. :)\n\n> @@ -103,14 +130,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \t\t\tdata.symrefs = 1;\n>  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n>  \t\t\tstrvec_push(&data.prefixes, out);\n> +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> +\t\t\tdata.unborn = 1;\n>  \t}\n\nSo if we have not set allow_unborn, we will not accept the client saying\n\"unborn\". Which makes sense, because we would not have advertised it in\nthat case.\n\nBut we use the same boolean for advertising, too. So this loses the\n\"allow us to accept it, but not advertise it\" logic that your earlier\nversions had, doesn't it? And that is the important element for making\nthings work across a non-atomic deploy of versions.\n\nThis straight-boolean version works as long as you can atomically update\nthe _config_ on each version. But that seems like roughly the same\nproblem (having dealt with this on GitHub servers, they are not\nequivalent, and depending on your infrastructure, it definitely _can_ be\neasier to do one versus the other. But it seems like a funny place to\nleave this upstream feature).\n\nOr is the intent that an unconfigured reader would silently ignore the\nunborn flag in that case? That would at least not cause it to bail on\nthe client in a mixed-version environment. But it does feel like a\nconfusing result.\n\n-Peff\n"},{"id":"414923","messageId":"YAnqQZaVByUYYRNr@coredump.intra.peff.net","threadId":"54790","inReplyTo":"14f3962adc0ce03505fbef2c40434400c539c40d.1608673963.git.jonathantanmy@google.com","subject":"Re: [PATCH v4 2/3] connect, transport: add no-op arg for future patch","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-21T20:55:29Z","receivedAt":"2021-01-21T20:57:41Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 22, 2020 at 01:54:19PM -0800, Jonathan Tan wrote:\n\n> In a future patch we plan to return the name of an unborn current branch\n> from deep in the callchain to a caller via a new pointer parameter that\n> points at a variable in the caller when the caller calls\n> get_remote_refs() and transport_get_remote_refs(). Add the parameter to\n> functions involved in the callchain, but no caller passes an actual\n> argument yet in this step. Thus, the future patch only needs to concern\n> itself with new logic.\n\nOK. Since the call stack is so deep, it's nice to get all of this diff\nnoise out of the way of the third patch.\n\nIt does make me wonder if we should be passing a struct like:\n\n  struct transport_fetch_options {\n\tstruct strvec ref_prefixes;\n\tchar **unborn_head;\n  }\n  #define TRANSPORT_FETCH_OPTIONS_INIT = { STRVEC_INIT }\n\nwhich would solve this problem once for any future options.\n\n> @@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \t\t\t     struct ref **list, int for_push,\n>  \t\t\t     const struct strvec *ref_prefixes,\n>  \t\t\t     const struct string_list *server_options,\n> -\t\t\t     int stateless_rpc)\n> +\t\t\t     int stateless_rpc,\n> +\t\t\t     char **unborn_head_target)\n\nIs a single string enough? The way the protocol is defined, I think the\nserver is free to tell us about other unborn symrefs, too (but of course\nour implementation does not). And I'm not sure what we'd do with such\nvalues (in a \"--mirror\" clone, I guess we could make local copies of\nthem).\n\nShould we be prepared for that at the transport layer, or is it\nover-engineering?\n\n-Peff\n"},{"id":"414925","messageId":"YAnr8lBESOO+ACL/@coredump.intra.peff.net","threadId":"54790","inReplyTo":"e770fc46eb3b600d8475d69964badaf2c63a7364.1608673963.git.jonathantanmy@google.com","subject":"Re: [PATCH v4 3/3] clone: respect remote unborn HEAD","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-21T21:02:42Z","receivedAt":"2021-01-21T21:06:29Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Dec 22, 2020 at 01:54:20PM -0800, Jonathan Tan wrote:\n\n> @@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \t\tremote_head = NULL;\n>  \t\toption_no_checkout = 1;\n>  \t\tif (!option_bare) {\n> -\t\t\tconst char *branch = git_default_branch_name();\n> -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\tconst char *branch;\n> +\t\t\tchar *ref;\n> +\n> +\t\t\tif (unborn_head_target &&\n> +\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n> +\t\t\t\tref = unborn_head_target;\n> +\t\t\t\tunborn_head_target = NULL;\n> +\t\t\t} else {\n> +\t\t\t\tbranch = git_default_branch_name();\n> +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\t}\n>  \n>  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n>  \t\t\tfree(ref);\n>  \t\t}\n\nIn the old code, we never called create_symref() at all. It makes sense\nthat we'd do it now when unborn_head_target is not NULL. But what about\nin the \"else\" clause there? Now we're adding an extra create_symref()\ncall. Who was setting up the HEAD symref before, and are we now doing it\ntwice?\n\nIf we have a valid unborn head, then we alias it to \"ref\" and we set the\noriginal to NULL. And it gets cleaned up here via free(ref). Makes\nsense. It confused me for a moment with this hunk...\n\n> @@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tstrbuf_release(&key);\n>  \tjunk_mode = JUNK_LEAVE_ALL;\n>  \n> +\tfree(unborn_head_target);\n\n...since this line will almost always be free(NULL) as a result (either\nthere was no unborn head, or we consumed the string already). But it is\ncovering the case that somebody gave us an unborn_head_target but it\ndidn't start with refs/heads/. So it's useful to have.\n\n-Peff\n"},{"id":"415332","messageId":"20210126181358.2333028-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"YAnotHAiuSz4Du/0@coredump.intra.peff.net","subject":"Re: [PATCH v4 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:13:58Z","receivedAt":"2021-01-26T22:08:56Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Tue, Dec 22, 2020 at 01:54:18PM -0800, Jonathan Tan wrote:\n> \n> > -static int ls_refs_config(const char *var, const char *value, void *data)\n> > +static void send_possibly_unborn_head(struct ls_refs_data *data)\n> >  {\n> > +\tstruct strbuf namespaced = STRBUF_INIT;\n> > +\tstruct object_id oid;\n> > +\tint flag;\n> > +\tint oid_is_null;\n> > +\n> > +\tmemset(&oid, 0, sizeof(oid));\n> > +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> > +\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n> \n> It feels weird to call resolve_ref_unsafe() without checking the return\n> value. How do we detect errors?\n> \n> I think the logic is that we make assumptions about which fields it will\n> touch (i.e., zeroing the flags, and not touching our zero'd oid), and\n> then check those. That feels a bit non-obvious and intimate with the\n> implementation, though (and was presumably the source of the \"oops, we\n> need to clear the oid bug between v3 and v4).\n> \n> I feel like that deserves a comment, but I also wonder if:\n> \n>   refname = resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n>   if (!refname)\n> \treturn; /* broken, bad name, not even a symref, etc */\n\nFrom my reading of this part of refs_resolve_ref_unsafe():\n\n                if (!(read_flags & REF_ISSYMREF)) {\n                        if (*flags & REF_BAD_NAME) {\n                                oidclr(oid);\n                                *flags |= REF_ISBROKEN;\n                        }\n                        return refname;\n                }\n\nit seems that resolve_ref_unsafe() returns non-NULL if the ref is not a\nsymref but is otherwise valid. But this is exactly what we want -\nsend_possibly_unborn_head() must send HEAD in this situation anyway.\nThanks - I've switched to checking the return value.\n\n(It was a bit confusing that refs_resolve_ref_unsafe() returns one of\nits input arguments if it succeeds and NULL if it fails, but that's\noutside the scope of this patch, I think.)\n\n> > +\tif (!oid_is_null ||\n> > +\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n> > +\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n> \n> It likewise feels a bit funny that we determine the symref name in the\n> earlier call to resolve_ref_unsafe(), but we do not pass it here (and in\n> fact, we'll end up looking it up again!).\n> \n> But that is not much different than what we do for normal refs passed to\n> the send_ref() callback. It would be nice if the iteration could pass in\n> \"by the way, here is the symref value\" to avoid that.\n\nYes, that would be nice.\n\n> But in practice it\n> isn't a big deal, since we only do the lookup when we see the ISSYMREF\n> flag set. So typically it is only one or two extra ref resolutions.\n\nOK.\n\n> > @@ -91,7 +118,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \n> >  \tmemset(&data, 0, sizeof(data));\n> >  \n> > -\tgit_config(ls_refs_config, NULL);\n> > +\tgit_config(ls_refs_config, &data);\n> \n> You will probably not be surprised that I would suggest defaulting\n> data->allow_unborn to 1 before this config call. :)\n\nI don't think many people have made comments either way, so I'll go\nahead with defaulting it to true. I can see arguments for both sides.\n\n> > @@ -103,14 +130,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \t\t\tdata.symrefs = 1;\n> >  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n> >  \t\t\tstrvec_push(&data.prefixes, out);\n> > +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> > +\t\t\tdata.unborn = 1;\n> >  \t}\n> \n> So if we have not set allow_unborn, we will not accept the client saying\n> \"unborn\". Which makes sense, because we would not have advertised it in\n> that case.\n> \n> But we use the same boolean for advertising, too. So this loses the\n> \"allow us to accept it, but not advertise it\" logic that your earlier\n> versions had, doesn't it?\n\nYes, it does.\n\n> And that is the important element for making\n> things work across a non-atomic deploy of versions.\n> \n> This straight-boolean version works as long as you can atomically update\n> the _config_ on each version. But that seems like roughly the same\n> problem (having dealt with this on GitHub servers, they are not\n> equivalent, and depending on your infrastructure, it definitely _can_ be\n> easier to do one versus the other. But it seems like a funny place to\n> leave this upstream feature).\n\nWell, I was just agreeing with what you said [1]. :-)\n\n[1] https://lore.kernel.org/git/X9xJLWdFJfNJTn0p@coredump.intra.peff.net/\n\n> Or is the intent that an unconfigured reader would silently ignore the\n> unborn flag in that case? That would at least not cause it to bail on\n> the client in a mixed-version environment. But it does feel like a\n> confusing result.\n\nRight now, an old server would ignore \"unborn\", yes. I'm not sure of\nwhat the intent should be - tightening ls-refs and fetch to forbid\nunknown arguments seems like a good idea to me.\n"},{"id":"415333","messageId":"20210126181606.2335396-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"YAnqQZaVByUYYRNr@coredump.intra.peff.net","subject":"Re: [PATCH v4 2/3] connect, transport: add no-op arg for future patch","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:16:06Z","receivedAt":"2021-01-26T22:08:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Tue, Dec 22, 2020 at 01:54:19PM -0800, Jonathan Tan wrote:\n> \n> > In a future patch we plan to return the name of an unborn current branch\n> > from deep in the callchain to a caller via a new pointer parameter that\n> > points at a variable in the caller when the caller calls\n> > get_remote_refs() and transport_get_remote_refs(). Add the parameter to\n> > functions involved in the callchain, but no caller passes an actual\n> > argument yet in this step. Thus, the future patch only needs to concern\n> > itself with new logic.\n> \n> OK. Since the call stack is so deep, it's nice to get all of this diff\n> noise out of the way of the third patch.\n> \n> It does make me wonder if we should be passing a struct like:\n> \n>   struct transport_fetch_options {\n> \tstruct strvec ref_prefixes;\n> \tchar **unborn_head;\n>   }\n>   #define TRANSPORT_FETCH_OPTIONS_INIT = { STRVEC_INIT }\n> \n> which would solve this problem once for any future options.\n\nThat's a good idea, and I've switched patch 2 to doing this. It also\nmakes it easier to explain (no \"unborn_head\" dummy variable that does\nnothing, since I can just introduce \"unborn_head\" in patch 3).\n\n> > @@ -455,7 +455,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n> >  \t\t\t     struct ref **list, int for_push,\n> >  \t\t\t     const struct strvec *ref_prefixes,\n> >  \t\t\t     const struct string_list *server_options,\n> > -\t\t\t     int stateless_rpc)\n> > +\t\t\t     int stateless_rpc,\n> > +\t\t\t     char **unborn_head_target)\n> \n> Is a single string enough? The way the protocol is defined, I think the\n> server is free to tell us about other unborn symrefs, too (but of course\n> our implementation does not). And I'm not sure what we'd do with such\n> values (in a \"--mirror\" clone, I guess we could make local copies of\n> them).\n> \n> Should we be prepared for that at the transport layer, or is it\n> over-engineering?\n\nWith the struct, I think we're prepared for it.\n"},{"id":"415335","messageId":"20210126182212.2337587-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"YAnr8lBESOO+ACL/@coredump.intra.peff.net","subject":"Re: [PATCH v4 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:22:12Z","receivedAt":"2021-01-26T22:08:57Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Tue, Dec 22, 2020 at 01:54:20PM -0800, Jonathan Tan wrote:\n> \n> > @@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n> >  \t\tremote_head = NULL;\n> >  \t\toption_no_checkout = 1;\n> >  \t\tif (!option_bare) {\n> > -\t\t\tconst char *branch = git_default_branch_name();\n> > -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> > +\t\t\tconst char *branch;\n> > +\t\t\tchar *ref;\n> > +\n> > +\t\t\tif (unborn_head_target &&\n> > +\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n> > +\t\t\t\tref = unborn_head_target;\n> > +\t\t\t\tunborn_head_target = NULL;\n> > +\t\t\t} else {\n> > +\t\t\t\tbranch = git_default_branch_name();\n> > +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> > +\t\t\t}\n> >  \n> >  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> > +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n> >  \t\t\tfree(ref);\n> >  \t\t}\n> \n> In the old code, we never called create_symref() at all. It makes sense\n> that we'd do it now when unborn_head_target is not NULL. But what about\n> in the \"else\" clause there? Now we're adding an extra create_symref()\n> call.\n\nThe \"else\" branch you're referring to is the one enclosing all of the\nlines quoted above, I believe?\n\n> Who was setting up the HEAD symref before, and are we now doing it\n> twice?\n\nIt was init_db(). Yes, we are now setting it once in init_db() and\nsetting it again, but this is the same as in the regular clone (as can\nbe seen by the invocation of update_head() that sets HEAD in some\nsituations).\n\n> If we have a valid unborn head, then we alias it to \"ref\" and we set the\n> original to NULL. And it gets cleaned up here via free(ref). Makes\n> sense. It confused me for a moment with this hunk...\n> \n> > @@ -1373,6 +1385,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n> >  \tstrbuf_release(&key);\n> >  \tjunk_mode = JUNK_LEAVE_ALL;\n> >  \n> > +\tfree(unborn_head_target);\n> \n> ...since this line will almost always be free(NULL) as a result (either\n> there was no unborn head, or we consumed the string already). But it is\n> covering the case that somebody gave us an unborn_head_target but it\n> didn't start with refs/heads/. So it's useful to have.\n\nYes.\n\nThanks for your review.\n"},{"id":"415343","messageId":"cover.1611686656.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"[PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:55:54Z","receivedAt":"2021-01-26T22:19:51Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Thanks, Peff, for your review. I have addressed your comments (through\nreplies to your emails and here in this v5 patch set).\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: encapsulate arg in struct\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 34 +++++++++++-----\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         | 18 +++++----\n builtin/ls-remote.c                     |  9 +++--\n connect.c                               | 32 +++++++++++++--\n ls-refs.c                               | 53 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  4 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  8 ++--\n t/t5701-git-serve.sh                    |  2 +-\n t/t5702-protocol-v2.sh                  | 25 ++++++++++++\n transport-helper.c                      |  5 ++-\n transport-internal.h                    |  9 +----\n transport.c                             | 23 ++++++-----\n transport.h                             | 29 ++++++++++----\n 20 files changed, 210 insertions(+), 64 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v4:\n1:  d7d2ba597e ! 1:  32e16dfdbd ls-refs: report unborn targets of symrefs\n    @@ Commit message\n     \n         Currently, symrefs that have unborn targets (such as in this case) are\n         not communicated by the protocol. Teach Git to advertise and support the\n    -    \"unborn\" feature in \"ls-refs\" (guarded by the lsrefs.allowunborn\n    +    \"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\n    +    server administrators may turn this off through the lsrefs.allowunborn\n         config). This feature indicates that \"ls-refs\" supports the \"unborn\"\n         argument; when it is specified, \"ls-refs\" will send the HEAD symref with\n         the name of its unborn target.\n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n     +\tint flag;\n     +\tint oid_is_null;\n     +\n    -+\tmemset(&oid, 0, sizeof(oid));\n     +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n    -+\tresolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);\n    ++\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n    ++\t\treturn; /* bad ref */\n     +\toid_is_null = is_null_oid(&oid);\n     +\tif (!oid_is_null ||\n     +\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n      \tmemset(&data, 0, sizeof(data));\n      \n     -\tgit_config(ls_refs_config, NULL);\n    ++\tdata.allow_unborn = 1;\n     +\tgit_config(ls_refs_config, &data);\n      \n      \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n     +\tif (value) {\n     +\t\tint allow_unborn_value;\n     +\n    -+\t\tif (!repo_config_get_bool(the_repository,\n    ++\t\tif (repo_config_get_bool(the_repository,\n     +\t\t\t\t\t \"lsrefs.allowunborn\",\n    -+\t\t\t\t\t &allow_unborn_value) &&\n    ++\t\t\t\t\t &allow_unborn_value) ||\n     +\t\t    allow_unborn_value)\n     +\t\t\tstrbuf_addstr(value, \"unborn\");\n     +\t}\n    @@ serve.c: struct protocol_capability {\n      \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n      \t{ \"server-option\", always_advertise, NULL },\n      \t{ \"object-format\", object_format_advertise, NULL },\n    +\n    + ## t/t5701-git-serve.sh ##\n    +@@ t/t5701-git-serve.sh: test_expect_success 'test capability advertisement' '\n    + \tcat >expect <<-EOF &&\n    + \tversion 2\n    + \tagent=git/$(git version | cut -d\" \" -f3)\n    +-\tls-refs\n    ++\tls-refs=unborn\n    + \tfetch=shallow\n    + \tserver-option\n    + \tobject-format=$(test_oid algo)\n2:  51d8a359c7 < -:  ---------- connect, transport: add no-op arg for future patch\n-:  ---------- > 2:  4eec551668 connect, transport: encapsulate arg in struct\n3:  896be550f1 ! 3:  922e8c229c clone: respect remote unborn HEAD\n    @@ Documentation/config/init.txt: init.templateDir::\n     +\ta new repository.\n     \n      ## builtin/clone.c ##\n    -@@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n    - \tint submodule_progress;\n    - \n    - \tstruct strvec ref_prefixes = STRVEC_INIT;\n    -+\tchar *unborn_head_target = NULL;\n    - \n    - \tpacket_trace_identity(\"clone\");\n    - \n    -@@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n    - \tif (!option_no_tags)\n    - \t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n    - \n    --\trefs = transport_get_remote_refs(transport, &ref_prefixes, NULL);\n    -+\trefs = transport_get_remote_refs(transport, &ref_prefixes,\n    -+\t\t\t\t\t &unborn_head_target);\n    - \n    - \tif (refs) {\n    - \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n     @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n      \t\tremote_head = NULL;\n      \t\toption_no_checkout = 1;\n    @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n     +\t\t\tconst char *branch;\n     +\t\t\tchar *ref;\n     +\n    -+\t\t\tif (unborn_head_target &&\n    -+\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n    -+\t\t\t\tref = unborn_head_target;\n    -+\t\t\t\tunborn_head_target = NULL;\n    ++\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n    ++\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n    ++\t\t\t\t\t\"refs/heads/\", &branch)) {\n    ++\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n    ++\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n     +\t\t\t} else {\n     +\t\t\t\tbranch = git_default_branch_name();\n     +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n    @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n      \t\t}\n      \t}\n     @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n    - \tstrbuf_release(&key);\n      \tjunk_mode = JUNK_LEAVE_ALL;\n      \n    -+\tfree(unborn_head_target);\n    - \tstrvec_clear(&ref_prefixes);\n    + \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n    ++\tfree(transport_ls_refs_options.unborn_head_target);\n      \treturn err;\n      }\n     \n    @@ connect.c: static int process_ref_v2(struct packet_reader *reader, struct ref **\n      \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n      \t    *end) {\n      \t\tret = 0;\n    +@@ connect.c: struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n    + \tconst char *hash_name;\n    + \tstruct strvec *ref_prefixes = transport_options ?\n    + \t\t&transport_options->ref_prefixes : NULL;\n    ++\tchar **unborn_head_target = transport_options ?\n    ++\t\t&transport_options->unborn_head_target : NULL;\n    + \t*list = NULL;\n    + \n    + \tif (server_supports_v2(\"ls-refs\", 1))\n     @@ connect.c: struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n      \tif (!for_push)\n      \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n    @@ connect.c: struct ref **get_remote_refs(int fd_out, struct packet_reader *reader\n      \n      \t/* Process response from server */\n      \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n    --\t\tif (unborn_head_target)\n    --\t\t\tBUG(\"NEEDSWORK: provide unborn HEAD target to caller while reading refs\");\n     -\t\tif (!process_ref_v2(reader, &list))\n     +\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n      \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n    @@ t/t5702-protocol-v2.sh: test_expect_success 'clone with file:// using protocol v\n      '\n      \n     +test_expect_success 'clone of empty repo propagates name of default branch' '\n    ++\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n    ++\n     +\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n    -+\ttest_config -C file_empty_parent lsrefs.allowUnborn true &&\n     +\n     +\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n     +\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n     +\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n     +'\n    ++\n    ++test_expect_success '...but not if explicitly forbidden by config' '\n    ++\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n    ++\n    ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n    ++\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n    ++\ttest_config -C file_empty_parent lsrefs.allowUnborn false &&\n    ++\n    ++\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n    ++\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n    ++\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n    ++\t! grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n    ++'\n     +\n      test_expect_success 'fetch with file:// using protocol v2' '\n      \ttest_when_finished \"rm -f log\" &&\n      \n    +\n    + ## transport.h ##\n    +@@ transport.h: struct transport_ls_refs_options {\n    + \t * provided ref_prefixes.\n    + \t */\n    + \tstruct strvec ref_prefixes;\n    ++\n    ++\t/*\n    ++\t * If unborn_head_target is not NULL, and the remote reports HEAD as\n    ++\t * pointing to an unborn branch, transport_get_remote_refs() stores the\n    ++\t * unborn branch in unborn_head_target. It should be freed by the\n    ++\t * caller.\n    ++\t */\n    ++\tchar *unborn_head_target;\n    + };\n    + #define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n    + \n-- \n2.30.0.280.ga3ce27912f-goog\n\n"},{"id":"415344","messageId":"32e16dfdbd3f54c0d4b39cbd555e66aa3950fffd.1611686656.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1611686656.git.jonathantanmy@google.com","subject":"[PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:55:55Z","receivedAt":"2021-01-26T22:19:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\nserver administrators may turn this off through the lsrefs.allowunborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n ls-refs.c                               | 53 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n t/t5701-git-serve.sh                    |  2 +-\n 7 files changed, 66 insertions(+), 7 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..dcbec11aaa\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,3 @@\n+lsrefs.allowUnborn::\n+\tAllow the server to send information about unborn symrefs during the\n+\tprotocol v2 ref advertisement.\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..4707511c10 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..4077adeb6a 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -32,6 +32,8 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned allow_unborn : 1;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -74,8 +79,30 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n-static int ls_refs_config(const char *var, const char *value, void *data)\n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n {\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n+\t\treturn; /* bad ref */\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n+{\n+\tstruct ls_refs_data *data = cb_data;\n+\n+\tif (!strcmp(\"lsrefs.allowunborn\", var))\n+\t\tdata->allow_unborn = git_config_bool(var, value);\n+\n \t/*\n \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n \t * config. This may need to eventually be expanded to \"receive\", but we\n@@ -91,7 +118,8 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n-\tgit_config(ls_refs_config, NULL);\n+\tdata.allow_unborn = 1;\n+\tgit_config(ls_refs_config, &data);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n \t\tconst char *arg = request->line;\n@@ -103,14 +131,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = 1;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tint allow_unborn_value;\n+\n+\t\tif (repo_config_get_bool(the_repository,\n+\t\t\t\t\t \"lsrefs.allowunborn\",\n+\t\t\t\t\t &allow_unborn_value) ||\n+\t\t    allow_unborn_value)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex a1f5fdc9fd..df29504161 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n \tcat >expect <<-EOF &&\n \tversion 2\n \tagent=git/$(git version | cut -d\" \" -f3)\n-\tls-refs\n+\tls-refs=unborn\n \tfetch=shallow\n \tserver-option\n \tobject-format=$(test_oid algo)\n-- \n2.30.0.280.ga3ce27912f-goog\n\n"},{"id":"415345","messageId":"4eec55166889f16e41b10ae65002fd2c821b6762.1611686656.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1611686656.git.jonathantanmy@google.com","subject":"[PATCH v5 2/3] connect, transport: encapsulate arg in struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:55:56Z","receivedAt":"2021-01-26T22:19:54Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs().\n\nIn preparation for that, encapsulate the existing ref_prefixes\nparameter into a struct. The aforementioned unborn current branch will\ngo into this new struct in the future patch.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clone.c      | 18 +++++++++++-------\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      | 18 +++++++++++-------\n builtin/ls-remote.c  |  9 +++++----\n connect.c            |  4 +++-\n remote.h             |  4 +++-\n transport-helper.c   |  5 +++--\n transport-internal.h |  9 +--------\n transport.c          | 23 ++++++++++++-----------\n transport.h          | 21 ++++++++++++++-------\n 10 files changed, 65 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a5630337e4..211d4f54b0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -979,7 +979,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint err = 0, complete_refs_before_fetch = 1;\n \tint submodule_progress;\n \n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1257,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport->smart_options->check_self_contained_and_connected = 1;\n \n \n-\tstrvec_push(&ref_prefixes, \"HEAD\");\n-\trefspec_ref_prefixes(&remote->fetch, &ref_prefixes);\n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\trefspec_ref_prefixes(&remote->fetch,\n+\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \tif (option_branch)\n-\t\texpand_ref_prefix(&ref_prefixes, option_branch);\n+\t\texpand_ref_prefix(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t  option_branch);\n \tif (!option_no_tags)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t    \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &transport_ls_refs_options);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1380,6 +1384,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \treturn err;\n }\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..c2d96f4c89 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..837382ef4f 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1349,7 +1349,8 @@ static int do_fetch(struct transport *transport,\n \tint autotags = (transport->remote->fetch_tags == 1);\n \tint retcode = 0;\n \tconst struct ref *remote_refs;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint must_list_refs = 1;\n \n \tif (tags == TAGS_DEFAULT) {\n@@ -1369,7 +1370,7 @@ static int do_fetch(struct transport *transport,\n \tif (rs->nr) {\n \t\tint i;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);\n \n \t\t/*\n \t\t * We can avoid listing refs if all of them are exact\n@@ -1383,22 +1384,25 @@ static int do_fetch(struct transport *transport,\n \t\t\t}\n \t\t}\n \t} else if (transport->remote && transport->remote->fetch.nr)\n-\t\trefspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);\n+\t\trefspec_ref_prefixes(&transport->remote->fetch,\n+\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \n \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n \t\tmust_list_refs = 1;\n-\t\tif (ref_prefixes.nr)\n-\t\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n+\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t    \"refs/tags/\");\n \t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport,\n+\t\t\t\t\t\t\t&transport_ls_refs_options);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \n \tref_map = get_ref_map(transport->remote, remote_refs, rs,\n \t\t\t      tags, &autotags);\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..ef604752a0 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tint show_symref_target = 0;\n \tconst char *uploadpack = NULL;\n \tconst char **pattern = NULL;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint i;\n \tstruct string_list server_options = STRING_LIST_INIT_DUP;\n \n@@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (flags & REF_TAGS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/tags/\");\n \tif (flags & REF_HEADS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/heads/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/heads/\");\n \n \tremote = remote_get(dest);\n \tif (!remote) {\n@@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &transport_options);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..328c279250 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -453,12 +453,14 @@ void check_stateless_delimiter(int stateless_rpc,\n \n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc)\n {\n \tint i;\n \tconst char *hash_name;\n+\tstruct strvec *ref_prefixes = transport_options ?\n+\t\t&transport_options->ref_prefixes : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..4ae676a11b 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -6,6 +6,8 @@\n #include \"hashmap.h\"\n #include \"refspec.h\"\n \n+struct transport_ls_refs_options;\n+\n /**\n  * The API gives access to the configuration related to remotes. It handles\n  * all three configuration mechanisms historically and currently used by Git,\n@@ -196,7 +198,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n /* Used for protocol v2 in order to retrieve refs from a remote */\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..49b7fb4dcb 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,14 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t struct transport_ls_refs_options *transport_options)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\ttransport_options);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..348daad3e4 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -18,19 +18,12 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     struct transport_ls_refs_options *transport_options);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..b13fab5dc3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,7 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *transport_options)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -280,7 +280,7 @@ static void die_if_server_options(struct transport *transport)\n  * remote refs.\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *options,\n \t\t\t     int must_list_refs)\n {\n \tstruct git_transport_data *data = transport->data;\n@@ -303,7 +303,7 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\ttrace2_data_string(\"transfer\", NULL, \"server-sid\", server_sid);\n \t\tif (must_list_refs)\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n-\t\t\t\t\tref_prefixes,\n+\t\t\t\t\toptions,\n \t\t\t\t\ttransport->server_options,\n \t\t\t\t\ttransport->stateless_rpc);\n \t\tbreak;\n@@ -334,9 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, options, 1);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -1252,19 +1252,20 @@ int transport_push(struct repository *r,\n \t\tint porcelain = flags & TRANSPORT_PUSH_PORCELAIN;\n \t\tint pretend = flags & TRANSPORT_PUSH_DRY_RUN;\n \t\tint push_ret, ret, err;\n-\t\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\t\tstruct transport_ls_refs_options transport_options =\n+\t\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \t\tif (check_push_refs(local_refs, rs) < 0)\n \t\t\treturn -1;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_options.ref_prefixes);\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &transport_options);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n-\t\tstrvec_clear(&ref_prefixes);\n+\t\tstrvec_clear(&transport_options.ref_prefixes);\n \n \t\tif (flags & TRANSPORT_PUSH_ALL)\n \t\t\tmatch_flags |= MATCH_REFS_ALL;\n@@ -1380,12 +1381,12 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t transport_options);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..1f5b60e4d3 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n \t\t   struct refspec *rs, int flags,\n \t\t   unsigned int * reject_reasons);\n \n+struct transport_ls_refs_options {\n+\t/*\n+\t * Optionally, a list of ref prefixes can be provided which can be sent\n+\t * to the server (when communicating using protocol v2) to enable it to\n+\t * limit the ref advertisement.  Since ref filtering is done on the\n+\t * server's end (and only when using protocol v2),\n+\t * transport_get_remote_refs() could return refs which don't match the\n+\t * provided ref_prefixes.\n+\t */\n+\tstruct strvec ref_prefixes;\n+};\n+#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n+\n /*\n  * Retrieve refs from a remote.\n- *\n- * Optionally a list of ref prefixes can be provided which can be sent to the\n- * server (when communicating using protocol v2) to enable it to limit the ref\n- * advertisement.  Since ref filtering is done on the server's end (and only\n- * when using protocol v2), this can return refs which don't match the provided\n- * ref_prefixes.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.30.0.280.ga3ce27912f-goog\n\n"},{"id":"415354","messageId":"922e8c229c359c15f1265876e6def87d7a18b763.1611686656.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1611686656.git.jonathantanmy@google.com","subject":"[PATCH v5 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-26T18:55:57Z","receivedAt":"2021-01-26T22:19:54Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 16 ++++++++++++++--\n connect.c                     | 28 ++++++++++++++++++++++++++--\n t/t5606-clone-options.sh      |  8 +++++---\n t/t5702-protocol-v2.sh        | 25 +++++++++++++++++++++++++\n transport.h                   |  8 ++++++++\n 6 files changed, 79 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 211d4f54b0..77fdc61f4d 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1330,10 +1330,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n+\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n+\t\t\t\t\t\"refs/heads/\", &branch)) {\n+\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n+\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n \t\t\tfree(ref);\n \t\t}\n \t}\n@@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n+\tfree(transport_ls_refs_options.unborn_head_target);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 328c279250..879669df93 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tconst char *hash_name;\n \tstruct strvec *ref_prefixes = transport_options ?\n \t\t&transport_options->ref_prefixes : NULL;\n+\tchar **unborn_head_target = transport_options ?\n+\t\t&transport_options->unborn_head_target : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\n@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..0111d4e8bd 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.allowUnborn true &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..a8ef92b644 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if explicitly forbidden by config' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.allowUnborn false &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\t! grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \ndiff --git a/transport.h b/transport.h\nindex 1f5b60e4d3..24e15799e7 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -243,6 +243,14 @@ struct transport_ls_refs_options {\n \t * provided ref_prefixes.\n \t */\n \tstruct strvec ref_prefixes;\n+\n+\t/*\n+\t * If unborn_head_target is not NULL, and the remote reports HEAD as\n+\t * pointing to an unborn branch, transport_get_remote_refs() stores the\n+\t * unborn branch in unborn_head_target. It should be freed by the\n+\t * caller.\n+\t */\n+\tchar *unborn_head_target;\n };\n #define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n \n-- \n2.30.0.280.ga3ce27912f-goog\n\n"},{"id":"415362","messageId":"xmqq8s8f1iqe.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"32e16dfdbd3f54c0d4b39cbd555e66aa3950fffd.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-26T21:38:49Z","receivedAt":"2021-01-26T22:19:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> When cloning, we choose the default branch based on the remote HEAD.\n> But if there is no remote HEAD reported (which could happen if the\n> target of the remote HEAD is unborn), we'll fall back to using our local\n> init.defaultBranch. Traditionally this hasn't been a big deal, because\n> most repos used \"master\" as the default. But these days it is likely to\n> cause confusion if the server and client implementations choose\n> different values (e.g., if the remote started with \"main\", we may choose\n> \"master\" locally, create commits there, and then the user is surprised\n> when they push to \"master\" and not \"main\").\n>\n> To solve this, the remote needs to communicate the target of the HEAD\n> symref, even if it is unborn, and \"git clone\" needs to use this\n> information.\n>\n> Currently, symrefs that have unborn targets (such as in this case) are\n> not communicated by the protocol. Teach Git to advertise and support the\n> \"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\n> server administrators may turn this off through the lsrefs.allowunborn\n> config). This feature indicates that \"ls-refs\" supports the \"unborn\"\n> argument; when it is specified, \"ls-refs\" will send the HEAD symref with\n> the name of its unborn target.\n>\n> This change is only for protocol v2. A similar change for protocol v0\n> would require independent protocol design (there being no analogous\n> position to signal support for \"unborn\") and client-side plumbing of the\n> data required, so the scope of this patch set is limited to protocol v2.\n>\n> The client side will be updated to use this in a subsequent commit.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  Documentation/config.txt                |  2 +\n>  Documentation/config/lsrefs.txt         |  3 ++\n>  Documentation/technical/protocol-v2.txt | 10 ++++-\n>  ls-refs.c                               | 53 +++++++++++++++++++++++--\n>  ls-refs.h                               |  1 +\n>  serve.c                                 |  2 +-\n>  t/t5701-git-serve.sh                    |  2 +-\n>  7 files changed, 66 insertions(+), 7 deletions(-)\n>  create mode 100644 Documentation/config/lsrefs.txt\n>\n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index 6ba50b1104..d08e83a148 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -398,6 +398,8 @@ include::config/interactive.txt[]\n>  \n>  include::config/log.txt[]\n>  \n> +include::config/lsrefs.txt[]\n> +\n>  include::config/mailinfo.txt[]\n>  \n>  include::config/mailmap.txt[]\n> diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\n> new file mode 100644\n> index 0000000000..dcbec11aaa\n> --- /dev/null\n> +++ b/Documentation/config/lsrefs.txt\n> @@ -0,0 +1,3 @@\n> +lsrefs.allowUnborn::\n> +\tAllow the server to send information about unborn symrefs during the\n> +\tprotocol v2 ref advertisement.\n> diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\n> index 85daeb5d9e..4707511c10 100644\n> --- a/Documentation/technical/protocol-v2.txt\n> +++ b/Documentation/technical/protocol-v2.txt\n> @@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n>  \tWhen specified, only references having a prefix matching one of\n>  \tthe provided prefixes are displayed.\n>  \n> +If the 'unborn' feature is advertised the following argument can be\n> +included in the client's request.\n> +\n> +    unborn\n> +\tThe server may send symrefs pointing to unborn branches in the form\n> +\t\"unborn <refname> symref-target:<target>\".\n> +\n>  The output of ls-refs is as follows:\n>  \n>      output = *ref\n>  \t     flush-pkt\n> -    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n> +    obj-id-or-unborn = (obj-id | \"unborn\")\n> +    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n>      ref-attribute = (symref | peeled)\n>      symref = \"symref-target:\" symref-target\n>      peeled = \"peeled:\" obj-id\n> diff --git a/ls-refs.c b/ls-refs.c\n> index a1e0b473e4..4077adeb6a 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -32,6 +32,8 @@ struct ls_refs_data {\n>  \tunsigned peel;\n>  \tunsigned symrefs;\n>  \tstruct strvec prefixes;\n> +\tunsigned allow_unborn : 1;\n> +\tunsigned unborn : 1;\n>  };\n>  \n>  static int send_ref(const char *refname, const struct object_id *oid,\n> @@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \tif (!ref_match(&data->prefixes, refname_nons))\n>  \t\treturn 0;\n>  \n> -\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\tif (oid)\n> +\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> +\telse\n> +\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n\nWhen a call is made to this helper with NULL \"oid\", it unconditionally\nsends the \"refname\" out as an 'unborn' thing.  If data->symrefs is not\ntrue, or flag does not have REF_ISSYMREF set, then we'd end up\nsending\n\n    \"unborn\" SP refname LF\n\nwithout any ref-attribute.  The caller is responsible for ensuring\nthat it passes sensible data->symrefs and flag when it passes\noid==NULL to this function, but it is OK because this is a private\nhelper.\n\nOK.\n\n>  \tif (data->symrefs && flag & REF_ISSYMREF) {\n>  \t\tstruct object_id unused;\n>  \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n> @@ -74,8 +79,30 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \treturn 0;\n>  }\n>  \n> -static int ls_refs_config(const char *var, const char *value, void *data)\n> +static void send_possibly_unborn_head(struct ls_refs_data *data)\n>  {\n> +\tstruct strbuf namespaced = STRBUF_INIT;\n> +\tstruct object_id oid;\n> +\tint flag;\n> +\tint oid_is_null;\n> +\n> +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> +\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n> +\t\treturn; /* bad ref */\n> +\toid_is_null = is_null_oid(&oid);\n> +\tif (!oid_is_null ||\n> +\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n> +\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n\nAnd this caller makes sure that send_ref()'s expectation holds.\n\n> +\tstrbuf_release(&namespaced);\n> +}\n> +\n> +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> +{\n> +\tstruct ls_refs_data *data = cb_data;\n> +\n> +\tif (!strcmp(\"lsrefs.allowunborn\", var))\n> +\t\tdata->allow_unborn = git_config_bool(var, value);\n> +\n>  \t/*\n>  \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n>  \t * config. This may need to eventually be expanded to \"receive\", but we\n> @@ -91,7 +118,8 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \n>  \tmemset(&data, 0, sizeof(data));\n>  \n> -\tgit_config(ls_refs_config, NULL);\n> +\tdata.allow_unborn = 1;\n> +\tgit_config(ls_refs_config, &data);\n\nThe above is a usual sequence of \"an unspecified allow-unborn\ndefaults to true, but the configuration can turn it off\".  OK\n>  \n>  \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n>  \t\tconst char *arg = request->line;\n> @@ -103,14 +131,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n>  \t\t\tdata.symrefs = 1;\n>  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n>  \t\t\tstrvec_push(&data.prefixes, out);\n> +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> +\t\t\tdata.unborn = 1;\n\nI think the use of &&-cascade is iffy here.  Even when we are *not*\naccepting request for unborn, we should still parse it as such.\nThis does not matter in today's code, but it is a basic courtesy for\nfuture developers who may add more \"else if\" after it.\n\nIOW\n\n\t\telse if (!strcmp(\"unborn\", arg)) {\n\t\t\tif (!data.allow_unborn)\n\t\t\t\t; /* we are not accepting the request */\n\t\t\telse\n\t\t\t\tdata.unborn = 1;\n\t\t}\n\nI wrote the above in longhand only for documentation purposes; in\npractice, \n\n\t\telse if (!strcmp(\"unborn\", arg))\n                \tdata.unborn = data.allow_unborn;\n\nmay suffice.\n\n>  \t}\n>  \n>  \tif (request->status != PACKET_READ_FLUSH)\n>  \t\tdie(_(\"expected flush after ls-refs arguments\"));\n>  \n> -\thead_ref_namespaced(send_ref, &data);\n> +\tsend_possibly_unborn_head(&data);\n>  \tfor_each_namespaced_ref(send_ref, &data);\n\nAnd here is another caller of send_ref().  Are we sure that\nsend_ref()'s expectation is satisfied by this caller when the\niteration encounters a broken ref (e.g. refs/heads/broken not a\nsymref but names an object that does not exist and get_sha1()\nyielding 0{40}), or a dangling symref (e.g. refs/remotes/origin/HEAD\npointing at something that does not exist)?\n\n>  \tpacket_flush(1);\n>  \tstrvec_clear(&data.prefixes);\n>  \treturn 0;\n>  }\n> +\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n> +{\n> +\tif (value) {\n> +\t\tint allow_unborn_value;\n> +\n> +\t\tif (repo_config_get_bool(the_repository,\n> +\t\t\t\t\t \"lsrefs.allowunborn\",\n> +\t\t\t\t\t &allow_unborn_value) ||\n> +\t\t    allow_unborn_value)\n> +\t\t\tstrbuf_addstr(value, \"unborn\");\n> +\t}\n\nThis reads \"when not explicitly disabled, stuff \"unborn\" in there\".\n\nIt feels somewhat brittle that we have to read the same variable and\napply the same \"default to true\" logic in two places and have to\nkeep them in sync.  Is this because the decision to advertize or not\nhas to be made way before the code that is specific to the\nimplementation of ls-refs is run?\n\nIf ls_refs_advertise() is always called first before ls_refs(), I\nwonder if it makes sense to reuse what we found out about the\nconfigured (or left unconfigured) state here and use it when\nls_refs() gets called?  I know that the way serve.c infrastructure\ncalls \"do we advertise?\" helper from each protocol-element handler\nis too narrow and does not allow us to pass such a necessary piece\nof information but I view it as a misdesign that can be corrected\n(and until that happens, we could use file-local static limited to\nls-refs.c).\n\n> +\treturn 1;\n> +}\n> diff --git a/ls-refs.h b/ls-refs.h\n> index 7b33a7c6b8..a99e4be0bd 100644\n> --- a/ls-refs.h\n> +++ b/ls-refs.h\n> @@ -6,5 +6,6 @@ struct strvec;\n>  struct packet_reader;\n>  int ls_refs(struct repository *r, struct strvec *keys,\n>  \t    struct packet_reader *request);\n> +int ls_refs_advertise(struct repository *r, struct strbuf *value);\n>  \n>  #endif /* LS_REFS_H */\n> diff --git a/serve.c b/serve.c\n> index eec2fe6f29..ac20c72763 100644\n> --- a/serve.c\n> +++ b/serve.c\n> @@ -73,7 +73,7 @@ struct protocol_capability {\n>  \n>  static struct protocol_capability capabilities[] = {\n>  \t{ \"agent\", agent_advertise, NULL },\n> -\t{ \"ls-refs\", always_advertise, ls_refs },\n> +\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n>  \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n>  \t{ \"server-option\", always_advertise, NULL },\n>  \t{ \"object-format\", object_format_advertise, NULL },\n> diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\n> index a1f5fdc9fd..df29504161 100755\n> --- a/t/t5701-git-serve.sh\n> +++ b/t/t5701-git-serve.sh\n> @@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n>  \tcat >expect <<-EOF &&\n>  \tversion 2\n>  \tagent=git/$(git version | cut -d\" \" -f3)\n> -\tls-refs\n> +\tls-refs=unborn\n>  \tfetch=shallow\n>  \tserver-option\n>  \tobject-format=$(test_oid algo)\n"},{"id":"415360","messageId":"xmqq4kj31hzn.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"4eec55166889f16e41b10ae65002fd2c821b6762.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 2/3] connect, transport: encapsulate arg in struct","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-26T21:54:52Z","receivedAt":"2021-01-26T22:20:01Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> In a future patch we plan to return the name of an unborn current branch\n> from deep in the callchain to a caller via a new pointer parameter that\n> points at a variable in the caller when the caller calls\n> get_remote_refs() and transport_get_remote_refs().\n>\n> In preparation for that, encapsulate the existing ref_prefixes\n> parameter into a struct. The aforementioned unborn current branch will\n> go into this new struct in the future patch.\n>\n> Signed-off-by: Jonathan Tan <jonathantanmy@google.com>\n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  builtin/clone.c      | 18 +++++++++++-------\n>  builtin/fetch-pack.c |  3 ++-\n>  builtin/fetch.c      | 18 +++++++++++-------\n>  builtin/ls-remote.c  |  9 +++++----\n>  connect.c            |  4 +++-\n>  remote.h             |  4 +++-\n>  transport-helper.c   |  5 +++--\n>  transport-internal.h |  9 +--------\n>  transport.c          | 23 ++++++++++++-----------\n>  transport.h          | 21 ++++++++++++++-------\n>  10 files changed, 65 insertions(+), 49 deletions(-)\n>\n> diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\n> index 58b7c1fbdc..c2d96f4c89 100644\n> --- a/builtin/fetch-pack.c\n> +++ b/builtin/fetch-pack.c\n> @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n>  \tversion = discover_version(&reader);\n>  \tswitch (version) {\n>  \tcase protocol_v2:\n> -\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n> +\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n> +\t\t\t\targs.stateless_rpc);\n>  \t\tbreak;\n\nThis seems to be an unrelated line-wrapping, but there are overlong\nlines that are longer than this line in the same function.\n\nEverything else looks sensible to change the assumption that a strvec\nis sufficient for the communication in the codepaths and make it more\neasily extended by passing a ls-refs-options structure, which makes\nquite a lot of sense.\n\n> diff --git a/transport.h b/transport.h\n> index 24558c027d..1f5b60e4d3 100644\n> --- a/transport.h\n> +++ b/transport.h\n> @@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n>  \t\t   struct refspec *rs, int flags,\n>  \t\t   unsigned int * reject_reasons);\n>  \n> +struct transport_ls_refs_options {\n> +\t/*\n> +\t * Optionally, a list of ref prefixes can be provided which can be sent\n> +\t * to the server (when communicating using protocol v2) to enable it to\n> +\t * limit the ref advertisement.  Since ref filtering is done on the\n> +\t * server's end (and only when using protocol v2),\n> +\t * transport_get_remote_refs() could return refs which don't match the\n> +\t * provided ref_prefixes.\n> +\t */\n> +\tstruct strvec ref_prefixes;\n> +};\n> +#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n> +\n\nAnd of course, the first step only carries the strvec we have been\npassing around, i.e. does not lose or gain features.\n\nLooking good.  Thanks.\n\n"},{"id":"415367","messageId":"YBCitNb75rpnuW2L@coredump.intra.peff.net","threadId":"54790","inReplyTo":"20210126181358.2333028-1-jonathantanmy@google.com","subject":"Re: [PATCH v4 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-26T23:16:04Z","receivedAt":"2021-01-27T03:58:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 26, 2021 at 10:13:58AM -0800, Jonathan Tan wrote:\n\n> (It was a bit confusing that refs_resolve_ref_unsafe() returns one of\n> its input arguments if it succeeds and NULL if it fails, but that's\n> outside the scope of this patch, I think.)\n\nYep. It would probably be much nicer for it to return a numeric success\ncode, and to take an optional strbuf into which to write the resolved\nsymref name (if the caller even cares about it). But definitely out of\nscope for your patch.\n\n> > This straight-boolean version works as long as you can atomically update\n> > the _config_ on each version. But that seems like roughly the same\n> > problem (having dealt with this on GitHub servers, they are not\n> > equivalent, and depending on your infrastructure, it definitely _can_ be\n> > easier to do one versus the other. But it seems like a funny place to\n> > leave this upstream feature).\n> \n> Well, I was just agreeing with what you said [1]. :-)\n> \n> [1] https://lore.kernel.org/git/X9xJLWdFJfNJTn0p@coredump.intra.peff.net/\n\nOh, I just need to you to agree harder then. ;)\n\nIf we are not going to support config that helps you do an atomic\ndeploy, then I don't really see the point of having config at all.\nHere are three plausible implementations I can conceive of:\n\n  - allowUnborn is a tri-state for \"accept-but-do-not-advertise\",\n    \"accept-and-advertise\", and \"disallow\". This helps with rollout in a\n    cluster by setting it to the accept-but-do-not-advertise.  The\n    default would be accept-and-advertise, which is what most servers\n    would want. I don't really know why anyone would want \"disallow\".\n\n  - allowUnborn is a bool for \"accept-and-advertise\" or \"disallow\". This\n    doesn't help cluster rollout. I don't know why anyone would want to\n    switch away from the default of accept-and-advertise.\n\n  - allowUnborn is always on.\n\nThe first one helps the cluster case, at the cost of introducing an\nextra config knob. The third one doesn't help that case, but is one less\nknob for server admins to think about. But the second one has a knob\nthat I don't understand why anybody would tweak. It seems like the worst\nof both.\n\nPerhaps there's a reason for setting \"disallow\" that I don't know. Or\nperhaps you're happy to help the cluster case using a simple bool with\natomic config rollouts (which are outside the scope of Git itself).\n\n> > Or is the intent that an unconfigured reader would silently ignore the\n> > unborn flag in that case? That would at least not cause it to bail on\n> > the client in a mixed-version environment. But it does feel like a\n> > confusing result.\n> \n> Right now, an old server would ignore \"unborn\", yes. I'm not sure of\n> what the intent should be - tightening ls-refs and fetch to forbid\n> unknown arguments seems like a good idea to me.\n\nIf we had a just a bool (case 2 from above), and there was an\nalways-implied \"accept unborn even if not advertised\", then that _does_\nlet the config help out the cluster case (it just turns off\nadvertisements, basically making the bool \"accept-but-do-not-advertise\"\nversus \"disallow\").\n\nI don't love it. The protocol spec does say \"don't ask for capability\nfoo if the server didn't say it knows about foo\". We'd be loosening the\nenforcement of that (if only for capabilities we _do_ in fact know\nabout), even though we don't know if it was due to a race, or if the\nclient is just misbehaving. But I wondered if that was the direction you\nwere going to try to solve your cluster-rollout problem.\n\n-Peff\n"},{"id":"415377","messageId":"87h7n3p363.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"cover.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-27T01:41:08Z","receivedAt":"2021-01-27T04:17:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 26 2021, Jonathan Tan wrote:\n\n[For some reason the patches didn't reach my mailbox, but I see them in\nthe list archive, so I'm replying to the cover-letter]\n\n>  Documentation/config.txt                |  2 +\n>  Documentation/config/init.txt           |  2 +-\n\nGood, now we have init.defaultBranch docs, but they say:\n    \n     init.defaultBranch::\n            Allows overriding the default branch name e.g. when initializing\n    -       a new repository or when cloning an empty repository.\n    +       a new repository.\n\nSo this still only applies to file:// and other \"protocol\" clones, but\nnot \"git clone /some/path\"?\n\nRe my reply to v1, do we consider that a bug, feature, something just\nleft unimplemented?\n\nI really don't care much, but this really needs a corresponding\ndocumentation update. I.e. something like:\n\n    init.defaultBranch::\n        Allows overriding the default branch name e.g. when initializing a\n        new repository or when cloning an empty repository.\n    \n        When cloning a repository over protocol v2 (i.e. ssh://, https://,\n        file://, but not a /some/path), and if that repository has\n        init.defaultBranch configured, the server will advertise its\n        preferred default branch name, and we'll take its configuration over\n        ours.\n\nWhich, just in terms of implementation makes me think it would make more\nsense if the server just had:\n\n    uploadPack.sendConfig = \"init.defaultBranch=xyz\"\n\nThe client:\n\n    receivePack.acceptConfig = \"init.defaultBranch\"\n\nAnd in terms of things on the wire we'd say:\n\n    \"set-config init.defaultBranch=main\"\n\nYou could have many such lines, but we'd just harcode only accepting\n\"init.defaultBranch\" by default for now.\n\nI.e. we set \"init.defaultBranch\" on the server, and the client ends up\ninterpreting things as if though \"init.defaultBranch\" was set to exactly\nthat value. So why not just ... send a line saying \"you should set your\ninit.defaultBranch config to this\".\n\nMakes it future-extensible pretty much for free, and I think also much\neasier to explain to users. I.e. instead of init.defaultBranch somehow\nbeing magical when talking with a remote server we can talk about a\nremote server being one source of config per git-config's documented\nconfig order, for a very narrow whitelist of config keys.\n\nOr (not clear to me, should have waited with my other E-Mail) are we\never expecting to send more than one of:\n\n    \"unborn <refname> symref-target:<target>\"\n\nOr is the reason closer to us being able to shoehorn this into the\nexisting ls-refs response, as opposed to some general \"here's config for\nyou\" response we don't have?\n"},{"id":"415381","messageId":"87k0rzp3qx.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"32e16dfdbd3f54c0d4b39cbd555e66aa3950fffd.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-27T01:28:38Z","receivedAt":"2021-01-27T04:20:44Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Jan 26 2021, Jonathan Tan wrote:\n\n> +If the 'unborn' feature is advertised the following argument can be\n> +included in the client's request.\n> +\n> +    unborn\n> +\tThe server may send symrefs pointing to unborn branches in the form\n> +\t\"unborn <refname> symref-target:<target>\".\n> +\n\n\"branches\" as in things under refs/heads/*? What should happen if you\nsend this for a refs/tags/* or refs/xyz/*? Maybe overly pedantic, but it\nseems we have no other explicit mention of refs/{heads,tags}/ in\nprotocol-v2.txt before this[1].\n\n1. Although as I've learned from another recent thread include-tag is\n   magical for refs/tags/* only.\n"},{"id":"415382","messageId":"xmqqim7jz2tw.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"YBCjwoc5LUrk3GBA@coredump.intra.peff.net","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-26T23:38:19Z","receivedAt":"2021-01-27T04:20:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> See the\n> discussion in the v4 thread (sorry, seems not to have hit the archive\n> yet, but hopefully this link will work soon):\n>\n>   https://lore.kernel.org/git/YBCitNb75rpnuW2L@coredump.intra.peff.net/\n\nI guess vger having some sort of constipation, we (not you-and-me\nbut list participants as a whole) would be doing this kind of\nback-and-force while untable to read what others have said. \n\nhttps://lore.kernel.org/git/xmqqmtwvz4g9.fsf@gitster.c.googlers.com/\n\nwill have the following.\n\n> It feels somewhat brittle that we have to read the same variable and\n> apply the same \"default to true\" logic in two places and have to\n> keep them in sync.  Is this because the decision to advertize or not\n> has to be made way before the code that is specific to the\n> implementation of ls-refs is run?\n>\n> If ls_refs_advertise() is always called first before ls_refs(), I\n> wonder if it makes sense to reuse what we found out about the\n> configured (or left unconfigured) state here and use it when\n> ls_refs() gets called?  I know that the way serve.c infrastructure\n> calls \"do we advertise?\" helper from each protocol-element handler\n> is too narrow and does not allow us to pass such a necessary piece\n> of information but I view it as a misdesign that can be corrected\n> (and until that happens, we could use file-local static limited to\n> ls-refs.c).\n\nAfter giving the above a bit more thought, here are a few random\nthoughts around the area.\n\n * As \"struct protocol_capability\" indicates, we have <name of\n   service, the logic to advertise, the logic to serve> as a\n   three-tuple for services.  The serving logic should know what\n   advertising logic advertised (or more precisely, what information\n   advertising logic used to make that decision) so that they can\n   work consistently.\n\n   For that, there should be a mechanism that advertising logic can\n   use to leave a note to serving logic, perhaps by adding a \"void\n   *\" to both of these functions.  The advertising function would\n   allocate a piece of memory it wants to use and returns the\n   pointer to it to the caller in serve.c, and that pointer is given\n   to the corresponding ls_refs() when it is called by serve.c.\n   Then ls_refs_advertise can say \"I found this configuration\n   setting and decided to advertise\" to later ls_refs() and the\n   latter can say \"ah, as you have advertised, I have to respond to\n   such a request\".\n\n * I am not sure if \"lsrefs.allowunborn = yes/no\" is a good way to\n   configure this feature.  Wouldn't it be more natural to make this\n   three-way, i.e. \"lsrefs.unborn = advertise/serve/ignore\", where\n   the server operator can choose among (1) advertise the presence\n   of the capability and respond to requests, (2) do not advertise\n   the capability but if a request comes, respond to it, and (3) do\n   not advertise and do not respond.  We could throw in 'deny' that\n   causes the request to result in a failure but I do not care too\n   deeply about that fourth option.\n\n   Using such a configuration mechanism, ls_refs_advertise may leave\n   the value of \"lsrefs.unborn\" (or lack thereof) it found and used\n   to base its decision to advertise, for use by ls_refs.  ls_refs\n   in turn can use the value found there to decide if it ignores or\n   responds to the \"unborn\" request.\n\n"},{"id":"415383","messageId":"xmqqeei7yyi9.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"cover.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-27T01:11:42Z","receivedAt":"2021-01-27T04:20:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Thanks, Peff, for your review. I have addressed your comments (through\n> replies to your emails and here in this v5 patch set).\n>\n> Jonathan Tan (3):\n>   ls-refs: report unborn targets of symrefs\n>   connect, transport: encapsulate arg in struct\n>   clone: respect remote unborn HEAD\n\nApplying this alone to 'master' seems to pass all tests, but\nthe topic seems to have funny interactions with another topic\nin flight, jk/peel-iterated-oid\n\nThere is textual conflict whose resolution seems trivial, but with\nthat resolved ...\n\ndiff --cc builtin/clone.c\nindex e335734b4c,77fdc61f4d..0000000000\n--- i/builtin/clone.c\n+++ w/builtin/clone.c\n@@@ -1326,10 -1330,21 +1330,21 @@@ int cmd_clone(int argc, const char **ar\n  \t\tremote_head = NULL;\n  \t\toption_no_checkout = 1;\n  \t\tif (!option_bare) {\n- \t\t\tconst char *branch = git_default_branch_name(0);\n- \t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+ \t\t\tconst char *branch;\n+ \t\t\tchar *ref;\n+ \n+ \t\t\tif (transport_ls_refs_options.unborn_head_target &&\n+ \t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n+ \t\t\t\t\t\"refs/heads/\", &branch)) {\n+ \t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n+ \t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n+ \t\t\t} else {\n -\t\t\t\tbranch = git_default_branch_name();\n++\t\t\t\tbranch = git_default_branch_name(0);\n+ \t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+ \t\t\t}\n  \n  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n+ \t\t\tcreate_symref(\"HEAD\", ref, \"\");\n  \t\t\tfree(ref);\n  \t\t}\n  \t}\n\n\n... numerous tests fail.\n\nFor example, t5702 dies like so:\n\nexpecting success of 5702.15 'clone of empty repo propagates name of default branch':\n        test_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n\n        GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n        git -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n\n        GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n        git -c init.defaultBranch=main -c protocol.version=2 \\\n                clone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n        grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n\nInitialized empty Git repository in /usr/local/google/home/jch/w/git.git/t/trash directory.t5702-protocol-v2/file_empty_parent/.git/\nCloning into 'file_empty_child'...\nfatal: expected flush after ref listing\nnot ok 15 - clone of empty repo propagates name of default branch\n"},{"id":"415384","messageId":"YBCjwoc5LUrk3GBA@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqq8s8f1iqe.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-26T23:20:34Z","receivedAt":"2021-01-27T04:20:48Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 26, 2021 at 01:38:49PM -0800, Junio C Hamano wrote:\n\n> > @@ -103,14 +131,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \t\t\tdata.symrefs = 1;\n> >  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n> >  \t\t\tstrvec_push(&data.prefixes, out);\n> > +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> > +\t\t\tdata.unborn = 1;\n> \n> I think the use of &&-cascade is iffy here.  Even when we are *not*\n> accepting request for unborn, we should still parse it as such.\n> This does not matter in today's code, but it is a basic courtesy for\n> future developers who may add more \"else if\" after it.\n> \n> IOW\n> \n> \t\telse if (!strcmp(\"unborn\", arg)) {\n> \t\t\tif (!data.allow_unborn)\n> \t\t\t\t; /* we are not accepting the request */\n> \t\t\telse\n> \t\t\t\tdata.unborn = 1;\n> \t\t}\n> \n> I wrote the above in longhand only for documentation purposes; in\n> practice, \n> \n> \t\telse if (!strcmp(\"unborn\", arg))\n>                 \tdata.unborn = data.allow_unborn;\n> \n> may suffice.\n\nDoing it that way is friendlier, but loosens enforcement of:\n\n  Client will then send a space separated list of capabilities it wants\n  to be in effect. The client MUST NOT ask for capabilities the server\n  did not say it supports.\n\nfrom Documentation/technical/protocol-capabilities.txt.\n\nIt does solve Jonathan's racy cluster-deploy problem, though. See the\ndiscussion in the v4 thread (sorry, seems not to have hit the archive\nyet, but hopefully this link will work soon):\n\n  https://lore.kernel.org/git/YBCitNb75rpnuW2L@coredump.intra.peff.net/\n\n-Peff\n"},{"id":"415385","messageId":"xmqqzh0vz69k.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"922e8c229c359c15f1265876e6def87d7a18b763.1611686656.git.jonathantanmy@google.com","subject":"Re: [PATCH v5 3/3] clone: respect remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-26T22:24:07Z","receivedAt":"2021-01-27T05:06:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>  init.defaultBranch::\n>  \tAllows overriding the default branch name e.g. when initializing\n> -\ta new repository or when cloning an empty repository.\n> +\ta new repository.\n\nLooking good.\n\n> diff --git a/builtin/clone.c b/builtin/clone.c\n> index 211d4f54b0..77fdc61f4d 100644\n> --- a/builtin/clone.c\n> +++ b/builtin/clone.c\n> @@ -1330,10 +1330,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \t\tremote_head = NULL;\n>  \t\toption_no_checkout = 1;\n>  \t\tif (!option_bare) {\n> -\t\t\tconst char *branch = git_default_branch_name();\n> -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\tconst char *branch;\n> +\t\t\tchar *ref;\n> +\n> +\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n> +\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n> +\t\t\t\t\t\"refs/heads/\", &branch)) {\n> +\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n> +\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n> +\t\t\t} else {\n> +\t\t\t\tbranch = git_default_branch_name();\n> +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> +\t\t\t}\n>  \n>  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n>  \t\t\tfree(ref);\n\nOK, we used to say \"point our HEAD always to the local default\nname\", and the code is still there in the else clause.  But when the\ntransport found what name the other side uses, we use that name\ninstead.\n\nI presume that clearing transport_ls_ref_options.unborn_head_target\nis to take ownership of this piece of memory ourselves?\n\nWe didn't call create_symref() in the original code, but now we do.\nIs this a valid bugfix even if we did not have this \"learn remote\nsymref even for unborn HEAD\" feature?  Or is the original codepath\nnow somehow got broken with an extra create_symref() that we used\nnot to do, but now we do?\n\n> @@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n>  \tjunk_mode = JUNK_LEAVE_ALL;\n>  \n>  \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n> +\tfree(transport_ls_refs_options.unborn_head_target);\n>  \treturn err;\n>  }\n> diff --git a/connect.c b/connect.c\n> index 328c279250..879669df93 100644\n> --- a/connect.c\n> +++ b/connect.c\n> @@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n>  }\n>  \n>  /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n> -static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n> +static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n> +\t\t\t  char **unborn_head_target)\n>  {\n>  \tint ret = 1;\n>  \tint i = 0;\n> @@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n>  \t\tgoto out;\n>  \t}\n>  \n> +\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n> +\t\ti++;\n> +\t\tif (unborn_head_target &&\n> +\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n> +\t\t\t/*\n> +\t\t\t * Look for the symref target (if any). If found,\n> +\t\t\t * return it to the caller.\n> +\t\t\t */\n> +\t\t\tfor (; i < line_sections.nr; i++) {\n> +\t\t\t\tconst char *arg = line_sections.items[i].string;\n> +\n> +\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n> +\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n> +\t\t\t\t\tbreak;\n> +\t\t\t\t}\n> +\t\t\t}\n> +\t\t}\n> +\t\tgoto out;\n> +\t}\n\nWe split the line and notice that the first token is \"unborn\"; if\nthe caller is not interested in the unborn head, we just skip the\nrest, but otherwise, if it is about HEAD (i.e. we do not care if a\ndangling symref that is not HEAD is reported), we notice the target\nin unborn_head_target.\n\nOK.  We already saw how this is used in cmd_clone().\n\n> @@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n>  \tconst char *hash_name;\n>  \tstruct strvec *ref_prefixes = transport_options ?\n>  \t\t&transport_options->ref_prefixes : NULL;\n> +\tchar **unborn_head_target = transport_options ?\n> +\t\t&transport_options->unborn_head_target : NULL;\n\nSo any caller that passes transport_options will get the unborn head\ninformation for free?  The other callers are in fetch-pack.c and\ntransport.c, which presumably are about fetching and not cloning.\n\nI recall discussions on filling a missing refs/remotes/X/HEAD when\nwe fetch from X and learn where X points at.  Such an extension can\nbe done on top of this mechanism to pass transport_options from the\nfetch codepath, I presume?\n\n\nThanks.  I tried to follow the thought in the patches aloud, and it\nwas mostly a pleasant read.\n\n"},{"id":"415387","messageId":"YBDrOrUIAcbTQ8cu@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqqeei7yyi9.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-27T04:25:30Z","receivedAt":"2021-01-27T05:42:42Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 26, 2021 at 05:11:42PM -0800, Junio C Hamano wrote:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > Thanks, Peff, for your review. I have addressed your comments (through\n> > replies to your emails and here in this v5 patch set).\n> >\n> > Jonathan Tan (3):\n> >   ls-refs: report unborn targets of symrefs\n> >   connect, transport: encapsulate arg in struct\n> >   clone: respect remote unborn HEAD\n> \n> Applying this alone to 'master' seems to pass all tests, but\n> the topic seems to have funny interactions with another topic\n> in flight, jk/peel-iterated-oid\n\nI was worried at first I really screwed up something subtle, but it is\nindeed just a funny local interaction.\n\nHere's a fix which can be applied on top of jt/clone-unborn-head. It\ncould equally well be applied as part of the merge (with a minor\nadjustment in the context), but I think it ought to be squashed into\nJonathan's patch 1 anyway.\n\nThe conflict you had to resolve was a red herring (it wasn't part of\njk/peel-iterated-oid at all, but rather other commits that got pulled in\nbecause my topic is based on a more recent master).\n\n-- >8 --\nSubject: [PATCH] ls-refs: don't peel NULL oid\n\nWhen the \"unborn\" feature is enabled, upload-pack serving an ls-refs\ncommand will pass a NULL oid into send_ref(). In this case, there is no\npoint trying to peel the ref, since we know it points to nothing.\n\nFor now this is a harmless waste of cycles (we re-resolve HEAD and find\nout that indeed, it points to nothing). But after merging with another\ntopic that contains 36a317929b (refs: switch peel_ref() to\npeel_iterated_oid(), 2021-01-20), we'd actually end up passing NULL to\npeel_object(), which segfaults!\n\nSigned-off-by: Jeff King <peff@peff.net>\n---\n ls-refs.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/ls-refs.c b/ls-refs.c\nindex 4077adeb6a..bc91f03653 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -66,7 +66,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel) {\n+\tif (data->peel && oid) {\n \t\tstruct object_id peeled;\n \t\tif (!peel_ref(refname, &peeled))\n \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n-- \n2.30.0.724.gc858251c49\n\n"},{"id":"415393","messageId":"xmqqwnvyykhm.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"YBDrOrUIAcbTQ8cu@coredump.intra.peff.net","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-27T06:14:29Z","receivedAt":"2021-01-27T06:29:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> Here's a fix which can be applied on top of jt/clone-unborn-head. It\n> could equally well be applied as part of the merge (with a minor\n> adjustment in the context), but I think it ought to be squashed into\n> Jonathan's patch 1 anyway.\n\nWill queue but we are not merging the topic to 'next' yet, so I'll\nask Jonathan to remember making it a part of the series if it needs\nto be updated later.\n\nThanks.\n\n>\n> -- >8 --\n> Subject: [PATCH] ls-refs: don't peel NULL oid\n>\n> When the \"unborn\" feature is enabled, upload-pack serving an ls-refs\n> command will pass a NULL oid into send_ref(). In this case, there is no\n> point trying to peel the ref, since we know it points to nothing.\n>\n> For now this is a harmless waste of cycles (we re-resolve HEAD and find\n> out that indeed, it points to nothing). But after merging with another\n> topic that contains 36a317929b (refs: switch peel_ref() to\n> peel_iterated_oid(), 2021-01-20), we'd actually end up passing NULL to\n> peel_object(), which segfaults!\n>\n> Signed-off-by: Jeff King <peff@peff.net>\n> ---\n>  ls-refs.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/ls-refs.c b/ls-refs.c\n> index 4077adeb6a..bc91f03653 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -66,7 +66,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n>  \t\t\t    strip_namespace(symref_target));\n>  \t}\n>  \n> -\tif (data->peel) {\n> +\tif (data->peel && oid) {\n>  \t\tstruct object_id peeled;\n>  \t\tif (!peel_ref(refname, &peeled))\n>  \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n"},{"id":"415410","messageId":"YBCf8SI3fK+rDyox@coredump.intra.peff.net","threadId":"54790","inReplyTo":"20210126182212.2337587-1-jonathantanmy@google.com","subject":"Re: [PATCH v4 3/3] clone: respect remote unborn HEAD","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-01-26T23:04:17Z","receivedAt":"2021-01-27T10:16:24Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Jan 26, 2021 at 10:22:12AM -0800, Jonathan Tan wrote:\n\n> > On Tue, Dec 22, 2020 at 01:54:20PM -0800, Jonathan Tan wrote:\n> > \n> > > @@ -1323,10 +1325,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n> > >  \t\tremote_head = NULL;\n> > >  \t\toption_no_checkout = 1;\n> > >  \t\tif (!option_bare) {\n> > > -\t\t\tconst char *branch = git_default_branch_name();\n> > > -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> > > +\t\t\tconst char *branch;\n> > > +\t\t\tchar *ref;\n> > > +\n> > > +\t\t\tif (unborn_head_target &&\n> > > +\t\t\t    skip_prefix(unborn_head_target, \"refs/heads/\", &branch)) {\n> > > +\t\t\t\tref = unborn_head_target;\n> > > +\t\t\t\tunborn_head_target = NULL;\n> > > +\t\t\t} else {\n> > > +\t\t\t\tbranch = git_default_branch_name();\n> > > +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> > > +\t\t\t}\n> > >  \n> > >  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> > > +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n> > >  \t\t\tfree(ref);\n> > >  \t\t}\n> > \n> > In the old code, we never called create_symref() at all. It makes sense\n> > that we'd do it now when unborn_head_target is not NULL. But what about\n> > in the \"else\" clause there? Now we're adding an extra create_symref()\n> > call.\n> \n> The \"else\" branch you're referring to is the one enclosing all of the\n> lines quoted above, I believe?\n\nI meant this clause:\n\n> > > +                 } else {\n> > > +                         branch = git_default_branch_name();\n> > > +                         ref = xstrfmt(\"refs/heads/%s\", branch);\n> > > +                 }\n\nwhich used to be what we always did unconditionally. So in the original\ncode, we did not call create_symref() in this code path. Afterwards, we\ncall it for the unborn HEAD (which I can buy is necessary) but _also_\nfor that regular path. I.e., why is the new code not:\n\n  if (unborn_head_target && ...) {\n          ref = unborn_head_target;\n\t  unborn_head_target = NULL;\n\t  create_symref(\"HEAD\", ref, \"\");\n  } else {\n          branch = git_default_branch_name();\n\t  ref = xstrfmt(\"refs/heads/%s\", branch);\n  }\n\nI.e., I don't understand:\n\n  - why create_symref() wasn't need before (assuming it was not), and\n    why it is OK to run it now in the non-unborn code path\n\n  - why we need create_symref() in the unborn path (which is probably\n    something mundane)\n\nI can even buy the argument that it is simply for consistency, so that\nall of the HEAD-setup commands are shared between the two paths. And\nthat it is OK to do so, because we are just overwriting what init-db did\nbefore (even if sometimes it is the same thing). But I feel like that\ndeserves explanation in the commit message. :)\n\n-Peff\n"},{"id":"415411","messageId":"xmqqmtwvz4g9.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"xmqq8s8f1iqe.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-26T23:03:18Z","receivedAt":"2021-01-27T10:17:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> It feels somewhat brittle that we have to read the same variable and\n> apply the same \"default to true\" logic in two places and have to\n> keep them in sync.  Is this because the decision to advertize or not\n> has to be made way before the code that is specific to the\n> implementation of ls-refs is run?\n>\n> If ls_refs_advertise() is always called first before ls_refs(), I\n> wonder if it makes sense to reuse what we found out about the\n> configured (or left unconfigured) state here and use it when\n> ls_refs() gets called?  I know that the way serve.c infrastructure\n> calls \"do we advertise?\" helper from each protocol-element handler\n> is too narrow and does not allow us to pass such a necessary piece\n> of information but I view it as a misdesign that can be corrected\n> (and until that happens, we could use file-local static limited to\n> ls-refs.c).\n\nAfter giving the above a bit more thought, here are a few random\nthoughts around the area.\n\n * As \"struct protocol_capability\" indicates, we have <name of\n   service, the logic to advertise, the logic to serve> as a\n   three-tuple for services.  The serving logic should know what\n   advertising logic advertised (or more precisely, what information\n   advertising logic used to make that decision) so that they can\n   work consistently.\n\n   For that, there should be a mechanism that advertising logic can\n   use to leave a note to serving logic, perhaps by adding a \"void\n   *\" to both of these functions.  The advertising function would\n   allocate a piece of memory it wants to use and returns the\n   pointer to it to the caller in serve.c, and that pointer is given\n   to the corresponding ls_refs() when it is called by serve.c.\n   Then ls_refs_advertise can say \"I found this configuration\n   setting and decided to advertise\" to later ls_refs() and the\n   latter can say \"ah, as you have advertised, I have to respond to\n   such a request\".\n\n * I am not sure if \"lsrefs.allowunborn = yes/no\" is a good way to\n   configure this feature.  Wouldn't it be more natural to make this\n   three-way, i.e. \"lsrefs.unborn = advertise/serve/ignore\", where\n   the server operator can choose among (1) advertise the presence\n   of the capability and respond to requests, (2) do not advertise\n   the capability but if a request comes, respond to it, and (3) do\n   not advertise and do not respond.  We could throw in 'deny' that\n   causes the request to result in a failure but I do not care too\n   deeply about that fourth option.\n\n   Using such a configuration mechanism, ls_refs_advertise may leave\n   the value of \"lsrefs.unborn\" (or lack thereof) it found and used\n   to base its decision to advertise, for use by ls_refs.  ls_refs\n   in turn can use the value found there to decide if it ignores or\n   responds to the \"unborn\" request.\n\n"},{"id":"415481","messageId":"xmqqeei5y5iq.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"YBCf8SI3fK+rDyox@coredump.intra.peff.net","subject":"Re: [PATCH v4 3/3] clone: respect remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-28T05:50:05Z","receivedAt":"2021-01-28T05:51:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> ... Afterwards, we\n> call it for the unborn HEAD (which I can buy is necessary) but _also_\n> for that regular path. I.e., why is the new code not:\n>\n>   if (unborn_head_target && ...) {\n>           ref = unborn_head_target;\n> \t  unborn_head_target = NULL;\n> \t  create_symref(\"HEAD\", ref, \"\");\n>   } else {\n>           branch = git_default_branch_name();\n> \t  ref = xstrfmt(\"refs/heads/%s\", branch);\n>   }\n>\n> I.e., I don't understand:\n>\n>   - why create_symref() wasn't need before (assuming it was not), and\n>     why it is OK to run it now in the non-unborn code path\n>\n>   - why we need create_symref() in the unborn path (which is probably\n>     something mundane)\n>\n> I can even buy the argument that it is simply for consistency, so that\n> all of the HEAD-setup commands are shared between the two paths. And\n> that it is OK to do so, because we are just overwriting what init-db did\n> before (even if sometimes it is the same thing). But I feel like that\n> deserves explanation in the commit message. :)\n\nYes, during yesterday's communication glitch, I also independently\nwas wondering about this and am dying to know if this is an\nunrelated \"fix\", applicable even without the \"unborn\" support, or\nbreaking the non \"unborn\" side of the codepath.\n\nThanks.\n"},{"id":"415590","messageId":"20210129202306.723272-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq8s8f1iqe.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-29T20:23:06Z","receivedAt":"2021-01-29T20:28:09Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > @@ -47,7 +49,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n> >  \tif (!ref_match(&data->prefixes, refname_nons))\n> >  \t\treturn 0;\n> >  \n> > -\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> > +\tif (oid)\n> > +\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n> > +\telse\n> > +\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n> \n> When a call is made to this helper with NULL \"oid\", it unconditionally\n> sends the \"refname\" out as an 'unborn' thing.  If data->symrefs is not\n> true, or flag does not have REF_ISSYMREF set, then we'd end up\n> sending\n> \n>     \"unborn\" SP refname LF\n> \n> without any ref-attribute.  The caller is responsible for ensuring\n> that it passes sensible data->symrefs and flag when it passes\n> oid==NULL to this function, but it is OK because this is a private\n> helper.\n> \n> OK.\n\nThanks for checking.\n\n> >  \tif (data->symrefs && flag & REF_ISSYMREF) {\n> >  \t\tstruct object_id unused;\n> >  \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n> > @@ -74,8 +79,30 @@ static int send_ref(const char *refname, const struct object_id *oid,\n> >  \treturn 0;\n> >  }\n> >  \n> > -static int ls_refs_config(const char *var, const char *value, void *data)\n> > +static void send_possibly_unborn_head(struct ls_refs_data *data)\n> >  {\n> > +\tstruct strbuf namespaced = STRBUF_INIT;\n> > +\tstruct object_id oid;\n> > +\tint flag;\n> > +\tint oid_is_null;\n> > +\n> > +\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n> > +\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n> > +\t\treturn; /* bad ref */\n> > +\toid_is_null = is_null_oid(&oid);\n> > +\tif (!oid_is_null ||\n> > +\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n> > +\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n> \n> And this caller makes sure that send_ref()'s expectation holds.\n\nThanks for checking.\n\n> > +\tstrbuf_release(&namespaced);\n> > +}\n> > +\n> > +static int ls_refs_config(const char *var, const char *value, void *cb_data)\n> > +{\n> > +\tstruct ls_refs_data *data = cb_data;\n> > +\n> > +\tif (!strcmp(\"lsrefs.allowunborn\", var))\n> > +\t\tdata->allow_unborn = git_config_bool(var, value);\n> > +\n> >  \t/*\n> >  \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n> >  \t * config. This may need to eventually be expanded to \"receive\", but we\n> > @@ -91,7 +118,8 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \n> >  \tmemset(&data, 0, sizeof(data));\n> >  \n> > -\tgit_config(ls_refs_config, NULL);\n> > +\tdata.allow_unborn = 1;\n> > +\tgit_config(ls_refs_config, &data);\n> \n> The above is a usual sequence of \"an unspecified allow-unborn\n> defaults to true, but the configuration can turn it off\".  OK\n\nLater, you address this issue again so I'll comment there.\n\n> > @@ -103,14 +131,31 @@ int ls_refs(struct repository *r, struct strvec *keys,\n> >  \t\t\tdata.symrefs = 1;\n> >  \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n> >  \t\t\tstrvec_push(&data.prefixes, out);\n> > +\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n> > +\t\t\tdata.unborn = 1;\n> \n> I think the use of &&-cascade is iffy here.  Even when we are *not*\n> accepting request for unborn, we should still parse it as such.\n> This does not matter in today's code, but it is a basic courtesy for\n> future developers who may add more \"else if\" after it.\n> \n> IOW\n> \n> \t\telse if (!strcmp(\"unborn\", arg)) {\n> \t\t\tif (!data.allow_unborn)\n> \t\t\t\t; /* we are not accepting the request */\n> \t\t\telse\n> \t\t\t\tdata.unborn = 1;\n> \t\t}\n> \n> I wrote the above in longhand only for documentation purposes; in\n> practice, \n> \n> \t\telse if (!strcmp(\"unborn\", arg))\n>                 \tdata.unborn = data.allow_unborn;\n> \n> may suffice.\n\nMy thinking was (and is) that falling through in the case of a\ndisallowed argument (as opposed to a completely unrecognized argument)\nmakes it more straightforward later if we ever decide to tighten\nvalidation of the ls-refs request - we would only have to put some code\nat the end that reports back to the user.\n\nIf we write it as you suggest, we would have to remember to replace the\n\"we are not accepting the request\" part (as in the comment in your\nsuggested code) with an error report, but perhaps that is a good thing -\nwe would be able to insert a custom error message instead of an\ninformation-hiding \"argument not supported\".\n\nI'm OK either way.\n\n> >  \t}\n> >  \n> >  \tif (request->status != PACKET_READ_FLUSH)\n> >  \t\tdie(_(\"expected flush after ls-refs arguments\"));\n> >  \n> > -\thead_ref_namespaced(send_ref, &data);\n> > +\tsend_possibly_unborn_head(&data);\n> >  \tfor_each_namespaced_ref(send_ref, &data);\n> \n> And here is another caller of send_ref().  Are we sure that\n> send_ref()'s expectation is satisfied by this caller when the\n> iteration encounters a broken ref (e.g. refs/heads/broken not a\n> symref but names an object that does not exist and get_sha1()\n> yielding 0{40}), or a dangling symref (e.g. refs/remotes/origin/HEAD\n> pointing at something that does not exist)?\n\nI assume that by \"this caller\" you mean for_each_namespaced_ref(), since\nyou mention an iteration. I believe so - send_ref has been changed to\ntolerate a NULL (as in (void*)0, not 0{40}) oid, and that is the only\nchange, so if it worked previously, it should still work now.\n\n> >  \tpacket_flush(1);\n> >  \tstrvec_clear(&data.prefixes);\n> >  \treturn 0;\n> >  }\n> > +\n> > +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n> > +{\n> > +\tif (value) {\n> > +\t\tint allow_unborn_value;\n> > +\n> > +\t\tif (repo_config_get_bool(the_repository,\n> > +\t\t\t\t\t \"lsrefs.allowunborn\",\n> > +\t\t\t\t\t &allow_unborn_value) ||\n> > +\t\t    allow_unborn_value)\n> > +\t\t\tstrbuf_addstr(value, \"unborn\");\n> > +\t}\n> \n> This reads \"when not explicitly disabled, stuff \"unborn\" in there\".\n> \n> It feels somewhat brittle that we have to read the same variable and\n> apply the same \"default to true\" logic in two places and have to\n> keep them in sync.  Is this because the decision to advertize or not\n> has to be made way before the code that is specific to the\n> implementation of ls-refs is run?\n> \n> If ls_refs_advertise() is always called first before ls_refs(), I\n> wonder if it makes sense to reuse what we found out about the\n> configured (or left unconfigured) state here and use it when\n> ls_refs() gets called?  I know that the way serve.c infrastructure\n> calls \"do we advertise?\" helper from each protocol-element handler\n> is too narrow and does not allow us to pass such a necessary piece\n> of information but I view it as a misdesign that can be corrected\n> (and until that happens, we could use file-local static limited to\n> ls-refs.c).\n\nPerhaps what I could do is have a static variable that tracks whether\nconfig has been read and what the config is (or if the default variable\nis used), and have each function call another function that sets that\nstatic variable if config has not yet been read. I think that will\naddress this concern.\n"},{"id":"415601","messageId":"xmqqtuqzs8m5.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20210129202306.723272-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-01-29T22:04:18Z","receivedAt":"2021-01-29T22:05:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> I think the use of &&-cascade is iffy here.  Even when we are *not*\n>> accepting request for unborn, we should still parse it as such.\n>> This does not matter in today's code, but it is a basic courtesy for\n>> future developers who may add more \"else if\" after it.\n>> \n>> IOW\n>> \n>> \t\telse if (!strcmp(\"unborn\", arg)) {\n>> \t\t\tif (!data.allow_unborn)\n>> \t\t\t\t; /* we are not accepting the request */\n>> \t\t\telse\n>> \t\t\t\tdata.unborn = 1;\n>> \t\t}\n>> \n>> I wrote the above in longhand only for documentation purposes; in\n>> practice, \n>> \n>> \t\telse if (!strcmp(\"unborn\", arg))\n>>                 \tdata.unborn = data.allow_unborn;\n>> \n>> may suffice.\n>\n> My thinking was (and is) that falling through in the case of a\n> disallowed argument (as opposed to a completely unrecognized argument)\n> makes it more straightforward later if we ever decide to tighten\n> validation of the ls-refs request - we would only have to put some code\n> at the end that reports back to the user.\n\nSorry, I do not quite follow.  If \"unborn\" is conditionally allowed,\nyou can extend what I suggested above like so:\n\n\tif (we see we got an unborn request) {\n-\t\tif (allowed)\n+\t\tif (partially allowed)\n+\t\t\trecord that we got unborn request and will\n+\t\t\tpartially respond to it\n+\t\telse if (allowed)\n\t\t\trecord that we got unborn request;\n+\t\telse\n+\t\t\treport that we don't accept unborn request;\n\t}\n\nThis will matter even more if you write more else-if.  The\ndownstream of else-if clauses are forced to interpret (and fail)\n\"unborn\" request they are not interested in.\n\n>> >  \tif (request->status != PACKET_READ_FLUSH)\n>> >  \t\tdie(_(\"expected flush after ls-refs arguments\"));\n>> >  \n>> > -\thead_ref_namespaced(send_ref, &data);\n>> > +\tsend_possibly_unborn_head(&data);\n>> >  \tfor_each_namespaced_ref(send_ref, &data);\n>> \n>> And here is another caller of send_ref().  Are we sure that\n>> send_ref()'s expectation is satisfied by this caller when the\n>> iteration encounters a broken ref (e.g. refs/heads/broken not a\n>> symref but names an object that does not exist and get_sha1()\n>> yielding 0{40}), or a dangling symref (e.g. refs/remotes/origin/HEAD\n>> pointing at something that does not exist)?\n>\n> I assume that by \"this caller\" you mean for_each_namespaced_ref(), since\n> you mention an iteration. I believe so - send_ref has been changed to\n> tolerate a NULL (as in (void*)0, not 0{40}) oid, and that is the only\n> change, so if it worked previously, it should still work now.\n\nSo a dangling symref, e.g. \"refs/remotes/origin/HEAD -> trunk\" when\nno \"refs/remotes/origin/trunk\" exists, is not reported to send_ref()\nin the same way as an unborn \"HEAD\"?  I would have expected that we'd\nreport where it points at, and for that to work, you'd have to use\nnot just the vanilla send_ref() as the callback, but something that\nknows how to do \"are we expected to send unborn symrefs\" logic, like\nsend_possibly_unborn_head does.\n\nThat \"changed to tolerate ... should work\" worries me.\n\nIf \"for_each_namespaced_ref(send_ref, &data)\" will never call send_ref()\nwith NULL (as in (void *)0) oid, then that would be OK, but if it\nends up calling with NULL somehow, it is responsible to ensure that\ndata->symrefs is true and flag has REF_ISSYMREF set, or send_ref()\nwould misbehave, (see the first part of your message, which I am\nresponding to), no?\n"},{"id":"415627","messageId":"20210130040429.798839-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"87k0rzp3qx.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-30T04:04:29Z","receivedAt":"2021-01-30T04:21:47Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> \n> On Tue, Jan 26 2021, Jonathan Tan wrote:\n> \n> > +If the 'unborn' feature is advertised the following argument can be\n> > +included in the client's request.\n> > +\n> > +    unborn\n> > +\tThe server may send symrefs pointing to unborn branches in the form\n> > +\t\"unborn <refname> symref-target:<target>\".\n> > +\n> \n> \"branches\" as in things under refs/heads/*? What should happen if you\n> send this for a refs/tags/* or refs/xyz/*? Maybe overly pedantic, but it\n> seems we have no other explicit mention of refs/{heads,tags}/ in\n> protocol-v2.txt before this[1].\n> \n> 1. Although as I've learned from another recent thread include-tag is\n>    magical for refs/tags/* only.\n\nThanks for spotting this. Right now the server sends anything, but the\nclient only uses the information if it is a branch. I think this is the\nmost flexible approach so I'll keep it this way and document it\nexplicitly.\n"},{"id":"415628","messageId":"20210130035556.796250-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqqmtwvz4g9.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-30T03:55:56Z","receivedAt":"2021-01-30T04:21:48Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > It feels somewhat brittle that we have to read the same variable and\n> > apply the same \"default to true\" logic in two places and have to\n> > keep them in sync.  Is this because the decision to advertize or not\n> > has to be made way before the code that is specific to the\n> > implementation of ls-refs is run?\n> >\n> > If ls_refs_advertise() is always called first before ls_refs(), I\n> > wonder if it makes sense to reuse what we found out about the\n> > configured (or left unconfigured) state here and use it when\n> > ls_refs() gets called?  I know that the way serve.c infrastructure\n> > calls \"do we advertise?\" helper from each protocol-element handler\n> > is too narrow and does not allow us to pass such a necessary piece\n> > of information but I view it as a misdesign that can be corrected\n> > (and until that happens, we could use file-local static limited to\n> > ls-refs.c).\n> \n> After giving the above a bit more thought, here are a few random\n> thoughts around the area.\n> \n>  * As \"struct protocol_capability\" indicates, we have <name of\n>    service, the logic to advertise, the logic to serve> as a\n>    three-tuple for services.  The serving logic should know what\n>    advertising logic advertised (or more precisely, what information\n>    advertising logic used to make that decision) so that they can\n>    work consistently.\n> \n>    For that, there should be a mechanism that advertising logic can\n>    use to leave a note to serving logic, perhaps by adding a \"void\n>    *\" to both of these functions.  The advertising function would\n>    allocate a piece of memory it wants to use and returns the\n>    pointer to it to the caller in serve.c, and that pointer is given\n>    to the corresponding ls_refs() when it is called by serve.c.\n>    Then ls_refs_advertise can say \"I found this configuration\n>    setting and decided to advertise\" to later ls_refs() and the\n>    latter can say \"ah, as you have advertised, I have to respond to\n>    such a request\".\n\nUsually the advertising is in the same file as the serving (true for\nls-refs and fetch, so far) so I think it's easier if they just\ncommunicate on their own instead of through this \"void *\". I agree with\nthe communication idea, though.\n\n>  * I am not sure if \"lsrefs.allowunborn = yes/no\" is a good way to\n>    configure this feature.  Wouldn't it be more natural to make this\n>    three-way, i.e. \"lsrefs.unborn = advertise/serve/ignore\", where\n>    the server operator can choose among (1) advertise the presence\n>    of the capability and respond to requests, (2) do not advertise\n>    the capability but if a request comes, respond to it, and (3) do\n>    not advertise and do not respond.  We could throw in 'deny' that\n>    causes the request to result in a failure but I do not care too\n>    deeply about that fourth option.\n> \n>    Using such a configuration mechanism, ls_refs_advertise may leave\n>    the value of \"lsrefs.unborn\" (or lack thereof) it found and used\n>    to base its decision to advertise, for use by ls_refs.  ls_refs\n>    in turn can use the value found there to decide if it ignores or\n>    responds to the \"unborn\" request.\n\nlsrefs.unborn = advertise/serve/ignore was how it was in version 2 [1]\n(with different names) and I changed it due to Peff's suggestion, but\nperhaps I landed up in the unhappy middle [2]. I think we should just\npick a standard and use it for this feature and whatever future features\nmay come. The distinction between advertise and serve (\"allow\" in my\nversion 2) is useful in some cases but is useless once migration has\noccurred (and thus clutters the code), but perhaps it could be argued\nthat all servers need to do the migration at least once.\n\n[1] https://lore.kernel.org/git/cover.1608084282.git.jonathantanmy@google.com/\n[2] https://lore.kernel.org/git/YBCitNb75rpnuW2L@coredump.intra.peff.net/\n"},{"id":"415629","messageId":"20210130040613.800403-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq4kj31hzn.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 2/3] connect, transport: encapsulate arg in struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-30T04:06:13Z","receivedAt":"2021-01-30T04:24:07Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\n> > index 58b7c1fbdc..c2d96f4c89 100644\n> > --- a/builtin/fetch-pack.c\n> > +++ b/builtin/fetch-pack.c\n> > @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n> >  \tversion = discover_version(&reader);\n> >  \tswitch (version) {\n> >  \tcase protocol_v2:\n> > -\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n> > +\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n> > +\t\t\t\targs.stateless_rpc);\n> >  \t\tbreak;\n> \n> This seems to be an unrelated line-wrapping, but there are overlong\n> lines that are longer than this line in the same function.\n\nAh...I'll undo this in the next version.\n\n> Everything else looks sensible to change the assumption that a strvec\n> is sufficient for the communication in the codepaths and make it more\n> easily extended by passing a ls-refs-options structure, which makes\n> quite a lot of sense.\n> \n> > diff --git a/transport.h b/transport.h\n> > index 24558c027d..1f5b60e4d3 100644\n> > --- a/transport.h\n> > +++ b/transport.h\n> > @@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n> >  \t\t   struct refspec *rs, int flags,\n> >  \t\t   unsigned int * reject_reasons);\n> >  \n> > +struct transport_ls_refs_options {\n> > +\t/*\n> > +\t * Optionally, a list of ref prefixes can be provided which can be sent\n> > +\t * to the server (when communicating using protocol v2) to enable it to\n> > +\t * limit the ref advertisement.  Since ref filtering is done on the\n> > +\t * server's end (and only when using protocol v2),\n> > +\t * transport_get_remote_refs() could return refs which don't match the\n> > +\t * provided ref_prefixes.\n> > +\t */\n> > +\tstruct strvec ref_prefixes;\n> > +};\n> > +#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n> > +\n> \n> And of course, the first step only carries the strvec we have been\n> passing around, i.e. does not lose or gain features.\n> \n> Looking good.  Thanks.\n\nThanks for taking a look.\n"},{"id":"415630","messageId":"20210130044142.808860-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"87h7n3p363.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-30T04:41:42Z","receivedAt":"2021-01-30T04:56:09Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> On Tue, Jan 26 2021, Jonathan Tan wrote:\n> \n> [For some reason the patches didn't reach my mailbox, but I see them in\n> the list archive, so I'm replying to the cover-letter]\n> \n> >  Documentation/config.txt                |  2 +\n> >  Documentation/config/init.txt           |  2 +-\n> \n> Good, now we have init.defaultBranch docs, but they say:\n>     \n>      init.defaultBranch::\n>             Allows overriding the default branch name e.g. when initializing\n>     -       a new repository or when cloning an empty repository.\n>     +       a new repository.\n> \n> So this still only applies to file:// and other \"protocol\" clones, but\n> not \"git clone /some/path\"?\n\nAh...that's true.\n\n> Re my reply to v1, do we consider that a bug, feature, something just\n> left unimplemented?\n> \n> I really don't care much, but this really needs a corresponding\n> documentation update. I.e. something like:\n> \n>     init.defaultBranch::\n>         Allows overriding the default branch name e.g. when initializing a\n>         new repository or when cloning an empty repository.\n>     \n>         When cloning a repository over protocol v2 (i.e. ssh://, https://,\n>         file://, but not a /some/path), and if that repository has\n>         init.defaultBranch configured, the server will advertise its\n>         preferred default branch name, and we'll take its configuration over\n>         ours.\n\nThanks - I'll use some of your wording, but I think it's best to leave\nopen the possibility that cloning using protocol v0 or the disk clone\n(/some/path) copies over the current HEAD as well.\n\n> Which, just in terms of implementation makes me think it would make more\n> sense if the server just had:\n> \n>     uploadPack.sendConfig = \"init.defaultBranch=xyz\"\n> \n> The client:\n> \n>     receivePack.acceptConfig = \"init.defaultBranch\"\n> \n> And in terms of things on the wire we'd say:\n> \n>     \"set-config init.defaultBranch=main\"\n> \n> You could have many such lines, but we'd just harcode only accepting\n> \"init.defaultBranch\" by default for now.\n> \n> I.e. we set \"init.defaultBranch\" on the server, and the client ends up\n> interpreting things as if though \"init.defaultBranch\" was set to exactly\n> that value. So why not just ... send a line saying \"you should set your\n> init.defaultBranch config to this\".\n> \n> Makes it future-extensible pretty much for free, and I think also much\n> easier to explain to users. I.e. instead of init.defaultBranch somehow\n> being magical when talking with a remote server we can talk about a\n> remote server being one source of config per git-config's documented\n> config order, for a very narrow whitelist of config keys.\n>\n> Or (not clear to me, should have waited with my other E-Mail) are we\n> ever expecting to send more than one of:\n> \n>     \"unborn <refname> symref-target:<target>\"\n> \n> Or is the reason closer to us being able to shoehorn this into the\n> existing ls-refs response, as opposed to some general \"here's config for\n> you\" response we don't have?\n\nIt's not the same - from what I understand, what you're suggesting is\nsetting a config in the repo that has just been cloned, but this patch\nset does not set any such config. Also, it may be strange for the server\nto be able to change the config of a currently running command - I would\nexpect such a thing to only take effect on future runs of Git on that\nrepo.\n"},{"id":"415636","messageId":"20210130042731.804454-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqqzh0vz69k.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-01-30T04:27:31Z","receivedAt":"2021-01-30T09:07:22Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> >  init.defaultBranch::\n> >  \tAllows overriding the default branch name e.g. when initializing\n> > -\ta new repository or when cloning an empty repository.\n> > +\ta new repository.\n> \n> Looking good.\n> \n> > diff --git a/builtin/clone.c b/builtin/clone.c\n> > index 211d4f54b0..77fdc61f4d 100644\n> > --- a/builtin/clone.c\n> > +++ b/builtin/clone.c\n> > @@ -1330,10 +1330,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n> >  \t\tremote_head = NULL;\n> >  \t\toption_no_checkout = 1;\n> >  \t\tif (!option_bare) {\n> > -\t\t\tconst char *branch = git_default_branch_name();\n> > -\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n> > +\t\t\tconst char *branch;\n> > +\t\t\tchar *ref;\n> > +\n> > +\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n> > +\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n> > +\t\t\t\t\t\"refs/heads/\", &branch)) {\n> > +\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n> > +\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n> > +\t\t\t} else {\n> > +\t\t\t\tbranch = git_default_branch_name();\n> > +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n> > +\t\t\t}\n> >  \n> >  \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n> > +\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n> >  \t\t\tfree(ref);\n> \n> OK, we used to say \"point our HEAD always to the local default\n> name\", and the code is still there in the else clause.  But when the\n> transport found what name the other side uses, we use that name\n> instead.\n> \n> I presume that clearing transport_ls_ref_options.unborn_head_target\n> is to take ownership of this piece of memory ourselves?\n\nYes - just to be consistent with the other branch where \"ref\" needs to\nbe freed.\n\n> We didn't call create_symref() in the original code, but now we do.\n> Is this a valid bugfix even if we did not have this \"learn remote\n> symref even for unborn HEAD\" feature?  Or is the original codepath\n> now somehow got broken with an extra create_symref() that we used\n> not to do, but now we do?\n\nAh...now I think I see what you and Peff [1] were saying. Yes I think\nthe symref creation is not necessary when we use the default branch name\n(like we currently do). I'll verify and write back with my findings in\nthe next version.\n\n[1] https://lore.kernel.org/git/YBCf8SI3fK+rDyox@coredump.intra.peff.net/\n\n> \n> > @@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n> >  \tjunk_mode = JUNK_LEAVE_ALL;\n> >  \n> >  \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n> > +\tfree(transport_ls_refs_options.unborn_head_target);\n> >  \treturn err;\n> >  }\n> > diff --git a/connect.c b/connect.c\n> > index 328c279250..879669df93 100644\n> > --- a/connect.c\n> > +++ b/connect.c\n> > @@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n> >  }\n> >  \n> >  /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n> > -static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n> > +static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n> > +\t\t\t  char **unborn_head_target)\n> >  {\n> >  \tint ret = 1;\n> >  \tint i = 0;\n> > @@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n> >  \t\tgoto out;\n> >  \t}\n> >  \n> > +\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n> > +\t\ti++;\n> > +\t\tif (unborn_head_target &&\n> > +\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n> > +\t\t\t/*\n> > +\t\t\t * Look for the symref target (if any). If found,\n> > +\t\t\t * return it to the caller.\n> > +\t\t\t */\n> > +\t\t\tfor (; i < line_sections.nr; i++) {\n> > +\t\t\t\tconst char *arg = line_sections.items[i].string;\n> > +\n> > +\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n> > +\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n> > +\t\t\t\t\tbreak;\n> > +\t\t\t\t}\n> > +\t\t\t}\n> > +\t\t}\n> > +\t\tgoto out;\n> > +\t}\n> \n> We split the line and notice that the first token is \"unborn\"; if\n> the caller is not interested in the unborn head, we just skip the\n> rest, but otherwise, if it is about HEAD (i.e. we do not care if a\n> dangling symref that is not HEAD is reported), we notice the target\n> in unborn_head_target.\n> \n> OK.  We already saw how this is used in cmd_clone().\n> \n> > @@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n> >  \tconst char *hash_name;\n> >  \tstruct strvec *ref_prefixes = transport_options ?\n> >  \t\t&transport_options->ref_prefixes : NULL;\n> > +\tchar **unborn_head_target = transport_options ?\n> > +\t\t&transport_options->unborn_head_target : NULL;\n> \n> So any caller that passes transport_options will get the unborn head\n> information for free?  The other callers are in fetch-pack.c and\n> transport.c, which presumably are about fetching and not cloning.\n> \n> I recall discussions on filling a missing refs/remotes/X/HEAD when\n> we fetch from X and learn where X points at.  Such an extension can\n> be done on top of this mechanism to pass transport_options from the\n> fetch codepath, I presume?\n\nI don't recall those discussions, but I think that we can do that (as\nlong as HEAD points to a branch that is part of the refspec we're\nfetching, because the ref-prefix check still applies).\n\n> Thanks.  I tried to follow the thought in the patches aloud, and it\n> was mostly a pleasant read.\n\nThanks.\n"},{"id":"415652","messageId":"878s8apthr.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"20210130044142.808860-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-01-30T11:13:52Z","receivedAt":"2021-01-30T11:15:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Jan 30 2021, Jonathan Tan wrote:\n\n>> On Tue, Jan 26 2021, Jonathan Tan wrote:\n>> \n>> [For some reason the patches didn't reach my mailbox, but I see them in\n>> the list archive, so I'm replying to the cover-letter]\n>> \n>> >  Documentation/config.txt                |  2 +\n>> >  Documentation/config/init.txt           |  2 +-\n>> \n>> Good, now we have init.defaultBranch docs, but they say:\n>>     \n>>      init.defaultBranch::\n>>             Allows overriding the default branch name e.g. when initializing\n>>     -       a new repository or when cloning an empty repository.\n>>     +       a new repository.\n>> \n>> So this still only applies to file:// and other \"protocol\" clones, but\n>> not \"git clone /some/path\"?\n>\n> Ah...that's true.\n>\n>> Re my reply to v1, do we consider that a bug, feature, something just\n>> left unimplemented?\n>> \n>> I really don't care much, but this really needs a corresponding\n>> documentation update. I.e. something like:\n>> \n>>     init.defaultBranch::\n>>         Allows overriding the default branch name e.g. when initializing a\n>>         new repository or when cloning an empty repository.\n>>     \n>>         When cloning a repository over protocol v2 (i.e. ssh://, https://,\n>>         file://, but not a /some/path), and if that repository has\n>>         init.defaultBranch configured, the server will advertise its\n>>         preferred default branch name, and we'll take its configuration over\n>>         ours.\n>\n> Thanks - I'll use some of your wording, but I think it's best to leave\n> open the possibility that cloning using protocol v0 or the disk clone\n> (/some/path) copies over the current HEAD as well.\n\nSure, and maybe a test_expect_failure for those cases? I.e. to\nexplicitly say in the current docs/tests what does / doesn't work, and\nif we consider that intentional or not.\n\n>> Which, just in terms of implementation makes me think it would make more\n>> sense if the server just had:\n>> \n>>     uploadPack.sendConfig = \"init.defaultBranch=xyz\"\n>> \n>> The client:\n>> \n>>     receivePack.acceptConfig = \"init.defaultBranch\"\n>> \n>> And in terms of things on the wire we'd say:\n>> \n>>     \"set-config init.defaultBranch=main\"\n>> \n>> You could have many such lines, but we'd just harcode only accepting\n>> \"init.defaultBranch\" by default for now.\n>> \n>> I.e. we set \"init.defaultBranch\" on the server, and the client ends up\n>> interpreting things as if though \"init.defaultBranch\" was set to exactly\n>> that value. So why not just ... send a line saying \"you should set your\n>> init.defaultBranch config to this\".\n>> \n>> Makes it future-extensible pretty much for free, and I think also much\n>> easier to explain to users. I.e. instead of init.defaultBranch somehow\n>> being magical when talking with a remote server we can talk about a\n>> remote server being one source of config per git-config's documented\n>> config order, for a very narrow whitelist of config keys.\n>>\n>> Or (not clear to me, should have waited with my other E-Mail) are we\n>> ever expecting to send more than one of:\n>> \n>>     \"unborn <refname> symref-target:<target>\"\n>> \n>> Or is the reason closer to us being able to shoehorn this into the\n>> existing ls-refs response, as opposed to some general \"here's config for\n>> you\" response we don't have?\n>\n> It's not the same - from what I understand, what you're suggesting is\n> setting a config in the repo that has just been cloned[...]\n\nNo, not to set config, i.e. during/after clone doing \"git config\ninit.defaultBranch <remote>\" wouldn't make any sense. Since that would\nset config in .git/config, and that would (also?) apply /after/ the\nclone, e.g. if you did \"git init /tmp/somewhere/else\" afterwards.\n\n> [...]but this patch set does not set any such config[...].\n\nIt does, within the scope of the runtime of the process. I.e. just like\n\"git -c\" or whatever. In builtin/clone.c you set \"branch\" from local\ninit.defaultBranch only if the remote did not provide us a value for it,\ni.e. remote config for that config key overrides local config.\n\n> Also, it may be strange for the server to be able to change the config\n> of a currently running command - I would expect such a thing to only\n> take effect on future runs of Git on that repo.\n\nYes, as I noted on v1 I think the semantics of this whole thing are a\nbit strange :)\n\nBut if we're keeping the \"strangeness\" all I'm saying is that I think\nit's more obvious to a user if we just declare the remote to be a\nlimited config source in tems of explaining this special-case.\n\nAnd that once we're doing that it's also more obvious IMO to have that\nbe what's happening on the protocol level, if we're not expecting more\nthan one of these values.\n\nI.e. if you ignore your current implementation internal and just view\ngit as a black box, then the functionality of this thing is\nindistinguishable from the remote being a (limited) source of config.\n\nSo isn't in simpler to explain it to the user in those terms?\n"},{"id":"415856","messageId":"cover.1612231707.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"[PATCH v6 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:14:58Z","receivedAt":"2021-02-02T02:15:54Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"I don't think we have consensus on whether the \"unborn\" config should be\nadvertise/allow/ignore or allow/ignore, so I have left that the same as\nin version 5.\n\nBut as Junio suggested, I have isolated the reading of config into a\nfunction and some global variables (so that one part can \"leave a note\"\nto the other), so we could have advertise/allow/ignore (if we want)\nwithout making the rest of the code much more complicated.\n\nOther changes:\n - Updated the if/else cascade that parses the input to ls-refs to\n   follow Junio's suggestion - \"else if (!strcmp(\"unborn\", arg)) {\".\n - Moved create_symref() to cover the exact case it's needed.\n - Squashed Ramsay Jones's and Peff's patches.\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: encapsulate arg in struct\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n builtin/clone.c                         | 34 ++++++++++----\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         | 18 +++++---\n builtin/ls-remote.c                     |  9 ++--\n connect.c                               | 32 ++++++++++++--\n ls-refs.c                               | 59 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n remote.h                                |  4 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  8 ++--\n t/t5701-git-serve.sh                    |  2 +-\n t/t5702-protocol-v2.sh                  | 25 +++++++++++\n transport-helper.c                      |  5 ++-\n transport-internal.h                    | 10 +----\n transport.c                             | 23 +++++-----\n transport.h                             | 29 +++++++++---\n 20 files changed, 218 insertions(+), 63 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v5:\n1:  cb033f9abc ! 1:  411bbafe25 ls-refs: report unborn targets of symrefs\n    @@ Documentation/technical/protocol-v2.txt: ls-refs takes in the following argument\n          peeled = \"peeled:\" obj-id\n     \n      ## ls-refs.c ##\n    +@@\n    + #include \"pkt-line.h\"\n    + #include \"config.h\"\n    + \n    ++static int config_read;\n    ++static int allow_unborn;\n    ++\n    ++static void ensure_config_read(void)\n    ++{\n    ++\tif (config_read)\n    ++\t\treturn;\n    ++\n    ++\tif (repo_config_get_bool(the_repository, \"lsrefs.allowunborn\",\n    ++\t\t\t\t &allow_unborn))\n    ++\t\t/*\n    ++\t\t * If there is no such config, set it to 1 to allow it by\n    ++\t\t * default.\n    ++\t\t */\n    ++\t\tallow_unborn = 1;\n    ++\tconfig_read = 1;\n    ++}\n    ++\n    + /*\n    +  * Check if one of the prefixes is a prefix of the ref.\n    +  * If no prefixes were provided, all refs match.\n     @@ ls-refs.c: struct ls_refs_data {\n      \tunsigned peel;\n      \tunsigned symrefs;\n      \tstruct strvec prefixes;\n    -+\tunsigned allow_unborn : 1;\n     +\tunsigned unborn : 1;\n      };\n      \n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n      \tif (data->symrefs && flag & REF_ISSYMREF) {\n      \t\tstruct object_id unused;\n      \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n    +@@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n    + \t\t\t    strip_namespace(symref_target));\n    + \t}\n    + \n    +-\tif (data->peel) {\n    ++\tif (data->peel && oid) {\n    + \t\tstruct object_id peeled;\n    + \t\tif (!peel_ref(refname, &peeled))\n    + \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n     @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n      \treturn 0;\n      }\n      \n    --static int ls_refs_config(const char *var, const char *value, void *data)\n     +static void send_possibly_unborn_head(struct ls_refs_data *data)\n    - {\n    ++{\n     +\tstruct strbuf namespaced = STRBUF_INIT;\n     +\tstruct object_id oid;\n     +\tint flag;\n    @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,\n     +\tstrbuf_release(&namespaced);\n     +}\n     +\n    -+static int ls_refs_config(const char *var, const char *value, void *cb_data)\n    -+{\n    -+\tstruct ls_refs_data *data = cb_data;\n    -+\n    -+\tif (!strcmp(\"lsrefs.allowunborn\", var))\n    -+\t\tdata->allow_unborn = git_config_bool(var, value);\n    -+\n    + static int ls_refs_config(const char *var, const char *value, void *data)\n    + {\n      \t/*\n    - \t * We only serve fetches over v2 for now, so respect only \"uploadpack\"\n    - \t * config. This may need to eventually be expanded to \"receive\", but we\n     @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n      \n      \tmemset(&data, 0, sizeof(data));\n      \n    --\tgit_config(ls_refs_config, NULL);\n    -+\tdata.allow_unborn = 1;\n    -+\tgit_config(ls_refs_config, &data);\n    ++\tensure_config_read();\n    + \tgit_config(ls_refs_config, NULL);\n      \n      \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n    - \t\tconst char *arg = request->line;\n     @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n      \t\t\tdata.symrefs = 1;\n      \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n      \t\t\tstrvec_push(&data.prefixes, out);\n    -+\t\telse if (data.allow_unborn && !strcmp(\"unborn\", arg))\n    -+\t\t\tdata.unborn = 1;\n    ++\t\telse if (!strcmp(\"unborn\", arg))\n    ++\t\t\tdata.unborn = allow_unborn;\n      \t}\n      \n      \tif (request->status != PACKET_READ_FLUSH)\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n     +int ls_refs_advertise(struct repository *r, struct strbuf *value)\n     +{\n     +\tif (value) {\n    -+\t\tint allow_unborn_value;\n    -+\n    -+\t\tif (repo_config_get_bool(the_repository,\n    -+\t\t\t\t\t \"lsrefs.allowunborn\",\n    -+\t\t\t\t\t &allow_unborn_value) ||\n    -+\t\t    allow_unborn_value)\n    ++\t\tensure_config_read();\n    ++\t\tif (allow_unborn)\n     +\t\t\tstrbuf_addstr(value, \"unborn\");\n     +\t}\n     +\n2:  0c7ab71872 ! 2:  fad1ebe6b6 connect, transport: encapsulate arg in struct\n    @@ transport-helper.c: static int has_attribute(const char *attrs, const char *attr\n      \treturn get_refs_list_using_list(transport, for_push);\n     \n      ## transport-internal.h ##\n    +@@\n    + struct ref;\n    + struct transport;\n    + struct strvec;\n    ++struct transport_ls_refs_options;\n    + \n    + struct transport_vtable {\n    + \t/**\n     @@ transport-internal.h: struct transport_vtable {\n      \t * the transport to try to share connections, for_push is a\n      \t * hint as to whether the ultimate operation is a push or a fetch.\n3:  8015415c79 ! 3:  45a48ccc0d clone: respect remote unborn HEAD\n    @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n     +\t\t\t\t\t\"refs/heads/\", &branch)) {\n     +\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n     +\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n    ++\t\t\t\tcreate_symref(\"HEAD\", ref, reflog_msg.buf);\n     +\t\t\t} else {\n     +\t\t\t\tbranch = git_default_branch_name();\n     +\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n     +\t\t\t}\n      \n      \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n    -+\t\t\tcreate_symref(\"HEAD\", ref, \"\");\n      \t\t\tfree(ref);\n    - \t\t}\n    - \t}\n     @@ builtin/clone.c: int cmd_clone(int argc, const char **argv, const char *prefix)\n      \tjunk_mode = JUNK_LEAVE_ALL;\n      \n4:  1c06db6494 < -:  ---------- ls-refs: don't peel NULL oid\n5:  30d83a9dfa < -:  ---------- transport-internal.h: fix a 'hdr-check' warning\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"415857","messageId":"411bbafe25f60c2ef9d9ab773e5eb046b8549b2d.1612231707.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612231707.git.jonathantanmy@google.com","subject":"[PATCH v6 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:14:59Z","receivedAt":"2021-02-02T02:15:55Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\nserver administrators may turn this off through the lsrefs.allowunborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  3 ++\n Documentation/technical/protocol-v2.txt | 10 ++++-\n ls-refs.c                               | 59 +++++++++++++++++++++++--\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n t/t5701-git-serve.sh                    |  2 +-\n 7 files changed, 73 insertions(+), 6 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..dcbec11aaa\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,3 @@\n+lsrefs.allowUnborn::\n+\tAllow the server to send information about unborn symrefs during the\n+\tprotocol v2 ref advertisement.\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..4707511c10 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server may send symrefs pointing to unborn branches in the form\n+\t\"unborn <refname> symref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..daf4e77a4a 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -7,6 +7,24 @@\n #include \"pkt-line.h\"\n #include \"config.h\"\n \n+static int config_read;\n+static int allow_unborn;\n+\n+static void ensure_config_read(void)\n+{\n+\tif (config_read)\n+\t\treturn;\n+\n+\tif (repo_config_get_bool(the_repository, \"lsrefs.allowunborn\",\n+\t\t\t\t &allow_unborn))\n+\t\t/*\n+\t\t * If there is no such config, set it to 1 to allow it by\n+\t\t * default.\n+\t\t */\n+\t\tallow_unborn = 1;\n+\tconfig_read = 1;\n+}\n+\n /*\n  * Check if one of the prefixes is a prefix of the ref.\n  * If no prefixes were provided, all refs match.\n@@ -32,6 +50,7 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +66,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -61,7 +83,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel) {\n+\tif (data->peel && oid) {\n \t\tstruct object_id peeled;\n \t\tif (!peel_ref(refname, &peeled))\n \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n@@ -74,6 +96,23 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n+{\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n+\t\treturn; /* bad ref */\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n static int ls_refs_config(const char *var, const char *value, void *data)\n {\n \t/*\n@@ -91,6 +130,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n+\tensure_config_read();\n \tgit_config(ls_refs_config, NULL);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n@@ -103,14 +143,27 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (!strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = allow_unborn;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tensure_config_read();\n+\t\tif (allow_unborn)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex a1f5fdc9fd..df29504161 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n \tcat >expect <<-EOF &&\n \tversion 2\n \tagent=git/$(git version | cut -d\" \" -f3)\n-\tls-refs\n+\tls-refs=unborn\n \tfetch=shallow\n \tserver-option\n \tobject-format=$(test_oid algo)\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"415858","messageId":"fad1ebe6b6b25e6caac6b9d1b3d7b3f8d5dca1d3.1612231707.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612231707.git.jonathantanmy@google.com","subject":"[PATCH v6 2/3] connect, transport: encapsulate arg in struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:15:00Z","receivedAt":"2021-02-02T02:15:56Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs().\n\nIn preparation for that, encapsulate the existing ref_prefixes\nparameter into a struct. The aforementioned unborn current branch will\ngo into this new struct in the future patch.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clone.c      | 18 +++++++++++-------\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      | 18 +++++++++++-------\n builtin/ls-remote.c  |  9 +++++----\n connect.c            |  4 +++-\n remote.h             |  4 +++-\n transport-helper.c   |  5 +++--\n transport-internal.h | 10 ++--------\n transport.c          | 23 ++++++++++++-----------\n transport.h          | 21 ++++++++++++++-------\n 10 files changed, 66 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a5630337e4..211d4f54b0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -979,7 +979,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint err = 0, complete_refs_before_fetch = 1;\n \tint submodule_progress;\n \n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1257,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport->smart_options->check_self_contained_and_connected = 1;\n \n \n-\tstrvec_push(&ref_prefixes, \"HEAD\");\n-\trefspec_ref_prefixes(&remote->fetch, &ref_prefixes);\n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\trefspec_ref_prefixes(&remote->fetch,\n+\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \tif (option_branch)\n-\t\texpand_ref_prefix(&ref_prefixes, option_branch);\n+\t\texpand_ref_prefix(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t  option_branch);\n \tif (!option_no_tags)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t    \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &transport_ls_refs_options);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1380,6 +1384,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \treturn err;\n }\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..c2d96f4c89 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..837382ef4f 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1349,7 +1349,8 @@ static int do_fetch(struct transport *transport,\n \tint autotags = (transport->remote->fetch_tags == 1);\n \tint retcode = 0;\n \tconst struct ref *remote_refs;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint must_list_refs = 1;\n \n \tif (tags == TAGS_DEFAULT) {\n@@ -1369,7 +1370,7 @@ static int do_fetch(struct transport *transport,\n \tif (rs->nr) {\n \t\tint i;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);\n \n \t\t/*\n \t\t * We can avoid listing refs if all of them are exact\n@@ -1383,22 +1384,25 @@ static int do_fetch(struct transport *transport,\n \t\t\t}\n \t\t}\n \t} else if (transport->remote && transport->remote->fetch.nr)\n-\t\trefspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);\n+\t\trefspec_ref_prefixes(&transport->remote->fetch,\n+\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \n \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n \t\tmust_list_refs = 1;\n-\t\tif (ref_prefixes.nr)\n-\t\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n+\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t    \"refs/tags/\");\n \t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport,\n+\t\t\t\t\t\t\t&transport_ls_refs_options);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \n \tref_map = get_ref_map(transport->remote, remote_refs, rs,\n \t\t\t      tags, &autotags);\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..ef604752a0 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tint show_symref_target = 0;\n \tconst char *uploadpack = NULL;\n \tconst char **pattern = NULL;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint i;\n \tstruct string_list server_options = STRING_LIST_INIT_DUP;\n \n@@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (flags & REF_TAGS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/tags/\");\n \tif (flags & REF_HEADS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/heads/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/heads/\");\n \n \tremote = remote_get(dest);\n \tif (!remote) {\n@@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &transport_options);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..328c279250 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -453,12 +453,14 @@ void check_stateless_delimiter(int stateless_rpc,\n \n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc)\n {\n \tint i;\n \tconst char *hash_name;\n+\tstruct strvec *ref_prefixes = transport_options ?\n+\t\t&transport_options->ref_prefixes : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..4ae676a11b 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -6,6 +6,8 @@\n #include \"hashmap.h\"\n #include \"refspec.h\"\n \n+struct transport_ls_refs_options;\n+\n /**\n  * The API gives access to the configuration related to remotes. It handles\n  * all three configuration mechanisms historically and currently used by Git,\n@@ -196,7 +198,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n /* Used for protocol v2 in order to retrieve refs from a remote */\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..49b7fb4dcb 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,14 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t struct transport_ls_refs_options *transport_options)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\ttransport_options);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..b60f1ba907 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -4,6 +4,7 @@\n struct ref;\n struct transport;\n struct strvec;\n+struct transport_ls_refs_options;\n \n struct transport_vtable {\n \t/**\n@@ -18,19 +19,12 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     struct transport_ls_refs_options *transport_options);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..b13fab5dc3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,7 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *transport_options)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -280,7 +280,7 @@ static void die_if_server_options(struct transport *transport)\n  * remote refs.\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *options,\n \t\t\t     int must_list_refs)\n {\n \tstruct git_transport_data *data = transport->data;\n@@ -303,7 +303,7 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\ttrace2_data_string(\"transfer\", NULL, \"server-sid\", server_sid);\n \t\tif (must_list_refs)\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n-\t\t\t\t\tref_prefixes,\n+\t\t\t\t\toptions,\n \t\t\t\t\ttransport->server_options,\n \t\t\t\t\ttransport->stateless_rpc);\n \t\tbreak;\n@@ -334,9 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, options, 1);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -1252,19 +1252,20 @@ int transport_push(struct repository *r,\n \t\tint porcelain = flags & TRANSPORT_PUSH_PORCELAIN;\n \t\tint pretend = flags & TRANSPORT_PUSH_DRY_RUN;\n \t\tint push_ret, ret, err;\n-\t\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\t\tstruct transport_ls_refs_options transport_options =\n+\t\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \t\tif (check_push_refs(local_refs, rs) < 0)\n \t\t\treturn -1;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_options.ref_prefixes);\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &transport_options);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n-\t\tstrvec_clear(&ref_prefixes);\n+\t\tstrvec_clear(&transport_options.ref_prefixes);\n \n \t\tif (flags & TRANSPORT_PUSH_ALL)\n \t\t\tmatch_flags |= MATCH_REFS_ALL;\n@@ -1380,12 +1381,12 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t transport_options);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..1f5b60e4d3 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n \t\t   struct refspec *rs, int flags,\n \t\t   unsigned int * reject_reasons);\n \n+struct transport_ls_refs_options {\n+\t/*\n+\t * Optionally, a list of ref prefixes can be provided which can be sent\n+\t * to the server (when communicating using protocol v2) to enable it to\n+\t * limit the ref advertisement.  Since ref filtering is done on the\n+\t * server's end (and only when using protocol v2),\n+\t * transport_get_remote_refs() could return refs which don't match the\n+\t * provided ref_prefixes.\n+\t */\n+\tstruct strvec ref_prefixes;\n+};\n+#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n+\n /*\n  * Retrieve refs from a remote.\n- *\n- * Optionally a list of ref prefixes can be provided which can be sent to the\n- * server (when communicating using protocol v2) to enable it to limit the ref\n- * advertisement.  Since ref filtering is done on the server's end (and only\n- * when using protocol v2), this can return refs which don't match the provided\n- * ref_prefixes.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"415859","messageId":"45a48ccc0d828db6107862620dd14128c50368e4.1612231707.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612231707.git.jonathantanmy@google.com","subject":"[PATCH v6 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:15:01Z","receivedAt":"2021-02-02T02:16:02Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 16 ++++++++++++++--\n connect.c                     | 28 ++++++++++++++++++++++++++--\n t/t5606-clone-options.sh      |  8 +++++---\n t/t5702-protocol-v2.sh        | 25 +++++++++++++++++++++++++\n transport.h                   |  8 ++++++++\n 6 files changed, 79 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 211d4f54b0..09dcd97a2e 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1330,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n+\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n+\t\t\t\t\t\"refs/heads/\", &branch)) {\n+\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n+\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n+\t\t\t\tcreate_symref(\"HEAD\", ref, reflog_msg.buf);\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n \t\t\tfree(ref);\n@@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n+\tfree(transport_ls_refs_options.unborn_head_target);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 328c279250..879669df93 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tconst char *hash_name;\n \tstruct strvec *ref_prefixes = transport_options ?\n \t\t&transport_options->ref_prefixes : NULL;\n+\tchar **unborn_head_target = transport_options ?\n+\t\t&transport_options->unborn_head_target : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\n@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..0111d4e8bd 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.allowUnborn true &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..a8ef92b644 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if explicitly forbidden by config' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.allowUnborn false &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\t! grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \ndiff --git a/transport.h b/transport.h\nindex 1f5b60e4d3..24e15799e7 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -243,6 +243,14 @@ struct transport_ls_refs_options {\n \t * provided ref_prefixes.\n \t */\n \tstruct strvec ref_prefixes;\n+\n+\t/*\n+\t * If unborn_head_target is not NULL, and the remote reports HEAD as\n+\t * pointing to an unborn branch, transport_get_remote_refs() stores the\n+\t * unborn branch in unborn_head_target. It should be freed by the\n+\t * caller.\n+\t */\n+\tchar *unborn_head_target;\n };\n #define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n \n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"415860","messageId":"20210202022024.1791749-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqqtuqzs8m5.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:20:24Z","receivedAt":"2021-02-02T02:21:10Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> So a dangling symref, e.g. \"refs/remotes/origin/HEAD -> trunk\" when\n> no \"refs/remotes/origin/trunk\" exists, is not reported to send_ref()\n> in the same way as an unborn \"HEAD\"?\n\nI've tried it, and yes, for_each_namespaced_ref() will not report it.\nLooking at the code, I think it is packed_ref_iterator_advance() which\nchecks for broken objects and skips over them.\n\n> I would have expected that we'd\n> report where it points at, and for that to work, you'd have to use\n> not just the vanilla send_ref() as the callback, but something that\n> knows how to do \"are we expected to send unborn symrefs\" logic, like\n> send_possibly_unborn_head does.\n> \n> That \"changed to tolerate ... should work\" worries me.\n> \n> If \"for_each_namespaced_ref(send_ref, &data)\" will never call send_ref()\n> with NULL (as in (void *)0) oid, then that would be OK,\n\nIf it called send_ref() with (void *)0 OID, it would segfault with the\ncurrent code, which calls oid_to_hex() on the OID unconditionally.\n\n> but if it\n> ends up calling with NULL somehow, it is responsible to ensure that\n> data->symrefs is true and flag has REF_ISSYMREF set, or send_ref()\n> would misbehave, (see the first part of your message, which I am\n> responding to), no?\n\nIf it did, then yes.\n"},{"id":"415861","messageId":"20210202022254.1793991-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"20210130044142.808860-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T02:22:54Z","receivedAt":"2021-02-02T02:23:43Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > I really don't care much, but this really needs a corresponding\n> > documentation update. I.e. something like:\n> > \n> >     init.defaultBranch::\n> >         Allows overriding the default branch name e.g. when initializing a\n> >         new repository or when cloning an empty repository.\n> >     \n> >         When cloning a repository over protocol v2 (i.e. ssh://, https://,\n> >         file://, but not a /some/path), and if that repository has\n> >         init.defaultBranch configured, the server will advertise its\n> >         preferred default branch name, and we'll take its configuration over\n> >         ours.\n> \n> Thanks - I'll use some of your wording, but I think it's best to leave\n> open the possibility that cloning using protocol v0 or the disk clone\n> (/some/path) copies over the current HEAD as well.\n\nLooking back on this, I think that it's natural to think that both an\nempty repository and a non-empty one have a HEAD that points somewhere,\nand \"git clone\" would behave the same way in both cases. So I'll hold\noff on the documentation change.\n"},{"id":"415881","messageId":"xmqqk0rrhxn7.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20210202022024.1791749-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T05:00:28Z","receivedAt":"2021-02-02T05:01:16Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n>> So a dangling symref, e.g. \"refs/remotes/origin/HEAD -> trunk\" when\n>> no \"refs/remotes/origin/trunk\" exists, is not reported to send_ref()\n>> in the same way as an unborn \"HEAD\"?\n>\n> I've tried it, and yes, for_each_namespaced_ref() will not report it.\n> Looking at the code, I think it is packed_ref_iterator_advance() which\n> checks for broken objects and skips over them.\n>\n>> I would have expected that we'd\n>> report where it points at, and for that to work, you'd have to use\n>> not just the vanilla send_ref() as the callback, but something that\n>> knows how to do \"are we expected to send unborn symrefs\" logic, like\n>> send_possibly_unborn_head does.\n>> \n>> That \"changed to tolerate ... should work\" worries me.\n>> \n>> If \"for_each_namespaced_ref(send_ref, &data)\" will never call send_ref()\n>> with NULL (as in (void *)0) oid, then that would be OK,\n>\n> If it called send_ref() with (void *)0 OID, it would segfault with the\n> current code, which calls oid_to_hex() on the OID unconditionally.\n>\n>> but if it\n>> ends up calling with NULL somehow, it is responsible to ensure that\n>> data->symrefs is true and flag has REF_ISSYMREF set, or send_ref()\n>> would misbehave, (see the first part of your message, which I am\n>> responding to), no?\n>\n> If it did, then yes.\n\nSo, in other words, the series is *only* about HEAD and no other\nsymrefs are reported when dangling as \"unborn\"?\n"},{"id":"415936","messageId":"xmqq4kiuif4d.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"411bbafe25f60c2ef9d9ab773e5eb046b8549b2d.1612231707.git.jonathantanmy@google.com","subject":"Re: [PATCH v6 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T16:55:14Z","receivedAt":"2021-02-02T16:59:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> When cloning, we choose the default branch based on the remote HEAD.\n> But if there is no remote HEAD reported (which could happen if the\n> target of the remote HEAD is unborn), we'll fall back to using our local\n> init.defaultBranch. Traditionally this hasn't been a big deal, because\n> most repos used \"master\" as the default. But these days it is likely to\n> cause confusion if the server and client implementations choose\n> different values (e.g., if the remote started with \"main\", we may choose\n> \"master\" locally, create commits there, and then the user is surprised\n> when they push to \"master\" and not \"main\").\n> ...\n> The client side will be updated to use this in a subsequent commit.\n\nNicely explained.\n\n> diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\n> index 85daeb5d9e..4707511c10 100644\n> --- a/Documentation/technical/protocol-v2.txt\n> +++ b/Documentation/technical/protocol-v2.txt\n> @@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n>  \tWhen specified, only references having a prefix matching one of\n>  \tthe provided prefixes are displayed.\n>  \n> +If the 'unborn' feature is advertised the following argument can be\n> +included in the client's request.\n> +\n> +    unborn\n> +\tThe server may send symrefs pointing to unborn branches in the form\n> +\t\"unborn <refname> symref-target:<target>\".\n> +\n\nI somehow had an impression that this is done only for HEAD and no\nother symrefs.\n\nIf this describes the ideal endgame state and the implementation at\nthe moment only covers what is practically the most useful (i.e.\nHEAD), that is fine.\n\nIf we do not plan to support symrefs other than HEAD that are\ndangling, that is fine as well, but then the description needs\nupdating, no?\n"},{"id":"415947","messageId":"20210202183453.1907985-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq4kiuif4d.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-02T18:34:53Z","receivedAt":"2021-02-02T18:38:13Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> > diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\n> > index 85daeb5d9e..4707511c10 100644\n> > --- a/Documentation/technical/protocol-v2.txt\n> > +++ b/Documentation/technical/protocol-v2.txt\n> > @@ -192,11 +192,19 @@ ls-refs takes in the following arguments:\n> >  \tWhen specified, only references having a prefix matching one of\n> >  \tthe provided prefixes are displayed.\n> >  \n> > +If the 'unborn' feature is advertised the following argument can be\n> > +included in the client's request.\n> > +\n> > +    unborn\n> > +\tThe server may send symrefs pointing to unborn branches in the form\n> > +\t\"unborn <refname> symref-target:<target>\".\n> > +\n> \n> I somehow had an impression that this is done only for HEAD and no\n> other symrefs.\n\nRight now, that's true.\n\n> If this describes the ideal endgame state and the implementation at\n> the moment only covers what is practically the most useful (i.e.\n> HEAD), that is fine.\n> \n> If we do not plan to support symrefs other than HEAD that are\n> dangling, that is fine as well, but then the description needs\n> updating, no?\n\nI'm not sure what the ideal endgame state is, but I could see how\nsending all symlinks would be useful (e.g. if a client wanted to mirror\nanother repo with more fidelity). Right now I don't plan on adding\nsupport for dangling symrefs other than HEAD, though. Maybe I'll update\nit to something like:\n\n  If HEAD is a symref pointing to an unborn branch, the server may send\n  it in the form \"unborn HEAD symref-target:<target>\". In the future,\n  this may be extended to other symrefs as well.\n\nI think that there is a discussion point to be decided\n(advertise/allow/ignore vs allow/ignore), so I'll wait for that before\nsending v7.\n"},{"id":"415968","messageId":"xmqq1rdyf71k.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"20210202183453.1907985-1-jonathantanmy@google.com","subject":"Re: [PATCH v6 1/3] ls-refs: report unborn targets of symrefs","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-02T22:17:59Z","receivedAt":"2021-02-02T22:20:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> I'm not sure what the ideal endgame state is, but I could see how\n> sending all symlinks would be useful (e.g. if a client wanted to mirror\n> another repo with more fidelity). Right now I don't plan on adding\n> support for dangling symrefs other than HEAD, though. Maybe I'll update\n> it to something like:\n>\n>   If HEAD is a symref pointing to an unborn branch, the server may send\n>   it in the form \"unborn HEAD symref-target:<target>\". In the future,\n>   this may be extended to other symrefs as well.\n\nUnless you plan to add support for symbolic refs that are not HEAD\nin immediate future, \"In the future, ...\" is not even necessary to\nsay.  The users cannot expect to exploit the missing feature anyway,\nand they cannot even plan to use it in near future.\n\nI've been disturbed by the phrase \"the server may send it\" quite a\nlot, actually.  In the original before the rewrite above, it was a\ngood cop-out excuse \"no, we do not send symbolic refs other than\nHEAD because we only say 'the server may' and do not promise\nanything beyond that\".  But now we are tightening the description\nto HEAD that we do intend to support well, it probably is a good\nidea to give users a promise a bit firmer than that.\n\n    unborn If HEAD is a symref pointing to an unborn branch <b>, the\n    server reports it as \"unborn HEAD symref-target:refs/heads/<b>\"\n    in its response.\n\nIt would make it clear that by sending 'unborn' in the request, the\nclient is not just allowing the server to include the unborn\ninformation in the response.  It is asking the server, that has\nadvertised that it is capable to do so, to exercise the feature.\n\n> I think that there is a discussion point to be decided\n> (advertise/allow/ignore vs allow/ignore), so I'll wait for that before\n> sending v7.\n\nWhat is the downside of having three choices (which allows phased\ndeployment, where everybody starts as capable of responding without\nadvertising in the first phase, and once everybody becomes capable\nof responding, they start advertising) and the reason we might\nprefer just allow/ignore instead?  Too much complexity?  It does not\nhelp the real deployment as much in practice as it seems on paper?\n\nI am not advocating three-choice option; I am neutral, but do not\nsee a good reason to reject it (while I can easily see a reason to\nreject the other one).\n\nThanks.\n"},{"id":"415983","messageId":"20210203010425.2097136-1-jonathantanmy@google.com","threadId":"54790","inReplyTo":"xmqq1rdyf71k.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v6 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-03T01:04:25Z","receivedAt":"2021-02-03T01:05:22Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > I'm not sure what the ideal endgame state is, but I could see how\n> > sending all symlinks would be useful (e.g. if a client wanted to mirror\n> > another repo with more fidelity). Right now I don't plan on adding\n> > support for dangling symrefs other than HEAD, though. Maybe I'll update\n> > it to something like:\n> >\n> >   If HEAD is a symref pointing to an unborn branch, the server may send\n> >   it in the form \"unborn HEAD symref-target:<target>\". In the future,\n> >   this may be extended to other symrefs as well.\n> \n> Unless you plan to add support for symbolic refs that are not HEAD\n> in immediate future, \"In the future, ...\" is not even necessary to\n> say.  The users cannot expect to exploit the missing feature anyway,\n> and they cannot even plan to use it in near future.\n> \n> I've been disturbed by the phrase \"the server may send it\" quite a\n> lot, actually.  In the original before the rewrite above, it was a\n> good cop-out excuse \"no, we do not send symbolic refs other than\n> HEAD because we only say 'the server may' and do not promise\n> anything beyond that\".  But now we are tightening the description\n> to HEAD that we do intend to support well, it probably is a good\n> idea to give users a promise a bit firmer than that.\n> \n>     unborn If HEAD is a symref pointing to an unborn branch <b>, the\n>     server reports it as \"unborn HEAD symref-target:refs/heads/<b>\"\n>     in its response.\n> \n> It would make it clear that by sending 'unborn' in the request, the\n> client is not just allowing the server to include the unborn\n> information in the response.  It is asking the server, that has\n> advertised that it is capable to do so, to exercise the feature.\n\nThat makes sense. OK, I'll make the promise firmer.\n\n> > I think that there is a discussion point to be decided\n> > (advertise/allow/ignore vs allow/ignore), so I'll wait for that before\n> > sending v7.\n> \n> What is the downside of having three choices (which allows phased\n> deployment, where everybody starts as capable of responding without\n> advertising in the first phase, and once everybody becomes capable\n> of responding, they start advertising) and the reason we might\n> prefer just allow/ignore instead?  Too much complexity?  It does not\n> help the real deployment as much in practice as it seems on paper?\n> \n> I am not advocating three-choice option; I am neutral, but do not\n> see a good reason to reject it (while I can easily see a reason to\n> reject the other one).\n\nHere's a reason from Peff's email [1] against advertise/allow/ignore (the \"code\nchange\" is a temporary hack that teaches Git to accept but not advertise\nreport-status-v2). Granted, he does say that this may be an oversimplification,\nand in the overall email, he was arguing more for having this feature on by\ndefault (whether we have advertise/allow/ignore, allow/ignore, or no config at\nall) rather than for any specific configuration scheme.\n\n  - one nice thing about the code change is that after the rollout is\n    done, it's safe to make the code unconditional again, which makes\n    it simpler to read/reason about.\n\n    This may be oversimplifying it a bit, of course. On one platform, we\n    know when the rollout is happening. But if it's something we ship\n    upstream, then \"rollout\" may be on the jump from v2.28 to v2.29, or\n    to v2.30, or v2.31, etc. You can never say \"rollouts are done, and\n    existing server versions know about this feature\". So any upstream\n    support like config has to stay forever.\n\nTo balance that out, from the same email [1], a slight argument against no\nconfig at all:\n\n  (I know there was also an indication that some people might want it off\n  because they somehow want to have no HEAD at all. I don't find this\n  particularly compelling, but even if it were, I think we could leave it\n  the config as an escape hatch for such folks, but still default it to\n  \"on\").\n\n[1] https://lore.kernel.org/git/X9xJLWdFJfNJTn0p@coredump.intra.peff.net/\n\nAnd an argument against the allow/ignore [2]:\n\n  If we are not going to support config that helps you do an atomic\n  deploy, then I don't really see the point of having config at all.\n  Here are three plausible implementations I can conceive of:\n  \n    - allowUnborn is a tri-state for \"accept-but-do-not-advertise\",\n      \"accept-and-advertise\", and \"disallow\". This helps with rollout in a\n      cluster by setting it to the accept-but-do-not-advertise.  The\n      default would be accept-and-advertise, which is what most servers\n      would want. I don't really know why anyone would want \"disallow\".\n  \n    - allowUnborn is a bool for \"accept-and-advertise\" or \"disallow\". This\n      doesn't help cluster rollout. I don't know why anyone would want to\n      switch away from the default of accept-and-advertise.\n  \n    - allowUnborn is always on.\n  \n  The first one helps the cluster case, at the cost of introducing an\n  extra config knob. The third one doesn't help that case, but is one less\n  knob for server admins to think about. But the second one has a knob\n  that I don't understand why anybody would tweak. It seems like the worst\n  of both.\n\n[2] https://lore.kernel.org/git/YBCitNb75rpnuW2L@coredump.intra.peff.net/\n"},{"id":"416042","messageId":"877dnpdybo.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"20210202022254.1793991-1-jonathantanmy@google.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-02-03T14:23:55Z","receivedAt":"2021-02-03T14:25:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Feb 02 2021, Jonathan Tan wrote:\n\n>> > I really don't care much, but this really needs a corresponding\n>> > documentation update. I.e. something like:\n>> > \n>> >     init.defaultBranch::\n>> >         Allows overriding the default branch name e.g. when initializing a\n>> >         new repository or when cloning an empty repository.\n>> >     \n>> >         When cloning a repository over protocol v2 (i.e. ssh://, https://,\n>> >         file://, but not a /some/path), and if that repository has\n>> >         init.defaultBranch configured, the server will advertise its\n>> >         preferred default branch name, and we'll take its configuration over\n>> >         ours.\n>> \n>> Thanks - I'll use some of your wording, but I think it's best to leave\n>> open the possibility that cloning using protocol v0 or the disk clone\n>> (/some/path) copies over the current HEAD as well.\n>\n> Looking back on this, I think that it's natural to think that both an\n> empty repository and a non-empty one have a HEAD that points somewhere,\n> and \"git clone\" would behave the same way in both cases. So I'll hold\n> off on the documentation change.\n\nYou mean for a v6 it'll do the same thing in the local clone case too\nand thus we won't need to document the exception? Sounds good.\n\nI was mainly pointing out the need to document the current divergent\nbehavior.\n\nDocumenting that something isn't consistent shouldn't be seen as a\nblessing that the divergence is a good idea, it's an aid to our users so\nthey can understand why their git version does X when they might be\nexpecting Y.\n"},{"id":"416234","messageId":"cover.1612493309.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"[PATCH v7 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T04:58:30Z","receivedAt":"2021-02-05T04:59:27Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"For what it's worth, here's v7 with advertise/allow/ignore and by\ndefault, advertise. I think that some server operators will have use for\nthis feature, and people who want to disable it for whatever reason can\nstill do so. The main disadvantage is complexity - the server knob that\nserver administrators will need to control (but between a simpler\nallow/ignore knob and a more complicated advertise/allow/ignore knob, I\nthink we might as well go for the slightly more complex one) and\ncomplexity in the code (but now that is constrained to one function and\na few global variables).\n\nAs you can see from the range-diff, not much has changed from v6.\n\nI've also included Junio's suggestion of tightening the promise made by\nthe server (when the client says \"unborn\").\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: encapsulate arg in struct\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  9 +++\n Documentation/technical/protocol-v2.txt | 11 +++-\n builtin/clone.c                         | 34 +++++++++---\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         | 18 +++---\n builtin/ls-remote.c                     |  9 +--\n connect.c                               | 32 ++++++++++-\n ls-refs.c                               | 74 ++++++++++++++++++++++++-\n ls-refs.h                               |  1 +\n remote.h                                |  4 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  8 ++-\n t/t5701-git-serve.sh                    |  2 +-\n t/t5702-protocol-v2.sh                  | 25 +++++++++\n transport-helper.c                      |  5 +-\n transport-internal.h                    | 10 +---\n transport.c                             | 23 ++++----\n transport.h                             | 29 +++++++---\n 20 files changed, 240 insertions(+), 63 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v6:\n1:  411bbafe25 ! 1:  2d35075369 ls-refs: report unborn targets of symrefs\n    @@ Commit message\n         Currently, symrefs that have unborn targets (such as in this case) are\n         not communicated by the protocol. Teach Git to advertise and support the\n         \"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\n    -    server administrators may turn this off through the lsrefs.allowunborn\n    +    server administrators may turn this off through the lsrefs.unborn\n         config). This feature indicates that \"ls-refs\" supports the \"unborn\"\n         argument; when it is specified, \"ls-refs\" will send the HEAD symref with\n         the name of its unborn target.\n    @@ Documentation/config.txt: include::config/interactive.txt[]\n     \n      ## Documentation/config/lsrefs.txt (new) ##\n     @@\n    -+lsrefs.allowUnborn::\n    -+\tAllow the server to send information about unborn symrefs during the\n    -+\tprotocol v2 ref advertisement.\n    ++lsrefs.unborn::\n    ++\tMay be \"advertise\" (the default), \"allow\", or \"ignore\". If \"advertise\",\n    ++\tthe server will respond to the client sending \"unborn\" (as described in\n    ++\tprotocol-v2.txt) and will advertise support for this feature during the\n    ++\tprotocol v2 capability advertisement. \"allow\" is the same as\n    ++\t\"advertise\" except that the server will not advertise support for this\n    ++\tfeature; this is useful for load-balanced servers that cannot be\n    ++\tupdated automatically (for example), since the administrator could\n    ++\tconfigure \"allow\", then after a delay, configure \"advertise\".\n     \n      ## Documentation/technical/protocol-v2.txt ##\n     @@ Documentation/technical/protocol-v2.txt: ls-refs takes in the following arguments:\n    @@ Documentation/technical/protocol-v2.txt: ls-refs takes in the following argument\n     +included in the client's request.\n     +\n     +    unborn\n    -+\tThe server may send symrefs pointing to unborn branches in the form\n    -+\t\"unborn <refname> symref-target:<target>\".\n    ++\tThe server will send information about HEAD even if it is a symref\n    ++\tpointing to an unborn branch in the form \"unborn HEAD\n    ++\tsymref-target:<target>\".\n     +\n      The output of ls-refs is as follows:\n      \n    @@ ls-refs.c\n      #include \"config.h\"\n      \n     +static int config_read;\n    ++static int advertise_unborn;\n     +static int allow_unborn;\n     +\n     +static void ensure_config_read(void)\n     +{\n    ++\tchar *str = NULL;\n    ++\n     +\tif (config_read)\n     +\t\treturn;\n     +\n    -+\tif (repo_config_get_bool(the_repository, \"lsrefs.allowunborn\",\n    -+\t\t\t\t &allow_unborn))\n    ++\tif (repo_config_get_string(the_repository, \"lsrefs.unborn\", &str)) {\n     +\t\t/*\n    -+\t\t * If there is no such config, set it to 1 to allow it by\n    ++\t\t * If there is no such config, advertise and allow it by\n     +\t\t * default.\n     +\t\t */\n    ++\t\tadvertise_unborn = 1;\n     +\t\tallow_unborn = 1;\n    ++\t} else {\n    ++\t\tif (!strcmp(str, \"advertise\")) {\n    ++\t\t\tadvertise_unborn = 1;\n    ++\t\t\tallow_unborn = 1;\n    ++\t\t} else if (!strcmp(str, \"allow\")) {\n    ++\t\t\tallow_unborn = 1;\n    ++\t\t} else if (!strcmp(str, \"ignore\")) {\n    ++\t\t\t/* do nothing */\n    ++\t\t} else {\n    ++\t\t\tdie(_(\"invalid value '%s' for lsrefs.unborn\"), str);\n    ++\t\t}\n    ++\t}\n     +\tconfig_read = 1;\n     +}\n     +\n    @@ ls-refs.c: int ls_refs(struct repository *r, struct strvec *keys,\n     +{\n     +\tif (value) {\n     +\t\tensure_config_read();\n    -+\t\tif (allow_unborn)\n    ++\t\tif (advertise_unborn)\n     +\t\t\tstrbuf_addstr(value, \"unborn\");\n     +\t}\n     +\n2:  fad1ebe6b6 = 2:  d4ed13d02e connect, transport: encapsulate arg in struct\n3:  45a48ccc0d ! 3:  a3e5a0a7c5 clone: respect remote unborn HEAD\n    @@ t/t5606-clone-options.sh: test_expect_success 'redirected clone -v does show pro\n     -\tgit init --bare empty &&\n     +\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=foo init --bare empty &&\n    -+\ttest_config -C empty lsrefs.allowUnborn true &&\n    ++\ttest_config -C empty lsrefs.unborn advertise &&\n      \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n      \tgit -c init.defaultBranch=up clone empty whats-up &&\n     -\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n    @@ t/t5702-protocol-v2.sh: test_expect_success 'clone with file:// using protocol v\n     +\n     +\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n    -+\ttest_config -C file_empty_parent lsrefs.allowUnborn false &&\n    ++\ttest_config -C file_empty_parent lsrefs.unborn ignore &&\n     +\n     +\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n     +\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416235","messageId":"2d3507536924346637bb138af5a3562ce8f2abc2.1612493309.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612493309.git.jonathantanmy@google.com","subject":"[PATCH v7 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T04:58:31Z","receivedAt":"2021-02-05T04:59:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\nserver administrators may turn this off through the lsrefs.unborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  9 +++\n Documentation/technical/protocol-v2.txt | 11 +++-\n ls-refs.c                               | 74 ++++++++++++++++++++++++-\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n t/t5701-git-serve.sh                    |  2 +-\n 7 files changed, 95 insertions(+), 6 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..e003856c08\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,9 @@\n+lsrefs.unborn::\n+\tMay be \"advertise\" (the default), \"allow\", or \"ignore\". If \"advertise\",\n+\tthe server will respond to the client sending \"unborn\" (as described in\n+\tprotocol-v2.txt) and will advertise support for this feature during the\n+\tprotocol v2 capability advertisement. \"allow\" is the same as\n+\t\"advertise\" except that the server will not advertise support for this\n+\tfeature; this is useful for load-balanced servers that cannot be\n+\tupdated automatically (for example), since the administrator could\n+\tconfigure \"allow\", then after a delay, configure \"advertise\".\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..f772d90eaf 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,20 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server will send information about HEAD even if it is a symref\n+\tpointing to an unborn branch in the form \"unborn HEAD\n+\tsymref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..e08fd43e7a 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -7,6 +7,39 @@\n #include \"pkt-line.h\"\n #include \"config.h\"\n \n+static int config_read;\n+static int advertise_unborn;\n+static int allow_unborn;\n+\n+static void ensure_config_read(void)\n+{\n+\tchar *str = NULL;\n+\n+\tif (config_read)\n+\t\treturn;\n+\n+\tif (repo_config_get_string(the_repository, \"lsrefs.unborn\", &str)) {\n+\t\t/*\n+\t\t * If there is no such config, advertise and allow it by\n+\t\t * default.\n+\t\t */\n+\t\tadvertise_unborn = 1;\n+\t\tallow_unborn = 1;\n+\t} else {\n+\t\tif (!strcmp(str, \"advertise\")) {\n+\t\t\tadvertise_unborn = 1;\n+\t\t\tallow_unborn = 1;\n+\t\t} else if (!strcmp(str, \"allow\")) {\n+\t\t\tallow_unborn = 1;\n+\t\t} else if (!strcmp(str, \"ignore\")) {\n+\t\t\t/* do nothing */\n+\t\t} else {\n+\t\t\tdie(_(\"invalid value '%s' for lsrefs.unborn\"), str);\n+\t\t}\n+\t}\n+\tconfig_read = 1;\n+}\n+\n /*\n  * Check if one of the prefixes is a prefix of the ref.\n  * If no prefixes were provided, all refs match.\n@@ -32,6 +65,7 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +81,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -61,7 +98,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel) {\n+\tif (data->peel && oid) {\n \t\tstruct object_id peeled;\n \t\tif (!peel_ref(refname, &peeled))\n \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n@@ -74,6 +111,23 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n+{\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n+\t\treturn; /* bad ref */\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n static int ls_refs_config(const char *var, const char *value, void *data)\n {\n \t/*\n@@ -91,6 +145,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n+\tensure_config_read();\n \tgit_config(ls_refs_config, NULL);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n@@ -103,14 +158,27 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (!strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = allow_unborn;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tensure_config_read();\n+\t\tif (advertise_unborn)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex a1f5fdc9fd..df29504161 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n \tcat >expect <<-EOF &&\n \tversion 2\n \tagent=git/$(git version | cut -d\" \" -f3)\n-\tls-refs\n+\tls-refs=unborn\n \tfetch=shallow\n \tserver-option\n \tobject-format=$(test_oid algo)\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416236","messageId":"d4ed13d02e589ed8b10ebe719dfd2ef4a326fbab.1612493309.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612493309.git.jonathantanmy@google.com","subject":"[PATCH v7 2/3] connect, transport: encapsulate arg in struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T04:58:32Z","receivedAt":"2021-02-05T04:59:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs().\n\nIn preparation for that, encapsulate the existing ref_prefixes\nparameter into a struct. The aforementioned unborn current branch will\ngo into this new struct in the future patch.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clone.c      | 18 +++++++++++-------\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      | 18 +++++++++++-------\n builtin/ls-remote.c  |  9 +++++----\n connect.c            |  4 +++-\n remote.h             |  4 +++-\n transport-helper.c   |  5 +++--\n transport-internal.h | 10 ++--------\n transport.c          | 23 ++++++++++++-----------\n transport.h          | 21 ++++++++++++++-------\n 10 files changed, 66 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a5630337e4..211d4f54b0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -979,7 +979,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint err = 0, complete_refs_before_fetch = 1;\n \tint submodule_progress;\n \n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1257,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport->smart_options->check_self_contained_and_connected = 1;\n \n \n-\tstrvec_push(&ref_prefixes, \"HEAD\");\n-\trefspec_ref_prefixes(&remote->fetch, &ref_prefixes);\n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\trefspec_ref_prefixes(&remote->fetch,\n+\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \tif (option_branch)\n-\t\texpand_ref_prefix(&ref_prefixes, option_branch);\n+\t\texpand_ref_prefix(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t  option_branch);\n \tif (!option_no_tags)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t    \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &transport_ls_refs_options);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1380,6 +1384,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \treturn err;\n }\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..c2d96f4c89 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..837382ef4f 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1349,7 +1349,8 @@ static int do_fetch(struct transport *transport,\n \tint autotags = (transport->remote->fetch_tags == 1);\n \tint retcode = 0;\n \tconst struct ref *remote_refs;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint must_list_refs = 1;\n \n \tif (tags == TAGS_DEFAULT) {\n@@ -1369,7 +1370,7 @@ static int do_fetch(struct transport *transport,\n \tif (rs->nr) {\n \t\tint i;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);\n \n \t\t/*\n \t\t * We can avoid listing refs if all of them are exact\n@@ -1383,22 +1384,25 @@ static int do_fetch(struct transport *transport,\n \t\t\t}\n \t\t}\n \t} else if (transport->remote && transport->remote->fetch.nr)\n-\t\trefspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);\n+\t\trefspec_ref_prefixes(&transport->remote->fetch,\n+\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \n \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n \t\tmust_list_refs = 1;\n-\t\tif (ref_prefixes.nr)\n-\t\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n+\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t    \"refs/tags/\");\n \t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport,\n+\t\t\t\t\t\t\t&transport_ls_refs_options);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \n \tref_map = get_ref_map(transport->remote, remote_refs, rs,\n \t\t\t      tags, &autotags);\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..ef604752a0 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tint show_symref_target = 0;\n \tconst char *uploadpack = NULL;\n \tconst char **pattern = NULL;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint i;\n \tstruct string_list server_options = STRING_LIST_INIT_DUP;\n \n@@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (flags & REF_TAGS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/tags/\");\n \tif (flags & REF_HEADS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/heads/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/heads/\");\n \n \tremote = remote_get(dest);\n \tif (!remote) {\n@@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &transport_options);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..328c279250 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -453,12 +453,14 @@ void check_stateless_delimiter(int stateless_rpc,\n \n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc)\n {\n \tint i;\n \tconst char *hash_name;\n+\tstruct strvec *ref_prefixes = transport_options ?\n+\t\t&transport_options->ref_prefixes : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..4ae676a11b 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -6,6 +6,8 @@\n #include \"hashmap.h\"\n #include \"refspec.h\"\n \n+struct transport_ls_refs_options;\n+\n /**\n  * The API gives access to the configuration related to remotes. It handles\n  * all three configuration mechanisms historically and currently used by Git,\n@@ -196,7 +198,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n /* Used for protocol v2 in order to retrieve refs from a remote */\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..49b7fb4dcb 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,14 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t struct transport_ls_refs_options *transport_options)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\ttransport_options);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..b60f1ba907 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -4,6 +4,7 @@\n struct ref;\n struct transport;\n struct strvec;\n+struct transport_ls_refs_options;\n \n struct transport_vtable {\n \t/**\n@@ -18,19 +19,12 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     struct transport_ls_refs_options *transport_options);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..b13fab5dc3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,7 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *transport_options)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -280,7 +280,7 @@ static void die_if_server_options(struct transport *transport)\n  * remote refs.\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *options,\n \t\t\t     int must_list_refs)\n {\n \tstruct git_transport_data *data = transport->data;\n@@ -303,7 +303,7 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\ttrace2_data_string(\"transfer\", NULL, \"server-sid\", server_sid);\n \t\tif (must_list_refs)\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n-\t\t\t\t\tref_prefixes,\n+\t\t\t\t\toptions,\n \t\t\t\t\ttransport->server_options,\n \t\t\t\t\ttransport->stateless_rpc);\n \t\tbreak;\n@@ -334,9 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, options, 1);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -1252,19 +1252,20 @@ int transport_push(struct repository *r,\n \t\tint porcelain = flags & TRANSPORT_PUSH_PORCELAIN;\n \t\tint pretend = flags & TRANSPORT_PUSH_DRY_RUN;\n \t\tint push_ret, ret, err;\n-\t\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\t\tstruct transport_ls_refs_options transport_options =\n+\t\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \t\tif (check_push_refs(local_refs, rs) < 0)\n \t\t\treturn -1;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_options.ref_prefixes);\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &transport_options);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n-\t\tstrvec_clear(&ref_prefixes);\n+\t\tstrvec_clear(&transport_options.ref_prefixes);\n \n \t\tif (flags & TRANSPORT_PUSH_ALL)\n \t\t\tmatch_flags |= MATCH_REFS_ALL;\n@@ -1380,12 +1381,12 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t transport_options);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..1f5b60e4d3 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n \t\t   struct refspec *rs, int flags,\n \t\t   unsigned int * reject_reasons);\n \n+struct transport_ls_refs_options {\n+\t/*\n+\t * Optionally, a list of ref prefixes can be provided which can be sent\n+\t * to the server (when communicating using protocol v2) to enable it to\n+\t * limit the ref advertisement.  Since ref filtering is done on the\n+\t * server's end (and only when using protocol v2),\n+\t * transport_get_remote_refs() could return refs which don't match the\n+\t * provided ref_prefixes.\n+\t */\n+\tstruct strvec ref_prefixes;\n+};\n+#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n+\n /*\n  * Retrieve refs from a remote.\n- *\n- * Optionally a list of ref prefixes can be provided which can be sent to the\n- * server (when communicating using protocol v2) to enable it to limit the ref\n- * advertisement.  Since ref filtering is done on the server's end (and only\n- * when using protocol v2), this can return refs which don't match the provided\n- * ref_prefixes.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416237","messageId":"a3e5a0a7c530c7fa41db5df639617218cdb1a2f6.1612493309.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612493309.git.jonathantanmy@google.com","subject":"[PATCH v7 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T04:58:33Z","receivedAt":"2021-02-05T04:59:53Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 16 ++++++++++++++--\n connect.c                     | 28 ++++++++++++++++++++++++++--\n t/t5606-clone-options.sh      |  8 +++++---\n t/t5702-protocol-v2.sh        | 25 +++++++++++++++++++++++++\n transport.h                   |  8 ++++++++\n 6 files changed, 79 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 211d4f54b0..09dcd97a2e 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1330,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n+\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n+\t\t\t\t\t\"refs/heads/\", &branch)) {\n+\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n+\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n+\t\t\t\tcreate_symref(\"HEAD\", ref, reflog_msg.buf);\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n \t\t\tfree(ref);\n@@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n+\tfree(transport_ls_refs_options.unborn_head_target);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 328c279250..879669df93 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tconst char *hash_name;\n \tstruct strvec *ref_prefixes = transport_options ?\n \t\t&transport_options->ref_prefixes : NULL;\n+\tchar **unborn_head_target = transport_options ?\n+\t\t&transport_options->unborn_head_target : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\n@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..ca6339a5fb 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.unborn advertise &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..b2ead93af9 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if explicitly forbidden by config' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.unborn ignore &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\t! grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \ndiff --git a/transport.h b/transport.h\nindex 1f5b60e4d3..24e15799e7 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -243,6 +243,14 @@ struct transport_ls_refs_options {\n \t * provided ref_prefixes.\n \t */\n \tstruct strvec ref_prefixes;\n+\n+\t/*\n+\t * If unborn_head_target is not NULL, and the remote reports HEAD as\n+\t * pointing to an unborn branch, transport_get_remote_refs() stores the\n+\t * unborn branch in unborn_head_target. It should be freed by the\n+\t * caller.\n+\t */\n+\tchar *unborn_head_target;\n };\n #define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n \n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416238","messageId":"xmqq5z372ihm.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"cover.1612493309.git.jonathantanmy@google.com","subject":"Re: [PATCH v7 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-05T05:25:57Z","receivedAt":"2021-02-05T05:26:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> For what it's worth, here's v7 with advertise/allow/ignore and by\n> default, advertise. I think that some server operators will have use for\n> this feature, and people who want to disable it for whatever reason can\n> still do so. The main disadvantage is complexity - the server knob that\n> server administrators will need to control (but between a simpler\n> allow/ignore knob and a more complicated advertise/allow/ignore knob, I\n> think we might as well go for the slightly more complex one) and\n> complexity in the code (but now that is constrained to one function and\n> a few global variables).\n>\n> As you can see from the range-diff, not much has changed from v6.\n>\n> I've also included Junio's suggestion of tightening the promise made by\n> the server (when the client says \"unborn\").\n\nThis looks reasonable overall, especially with the feature turned on\nby default, we'd hopefully get reasonable exposure from the get-go.\n\nLet's mark the topic to be merged to 'next' soonish, unless people\nobject.\n\nThanks.\n"},{"id":"416288","messageId":"cover.1612557937.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"20201208013121.677494-1-jonathantanmy@google.com","subject":"[PATCH v8 0/3] Cloning with remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T20:48:46Z","receivedAt":"2021-02-05T20:58:31Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Peff sent a review (which I don't see in lore.kernel.org/git, but I do\nsee it in my inbox); here's v8 in response to that.\n\nAs you can see from the range-diff, there are just minor changes to v7\n(wording in documentation and a memory leak fix).\n\nJonathan Tan (3):\n  ls-refs: report unborn targets of symrefs\n  connect, transport: encapsulate arg in struct\n  clone: respect remote unborn HEAD\n\n Documentation/config.txt                |  2 +\n Documentation/config/init.txt           |  2 +-\n Documentation/config/lsrefs.txt         |  9 +++\n Documentation/technical/protocol-v2.txt | 11 +++-\n builtin/clone.c                         | 34 +++++++++---\n builtin/fetch-pack.c                    |  3 +-\n builtin/fetch.c                         | 18 +++---\n builtin/ls-remote.c                     |  9 +--\n connect.c                               | 32 ++++++++++-\n ls-refs.c                               | 74 ++++++++++++++++++++++++-\n ls-refs.h                               |  1 +\n remote.h                                |  4 +-\n serve.c                                 |  2 +-\n t/t5606-clone-options.sh                |  8 ++-\n t/t5701-git-serve.sh                    |  2 +-\n t/t5702-protocol-v2.sh                  | 25 +++++++++\n transport-helper.c                      |  5 +-\n transport-internal.h                    | 10 +---\n transport.c                             | 23 ++++----\n transport.h                             | 29 +++++++---\n 20 files changed, 240 insertions(+), 63 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\nRange-diff against v7:\n1:  2d35075369 ! 1:  8b0f55b5e4 ls-refs: report unborn targets of symrefs\n    @@ Documentation/config/lsrefs.txt (new)\n     +\tprotocol v2 capability advertisement. \"allow\" is the same as\n     +\t\"advertise\" except that the server will not advertise support for this\n     +\tfeature; this is useful for load-balanced servers that cannot be\n    -+\tupdated automatically (for example), since the administrator could\n    ++\tupdated atomically (for example), since the administrator could\n     +\tconfigure \"allow\", then after a delay, configure \"advertise\".\n     \n      ## Documentation/technical/protocol-v2.txt ##\n    @@ ls-refs.c\n     +\n     +static void ensure_config_read(void)\n     +{\n    -+\tchar *str = NULL;\n    ++\tconst char *str = NULL;\n     +\n     +\tif (config_read)\n     +\t\treturn;\n     +\n    -+\tif (repo_config_get_string(the_repository, \"lsrefs.unborn\", &str)) {\n    ++\tif (repo_config_get_string_tmp(the_repository, \"lsrefs.unborn\", &str)) {\n     +\t\t/*\n     +\t\t * If there is no such config, advertise and allow it by\n     +\t\t * default.\n2:  d4ed13d02e = 2:  f09bd56d5f connect, transport: encapsulate arg in struct\n3:  a3e5a0a7c5 = 3:  a5495a42f1 clone: respect remote unborn HEAD\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416289","messageId":"a5495a42f186ab5acc4214e892d7becd03cfa67f.1612557937.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612557937.git.jonathantanmy@google.com","subject":"[PATCH v8 3/3] clone: respect remote unborn HEAD","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T20:48:49Z","receivedAt":"2021-02-05T21:05:01Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"Teach Git to use the \"unborn\" feature introduced in a previous patch as\nfollows: Git will always send the \"unborn\" argument if it is supported\nby the server. During \"git clone\", if cloning an empty repository, Git\nwill use the new information to determine the local branch to create. In\nall other cases, Git will ignore it.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config/init.txt |  2 +-\n builtin/clone.c               | 16 ++++++++++++++--\n connect.c                     | 28 ++++++++++++++++++++++++++--\n t/t5606-clone-options.sh      |  8 +++++---\n t/t5702-protocol-v2.sh        | 25 +++++++++++++++++++++++++\n transport.h                   |  8 ++++++++\n 6 files changed, 79 insertions(+), 8 deletions(-)\n\ndiff --git a/Documentation/config/init.txt b/Documentation/config/init.txt\nindex dc77f8c844..79c79d6617 100644\n--- a/Documentation/config/init.txt\n+++ b/Documentation/config/init.txt\n@@ -4,4 +4,4 @@ init.templateDir::\n \n init.defaultBranch::\n \tAllows overriding the default branch name e.g. when initializing\n-\ta new repository or when cloning an empty repository.\n+\ta new repository.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 211d4f54b0..09dcd97a2e 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -1330,8 +1330,19 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\tremote_head = NULL;\n \t\toption_no_checkout = 1;\n \t\tif (!option_bare) {\n-\t\t\tconst char *branch = git_default_branch_name();\n-\t\t\tchar *ref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\tconst char *branch;\n+\t\t\tchar *ref;\n+\n+\t\t\tif (transport_ls_refs_options.unborn_head_target &&\n+\t\t\t    skip_prefix(transport_ls_refs_options.unborn_head_target,\n+\t\t\t\t\t\"refs/heads/\", &branch)) {\n+\t\t\t\tref = transport_ls_refs_options.unborn_head_target;\n+\t\t\t\ttransport_ls_refs_options.unborn_head_target = NULL;\n+\t\t\t\tcreate_symref(\"HEAD\", ref, reflog_msg.buf);\n+\t\t\t} else {\n+\t\t\t\tbranch = git_default_branch_name();\n+\t\t\t\tref = xstrfmt(\"refs/heads/%s\", branch);\n+\t\t\t}\n \n \t\t\tinstall_branch_config(0, branch, remote_name, ref);\n \t\t\tfree(ref);\n@@ -1385,5 +1396,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n \tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n+\tfree(transport_ls_refs_options.unborn_head_target);\n \treturn err;\n }\ndiff --git a/connect.c b/connect.c\nindex 328c279250..879669df93 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -376,7 +376,8 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n }\n \n /* Returns 1 when a valid ref has been added to `list`, 0 otherwise */\n-static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n+static int process_ref_v2(struct packet_reader *reader, struct ref ***list,\n+\t\t\t  char **unborn_head_target)\n {\n \tint ret = 1;\n \tint i = 0;\n@@ -397,6 +398,25 @@ static int process_ref_v2(struct packet_reader *reader, struct ref ***list)\n \t\tgoto out;\n \t}\n \n+\tif (!strcmp(\"unborn\", line_sections.items[i].string)) {\n+\t\ti++;\n+\t\tif (unborn_head_target &&\n+\t\t    !strcmp(\"HEAD\", line_sections.items[i++].string)) {\n+\t\t\t/*\n+\t\t\t * Look for the symref target (if any). If found,\n+\t\t\t * return it to the caller.\n+\t\t\t */\n+\t\t\tfor (; i < line_sections.nr; i++) {\n+\t\t\t\tconst char *arg = line_sections.items[i].string;\n+\n+\t\t\t\tif (skip_prefix(arg, \"symref-target:\", &arg)) {\n+\t\t\t\t\t*unborn_head_target = xstrdup(arg);\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t\tgoto out;\n+\t}\n \tif (parse_oid_hex_algop(line_sections.items[i++].string, &old_oid, &end, reader->hash_algo) ||\n \t    *end) {\n \t\tret = 0;\n@@ -461,6 +481,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tconst char *hash_name;\n \tstruct strvec *ref_prefixes = transport_options ?\n \t\t&transport_options->ref_prefixes : NULL;\n+\tchar **unborn_head_target = transport_options ?\n+\t\t&transport_options->unborn_head_target : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\n@@ -490,6 +512,8 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \tif (!for_push)\n \t\tpacket_write_fmt(fd_out, \"peel\\n\");\n \tpacket_write_fmt(fd_out, \"symrefs\\n\");\n+\tif (server_supports_feature(\"ls-refs\", \"unborn\", 0))\n+\t\tpacket_write_fmt(fd_out, \"unborn\\n\");\n \tfor (i = 0; ref_prefixes && i < ref_prefixes->nr; i++) {\n \t\tpacket_write_fmt(fd_out, \"ref-prefix %s\\n\",\n \t\t\t\t ref_prefixes->v[i]);\n@@ -498,7 +522,7 @@ struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \n \t/* Process response from server */\n \twhile (packet_reader_read(reader) == PACKET_READ_NORMAL) {\n-\t\tif (!process_ref_v2(reader, &list))\n+\t\tif (!process_ref_v2(reader, &list, unborn_head_target))\n \t\t\tdie(_(\"invalid ls-refs response: %s\"), reader->line);\n \t}\n \ndiff --git a/t/t5606-clone-options.sh b/t/t5606-clone-options.sh\nindex 7f082fb23b..ca6339a5fb 100755\n--- a/t/t5606-clone-options.sh\n+++ b/t/t5606-clone-options.sh\n@@ -102,11 +102,13 @@ test_expect_success 'redirected clone -v does show progress' '\n '\n \n test_expect_success 'chooses correct default initial branch name' '\n-\tgit init --bare empty &&\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=foo init --bare empty &&\n+\ttest_config -C empty lsrefs.unborn advertise &&\n \tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n \tgit -c init.defaultBranch=up clone empty whats-up &&\n-\ttest refs/heads/up = $(git -C whats-up symbolic-ref HEAD) &&\n-\ttest refs/heads/up = $(git -C whats-up config branch.up.merge)\n+\ttest refs/heads/foo = $(git -C whats-up symbolic-ref HEAD) &&\n+\ttest refs/heads/foo = $(git -C whats-up config branch.foo.merge)\n '\n \n test_expect_success 'guesses initial branch name correctly' '\ndiff --git a/t/t5702-protocol-v2.sh b/t/t5702-protocol-v2.sh\nindex 7d5b17909b..b2ead93af9 100755\n--- a/t/t5702-protocol-v2.sh\n+++ b/t/t5702-protocol-v2.sh\n@@ -209,6 +209,31 @@ test_expect_success 'clone with file:// using protocol v2' '\n \tgrep \"ref-prefix refs/tags/\" log\n '\n \n+test_expect_success 'clone of empty repo propagates name of default branch' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\tgrep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n+test_expect_success '...but not if explicitly forbidden by config' '\n+\ttest_when_finished \"rm -rf file_empty_parent file_empty_child\" &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=mydefaultbranch init file_empty_parent &&\n+\ttest_config -C file_empty_parent lsrefs.unborn ignore &&\n+\n+\tGIT_TEST_DEFAULT_INITIAL_BRANCH_NAME= \\\n+\tgit -c init.defaultBranch=main -c protocol.version=2 \\\n+\t\tclone \"file://$(pwd)/file_empty_parent\" file_empty_child &&\n+\t! grep \"refs/heads/mydefaultbranch\" file_empty_child/.git/HEAD\n+'\n+\n test_expect_success 'fetch with file:// using protocol v2' '\n \ttest_when_finished \"rm -f log\" &&\n \ndiff --git a/transport.h b/transport.h\nindex 1f5b60e4d3..24e15799e7 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -243,6 +243,14 @@ struct transport_ls_refs_options {\n \t * provided ref_prefixes.\n \t */\n \tstruct strvec ref_prefixes;\n+\n+\t/*\n+\t * If unborn_head_target is not NULL, and the remote reports HEAD as\n+\t * pointing to an unborn branch, transport_get_remote_refs() stores the\n+\t * unborn branch in unborn_head_target. It should be freed by the\n+\t * caller.\n+\t */\n+\tchar *unborn_head_target;\n };\n #define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n \n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416290","messageId":"f09bd56d5fba5f97645f9a528161f4e871c8f31b.1612557937.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612557937.git.jonathantanmy@google.com","subject":"[PATCH v8 2/3] connect, transport: encapsulate arg in struct","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T20:48:48Z","receivedAt":"2021-02-05T21:06:12Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"In a future patch we plan to return the name of an unborn current branch\nfrom deep in the callchain to a caller via a new pointer parameter that\npoints at a variable in the caller when the caller calls\nget_remote_refs() and transport_get_remote_refs().\n\nIn preparation for that, encapsulate the existing ref_prefixes\nparameter into a struct. The aforementioned unborn current branch will\ngo into this new struct in the future patch.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/clone.c      | 18 +++++++++++-------\n builtin/fetch-pack.c |  3 ++-\n builtin/fetch.c      | 18 +++++++++++-------\n builtin/ls-remote.c  |  9 +++++----\n connect.c            |  4 +++-\n remote.h             |  4 +++-\n transport-helper.c   |  5 +++--\n transport-internal.h | 10 ++--------\n transport.c          | 23 ++++++++++++-----------\n transport.h          | 21 ++++++++++++++-------\n 10 files changed, 66 insertions(+), 49 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex a5630337e4..211d4f54b0 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -979,7 +979,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tint err = 0, complete_refs_before_fetch = 1;\n \tint submodule_progress;\n \n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \tpacket_trace_identity(\"clone\");\n \n@@ -1257,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \t\ttransport->smart_options->check_self_contained_and_connected = 1;\n \n \n-\tstrvec_push(&ref_prefixes, \"HEAD\");\n-\trefspec_ref_prefixes(&remote->fetch, &ref_prefixes);\n+\tstrvec_push(&transport_ls_refs_options.ref_prefixes, \"HEAD\");\n+\trefspec_ref_prefixes(&remote->fetch,\n+\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \tif (option_branch)\n-\t\texpand_ref_prefix(&ref_prefixes, option_branch);\n+\t\texpand_ref_prefix(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t  option_branch);\n \tif (!option_no_tags)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t    \"refs/tags/\");\n \n-\trefs = transport_get_remote_refs(transport, &ref_prefixes);\n+\trefs = transport_get_remote_refs(transport, &transport_ls_refs_options);\n \n \tif (refs) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n@@ -1380,6 +1384,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&key);\n \tjunk_mode = JUNK_LEAVE_ALL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \treturn err;\n }\ndiff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c\nindex 58b7c1fbdc..c2d96f4c89 100644\n--- a/builtin/fetch-pack.c\n+++ b/builtin/fetch-pack.c\n@@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix)\n \tversion = discover_version(&reader);\n \tswitch (version) {\n \tcase protocol_v2:\n-\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc);\n+\t\tget_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL,\n+\t\t\t\targs.stateless_rpc);\n \t\tbreak;\n \tcase protocol_v1:\n \tcase protocol_v0:\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex ecf8537605..837382ef4f 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1349,7 +1349,8 @@ static int do_fetch(struct transport *transport,\n \tint autotags = (transport->remote->fetch_tags == 1);\n \tint retcode = 0;\n \tconst struct ref *remote_refs;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_ls_refs_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint must_list_refs = 1;\n \n \tif (tags == TAGS_DEFAULT) {\n@@ -1369,7 +1370,7 @@ static int do_fetch(struct transport *transport,\n \tif (rs->nr) {\n \t\tint i;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes);\n \n \t\t/*\n \t\t * We can avoid listing refs if all of them are exact\n@@ -1383,22 +1384,25 @@ static int do_fetch(struct transport *transport,\n \t\t\t}\n \t\t}\n \t} else if (transport->remote && transport->remote->fetch.nr)\n-\t\trefspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes);\n+\t\trefspec_ref_prefixes(&transport->remote->fetch,\n+\t\t\t\t     &transport_ls_refs_options.ref_prefixes);\n \n \tif (tags == TAGS_SET || tags == TAGS_DEFAULT) {\n \t\tmust_list_refs = 1;\n-\t\tif (ref_prefixes.nr)\n-\t\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tif (transport_ls_refs_options.ref_prefixes.nr)\n+\t\t\tstrvec_push(&transport_ls_refs_options.ref_prefixes,\n+\t\t\t\t    \"refs/tags/\");\n \t}\n \n \tif (must_list_refs) {\n \t\ttrace2_region_enter(\"fetch\", \"remote_refs\", the_repository);\n-\t\tremote_refs = transport_get_remote_refs(transport, &ref_prefixes);\n+\t\tremote_refs = transport_get_remote_refs(transport,\n+\t\t\t\t\t\t\t&transport_ls_refs_options);\n \t\ttrace2_region_leave(\"fetch\", \"remote_refs\", the_repository);\n \t} else\n \t\tremote_refs = NULL;\n \n-\tstrvec_clear(&ref_prefixes);\n+\tstrvec_clear(&transport_ls_refs_options.ref_prefixes);\n \n \tref_map = get_ref_map(transport->remote, remote_refs, rs,\n \t\t\t      tags, &autotags);\ndiff --git a/builtin/ls-remote.c b/builtin/ls-remote.c\nindex 092917eca2..ef604752a0 100644\n--- a/builtin/ls-remote.c\n+++ b/builtin/ls-remote.c\n@@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tint show_symref_target = 0;\n \tconst char *uploadpack = NULL;\n \tconst char **pattern = NULL;\n-\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\tstruct transport_ls_refs_options transport_options =\n+\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \tint i;\n \tstruct string_list server_options = STRING_LIST_INIT_DUP;\n \n@@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (flags & REF_TAGS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/tags/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/tags/\");\n \tif (flags & REF_HEADS)\n-\t\tstrvec_push(&ref_prefixes, \"refs/heads/\");\n+\t\tstrvec_push(&transport_options.ref_prefixes, \"refs/heads/\");\n \n \tremote = remote_get(dest);\n \tif (!remote) {\n@@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix)\n \tif (server_options.nr)\n \t\ttransport->server_options = &server_options;\n \n-\tref = transport_get_remote_refs(transport, &ref_prefixes);\n+\tref = transport_get_remote_refs(transport, &transport_options);\n \tif (ref) {\n \t\tint hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport));\n \t\trepo_set_hash_algo(the_repository, hash_algo);\ndiff --git a/connect.c b/connect.c\nindex 8b8f56cf6d..328c279250 100644\n--- a/connect.c\n+++ b/connect.c\n@@ -453,12 +453,14 @@ void check_stateless_delimiter(int stateless_rpc,\n \n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc)\n {\n \tint i;\n \tconst char *hash_name;\n+\tstruct strvec *ref_prefixes = transport_options ?\n+\t\t&transport_options->ref_prefixes : NULL;\n \t*list = NULL;\n \n \tif (server_supports_v2(\"ls-refs\", 1))\ndiff --git a/remote.h b/remote.h\nindex 3211abdf05..4ae676a11b 100644\n--- a/remote.h\n+++ b/remote.h\n@@ -6,6 +6,8 @@\n #include \"hashmap.h\"\n #include \"refspec.h\"\n \n+struct transport_ls_refs_options;\n+\n /**\n  * The API gives access to the configuration related to remotes. It handles\n  * all three configuration mechanisms historically and currently used by Git,\n@@ -196,7 +198,7 @@ struct ref **get_remote_heads(struct packet_reader *reader,\n /* Used for protocol v2 in order to retrieve refs from a remote */\n struct ref **get_remote_refs(int fd_out, struct packet_reader *reader,\n \t\t\t     struct ref **list, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *transport_options,\n \t\t\t     const struct string_list *server_options,\n \t\t\t     int stateless_rpc);\n \ndiff --git a/transport-helper.c b/transport-helper.c\nindex 5f6e0b3bd8..49b7fb4dcb 100644\n--- a/transport-helper.c\n+++ b/transport-helper.c\n@@ -1162,13 +1162,14 @@ static int has_attribute(const char *attrs, const char *attr)\n }\n \n static struct ref *get_refs_list(struct transport *transport, int for_push,\n-\t\t\t\t const struct strvec *ref_prefixes)\n+\t\t\t\t struct transport_ls_refs_options *transport_options)\n {\n \tget_helper(transport);\n \n \tif (process_connect(transport, for_push)) {\n \t\tdo_take_over(transport);\n-\t\treturn transport->vtable->get_refs_list(transport, for_push, ref_prefixes);\n+\t\treturn transport->vtable->get_refs_list(transport, for_push,\n+\t\t\t\t\t\t\ttransport_options);\n \t}\n \n \treturn get_refs_list_using_list(transport, for_push);\ndiff --git a/transport-internal.h b/transport-internal.h\nindex 27c9daffc4..b60f1ba907 100644\n--- a/transport-internal.h\n+++ b/transport-internal.h\n@@ -4,6 +4,7 @@\n struct ref;\n struct transport;\n struct strvec;\n+struct transport_ls_refs_options;\n \n struct transport_vtable {\n \t/**\n@@ -18,19 +19,12 @@ struct transport_vtable {\n \t * the transport to try to share connections, for_push is a\n \t * hint as to whether the ultimate operation is a push or a fetch.\n \t *\n-\t * If communicating using protocol v2 a list of prefixes can be\n-\t * provided to be sent to the server to enable it to limit the ref\n-\t * advertisement.  Since ref filtering is done on the server's end, and\n-\t * only when using protocol v2, this list will be ignored when not\n-\t * using protocol v2 meaning this function can return refs which don't\n-\t * match the provided ref_prefixes.\n-\t *\n \t * If the transport is able to determine the remote hash for\n \t * the ref without a huge amount of effort, it should store it\n \t * in the ref's old_sha1 field; otherwise it should be all 0.\n \t **/\n \tstruct ref *(*get_refs_list)(struct transport *transport, int for_push,\n-\t\t\t\t     const struct strvec *ref_prefixes);\n+\t\t\t\t     struct transport_ls_refs_options *transport_options);\n \n \t/**\n \t * Fetch the objects for the given refs. Note that this gets\ndiff --git a/transport.c b/transport.c\nindex 679a35e7c1..b13fab5dc3 100644\n--- a/transport.c\n+++ b/transport.c\n@@ -127,7 +127,7 @@ struct bundle_transport_data {\n \n static struct ref *get_refs_from_bundle(struct transport *transport,\n \t\t\t\t\tint for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *transport_options)\n {\n \tstruct bundle_transport_data *data = transport->data;\n \tstruct ref *result = NULL;\n@@ -280,7 +280,7 @@ static void die_if_server_options(struct transport *transport)\n  * remote refs.\n  */\n static struct ref *handshake(struct transport *transport, int for_push,\n-\t\t\t     const struct strvec *ref_prefixes,\n+\t\t\t     struct transport_ls_refs_options *options,\n \t\t\t     int must_list_refs)\n {\n \tstruct git_transport_data *data = transport->data;\n@@ -303,7 +303,7 @@ static struct ref *handshake(struct transport *transport, int for_push,\n \t\t\ttrace2_data_string(\"transfer\", NULL, \"server-sid\", server_sid);\n \t\tif (must_list_refs)\n \t\t\tget_remote_refs(data->fd[1], &reader, &refs, for_push,\n-\t\t\t\t\tref_prefixes,\n+\t\t\t\t\toptions,\n \t\t\t\t\ttransport->server_options,\n \t\t\t\t\ttransport->stateless_rpc);\n \t\tbreak;\n@@ -334,9 +334,9 @@ static struct ref *handshake(struct transport *transport, int for_push,\n }\n \n static struct ref *get_refs_via_connect(struct transport *transport, int for_push,\n-\t\t\t\t\tconst struct strvec *ref_prefixes)\n+\t\t\t\t\tstruct transport_ls_refs_options *options)\n {\n-\treturn handshake(transport, for_push, ref_prefixes, 1);\n+\treturn handshake(transport, for_push, options, 1);\n }\n \n static int fetch_refs_via_pack(struct transport *transport,\n@@ -1252,19 +1252,20 @@ int transport_push(struct repository *r,\n \t\tint porcelain = flags & TRANSPORT_PUSH_PORCELAIN;\n \t\tint pretend = flags & TRANSPORT_PUSH_DRY_RUN;\n \t\tint push_ret, ret, err;\n-\t\tstruct strvec ref_prefixes = STRVEC_INIT;\n+\t\tstruct transport_ls_refs_options transport_options =\n+\t\t\tTRANSPORT_LS_REFS_OPTIONS_INIT;\n \n \t\tif (check_push_refs(local_refs, rs) < 0)\n \t\t\treturn -1;\n \n-\t\trefspec_ref_prefixes(rs, &ref_prefixes);\n+\t\trefspec_ref_prefixes(rs, &transport_options.ref_prefixes);\n \n \t\ttrace2_region_enter(\"transport_push\", \"get_refs_list\", r);\n \t\tremote_refs = transport->vtable->get_refs_list(transport, 1,\n-\t\t\t\t\t\t\t       &ref_prefixes);\n+\t\t\t\t\t\t\t       &transport_options);\n \t\ttrace2_region_leave(\"transport_push\", \"get_refs_list\", r);\n \n-\t\tstrvec_clear(&ref_prefixes);\n+\t\tstrvec_clear(&transport_options.ref_prefixes);\n \n \t\tif (flags & TRANSPORT_PUSH_ALL)\n \t\t\tmatch_flags |= MATCH_REFS_ALL;\n@@ -1380,12 +1381,12 @@ int transport_push(struct repository *r,\n }\n \n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes)\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options)\n {\n \tif (!transport->got_remote_refs) {\n \t\ttransport->remote_refs =\n \t\t\ttransport->vtable->get_refs_list(transport, 0,\n-\t\t\t\t\t\t\t ref_prefixes);\n+\t\t\t\t\t\t\t transport_options);\n \t\ttransport->got_remote_refs = 1;\n \t}\n \ndiff --git a/transport.h b/transport.h\nindex 24558c027d..1f5b60e4d3 100644\n--- a/transport.h\n+++ b/transport.h\n@@ -233,17 +233,24 @@ int transport_push(struct repository *repo,\n \t\t   struct refspec *rs, int flags,\n \t\t   unsigned int * reject_reasons);\n \n+struct transport_ls_refs_options {\n+\t/*\n+\t * Optionally, a list of ref prefixes can be provided which can be sent\n+\t * to the server (when communicating using protocol v2) to enable it to\n+\t * limit the ref advertisement.  Since ref filtering is done on the\n+\t * server's end (and only when using protocol v2),\n+\t * transport_get_remote_refs() could return refs which don't match the\n+\t * provided ref_prefixes.\n+\t */\n+\tstruct strvec ref_prefixes;\n+};\n+#define TRANSPORT_LS_REFS_OPTIONS_INIT { STRVEC_INIT }\n+\n /*\n  * Retrieve refs from a remote.\n- *\n- * Optionally a list of ref prefixes can be provided which can be sent to the\n- * server (when communicating using protocol v2) to enable it to limit the ref\n- * advertisement.  Since ref filtering is done on the server's end (and only\n- * when using protocol v2), this can return refs which don't match the provided\n- * ref_prefixes.\n  */\n const struct ref *transport_get_remote_refs(struct transport *transport,\n-\t\t\t\t\t    const struct strvec *ref_prefixes);\n+\t\t\t\t\t    struct transport_ls_refs_options *transport_options);\n \n /*\n  * Fetch the hash algorithm used by a remote.\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416291","messageId":"8b0f55b5e49e2a4e7bb2dc0cf3b9a4c96e03af32.1612557937.git.jonathantanmy@google.com","threadId":"54790","inReplyTo":"cover.1612557937.git.jonathantanmy@google.com","subject":"[PATCH v8 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jonathan Tan","fromEmail":"jonathantanmy@google.com","sentAt":"2021-02-05T20:48:47Z","receivedAt":"2021-02-05T21:06:13Z","isPatch":true,"sender":{"key":"jonathantanmy@fastmail.com","avatar":null},"body":"When cloning, we choose the default branch based on the remote HEAD.\nBut if there is no remote HEAD reported (which could happen if the\ntarget of the remote HEAD is unborn), we'll fall back to using our local\ninit.defaultBranch. Traditionally this hasn't been a big deal, because\nmost repos used \"master\" as the default. But these days it is likely to\ncause confusion if the server and client implementations choose\ndifferent values (e.g., if the remote started with \"main\", we may choose\n\"master\" locally, create commits there, and then the user is surprised\nwhen they push to \"master\" and not \"main\").\n\nTo solve this, the remote needs to communicate the target of the HEAD\nsymref, even if it is unborn, and \"git clone\" needs to use this\ninformation.\n\nCurrently, symrefs that have unborn targets (such as in this case) are\nnot communicated by the protocol. Teach Git to advertise and support the\n\"unborn\" feature in \"ls-refs\" (by default, this is advertised, but\nserver administrators may turn this off through the lsrefs.unborn\nconfig). This feature indicates that \"ls-refs\" supports the \"unborn\"\nargument; when it is specified, \"ls-refs\" will send the HEAD symref with\nthe name of its unborn target.\n\nThis change is only for protocol v2. A similar change for protocol v0\nwould require independent protocol design (there being no analogous\nposition to signal support for \"unborn\") and client-side plumbing of the\ndata required, so the scope of this patch set is limited to protocol v2.\n\nThe client side will be updated to use this in a subsequent commit.\n\nSigned-off-by: Jonathan Tan <jonathantanmy@google.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt                |  2 +\n Documentation/config/lsrefs.txt         |  9 +++\n Documentation/technical/protocol-v2.txt | 11 +++-\n ls-refs.c                               | 74 ++++++++++++++++++++++++-\n ls-refs.h                               |  1 +\n serve.c                                 |  2 +-\n t/t5701-git-serve.sh                    |  2 +-\n 7 files changed, 95 insertions(+), 6 deletions(-)\n create mode 100644 Documentation/config/lsrefs.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex 6ba50b1104..d08e83a148 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -398,6 +398,8 @@ include::config/interactive.txt[]\n \n include::config/log.txt[]\n \n+include::config/lsrefs.txt[]\n+\n include::config/mailinfo.txt[]\n \n include::config/mailmap.txt[]\ndiff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\nnew file mode 100644\nindex 0000000000..adeda0f24d\n--- /dev/null\n+++ b/Documentation/config/lsrefs.txt\n@@ -0,0 +1,9 @@\n+lsrefs.unborn::\n+\tMay be \"advertise\" (the default), \"allow\", or \"ignore\". If \"advertise\",\n+\tthe server will respond to the client sending \"unborn\" (as described in\n+\tprotocol-v2.txt) and will advertise support for this feature during the\n+\tprotocol v2 capability advertisement. \"allow\" is the same as\n+\t\"advertise\" except that the server will not advertise support for this\n+\tfeature; this is useful for load-balanced servers that cannot be\n+\tupdated atomically (for example), since the administrator could\n+\tconfigure \"allow\", then after a delay, configure \"advertise\".\ndiff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\nindex 85daeb5d9e..f772d90eaf 100644\n--- a/Documentation/technical/protocol-v2.txt\n+++ b/Documentation/technical/protocol-v2.txt\n@@ -192,11 +192,20 @@ ls-refs takes in the following arguments:\n \tWhen specified, only references having a prefix matching one of\n \tthe provided prefixes are displayed.\n \n+If the 'unborn' feature is advertised the following argument can be\n+included in the client's request.\n+\n+    unborn\n+\tThe server will send information about HEAD even if it is a symref\n+\tpointing to an unborn branch in the form \"unborn HEAD\n+\tsymref-target:<target>\".\n+\n The output of ls-refs is as follows:\n \n     output = *ref\n \t     flush-pkt\n-    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)\n+    obj-id-or-unborn = (obj-id | \"unborn\")\n+    ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF)\n     ref-attribute = (symref | peeled)\n     symref = \"symref-target:\" symref-target\n     peeled = \"peeled:\" obj-id\ndiff --git a/ls-refs.c b/ls-refs.c\nindex a1e0b473e4..32deb7be44 100644\n--- a/ls-refs.c\n+++ b/ls-refs.c\n@@ -7,6 +7,39 @@\n #include \"pkt-line.h\"\n #include \"config.h\"\n \n+static int config_read;\n+static int advertise_unborn;\n+static int allow_unborn;\n+\n+static void ensure_config_read(void)\n+{\n+\tconst char *str = NULL;\n+\n+\tif (config_read)\n+\t\treturn;\n+\n+\tif (repo_config_get_string_tmp(the_repository, \"lsrefs.unborn\", &str)) {\n+\t\t/*\n+\t\t * If there is no such config, advertise and allow it by\n+\t\t * default.\n+\t\t */\n+\t\tadvertise_unborn = 1;\n+\t\tallow_unborn = 1;\n+\t} else {\n+\t\tif (!strcmp(str, \"advertise\")) {\n+\t\t\tadvertise_unborn = 1;\n+\t\t\tallow_unborn = 1;\n+\t\t} else if (!strcmp(str, \"allow\")) {\n+\t\t\tallow_unborn = 1;\n+\t\t} else if (!strcmp(str, \"ignore\")) {\n+\t\t\t/* do nothing */\n+\t\t} else {\n+\t\t\tdie(_(\"invalid value '%s' for lsrefs.unborn\"), str);\n+\t\t}\n+\t}\n+\tconfig_read = 1;\n+}\n+\n /*\n  * Check if one of the prefixes is a prefix of the ref.\n  * If no prefixes were provided, all refs match.\n@@ -32,6 +65,7 @@ struct ls_refs_data {\n \tunsigned peel;\n \tunsigned symrefs;\n \tstruct strvec prefixes;\n+\tunsigned unborn : 1;\n };\n \n static int send_ref(const char *refname, const struct object_id *oid,\n@@ -47,7 +81,10 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \tif (!ref_match(&data->prefixes, refname_nons))\n \t\treturn 0;\n \n-\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\tif (oid)\n+\t\tstrbuf_addf(&refline, \"%s %s\", oid_to_hex(oid), refname_nons);\n+\telse\n+\t\tstrbuf_addf(&refline, \"unborn %s\", refname_nons);\n \tif (data->symrefs && flag & REF_ISSYMREF) {\n \t\tstruct object_id unused;\n \t\tconst char *symref_target = resolve_ref_unsafe(refname, 0,\n@@ -61,7 +98,7 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \t\t\t    strip_namespace(symref_target));\n \t}\n \n-\tif (data->peel) {\n+\tif (data->peel && oid) {\n \t\tstruct object_id peeled;\n \t\tif (!peel_ref(refname, &peeled))\n \t\t\tstrbuf_addf(&refline, \" peeled:%s\", oid_to_hex(&peeled));\n@@ -74,6 +111,23 @@ static int send_ref(const char *refname, const struct object_id *oid,\n \treturn 0;\n }\n \n+static void send_possibly_unborn_head(struct ls_refs_data *data)\n+{\n+\tstruct strbuf namespaced = STRBUF_INIT;\n+\tstruct object_id oid;\n+\tint flag;\n+\tint oid_is_null;\n+\n+\tstrbuf_addf(&namespaced, \"%sHEAD\", get_git_namespace());\n+\tif (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag))\n+\t\treturn; /* bad ref */\n+\toid_is_null = is_null_oid(&oid);\n+\tif (!oid_is_null ||\n+\t    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))\n+\t\tsend_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);\n+\tstrbuf_release(&namespaced);\n+}\n+\n static int ls_refs_config(const char *var, const char *value, void *data)\n {\n \t/*\n@@ -91,6 +145,7 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \n \tmemset(&data, 0, sizeof(data));\n \n+\tensure_config_read();\n \tgit_config(ls_refs_config, NULL);\n \n \twhile (packet_reader_read(request) == PACKET_READ_NORMAL) {\n@@ -103,14 +158,27 @@ int ls_refs(struct repository *r, struct strvec *keys,\n \t\t\tdata.symrefs = 1;\n \t\telse if (skip_prefix(arg, \"ref-prefix \", &out))\n \t\t\tstrvec_push(&data.prefixes, out);\n+\t\telse if (!strcmp(\"unborn\", arg))\n+\t\t\tdata.unborn = allow_unborn;\n \t}\n \n \tif (request->status != PACKET_READ_FLUSH)\n \t\tdie(_(\"expected flush after ls-refs arguments\"));\n \n-\thead_ref_namespaced(send_ref, &data);\n+\tsend_possibly_unborn_head(&data);\n \tfor_each_namespaced_ref(send_ref, &data);\n \tpacket_flush(1);\n \tstrvec_clear(&data.prefixes);\n \treturn 0;\n }\n+\n+int ls_refs_advertise(struct repository *r, struct strbuf *value)\n+{\n+\tif (value) {\n+\t\tensure_config_read();\n+\t\tif (advertise_unborn)\n+\t\t\tstrbuf_addstr(value, \"unborn\");\n+\t}\n+\n+\treturn 1;\n+}\ndiff --git a/ls-refs.h b/ls-refs.h\nindex 7b33a7c6b8..a99e4be0bd 100644\n--- a/ls-refs.h\n+++ b/ls-refs.h\n@@ -6,5 +6,6 @@ struct strvec;\n struct packet_reader;\n int ls_refs(struct repository *r, struct strvec *keys,\n \t    struct packet_reader *request);\n+int ls_refs_advertise(struct repository *r, struct strbuf *value);\n \n #endif /* LS_REFS_H */\ndiff --git a/serve.c b/serve.c\nindex eec2fe6f29..ac20c72763 100644\n--- a/serve.c\n+++ b/serve.c\n@@ -73,7 +73,7 @@ struct protocol_capability {\n \n static struct protocol_capability capabilities[] = {\n \t{ \"agent\", agent_advertise, NULL },\n-\t{ \"ls-refs\", always_advertise, ls_refs },\n+\t{ \"ls-refs\", ls_refs_advertise, ls_refs },\n \t{ \"fetch\", upload_pack_advertise, upload_pack_v2 },\n \t{ \"server-option\", always_advertise, NULL },\n \t{ \"object-format\", object_format_advertise, NULL },\ndiff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh\nindex a1f5fdc9fd..df29504161 100755\n--- a/t/t5701-git-serve.sh\n+++ b/t/t5701-git-serve.sh\n@@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' '\n \tcat >expect <<-EOF &&\n \tversion 2\n \tagent=git/$(git version | cut -d\" \" -f3)\n-\tls-refs\n+\tls-refs=unborn\n \tfetch=shallow\n \tserver-option\n \tobject-format=$(test_oid algo)\n-- \n2.30.0.365.g02bc693789-goog\n\n"},{"id":"416292","messageId":"87eehucj33.fsf@evledraar.gmail.com","threadId":"54790","inReplyTo":"xmqq5z372ihm.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v7 0/3] Cloning with remote unborn HEAD","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-02-05T21:15:12Z","receivedAt":"2021-02-05T21:16:39Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Fri, Feb 05 2021, Junio C Hamano wrote:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n>\n>> For what it's worth, here's v7 with advertise/allow/ignore and by\n>> default, advertise. I think that some server operators will have use for\n>> this feature, and people who want to disable it for whatever reason can\n>> still do so. The main disadvantage is complexity - the server knob that\n>> server administrators will need to control (but between a simpler\n>> allow/ignore knob and a more complicated advertise/allow/ignore knob, I\n>> think we might as well go for the slightly more complex one) and\n>> complexity in the code (but now that is constrained to one function and\n>> a few global variables).\n>>\n>> As you can see from the range-diff, not much has changed from v6.\n>>\n>> I've also included Junio's suggestion of tightening the promise made by\n>> the server (when the client says \"unborn\").\n>\n> This looks reasonable overall, especially with the feature turned on\n> by default, we'd hopefully get reasonable exposure from the get-go.\n>\n> Let's mark the topic to be merged to 'next' soonish, unless people\n> object.\n\nFWIW I'm still unclear on re [1] whether Jonathan thinks the semantics\nof this shouldn't be documented for <reasons>, or whether he just\ndoesn't want to submit the patch and I should, or something else.\n\nI still think this \"remote as config source\" without actually explaining\nthat it is is a very glaring hole in the docs[2].\n\nAnd in [3] I noted that we introduced the word \"branches\" into\nprotocol-v2.txt for the first time without defining what it means\n(presumably just refs/heads/*, but then let's say so...). There was a\nreply promising clarification, but I note that v7 still just says\n\"branches\".\n\nTo be clear I'm perfectly fine with a note in a CL to the effect of \"had\nX feedback on last version, Ævar said Y and Z both of which I think are\nstupid ideas, so I'm not doing them\" :)\n\nThe only thing I mind is being left hanging to the effect of not knowing\nif a diff you proposed is considered bad by the primary author, or if I\nshould just submit it myself as a patch on top or whatever.\n\nIt also saves other people following along with reviews time, since they\ncan read later cover letters and get a brief summary of some\nside-discussion in an earlier round without diving into it themselves.\n\nMe too honestly, sometimes I come back to these threads and completely\nforgot what I had to say in earlier rounds, and when I try to find out\nit's hit-and-miss whether I agree with much/any of it :)\n\n\n\n\n\n1. https://lore.kernel.org/git/87h7n3p363.fsf@evledraar.gmail.com/\n2. https://lore.kernel.org/git/878s8apthr.fsf@evledraar.gmail.com/\n3. https://lore.kernel.org/git/87k0rzp3qx.fsf@evledraar.gmail.com/\n"},{"id":"416307","messageId":"YB1vMcR1hYie1XLb@coredump.intra.peff.net","threadId":"54790","inReplyTo":"xmqq5z372ihm.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v7 0/3] Cloning with remote unborn HEAD","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-05T16:15:45Z","receivedAt":"2021-02-05T22:12:13Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 04, 2021 at 09:25:57PM -0800, Junio C Hamano wrote:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n> \n> > For what it's worth, here's v7 with advertise/allow/ignore and by\n> > default, advertise. I think that some server operators will have use for\n> > this feature, and people who want to disable it for whatever reason can\n> > still do so. The main disadvantage is complexity - the server knob that\n> > server administrators will need to control (but between a simpler\n> > allow/ignore knob and a more complicated advertise/allow/ignore knob, I\n> > think we might as well go for the slightly more complex one) and\n> > complexity in the code (but now that is constrained to one function and\n> > a few global variables).\n> >\n> > As you can see from the range-diff, not much has changed from v6.\n> >\n> > I've also included Junio's suggestion of tightening the promise made by\n> > the server (when the client says \"unborn\").\n> \n> This looks reasonable overall, especially with the feature turned on\n> by default, we'd hopefully get reasonable exposure from the get-go.\n> \n> Let's mark the topic to be merged to 'next' soonish, unless people\n> object.\n\nNo objection here. I sent a few comments in response to patch 1; the doc\nfix and the leak are probably worth addressing before it hits next. I\ncouldn't help express my thoughts on the protocol wording, but it may be\nbest to ignore me. ;)\n\nThanks for working on this, Jonathan. I think it's a very useful\nfeature.\n\n-Peff\n"},{"id":"416312","messageId":"YB1t+FFN+DUVl3G6@coredump.intra.peff.net","threadId":"54790","inReplyTo":"2d3507536924346637bb138af5a3562ce8f2abc2.1612493309.git.jonathantanmy@google.com","subject":"Re: [PATCH v7 1/3] ls-refs: report unborn targets of symrefs","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2021-02-05T16:10:32Z","receivedAt":"2021-02-05T22:16:39Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Feb 04, 2021 at 08:58:31PM -0800, Jonathan Tan wrote:\n\n> diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt\n> new file mode 100644\n> index 0000000000..e003856c08\n> --- /dev/null\n> +++ b/Documentation/config/lsrefs.txt\n> @@ -0,0 +1,9 @@\n> +lsrefs.unborn::\n> +\tMay be \"advertise\" (the default), \"allow\", or \"ignore\". If \"advertise\",\n> +\tthe server will respond to the client sending \"unborn\" (as described in\n> +\tprotocol-v2.txt) and will advertise support for this feature during the\n> +\tprotocol v2 capability advertisement. \"allow\" is the same as\n> +\t\"advertise\" except that the server will not advertise support for this\n> +\tfeature; this is useful for load-balanced servers that cannot be\n> +\tupdated automatically (for example), since the administrator could\n> +\tconfigure \"allow\", then after a delay, configure \"advertise\".\n\nMinor nit, but did you mean \"updated atomically\" in the second-to-last\nline?\n\n> diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt\n> index 85daeb5d9e..f772d90eaf 100644\n> --- a/Documentation/technical/protocol-v2.txt\n> +++ b/Documentation/technical/protocol-v2.txt\n> @@ -192,11 +192,20 @@ ls-refs takes in the following arguments:\n>  \tWhen specified, only references having a prefix matching one of\n>  \tthe provided prefixes are displayed.\n>  \n> +If the 'unborn' feature is advertised the following argument can be\n> +included in the client's request.\n> +\n> +    unborn\n> +\tThe server will send information about HEAD even if it is a symref\n> +\tpointing to an unborn branch in the form \"unborn HEAD\n> +\tsymref-target:<target>\".\n\nI saw there was some discussion back-and-forth on the wording here.\nReading this, I'm left with the impression that an implementation would\nbe wrong (i.e., violating the protocol) to send a non-HEAD symref with\nthe unborn value.\n\nI'd have thought we would describe the protocol in the most liberal\nfashion, so that clients would be made ready to handle any future\nimprovements on the server side.\n\nThat kind of sounds like the opposite of what Junio said in [1]. And I'm\nnot sure it's worth re-opening if it's going to derail or delay this\notherwise useful feature. So I offer it as my 2 cents, but not a real\nobjection to moving forward.\n\n[1] https://lore.kernel.org/git/xmqq1rdyf71k.fsf@gitster.c.googlers.com/\n\n> diff --git a/ls-refs.c b/ls-refs.c\n> index a1e0b473e4..e08fd43e7a 100644\n> --- a/ls-refs.c\n> +++ b/ls-refs.c\n> @@ -7,6 +7,39 @@\n>  #include \"pkt-line.h\"\n>  #include \"config.h\"\n>  \n> +static int config_read;\n> +static int advertise_unborn;\n> +static int allow_unborn;\n\nOK, we're back to the three-way config. I'm happy with it, as the\ndefault is now \"advertise+accept\". :)\n\n> +static void ensure_config_read(void)\n> +{\n> +\tchar *str = NULL;\n> +\n> +\tif (config_read)\n> +\t\treturn;\n> +\n> +\tif (repo_config_get_string(the_repository, \"lsrefs.unborn\", &str)) {\n\nThis function leaks \"str\". I think you can use repo_config_get_string_tmp()\nto avoid the allocation entirely.\n\n(I think the initialization to NULL is also unnecessary, but I don't\nmind it).\n\nThe rest of the patch looks good to me.\n\n-Peff\n"},{"id":"416329","messageId":"xmqqczxeyuyp.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"87eehucj33.fsf@evledraar.gmail.com","subject":"Re: [PATCH v7 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-05T23:07:42Z","receivedAt":"2021-02-06T03:01:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> And in [3] I noted that we introduced the word \"branches\" into\n> protocol-v2.txt for the first time without defining what it means\n> (presumably just refs/heads/*, but then let's say so...). There was a\n> reply promising clarification, but I note that v7 still just says\n> \"branches\".\n\nI do not think it so bad to mention \"branch\" in the part that\nexplains things to humans in the terminology they are used to.  It\nis a different matter to introduce EBNF terminal <branch> without a\nproper definition of the word, but I od not think we are doing so\nhere.\n\nI however have to agree with the need to tighten what gets sent;\nthat is why a suggested replacement in my earlier review phrased it\nthis way:\n\n    unborn\n\n    If HEAD is a symref pointing to an unborn branch <b>, the\n    server reports it as \"unborn HEAD symref-target:refs/heads/<b>\"\n    in its response.\n\nto make it clear that a full refname is sent for the pointee by\nHEAD.\n\nThanks.\n"},{"id":"416330","messageId":"xmqqh7mqyws4.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"87h7n3p363.fsf@evledraar.gmail.com","subject":"Re: [PATCH v5 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-05T22:28:27Z","receivedAt":"2021-02-06T03:19:36Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes:\n\n> On Tue, Jan 26 2021, Jonathan Tan wrote:\n>\n> [For some reason the patches didn't reach my mailbox, but I see them in\n> the list archive, so I'm replying to the cover-letter]\n>\n>>  Documentation/config.txt                |  2 +\n>>  Documentation/config/init.txt           |  2 +-\n>\n> Good, now we have init.defaultBranch docs, but they say:\n>     \n>      init.defaultBranch::\n>             Allows overriding the default branch name e.g. when initializing\n>     -       a new repository or when cloning an empty repository.\n>     +       a new repository.\n>\n> So this still only applies to file:// and other \"protocol\" clones, but\n> not \"git clone /some/path\"?\n\nI agree with you that the new \"unborn HEAD will also follow what the\nupstream has\" should be done for --local transport.  It is a bug\nwaiting to be complained about by users.\n\n>     init.defaultBranch::\n>         Allows overriding the default branch name e.g. when initializing a\n>         new repository or when cloning an empty repository.\n>     \n>         When cloning a repository over protocol v2 (i.e. ssh://, https://,\n>         file://, but not a /some/path), and if that repository has\n>         init.defaultBranch configured, the server will advertise its\n>         preferred default branch name, and we'll take its configuration over\n>         ours.\n\nI actually do not think that is what is going on.  What the other\nside advertises is *NOT* their preferred default branch name and it\ndoes not matter if they have init.defaultBranch configured or not.\n\nWhat the new protocol extension gives us is that we can learn what\nthe other side is actually using (not their preferred default) as\ntheir primary branch.  We've always done so since very early days of\n\"git clone\" (even back when we failed to clone an empty repository),\nby trying to guess which branch their HEAD points at.\n\nThe only thing that is new with this topic is that it now gives us\na reliable way to learn what their HEAD points at, even when it is\npointing at an unborn branch.\n\nIn general we do not let other side _dictate_ what our configuration\nshould look like, as that can have security implications, and this\nis not sending any configuration at all.\n\nTheir HEAD may be pointing at a specific branch (which may or may\nnot be unborn) because that is what they configured their\ninit.defaultBranch to, or their version of Git created the branch\nand they haven't changed it since repository creation, or the user\nusing that repository just started working with that branch with\n\"git checkout [--orphan]\" (the repository being cloned does not have\nto be a bare repository).  It does not matter how their HEAD ended\nup pointing at a specific branch---we just try to mimic their\ncurrent status---it is because it would make it easier to give our\nchanges back to them if everybody involved used the same name for\nthe primary integration branch, and the repositories the people\nclone from are most often have their primary integration branch\npointed at by their HEAD.\n\nAnd I do not consider it transfering any configuration.\n\nSo while I agree that the logic to choose the branch that gets\nchecked out in a new repository created by \"git clone\" needs to be\ndocumented well, it has very little to do with \"init.defaultBranch\".\n\n"},{"id":"416339","messageId":"xmqqmtwh9ghf.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"cover.1612557937.git.jonathantanmy@google.com","subject":"Re: [PATCH v8 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-06T18:51:56Z","receivedAt":"2021-02-06T18:53:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jonathan Tan <jonathantanmy@google.com> writes:\n\n> Peff sent a review (which I don't see in lore.kernel.org/git, but I do\n> see it in my inbox); here's v8 in response to that.\n>\n> As you can see from the range-diff, there are just minor changes to v7\n> (wording in documentation and a memory leak fix).\n>\n> Jonathan Tan (3):\n>   ls-refs: report unborn targets of symrefs\n>   connect, transport: encapsulate arg in struct\n>   clone: respect remote unborn HEAD\n\nQueued and pushed out, but with vger.kernel.org seem to be delaying\nmessages randomly, I'll hold off for a few days before merging them\ndown to 'next'.  To me this version looks good (at least good enough\nto cook in 'next' and details can be tweaked incrementally).\n\nThanks.\n\n>  Documentation/config.txt                |  2 +\n>  Documentation/config/init.txt           |  2 +-\n>  Documentation/config/lsrefs.txt         |  9 +++\n>  Documentation/technical/protocol-v2.txt | 11 +++-\n>  builtin/clone.c                         | 34 +++++++++---\n>  builtin/fetch-pack.c                    |  3 +-\n>  builtin/fetch.c                         | 18 +++---\n>  builtin/ls-remote.c                     |  9 +--\n>  connect.c                               | 32 ++++++++++-\n>  ls-refs.c                               | 74 ++++++++++++++++++++++++-\n>  ls-refs.h                               |  1 +\n>  remote.h                                |  4 +-\n>  serve.c                                 |  2 +-\n>  t/t5606-clone-options.sh                |  8 ++-\n>  t/t5701-git-serve.sh                    |  2 +-\n>  t/t5702-protocol-v2.sh                  | 25 +++++++++\n>  transport-helper.c                      |  5 +-\n>  transport-internal.h                    | 10 +---\n>  transport.c                             | 23 ++++----\n>  transport.h                             | 29 +++++++---\n>  20 files changed, 240 insertions(+), 63 deletions(-)\n>  create mode 100644 Documentation/config/lsrefs.txt\n>\n> Range-diff against v7:\n> 1:  2d35075369 ! 1:  8b0f55b5e4 ls-refs: report unborn targets of symrefs\n>     @@ Documentation/config/lsrefs.txt (new)\n>      +\tprotocol v2 capability advertisement. \"allow\" is the same as\n>      +\t\"advertise\" except that the server will not advertise support for this\n>      +\tfeature; this is useful for load-balanced servers that cannot be\n>     -+\tupdated automatically (for example), since the administrator could\n>     ++\tupdated atomically (for example), since the administrator could\n>      +\tconfigure \"allow\", then after a delay, configure \"advertise\".\n>      \n>       ## Documentation/technical/protocol-v2.txt ##\n>     @@ ls-refs.c\n>      +\n>      +static void ensure_config_read(void)\n>      +{\n>     -+\tchar *str = NULL;\n>     ++\tconst char *str = NULL;\n>      +\n>      +\tif (config_read)\n>      +\t\treturn;\n>      +\n>     -+\tif (repo_config_get_string(the_repository, \"lsrefs.unborn\", &str)) {\n>     ++\tif (repo_config_get_string_tmp(the_repository, \"lsrefs.unborn\", &str)) {\n>      +\t\t/*\n>      +\t\t * If there is no such config, advertise and allow it by\n>      +\t\t * default.\n> 2:  d4ed13d02e = 2:  f09bd56d5f connect, transport: encapsulate arg in struct\n> 3:  a3e5a0a7c5 = 3:  a5495a42f1 clone: respect remote unborn HEAD\n"},{"id":"416451","messageId":"xmqqczxa42kb.fsf@gitster.c.googlers.com","threadId":"54790","inReplyTo":"xmqqmtwh9ghf.fsf@gitster.c.googlers.com","subject":"Re: [PATCH v8 0/3] Cloning with remote unborn HEAD","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-02-08T22:28:20Z","receivedAt":"2021-02-08T22:29:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Jonathan Tan <jonathantanmy@google.com> writes:\n>\n>> Peff sent a review (which I don't see in lore.kernel.org/git, but I do\n>> see it in my inbox); here's v8 in response to that.\n>>\n>> As you can see from the range-diff, there are just minor changes to v7\n>> (wording in documentation and a memory leak fix).\n>>\n>> Jonathan Tan (3):\n>>   ls-refs: report unborn targets of symrefs\n>>   connect, transport: encapsulate arg in struct\n>>   clone: respect remote unborn HEAD\n>\n> Queued and pushed out, but with vger.kernel.org seem to be delaying\n> messages randomly, I'll hold off for a few days before merging them\n> down to 'next'.  To me this version looks good (at least good enough\n> to cook in 'next' and details can be tweaked incrementally).\n\nA few days have passed; let's merge it to 'next'.\n\nThanks.\n"}]}