diff --git a/Documentation/config.txt b/Documentation/config.txt index 6ba50b1104..d08e83a148 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -398,6 +398,8 @@ include::config/interactive.txt[] include::config/log.txt[] +include::config/lsrefs.txt[] + include::config/mailinfo.txt[] include::config/mailmap.txt[] diff --git a/Documentation/config/lsrefs.txt b/Documentation/config/lsrefs.txt new file mode 100644 index 0000000000..adeda0f24d --- /dev/null +++ b/Documentation/config/lsrefs.txt @@ -0,0 +1,9 @@ +lsrefs.unborn:: + May be "advertise" (the default), "allow", or "ignore". If "advertise", + the server will respond to the client sending "unborn" (as described in + protocol-v2.txt) and will advertise support for this feature during the + protocol v2 capability advertisement. "allow" is the same as + "advertise" except that the server will not advertise support for this + feature; this is useful for load-balanced servers that cannot be + updated atomically (for example), since the administrator could + configure "allow", then after a delay, configure "advertise". diff --git a/Documentation/technical/protocol-v2.txt b/Documentation/technical/protocol-v2.txt index 85daeb5d9e..f772d90eaf 100644 --- a/Documentation/technical/protocol-v2.txt +++ b/Documentation/technical/protocol-v2.txt @@ -192,11 +192,20 @@ ls-refs takes in the following arguments: When specified, only references having a prefix matching one of the provided prefixes are displayed. +If the 'unborn' feature is advertised the following argument can be +included in the client's request. + + unborn + The server will send information about HEAD even if it is a symref + pointing to an unborn branch in the form "unborn HEAD + symref-target:". + The output of ls-refs is as follows: output = *ref flush-pkt - ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF) + obj-id-or-unborn = (obj-id | "unborn") + ref = PKT-LINE(obj-id-or-unborn SP refname *(SP ref-attribute) LF) ref-attribute = (symref | peeled) symref = "symref-target:" symref-target peeled = "peeled:" obj-id diff --git a/ls-refs.c b/ls-refs.c index a1e0b473e4..32deb7be44 100644 --- a/ls-refs.c +++ b/ls-refs.c @@ -7,6 +7,39 @@ #include "pkt-line.h" #include "config.h" +static int config_read; +static int advertise_unborn; +static int allow_unborn; + +static void ensure_config_read(void) +{ + const char *str = NULL; + + if (config_read) + return; + + if (repo_config_get_string_tmp(the_repository, "lsrefs.unborn", &str)) { + /* + * If there is no such config, advertise and allow it by + * default. + */ + advertise_unborn = 1; + allow_unborn = 1; + } else { + if (!strcmp(str, "advertise")) { + advertise_unborn = 1; + allow_unborn = 1; + } else if (!strcmp(str, "allow")) { + allow_unborn = 1; + } else if (!strcmp(str, "ignore")) { + /* do nothing */ + } else { + die(_("invalid value '%s' for lsrefs.unborn"), str); + } + } + config_read = 1; +} + /* * Check if one of the prefixes is a prefix of the ref. * If no prefixes were provided, all refs match. @@ -32,6 +65,7 @@ struct ls_refs_data { unsigned peel; unsigned symrefs; struct strvec prefixes; + unsigned unborn : 1; }; static int send_ref(const char *refname, const struct object_id *oid, @@ -47,7 +81,10 @@ static int send_ref(const char *refname, const struct object_id *oid, if (!ref_match(&data->prefixes, refname_nons)) return 0; - strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + if (oid) + strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons); + else + strbuf_addf(&refline, "unborn %s", refname_nons); if (data->symrefs && flag & REF_ISSYMREF) { struct object_id unused; const char *symref_target = resolve_ref_unsafe(refname, 0, @@ -61,7 +98,7 @@ static int send_ref(const char *refname, const struct object_id *oid, strip_namespace(symref_target)); } - if (data->peel) { + if (data->peel && oid) { struct object_id peeled; if (!peel_ref(refname, &peeled)) strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled)); @@ -74,6 +111,23 @@ static int send_ref(const char *refname, const struct object_id *oid, return 0; } +static void send_possibly_unborn_head(struct ls_refs_data *data) +{ + struct strbuf namespaced = STRBUF_INIT; + struct object_id oid; + int flag; + int oid_is_null; + + strbuf_addf(&namespaced, "%sHEAD", get_git_namespace()); + if (!resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag)) + return; /* bad ref */ + oid_is_null = is_null_oid(&oid); + if (!oid_is_null || + (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) + send_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data); + strbuf_release(&namespaced); +} + static int ls_refs_config(const char *var, const char *value, void *data) { /* @@ -91,6 +145,7 @@ int ls_refs(struct repository *r, struct strvec *keys, memset(&data, 0, sizeof(data)); + ensure_config_read(); git_config(ls_refs_config, NULL); while (packet_reader_read(request) == PACKET_READ_NORMAL) { @@ -103,14 +158,27 @@ int ls_refs(struct repository *r, struct strvec *keys, data.symrefs = 1; else if (skip_prefix(arg, "ref-prefix ", &out)) strvec_push(&data.prefixes, out); + else if (!strcmp("unborn", arg)) + data.unborn = allow_unborn; } if (request->status != PACKET_READ_FLUSH) die(_("expected flush after ls-refs arguments")); - head_ref_namespaced(send_ref, &data); + send_possibly_unborn_head(&data); for_each_namespaced_ref(send_ref, &data); packet_flush(1); strvec_clear(&data.prefixes); return 0; } + +int ls_refs_advertise(struct repository *r, struct strbuf *value) +{ + if (value) { + ensure_config_read(); + if (advertise_unborn) + strbuf_addstr(value, "unborn"); + } + + return 1; +} diff --git a/ls-refs.h b/ls-refs.h index 7b33a7c6b8..a99e4be0bd 100644 --- a/ls-refs.h +++ b/ls-refs.h @@ -6,5 +6,6 @@ struct strvec; struct packet_reader; int ls_refs(struct repository *r, struct strvec *keys, struct packet_reader *request); +int ls_refs_advertise(struct repository *r, struct strbuf *value); #endif /* LS_REFS_H */ diff --git a/serve.c b/serve.c index eec2fe6f29..ac20c72763 100644 --- a/serve.c +++ b/serve.c @@ -73,7 +73,7 @@ struct protocol_capability { static struct protocol_capability capabilities[] = { { "agent", agent_advertise, NULL }, - { "ls-refs", always_advertise, ls_refs }, + { "ls-refs", ls_refs_advertise, ls_refs }, { "fetch", upload_pack_advertise, upload_pack_v2 }, { "server-option", always_advertise, NULL }, { "object-format", object_format_advertise, NULL }, diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index a1f5fdc9fd..df29504161 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -12,7 +12,7 @@ test_expect_success 'test capability advertisement' ' cat >expect <<-EOF && version 2 agent=git/$(git version | cut -d" " -f3) - ls-refs + ls-refs=unborn fetch=shallow server-option object-format=$(test_oid algo)