ls-refs: introduce ls-refs server command
Introduce the ls-refs server command. In protocol v2, the ls-refs command is used to request the ref advertisement from the server. Since it is a command which can be requested (as opposed to mandatory in v1), a client can sent a number of parameters in its request to limit the ref advertisement based on provided ref-prefixes. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
		
				
					committed by
					
						
						Junio C Hamano
					
				
			
			
				
	
			
			
			
						parent
						
							ed10cb952d
						
					
				
				
					commit
					72d0ea0056
				
			@ -168,3 +168,34 @@ printable ASCII characters except space (i.e., the byte range 32 < x <
 | 
			
		||||
"git/1.8.3.1"). The agent strings are purely informative for statistics
 | 
			
		||||
and debugging purposes, and MUST NOT be used to programmatically assume
 | 
			
		||||
the presence or absence of particular features.
 | 
			
		||||
 | 
			
		||||
 ls-refs
 | 
			
		||||
~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
`ls-refs` is the command used to request a reference advertisement in v2.
 | 
			
		||||
Unlike the current reference advertisement, ls-refs takes in arguments
 | 
			
		||||
which can be used to limit the refs sent from the server.
 | 
			
		||||
 | 
			
		||||
Additional features not supported in the base command will be advertised
 | 
			
		||||
as the value of the command in the capability advertisement in the form
 | 
			
		||||
of a space separated list of features: "<command>=<feature 1> <feature 2>"
 | 
			
		||||
 | 
			
		||||
ls-refs takes in the following arguments:
 | 
			
		||||
 | 
			
		||||
    symrefs
 | 
			
		||||
	In addition to the object pointed by it, show the underlying ref
 | 
			
		||||
	pointed by it when showing a symbolic ref.
 | 
			
		||||
    peel
 | 
			
		||||
	Show peeled tags.
 | 
			
		||||
    ref-prefix <prefix>
 | 
			
		||||
	When specified, only references having a prefix matching one of
 | 
			
		||||
	the provided prefixes are displayed.
 | 
			
		||||
 | 
			
		||||
The output of ls-refs is as follows:
 | 
			
		||||
 | 
			
		||||
    output = *ref
 | 
			
		||||
	     flush-pkt
 | 
			
		||||
    ref = PKT-LINE(obj-id SP refname *(SP ref-attribute) LF)
 | 
			
		||||
    ref-attribute = (symref | peeled)
 | 
			
		||||
    symref = "symref-target:" symref-target
 | 
			
		||||
    peeled = "peeled:" obj-id
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Makefile
									
									
									
									
									
								
							@ -820,6 +820,7 @@ LIB_OBJS += list-objects-filter-options.o
 | 
			
		||||
LIB_OBJS += ll-merge.o
 | 
			
		||||
LIB_OBJS += lockfile.o
 | 
			
		||||
LIB_OBJS += log-tree.o
 | 
			
		||||
LIB_OBJS += ls-refs.o
 | 
			
		||||
LIB_OBJS += mailinfo.o
 | 
			
		||||
LIB_OBJS += mailmap.o
 | 
			
		||||
LIB_OBJS += match-trees.o
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								ls-refs.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								ls-refs.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,96 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "repository.h"
 | 
			
		||||
#include "refs.h"
 | 
			
		||||
#include "remote.h"
 | 
			
		||||
#include "argv-array.h"
 | 
			
		||||
#include "ls-refs.h"
 | 
			
		||||
#include "pkt-line.h"
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Check if one of the prefixes is a prefix of the ref.
 | 
			
		||||
 * If no prefixes were provided, all refs match.
 | 
			
		||||
 */
 | 
			
		||||
static int ref_match(const struct argv_array *prefixes, const char *refname)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (!prefixes->argc)
 | 
			
		||||
		return 1; /* no restriction */
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < prefixes->argc; i++) {
 | 
			
		||||
		const char *prefix = prefixes->argv[i];
 | 
			
		||||
 | 
			
		||||
		if (starts_with(refname, prefix))
 | 
			
		||||
			return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct ls_refs_data {
 | 
			
		||||
	unsigned peel;
 | 
			
		||||
	unsigned symrefs;
 | 
			
		||||
	struct argv_array prefixes;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static int send_ref(const char *refname, const struct object_id *oid,
 | 
			
		||||
		    int flag, void *cb_data)
 | 
			
		||||
{
 | 
			
		||||
	struct ls_refs_data *data = cb_data;
 | 
			
		||||
	const char *refname_nons = strip_namespace(refname);
 | 
			
		||||
	struct strbuf refline = STRBUF_INIT;
 | 
			
		||||
 | 
			
		||||
	if (!ref_match(&data->prefixes, refname))
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	strbuf_addf(&refline, "%s %s", oid_to_hex(oid), refname_nons);
 | 
			
		||||
	if (data->symrefs && flag & REF_ISSYMREF) {
 | 
			
		||||
		struct object_id unused;
 | 
			
		||||
		const char *symref_target = resolve_ref_unsafe(refname, 0,
 | 
			
		||||
							       &unused,
 | 
			
		||||
							       &flag);
 | 
			
		||||
 | 
			
		||||
		if (!symref_target)
 | 
			
		||||
			die("'%s' is a symref but it is not?", refname);
 | 
			
		||||
 | 
			
		||||
		strbuf_addf(&refline, " symref-target:%s", symref_target);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (data->peel) {
 | 
			
		||||
		struct object_id peeled;
 | 
			
		||||
		if (!peel_ref(refname, &peeled))
 | 
			
		||||
			strbuf_addf(&refline, " peeled:%s", oid_to_hex(&peeled));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	strbuf_addch(&refline, '\n');
 | 
			
		||||
	packet_write(1, refline.buf, refline.len);
 | 
			
		||||
 | 
			
		||||
	strbuf_release(&refline);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ls_refs(struct repository *r, struct argv_array *keys,
 | 
			
		||||
	    struct packet_reader *request)
 | 
			
		||||
{
 | 
			
		||||
	struct ls_refs_data data;
 | 
			
		||||
 | 
			
		||||
	memset(&data, 0, sizeof(data));
 | 
			
		||||
 | 
			
		||||
	while (packet_reader_read(request) != PACKET_READ_FLUSH) {
 | 
			
		||||
		const char *arg = request->line;
 | 
			
		||||
		const char *out;
 | 
			
		||||
 | 
			
		||||
		if (!strcmp("peel", arg))
 | 
			
		||||
			data.peel = 1;
 | 
			
		||||
		else if (!strcmp("symrefs", arg))
 | 
			
		||||
			data.symrefs = 1;
 | 
			
		||||
		else if (skip_prefix(arg, "ref-prefix ", &out))
 | 
			
		||||
			argv_array_push(&data.prefixes, out);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	head_ref_namespaced(send_ref, &data);
 | 
			
		||||
	for_each_namespaced_ref(send_ref, &data);
 | 
			
		||||
	packet_flush(1);
 | 
			
		||||
	argv_array_clear(&data.prefixes);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										10
									
								
								ls-refs.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								ls-refs.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
			
		||||
#ifndef LS_REFS_H
 | 
			
		||||
#define LS_REFS_H
 | 
			
		||||
 | 
			
		||||
struct repository;
 | 
			
		||||
struct argv_array;
 | 
			
		||||
struct packet_reader;
 | 
			
		||||
extern int ls_refs(struct repository *r, struct argv_array *keys,
 | 
			
		||||
		   struct packet_reader *request);
 | 
			
		||||
 | 
			
		||||
#endif /* LS_REFS_H */
 | 
			
		||||
							
								
								
									
										8
									
								
								serve.c
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								serve.c
									
									
									
									
									
								
							@ -4,8 +4,15 @@
 | 
			
		||||
#include "pkt-line.h"
 | 
			
		||||
#include "version.h"
 | 
			
		||||
#include "argv-array.h"
 | 
			
		||||
#include "ls-refs.h"
 | 
			
		||||
#include "serve.h"
 | 
			
		||||
 | 
			
		||||
static int always_advertise(struct repository *r,
 | 
			
		||||
			    struct strbuf *value)
 | 
			
		||||
{
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int agent_advertise(struct repository *r,
 | 
			
		||||
			   struct strbuf *value)
 | 
			
		||||
{
 | 
			
		||||
@ -46,6 +53,7 @@ struct protocol_capability {
 | 
			
		||||
 | 
			
		||||
static struct protocol_capability capabilities[] = {
 | 
			
		||||
	{ "agent", agent_advertise, NULL },
 | 
			
		||||
	{ "ls-refs", always_advertise, ls_refs },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static void advertise_capabilities(void)
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,7 @@ test_expect_success 'test capability advertisement' '
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	version 2
 | 
			
		||||
	agent=git/$(git version | cut -d" " -f3)
 | 
			
		||||
	ls-refs
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
@ -57,4 +58,118 @@ test_expect_success 'request invalid command' '
 | 
			
		||||
	test_i18ngrep "invalid command" err
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
# Test the basics of ls-refs
 | 
			
		||||
#
 | 
			
		||||
test_expect_success 'setup some refs and tags' '
 | 
			
		||||
	test_commit one &&
 | 
			
		||||
	git branch dev master &&
 | 
			
		||||
	test_commit two &&
 | 
			
		||||
	git symbolic-ref refs/heads/release refs/heads/master &&
 | 
			
		||||
	git tag -a -m "annotated tag" annotated-tag
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'basics of ls-refs' '
 | 
			
		||||
	test-pkt-line pack >in <<-EOF &&
 | 
			
		||||
	command=ls-refs
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	$(git rev-parse HEAD) HEAD
 | 
			
		||||
	$(git rev-parse refs/heads/dev) refs/heads/dev
 | 
			
		||||
	$(git rev-parse refs/heads/master) refs/heads/master
 | 
			
		||||
	$(git rev-parse refs/heads/release) refs/heads/release
 | 
			
		||||
	$(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag
 | 
			
		||||
	$(git rev-parse refs/tags/one) refs/tags/one
 | 
			
		||||
	$(git rev-parse refs/tags/two) refs/tags/two
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	git serve --stateless-rpc <in >out &&
 | 
			
		||||
	test-pkt-line unpack <out >actual &&
 | 
			
		||||
	test_cmp actual expect
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'basic ref-prefixes' '
 | 
			
		||||
	test-pkt-line pack >in <<-EOF &&
 | 
			
		||||
	command=ls-refs
 | 
			
		||||
	0001
 | 
			
		||||
	ref-prefix refs/heads/master
 | 
			
		||||
	ref-prefix refs/tags/one
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	$(git rev-parse refs/heads/master) refs/heads/master
 | 
			
		||||
	$(git rev-parse refs/tags/one) refs/tags/one
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	git serve --stateless-rpc <in >out &&
 | 
			
		||||
	test-pkt-line unpack <out >actual &&
 | 
			
		||||
	test_cmp actual expect
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'refs/heads prefix' '
 | 
			
		||||
	test-pkt-line pack >in <<-EOF &&
 | 
			
		||||
	command=ls-refs
 | 
			
		||||
	0001
 | 
			
		||||
	ref-prefix refs/heads/
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	$(git rev-parse refs/heads/dev) refs/heads/dev
 | 
			
		||||
	$(git rev-parse refs/heads/master) refs/heads/master
 | 
			
		||||
	$(git rev-parse refs/heads/release) refs/heads/release
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	git serve --stateless-rpc <in >out &&
 | 
			
		||||
	test-pkt-line unpack <out >actual &&
 | 
			
		||||
	test_cmp actual expect
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'peel parameter' '
 | 
			
		||||
	test-pkt-line pack >in <<-EOF &&
 | 
			
		||||
	command=ls-refs
 | 
			
		||||
	0001
 | 
			
		||||
	peel
 | 
			
		||||
	ref-prefix refs/tags/
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	$(git rev-parse refs/tags/annotated-tag) refs/tags/annotated-tag peeled:$(git rev-parse refs/tags/annotated-tag^{})
 | 
			
		||||
	$(git rev-parse refs/tags/one) refs/tags/one
 | 
			
		||||
	$(git rev-parse refs/tags/two) refs/tags/two
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	git serve --stateless-rpc <in >out &&
 | 
			
		||||
	test-pkt-line unpack <out >actual &&
 | 
			
		||||
	test_cmp actual expect
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'symrefs parameter' '
 | 
			
		||||
	test-pkt-line pack >in <<-EOF &&
 | 
			
		||||
	command=ls-refs
 | 
			
		||||
	0001
 | 
			
		||||
	symrefs
 | 
			
		||||
	ref-prefix refs/heads/
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >expect <<-EOF &&
 | 
			
		||||
	$(git rev-parse refs/heads/dev) refs/heads/dev
 | 
			
		||||
	$(git rev-parse refs/heads/master) refs/heads/master
 | 
			
		||||
	$(git rev-parse refs/heads/release) refs/heads/release symref-target:refs/heads/master
 | 
			
		||||
	0000
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	git serve --stateless-rpc <in >out &&
 | 
			
		||||
	test-pkt-line unpack <out >actual &&
 | 
			
		||||
	test_cmp actual expect
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user