Browse Source

Merge branch 'ab/serve-cleanup'

Code clean-up around "git serve".

* ab/serve-cleanup:
  upload-pack: document and rename --advertise-refs
  serve.[ch]: remove "serve_options", split up --advertise-refs code
  {upload,receive}-pack tests: add --advertise-refs tests
  serve.c: move version line to advertise_capabilities()
  serve: move transfer.advertiseSID check into session_id_advertise()
  serve.[ch]: don't pass "struct strvec *keys" to commands
  serve: use designated initializers
  transport: use designated initializers
  transport: rename "fetch" in transport_vtable to "fetch_refs"
  serve: mark has_capability() as static
pull/1094/head
Junio C Hamano 11 months ago
parent
commit
5331af2352
  1. 5
      Documentation/git-receive-pack.txt
  2. 12
      Documentation/git-upload-pack.txt
  3. 3
      Documentation/technical/http-protocol.txt
  4. 3
      Documentation/technical/protocol-v2.txt
  5. 3
      builtin/receive-pack.c
  6. 28
      builtin/upload-pack.c
  7. 2
      http-backend.c
  8. 3
      ls-refs.c
  9. 4
      ls-refs.h
  10. 3
      protocol-caps.c
  11. 4
      protocol-caps.h
  12. 82
      serve.c
  13. 12
      serve.h
  14. 14
      t/helper/test-serve-v2.c
  15. 161
      t/t5555-http-smart-common.sh
  16. 18
      transport-helper.c
  17. 2
      transport-internal.h
  18. 32
      transport.c
  19. 21
      upload-pack.c
  20. 14
      upload-pack.h

5
Documentation/git-receive-pack.txt

@ -41,6 +41,11 @@ OPTIONS
<directory>::
The repository to sync into.
--http-backend-info-refs::
Used by linkgit:git-http-backend[1] to serve up
`$GIT_URL/info/refs?service=git-receive-pack` requests. See
`--http-backend-info-refs` in linkgit:git-upload-pack[1].
PRE-RECEIVE HOOK
----------------
Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists

12
Documentation/git-upload-pack.txt

@ -36,10 +36,14 @@ OPTIONS
This fits with the HTTP POST request processing model where
a program may read the request, write a response, and must exit.
--advertise-refs::
Only the initial ref advertisement is output, and the program exits
immediately. This fits with the HTTP GET request model, where
no request content is received but a response must be produced.
--http-backend-info-refs::
Used by linkgit:git-http-backend[1] to serve up
`$GIT_URL/info/refs?service=git-upload-pack` requests. See
"Smart Clients" in link:technical/http-protocol.html[the HTTP
transfer protocols] documentation and "HTTP Transport" in
link:technical/protocol-v2.html[the Git Wire Protocol, Version
2] documentation. Also understood by
linkgit:git-receive-pack[1].
<directory>::
The repository to sync from.

3
Documentation/technical/http-protocol.txt

@ -225,6 +225,9 @@ The client may send Extra Parameters (see
Documentation/technical/pack-protocol.txt) as a colon-separated string
in the Git-Protocol HTTP header.
Uses the `--http-backend-info-refs` option to
linkgit:git-upload-pack[1].
Dumb Server Response
^^^^^^^^^^^^^^^^^^^^
Dumb servers MUST respond with the dumb server reply format.

3
Documentation/technical/protocol-v2.txt

@ -81,6 +81,9 @@ A v2 server would reply:
Subsequent requests are then made directly to the service
`$GIT_URL/git-upload-pack`. (This works the same for git-receive-pack).
Uses the `--http-backend-info-refs` option to
linkgit:git-upload-pack[1].
Capability Advertisement
------------------------

3
builtin/receive-pack.c

@ -2474,7 +2474,8 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
struct option options[] = {
OPT__QUIET(&quiet, N_("quiet")),
OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL),
OPT_HIDDEN_BOOL(0, "advertise-refs", &advertise_refs, NULL),
OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, NULL),
OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
OPT_HIDDEN_BOOL(0, "reject-thin-pack-for-testing", &reject_thin, NULL),
OPT_END()
};

28
builtin/upload-pack.c

@ -16,16 +16,18 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
{
const char *dir;
int strict = 0;
struct upload_pack_options opts = { 0 };
struct serve_options serve_opts = SERVE_OPTIONS_INIT;
int advertise_refs = 0;
int stateless_rpc = 0;
int timeout = 0;
struct option options[] = {
OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
N_("quit after a single request/response exchange")),
OPT_BOOL(0, "advertise-refs", &opts.advertise_refs,
N_("exit immediately after initial ref advertisement")),
OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs,
N_("serve up the info/refs for git-http-backend")),
OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"),
OPT_BOOL(0, "strict", &strict,
N_("do not try <directory>/.git/ if <directory> is no Git directory")),
OPT_INTEGER(0, "timeout", &opts.timeout,
OPT_INTEGER(0, "timeout", &timeout,
N_("interrupt transfer after <n> seconds of inactivity")),
OPT_END()
};
@ -38,9 +40,6 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
if (argc != 1)
usage_with_options(upload_pack_usage, options);
if (opts.timeout)
opts.daemon_mode = 1;
setup_path();
dir = argv[0];
@ -50,21 +49,22 @@ int cmd_upload_pack(int argc, const char **argv, const char *prefix)
switch (determine_protocol_version_server()) {
case protocol_v2:
serve_opts.advertise_capabilities = opts.advertise_refs;
serve_opts.stateless_rpc = opts.stateless_rpc;
serve(&serve_opts);
if (advertise_refs)
protocol_v2_advertise_capabilities();
else
protocol_v2_serve_loop(stateless_rpc);
break;
case protocol_v1:
/*
* v1 is just the original protocol with a version string,
* so just fall through after writing the version string.
*/
if (opts.advertise_refs || !opts.stateless_rpc)
if (advertise_refs || !stateless_rpc)
packet_write_fmt(1, "version 1\n");
/* fallthrough */
case protocol_v0:
upload_pack(&opts);
upload_pack(advertise_refs, stateless_rpc, timeout);
break;
case protocol_unknown_version:
BUG("unknown protocol version");

2
http-backend.c

@ -534,7 +534,7 @@ static void get_info_refs(struct strbuf *hdr, char *arg)
if (service_name) {
const char *argv[] = {NULL /* service name */,
"--stateless-rpc", "--advertise-refs",
"--http-backend-info-refs",
".", NULL};
struct rpc_service *svc = select_service(hdr, service_name);

3
ls-refs.c

@ -139,8 +139,7 @@ static int ls_refs_config(const char *var, const char *value, void *data)
return parse_hide_refs_config(var, value, "uploadpack");
}
int ls_refs(struct repository *r, struct strvec *keys,
struct packet_reader *request)
int ls_refs(struct repository *r, struct packet_reader *request)
{
struct ls_refs_data data;

4
ls-refs.h

@ -2,10 +2,8 @@
#define LS_REFS_H
struct repository;
struct strvec;
struct packet_reader;
int ls_refs(struct repository *r, struct strvec *keys,
struct packet_reader *request);
int ls_refs(struct repository *r, struct packet_reader *request);
int ls_refs_advertise(struct repository *r, struct strbuf *value);
#endif /* LS_REFS_H */

3
protocol-caps.c

@ -75,8 +75,7 @@ static void send_info(struct repository *r, struct packet_writer *writer,
strbuf_release(&send_buffer);
}
int cap_object_info(struct repository *r, struct strvec *keys,
struct packet_reader *request)
int cap_object_info(struct repository *r, struct packet_reader *request)
{
struct requested_info info;
struct packet_writer writer;

4
protocol-caps.h

@ -2,9 +2,7 @@
#define PROTOCOL_CAPS_H
struct repository;
struct strvec;
struct packet_reader;
int cap_object_info(struct repository *r, struct strvec *keys,
struct packet_reader *request);
int cap_object_info(struct repository *r, struct packet_reader *request);
#endif /* PROTOCOL_CAPS_H */

82
serve.c

@ -9,7 +9,7 @@
#include "serve.h"
#include "upload-pack.h"
static int advertise_sid;
static int advertise_sid = -1;
static int always_advertise(struct repository *r,
struct strbuf *value)
@ -35,6 +35,9 @@ static int object_format_advertise(struct repository *r,
static int session_id_advertise(struct repository *r, struct strbuf *value)
{
if (advertise_sid == -1 &&
git_config_get_bool("transfer.advertisesid", &advertise_sid))
advertise_sid = 0;
if (!advertise_sid)
return 0;
if (value)
@ -60,34 +63,58 @@ struct protocol_capability {
/*
* Function called when a client requests the capability as a command.
* The function will be provided the capabilities requested via 'keys'
* as well as a struct packet_reader 'request' which the command should
* Will be provided a struct packet_reader 'request' which it should
* use to read the command specific part of the request. Every command
* MUST read until a flush packet is seen before sending a response.
*
* This field should be NULL for capabilities which are not commands.
*/
int (*command)(struct repository *r,
struct strvec *keys,
struct packet_reader *request);
int (*command)(struct repository *r, struct packet_reader *request);
};
static struct protocol_capability capabilities[] = {
{ "agent", agent_advertise, NULL },
{ "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 },
{ "session-id", session_id_advertise, NULL },
{ "object-info", always_advertise, cap_object_info },
{
.name = "agent",
.advertise = agent_advertise,
},
{
.name = "ls-refs",
.advertise = ls_refs_advertise,
.command = ls_refs,
},
{
.name = "fetch",
.advertise = upload_pack_advertise,
.command = upload_pack_v2,
},
{
.name = "server-option",
.advertise = always_advertise,
},
{
.name = "object-format",
.advertise = object_format_advertise,
},
{
.name = "session-id",
.advertise = session_id_advertise,
},
{
.name = "object-info",
.advertise = always_advertise,
.command = cap_object_info,
},
};
static void advertise_capabilities(void)
void protocol_v2_advertise_capabilities(void)
{
struct strbuf capability = STRBUF_INIT;
struct strbuf value = STRBUF_INIT;
int i;
/* serve by default supports v2 */
packet_write_fmt(1, "version 2\n");
for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
struct protocol_capability *c = &capabilities[i];
@ -156,8 +183,8 @@ static int is_command(const char *key, struct protocol_capability **command)
return 0;
}
int has_capability(const struct strvec *keys, const char *capability,
const char **value)
static int has_capability(const struct strvec *keys, const char *capability,
const char **value)
{
int i;
for (i = 0; i < keys->nr; i++) {
@ -270,35 +297,22 @@ static int process_request(void)
if (has_capability(&keys, "session-id", &client_sid))
trace2_data_string("transfer", NULL, "client-sid", client_sid);
command->command(the_repository, &keys, &reader);
command->command(the_repository, &reader);
strvec_clear(&keys);
return 0;
}
/* Main serve loop for protocol version 2 */
void serve(struct serve_options *options)
void protocol_v2_serve_loop(int stateless_rpc)
{
git_config_get_bool("transfer.advertisesid", &advertise_sid);
if (options->advertise_capabilities || !options->stateless_rpc) {
/* serve by default supports v2 */
packet_write_fmt(1, "version 2\n");
advertise_capabilities();
/*
* If only the list of capabilities was requested exit
* immediately after advertising capabilities
*/
if (options->advertise_capabilities)
return;
}
if (!stateless_rpc)
protocol_v2_advertise_capabilities();
/*
* If stateless-rpc was requested then exit after
* a single request/response exchange
*/
if (options->stateless_rpc) {
if (stateless_rpc) {
process_request();
} else {
for (;;)

12
serve.h

@ -1,15 +1,7 @@
#ifndef SERVE_H
#define SERVE_H
struct strvec;
int has_capability(const struct strvec *keys, const char *capability,
const char **value);
struct serve_options {
unsigned advertise_capabilities;
unsigned stateless_rpc;
};
#define SERVE_OPTIONS_INIT { 0 }
void serve(struct serve_options *options);
void protocol_v2_advertise_capabilities(void);
void protocol_v2_serve_loop(int stateless_rpc);
#endif /* SERVE_H */

14
t/helper/test-serve-v2.c

@ -10,12 +10,12 @@ static char const * const serve_usage[] = {
int cmd__serve_v2(int argc, const char **argv)
{
struct serve_options opts = SERVE_OPTIONS_INIT;
int stateless_rpc = 0;
int advertise_capabilities = 0;
struct option options[] = {
OPT_BOOL(0, "stateless-rpc", &opts.stateless_rpc,
OPT_BOOL(0, "stateless-rpc", &stateless_rpc,
N_("quit after a single request/response exchange")),
OPT_BOOL(0, "advertise-capabilities", &opts.advertise_capabilities,
OPT_BOOL(0, "advertise-capabilities", &advertise_capabilities,
N_("exit immediately after advertising capabilities")),
OPT_END()
};
@ -25,7 +25,11 @@ int cmd__serve_v2(int argc, const char **argv)
argc = parse_options(argc, argv, prefix, options, serve_usage,
PARSE_OPT_KEEP_DASHDASH |
PARSE_OPT_KEEP_UNKNOWN);
serve(&opts);
if (advertise_capabilities)
protocol_v2_advertise_capabilities();
else
protocol_v2_serve_loop(stateless_rpc);
return 0;
}

161
t/t5555-http-smart-common.sh

@ -0,0 +1,161 @@
#!/bin/sh
test_description='test functionality common to smart fetch & push'
. ./test-lib.sh
test_expect_success 'setup' '
test_commit --no-tag initial
'
test_expect_success 'git upload-pack --http-backend-info-refs and --advertise-refs are aliased' '
git upload-pack --http-backend-info-refs . >expected 2>err.expected &&
git upload-pack --advertise-refs . >actual 2>err.actual &&
test_cmp err.expected err.actual &&
test_cmp expected actual
'
test_expect_success 'git receive-pack --http-backend-info-refs and --advertise-refs are aliased' '
git receive-pack --http-backend-info-refs . >expected 2>err.expected &&
git receive-pack --advertise-refs . >actual 2>err.actual &&
test_cmp err.expected err.actual &&
test_cmp expected actual
'
test_expect_success 'git upload-pack --advertise-refs' '
cat >expect <<-EOF &&
$(git rev-parse HEAD) HEAD
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
# We only care about GIT_PROTOCOL, not GIT_TEST_PROTOCOL_VERSION
sane_unset GIT_PROTOCOL &&
GIT_TEST_PROTOCOL_VERSION=2 \
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect &&
# The --advertise-refs alias works
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git upload-pack --advertise-refs: v0' '
# With no specified protocol
cat >expect <<-EOF &&
$(git rev-parse HEAD) HEAD
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect &&
# With explicit v0
GIT_PROTOCOL=version=0 \
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual 2>err &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git receive-pack --advertise-refs: v0' '
# With no specified protocol
cat >expect <<-EOF &&
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
git receive-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect &&
# With explicit v0
GIT_PROTOCOL=version=0 \
git receive-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual 2>err &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git upload-pack --advertise-refs: v1' '
# With no specified protocol
cat >expect <<-EOF &&
version 1
$(git rev-parse HEAD) HEAD
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
GIT_PROTOCOL=version=1 \
git upload-pack --advertise-refs . >out &&
test-tool pkt-line unpack <out >actual 2>err &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git receive-pack --advertise-refs: v1' '
# With no specified protocol
cat >expect <<-EOF &&
version 1
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
GIT_PROTOCOL=version=1 \
git receive-pack --advertise-refs . >out &&
test-tool pkt-line unpack <out >actual 2>err &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git upload-pack --advertise-refs: v2' '
cat >expect <<-EOF &&
version 2
agent=FAKE
ls-refs=unborn
fetch=shallow wait-for-done
server-option
object-format=$(test_oid algo)
object-info
0000
EOF
GIT_PROTOCOL=version=2 \
GIT_USER_AGENT=FAKE \
git upload-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect
'
test_expect_success 'git receive-pack --advertise-refs: v2' '
# There is no v2 yet for receive-pack, implicit v0
cat >expect <<-EOF &&
$(git rev-parse HEAD) $(git symbolic-ref HEAD)
0000
EOF
GIT_PROTOCOL=version=2 \
git receive-pack --advertise-refs . >out 2>err &&
test-tool pkt-line unpack <out >actual &&
test_must_be_empty err &&
test_cmp actual expect
'
test_done

18
transport-helper.c

@ -671,8 +671,8 @@ static int connect_helper(struct transport *transport, const char *name,
static struct ref *get_refs_list_using_list(struct transport *transport,
int for_push);
static int fetch(struct transport *transport,
int nr_heads, struct ref **to_fetch)
static int fetch_refs(struct transport *transport,
int nr_heads, struct ref **to_fetch)
{
struct helper_data *data = transport->data;
int i, count;
@ -681,7 +681,7 @@ static int fetch(struct transport *transport,
if (process_connect(transport, 0)) {
do_take_over(transport);
return transport->vtable->fetch(transport, nr_heads, to_fetch);
return transport->vtable->fetch_refs(transport, nr_heads, to_fetch);
}
/*
@ -1261,12 +1261,12 @@ static struct ref *get_refs_list_using_list(struct transport *transport,
}
static struct transport_vtable vtable = {
set_helper_option,
get_refs_list,
fetch,
push_refs,
connect_helper,
release_helper
.set_option = set_helper_option,
.get_refs_list = get_refs_list,
.fetch_refs = fetch_refs,
.push_refs = push_refs,
.connect = connect_helper,
.disconnect = release_helper
};
int transport_helper_init(struct transport *transport, const char *name)

2
transport-internal.h

@ -34,7 +34,7 @@ struct transport_vtable {
* get_refs_list(), it should set the old_sha1 fields in the
* provided refs now.
**/
int (*fetch)(struct transport *transport, int refs_nr, struct ref **refs);
int (*fetch_refs)(struct transport *transport, int refs_nr, struct ref **refs);
/**
* Push the objects and refs. Send the necessary objects, and

32
transport.c

@ -887,12 +887,10 @@ static int disconnect_git(struct transport *transport)
}
static struct transport_vtable taken_over_vtable = {
NULL,
get_refs_via_connect,
fetch_refs_via_pack,
git_transport_push,
NULL,
disconnect_git
.get_refs_list = get_refs_via_connect,
.fetch_refs = fetch_refs_via_pack,
.push_refs = git_transport_push,
.disconnect = disconnect_git
};
void transport_take_over(struct transport *transport,
@ -1036,21 +1034,17 @@ void transport_check_allowed(const char *type)
}
static struct transport_vtable bundle_vtable = {
NULL,
get_refs_from_bundle,
fetch_refs_from_bundle,
NULL,
NULL,
close_bundle
.get_refs_list = get_refs_from_bundle,
.fetch_refs = fetch_refs_from_bundle,
.disconnect = close_bundle
};
static struct transport_vtable builtin_smart_vtable = {
NULL,
get_refs_via_connect,
fetch_refs_via_pack,
git_transport_push,
connect_git,
disconnect_git
.get_refs_list = get_refs_via_connect,
.fetch_refs = fetch_refs_via_pack,
.push_refs = git_transport_push,
.connect = connect_git,
.disconnect = disconnect_git
};
struct transport *transport_get(struct remote *remote, const char *url)
@ -1457,7 +1451,7 @@ int transport_fetch_refs(struct transport *transport, struct ref *refs)
heads[nr_heads++] = rm;
}
rc = transport->vtable->fetch(transport, nr_heads, heads);
rc = transport->vtable->fetch_refs(transport, nr_heads, heads);
free(heads);
return rc;

21
upload-pack.c

@ -1214,7 +1214,7 @@ static int send_ref(const char *refname, const struct object_id *oid,
" allow-tip-sha1-in-want" : "",
(data->allow_uor & ALLOW_REACHABLE_SHA1) ?
" allow-reachable-sha1-in-want" : "",
data->stateless_rpc ? " no-done" : "",
data->no_done ? " no-done" : "",
symref_info.buf,
data->allow_filter ? " filter" : "",
session_id.buf,
@ -1329,7 +1329,8 @@ static int upload_pack_config(const char *var, const char *value, void *cb_data)
return parse_hide_refs_config(var, value, "uploadpack");
}
void upload_pack(struct upload_pack_options *options)
void upload_pack(const int advertise_refs, const int stateless_rpc,
const int timeout)
{
struct packet_reader reader;
struct upload_pack_data data;
@ -1338,14 +1339,17 @@ void upload_pack(struct upload_pack_options *options)
git_config(upload_pack_config, &data);
data.stateless_rpc = options->stateless_rpc;
data.daemon_mode = options->daemon_mode;
data.timeout = options->timeout;
data.stateless_rpc = stateless_rpc;
data.timeout = timeout;
if (data.timeout)
data.daemon_mode = 1;
head_ref_namespaced(find_symref, &data.symref);
if (options->advertise_refs || !data.stateless_rpc) {
if (advertise_refs || !data.stateless_rpc) {
reset_timeout(data.timeout);
if (advertise_refs)
data.no_done = 1;
head_ref_namespaced(send_ref, &data);
for_each_namespaced_ref(send_ref, &data);
/*
@ -1360,7 +1364,7 @@ void upload_pack(struct upload_pack_options *options)
for_each_namespaced_ref(check_ref, NULL);
}
if (!options->advertise_refs) {
if (!advertise_refs) {
packet_reader_init(&reader, 0, NULL, 0,
PACKET_READ_CHOMP_NEWLINE |
PACKET_READ_DIE_ON_ERR_PACKET);
@ -1664,8 +1668,7 @@ enum fetch_state {
FETCH_DONE,
};
int upload_pack_v2(struct repository *r, struct strvec *keys,
struct packet_reader *request)
int upload_pack_v2(struct repository *r, struct packet_reader *request)
{
enum fetch_state state = FETCH_PROCESS_ARGS;
struct upload_pack_data data;

14
upload-pack.h

@ -1,20 +1,12 @@
#ifndef UPLOAD_PACK_H
#define UPLOAD_PACK_H
struct upload_pack_options {
int stateless_rpc;
int advertise_refs;
unsigned int timeout;
int daemon_mode;
};
void upload_pack(struct upload_pack_options *options);
void upload_pack(const int advertise_refs, const int stateless_rpc,
const int timeout);
struct repository;
struct strvec;
struct packet_reader;
int upload_pack_v2(struct repository *r, struct strvec *keys,
struct packet_reader *request);
int upload_pack_v2(struct repository *r, struct packet_reader *request);
struct strbuf;
int upload_pack_advertise(struct repository *r,

Loading…
Cancel
Save