Subject: [PATCH v2 0/3] hide-refs: add hook to force hide refs

From: "Sun Chao via GitGitGadget" <[email-masked]>
Date: Mon, 15 Aug 2022 00:54:22 +0000

Gerrit is implemented by JGit and is known as a centralized workflow system
which supports reference-level access control for repository. If we choose
to work in centralized workflow like what Gerrit provided, reference-level
access control is needed and we might add a reference filter hook hide-refs
to hide the private data.

This hook would be invoked by 'git-receive-pack' and 'git-upload-pack'
during the reference discovery phase, each reference and will be filtered
with this hook. The hook executes once with no arguments for each
'git-upload-pack' and 'git-receive-pack' process. Once the hook is invoked,
a version number and server process name ('uploadpack' or 'receive') will
send to it in pkt-line format, followed by a flush-pkt. The hook should
response with its version number.

During reference discovery phase, each reference will be filtered by this
hook. In the following example, the letter 'G' stands for 'git-receive-pack'
or 'git-upload-pack' and the letter 'H' stands for this hook. The hook
decides if the reference will be hidden or not, it sends result back in
pkt-line format protocol, a response "hide" the references will hide to the
client and can not fetch its private data even in protocol V2.

    # Version negotiation
    G: PKT-LINE(version=1\0uploadpack)
    G: flush-pkt
    H: PKT-LINE(version=1)
    H: flush-pkt

    # Send reference filter request to hook
    G: PKT-LINE(ref <refname>:<refname_full>)
    G: flush-pkt

    # Receive result from the hook.
    # Case 1: this reference is hidden
    H: PKT-LINE(hide)
    H: flush-pkt

    # Case 2: this reference can be advertised
    H: flush-pkt

To enable the hide-refs hook, we should config hiderefs with force: option,
eg:

    git config --add transfer.hiderefs force:refs/prefix1/
    git config --add uploadpack.hiderefs force:!refs/prefix2/

the hide-refs will be called during reference discovery phase and check each
matched reference, a 'hide' response means the reference will be hidden for
its private data and even the allowTipSHA1InWant or allowReachableSHA1InWant
is set to true.

Sun Chao (3):
  hide-refs: add hook to force hide refs
  t1419: add test cases for hide-refs hook
  doc: add documentation for the hide-refs hook

 Documentation/githooks.txt                    |  48 ++++
 Makefile                                      |   1 +
 builtin/receive-pack.c                        |   5 +-
 ls-refs.c                                     |   2 +-
 refs.c                                        | 221 +++++++++++++++++-
 refs.h                                        |   6 +
 serve.c                                       |   2 +
 t/helper/test-hide-refs.c                     | 152 ++++++++++++
 t/helper/test-tool.c                          |   1 +
 t/helper/test-tool.h                          |   1 +
 t/t1419-hide-refs-hook.sh                     | 142 +++++++++++
 t/t1419/common-functions.sh                   |  80 +++++++
 t/t1419/once-0000-abnormal-hide-refs-hook.sh  | 161 +++++++++++++
 ...test-0001-ls-remote-with-hide-refs-hook.sh |  77 ++++++
 ...st-0002-upload-pack-with-hide-refs-hook.sh | 122 ++++++++++
 ...t-0003-receive-pack-with-hide-refs-hook.sh |  87 +++++++
 upload-pack.c                                 |  32 +--
 upload-pack.h                                 |   1 +
 18 files changed, 1111 insertions(+), 30 deletions(-)
 create mode 100644 t/helper/test-hide-refs.c
 create mode 100755 t/t1419-hide-refs-hook.sh
 create mode 100644 t/t1419/common-functions.sh
 create mode 100644 t/t1419/once-0000-abnormal-hide-refs-hook.sh
 create mode 100644 t/t1419/test-0001-ls-remote-with-hide-refs-hook.sh
 create mode 100644 t/t1419/test-0002-upload-pack-with-hide-refs-hook.sh
 create mode 100644 t/t1419/test-0003-receive-pack-with-hide-refs-hook.sh

base-commit: afa70145a25e81faa685dc0b465e52b45d2444bd
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1301%2Fsunchao9%2Frefs_advertise-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1301/sunchao9/refs_advertise-v2
Pull-Request: https://github.com/git/git/pull/1301

Range-diff vs v1:

 1:  b4b5ce5a361 ! 1:  3b8fb63cc78 refs-advertise: add hook to filter advertised refs
     @@ Metadata
      Author: Sun Chao <[email-masked]>
      
       ## Commit message ##
     -    refs-advertise: add hook to filter advertised refs
     +    hide-refs: add hook to force hide refs
      
          Gerrit is implemented by JGit and is known as a centralized workflow system
          which supports reference-level access control for repository. If we choose
          to work in centralized workflow like what Gerrit provided, reference-level
     -    access control is needed and is possible if we add a reference advertise
     -    filter hook just like what Gerrit did.
     +    access control is needed and we might add a reference filter hook
     +    `hide-refs` to hide the private data.
      
          This hook would be invoked by 'git-receive-pack' and 'git-upload-pack'
     -    during the reference discovery phase and the commit fetching phase,
     -    each reference and will be filtered by this hook. The hook executes once
     -    with no arguments for each 'git-upload-pack' and 'git-receive-pack' process
     -    and if the exit status is non-zero, `git push` and `git fetch` will abort.
     -
     -    Once the hook is invoked, a version number and server process name
     -    ('git-upload-pack' or 'git-receive-pack' or 'ls-refs') should send to it in
     -    packet-line format, followed by a flush-pkt. The hook should response with
     -    its version number and process name list it support. If the list does not
     -    contains the server process name, the server will close the connection with
     -    the hook and keep going without the hook child process.
     +    during the reference discovery phase, each reference and will be filtered
     +    with this hook. The hook executes once with no arguments for each
     +    'git-upload-pack' and 'git-receive-pack' process. Once the hook is invoked,
     +    a version number and server process name ('uploadpack' or 'receive') will
     +    send to it in pkt-line format, followed by a flush-pkt. The hook should
     +    response with its version number.
      
          During reference discovery phase, each reference will be filtered by this
          hook. In the following example, the letter 'G' stands for 'git-receive-pack'
          or 'git-upload-pack' and the letter 'H' stands for this hook. The hook
     -    decides if the reference will be advertised or not, it sends result back in
     -    pkt-line format protocol, a response in "ok ref <ref>" format followed by a
     -    flush-pkt means the references "<ref>" can be advertised, and "ng ref <ref>"
     -    means not.
     -
     -        # Version negotiation
     -        G: PKT-LINE(version=1\0git-upload-pack)
     -        G: flush-pkt
     -        H: PKT-LINE(version=1\0git-upload-pack git-receive-pack ls-refs)
     -        H: flush-pkt
     +    decides if the reference will be hidden or not, it sends result back in
     +    pkt-line format protocol, a response "hide" the references will hide
     +    to the client and can not fetch its private data even in protocol V2.
      
     -        # Send reference filter request to hook
     -        G: PKT-LINE(ref <ref> <oid>)
     -        G: flush-pkt
     +            # Version negotiation
     +            G: PKT-LINE(version=1\0uploadpack)
     +            G: flush-pkt
     +            H: PKT-LINE(version=1)
     +            H: flush-pkt
      
     -        # Receive result from the hook.
     -        # Case 1: this reference is valid
     -        H: PKT-LINE(ok ref <ref>)
     -        H: flush-pkt
     -        # Case 2: this reference is filtered out
     -        H: PKT-LINE(ng ref <ref>)
     -        H: flush-pkt
     +            # Send reference filter request to hook
     +            G: PKT-LINE(ref <refname>:<refname_full>)
     +            G: flush-pkt
      
     -    During commit fetch phase of 'git-upload-pack' process, git client may send
     -    `want <oid>` requests and 'git-upload-pack' will send object filter requests
     -    to the hook to check if the object "<oid>" will be sent to the client or
     -    not. In the following example, the letter 'G' stands for 'git-upload-pack'
     -    and the letter 'H' stands for this hook.
     +            # Receive result from the hook.
     +            # Case 1: this reference is hidden
     +            H: PKT-LINE(hide)
     +            H: flush-pkt
      
     -    The hook will decides if a commit will be sent to the client or not, it
     -    sends result in pkt-line format protocol to `git-upload-pack`, a response
     -    with "ok obj <oid>" format followed by a flush-pkt means the object "<oid>"
     -    can be sent to client, and "ng obj <oid>" means not.
     +            # Case 2: this reference can be advertised
     +            H: flush-pkt
      
     -        # Version negotiation
     -        G: PKT-LINE(version=1\0ls-refs)
     -        G: flush-pkt
     -        H: PKT-LINE(version=1\0git-upload-pack git-receive-pack ls-refs)
     -        H: flush-pkt
     +    To enable the `hide-refs` hook, we should config hiderefs with `force:`
     +    option, eg:
      
     -        # Send commit filter request to hook
     -        G: PKT-LINE(obj <oid>)
     -        G: flush-pkt
     +            git config --add transfer.hiderefs force:refs/prefix1/
     +            git config --add uploadpack.hiderefs force:!refs/prefix2/
      
     -        # Receive result from the hook.
     -        # Case 1: this object is valid
     -        H: PKT-LINE(ok obj <oid>)
     -        H: flush-pkt
     -        # Case 2: this object is filtered out
     -        H: PKT-LINE(ng obj <oid>)
     -        H: flush-pkt
     +    the `hide-refs` will be called during reference discovery phase and
     +    check each matched reference, a 'hide' response means the reference will
     +    be hidden for its private data and even the `allowTipSHA1InWant` or
     +    `allowReachableSHA1InWant` is set to true.
      
          Signed-off-by: Sun Chao <[email-masked]>
      
     @@ Makefile: TEST_BUILTINS_OBJS += test-wildmatch.o
       TEST_BUILTINS_OBJS += test-windows-named-pipe.o
       TEST_BUILTINS_OBJS += test-write-cache.o
       TEST_BUILTINS_OBJS += test-xml-encode.o
     -+TEST_BUILTINS_OBJS += test-refs-advertise.o
     ++TEST_BUILTINS_OBJS += test-hide-refs.o
       
       # Do not add more tests here unless they have extra dependencies. Add
       # them in TEST_BUILTINS_OBJS above.
     -@@ Makefile: LIB_OBJS += refs/files-backend.o
     - LIB_OBJS += refs/iterator.o
     - LIB_OBJS += refs/packed-backend.o
     - LIB_OBJS += refs/ref-cache.o
     -+LIB_OBJS += refs/refs-advertise.o
     - LIB_OBJS += refspec.o
     - LIB_OBJS += remote.o
     - LIB_OBJS += replace-object.o
      
       ## builtin/receive-pack.c ##
     -@@
     - #include "commit-reach.h"
     - #include "worktree.h"
     - #include "shallow.h"
     -+#include "refs/refs-advertise.h"
     - 
     - static const char * const receive_pack_usage[] = {
     - 	N_("git receive-pack <git-dir>"),
      @@ builtin/receive-pack.c: static int show_ref_cb(const char *path_full, const struct object_id *oid,
     - 	if (ref_is_hidden(path, path_full))
     + 	struct oidset *seen = data;
     + 	const char *path = strip_namespace(path_full);
     + 
     +-	if (ref_is_hidden(path, path_full))
     ++	if (ref_is_hidden(path, path_full) || ref_is_force_hidden(path, path_full))
       		return 0;
       
     -+	if (filter_advertise_ref(path, oid)) {
     -+		return 0;
     -+	}
     -+
       	/*
     - 	 * Advertise refs outside our current namespace as ".have"
     - 	 * refs, so that the client can use them to minimize data
     -@@ builtin/receive-pack.c: static int delete_only(struct command *commands)
     - 	return 1;
     - }
     - 
     -+static void clean_refs_advertise_filter(void) {
     -+	clean_advertise_refs_filter();
     -+}
     -+
     - int cmd_receive_pack(int argc, const char **argv, const char *prefix)
     - {
     - 	int advertise_refs = 0;
     -@@ builtin/receive-pack.c: int cmd_receive_pack(int argc, const char **argv, const char *prefix)
     - 	if (!enter_repo(service_dir, 0))
     - 		die("'%s' does not appear to be a git repository", service_dir);
     +@@ builtin/receive-pack.c: static void reject_updates_to_hidden(struct command *commands)
     + 		strbuf_setlen(&refname_full, prefix_len);
     + 		strbuf_addstr(&refname_full, cmd->ref_name);
       
     -+	create_advertise_refs_filter("git-receive-pack");
     -+	atexit(clean_refs_advertise_filter);
     -+
     - 	git_config(receive_pack_config, NULL);
     - 	if (cert_nonce_seed)
     - 		push_cert_nonce = prepare_push_cert_nonce(service_dir, time(NULL));
     +-		if (!ref_is_hidden(cmd->ref_name, refname_full.buf))
     ++		if (!ref_is_hidden(cmd->ref_name, refname_full.buf) &&
     ++			!ref_is_force_hidden(cmd->ref_name, refname_full.buf))
     + 			continue;
     + 		if (is_null_oid(&cmd->new_oid))
     + 			cmd->error_string = "deny deleting a hidden ref";
      
       ## ls-refs.c ##
     -@@
     - #include "ls-refs.h"
     - #include "pkt-line.h"
     - #include "config.h"
     -+#include "run-command.h"
     -+#include "refs/refs-advertise.h"
     - 
     - static int config_read;
     - static int advertise_unborn;
     - static int allow_unborn;
     -+static struct string_list symref = STRING_LIST_INIT_DUP;
     - 
     - static void ensure_config_read(void)
     - {
      @@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,
     - {
     - 	struct ls_refs_data *data = cb_data;
     - 	const char *refname_nons = strip_namespace(refname);
     -+	const char *refname_to_filter = refname_nons;
       
       	strbuf_reset(&data->buf);
       
     -@@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,
     - 	if (!ref_match(&data->prefixes, refname_nons))
     +-	if (ref_is_hidden(refname_nons, refname))
     ++	if (mark_our_ref(refname_nons, refname, oid))
       		return 0;
       
     -+	if (!strcmp(refname_nons, "HEAD")) {
     -+		struct string_list_item *item = string_list_lookup(&symref, "HEAD");
     -+		if (item) {
     -+			refname_to_filter = (const char *)item->util;
     -+		}
     -+	}
     -+
     -+	if (filter_advertise_ref(refname_to_filter, oid))
     -+		return 0;
     -+
     - 	if (oid)
     - 		strbuf_addf(&data->buf, "%s %s", oid_to_hex(oid), refname_nons);
     - 	else
     -@@ ls-refs.c: static int send_ref(const char *refname, const struct object_id *oid,
     - 
     - static void send_possibly_unborn_head(struct ls_refs_data *data)
     - {
     -+	const char *symref_target;
     -+	struct string_list_item *item;
     - 	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))
     -+	symref_target = resolve_ref_unsafe(namespaced.buf, 0, &oid, &flag);
     -+	if (!symref_target)
     - 		return; /* bad ref */
     - 	oid_is_null = is_null_oid(&oid);
     - 	if (!oid_is_null ||
     --	    (data->unborn && data->symrefs && (flag & REF_ISSYMREF)))
     -+	    (data->unborn && data->symrefs && (flag & REF_ISSYMREF))) {
     -+		item = string_list_append(&symref, "HEAD");
     -+		item->util = xstrdup(strip_namespace(symref_target));
     - 		send_ref(namespaced.buf, oid_is_null ? NULL : &oid, flag, data);
     -+	}
     - 	strbuf_release(&namespaced);
     - }
     - 
     -@@ ls-refs.c: static int ls_refs_config(const char *var, const char *value, void *data)
     - 	return parse_hide_refs_config(var, value, "uploadpack");
     + 	if (!ref_match(&data->prefixes, refname_nons))
     +
     + ## refs.c ##
     +@@
     + #include "lockfile.h"
     + #include "iterator.h"
     + #include "refs.h"
     ++#include "pkt-line.h"
     + #include "refs/refs-internal.h"
     + #include "run-command.h"
     + #include "hook.h"
     +@@ refs.c: char *shorten_unambiguous_ref(const char *refname, int strict)
       }
       
     -+static void clean_refs_advertise_filter(void) {
     -+	clean_advertise_refs_filter();
     -+}
     -+
     - int ls_refs(struct repository *r, struct packet_reader *request)
     + static struct string_list *hide_refs;
     +-
     ++static struct string_list *force_hide_refs;
     ++static struct strbuf hide_refs_section = STRBUF_INIT;
     + int parse_hide_refs_config(const char *var, const char *value, const char *section)
       {
     - 	struct ls_refs_data data;
     -@@ ls-refs.c: int ls_refs(struct repository *r, struct packet_reader *request)
     + 	const char *key;
     ++	int force = 0;
     ++
     + 	if (!strcmp("transfer.hiderefs", var) ||
     + 	    (!parse_config_key(var, section, NULL, NULL, &key) &&
     + 	     !strcmp(key, "hiderefs"))) {
     + 		char *ref;
     + 		int len;
     ++		int forcelen;
       
     - 	ensure_config_read();
     - 	git_config(ls_refs_config, NULL);
     -+	create_advertise_refs_filter("ls-refs");
     -+	atexit(clean_refs_advertise_filter);
     - 
     - 	while (packet_reader_read(request) == PACKET_READ_NORMAL) {
     - 		const char *arg = request->line;
     -
     - ## refs/refs-advertise.c (new) ##
     -@@
     -+#include "../cache.h"
     -+#include "../config.h"
     -+#include "../strbuf.h"
     -+#include "../hook.h"
     -+#include "../sigchain.h"
     -+#include "../pkt-line.h"
     -+#include "../refs.h"
     -+#include "../run-command.h"
     -+#include "connect.h"
     -+#include "ref-cache.h"
     -+#include "refs-advertise.h"
     + 		if (!value)
     + 			return config_error_nonbool(var);
      +
     -+struct advertise_refs_filter {
     -+	struct child_process proc;
     -+	struct packet_reader reader;
     -+};
     ++		forcelen = strlen("force:");
     ++		len = strlen(value);
     ++		if ((len >= forcelen) && !strncmp(value, "force:", forcelen)) {
     ++			if (len == forcelen)
     ++				return error(_("missing value for '%s' with force option"), var);
     ++
     ++			force = 1;
     ++			value += forcelen;
     ++		}
     ++
     + 		ref = xstrdup(value);
     + 		len = strlen(ref);
     + 		while (len && ref[len - 1] == '/')
     + 			ref[--len] = '\0';
     +-		if (!hide_refs) {
     +-			CALLOC_ARRAY(hide_refs, 1);
     +-			hide_refs->strdup_strings = 1;
     ++
     ++		if (force) {
     ++			if (!force_hide_refs) {
     ++				CALLOC_ARRAY(force_hide_refs, 1);
     ++				force_hide_refs->strdup_strings = 1;
     ++			}
     ++			string_list_append(force_hide_refs, ref);
     ++		} else {
     ++			if (!hide_refs) {
     ++				CALLOC_ARRAY(hide_refs, 1);
     ++				hide_refs->strdup_strings = 1;
     ++			}
     ++			string_list_append(hide_refs, ref);
     + 		}
     +-		string_list_append(hide_refs, ref);
     + 	}
      +
     -+static struct advertise_refs_filter *hook_filter = NULL;
     ++	if (hide_refs_section.len == 0) {
     ++		strbuf_addstr(&hide_refs_section, section);
     ++	}
      +
     -+void create_advertise_refs_filter(const char *command) {
     -+	struct advertise_refs_filter *filter;
     + 	return 0;
     + }
     + 
     +-int ref_is_hidden(const char *refname, const char *refname_full)
     ++static struct child_process *hide_refs_proc;
     ++static struct packet_reader *hide_refs_reader;
     ++static void create_hide_refs_process(void) {
      +	struct child_process *proc;
      +	struct packet_reader *reader;
      +	const char *hook_path;
     -+	int command_support = 0;
      +	int version = 0;
      +	int code;
      +
     -+	if (hook_filter != NULL)
     -+		return;
     -+
     -+	hook_path = find_hook("refs-advertise");
     ++	hook_path = find_hook("hide-refs");
      +	if (!hook_path) {
     -+		return;
     ++		die("can not find hide-refs hook");
      +	}
      +
     -+	filter = (struct advertise_refs_filter *) xcalloc (1, sizeof (struct advertise_refs_filter));
     -+	proc = &filter->proc;
     -+	reader = &filter->reader;
     ++	proc = (struct child_process *) xcalloc (1, sizeof (struct child_process));
     ++	reader = (struct packet_reader *) xcalloc (1, sizeof(struct packet_reader));
      +
      +	child_process_init(proc);
      +	strvec_push(&proc->args, hook_path);
      +	proc->in = -1;
      +	proc->out = -1;
     -+	proc->trace2_hook_name = "refs-advertise";
     ++	proc->trace2_hook_name = "hide-refs";
      +	proc->err = 0;
      +
      +	code = start_command(proc);
      +	if (code)
     -+		die("can not run hook refs-advertise");
     ++		die("can not run hook hide-refs");
      +
      +	sigchain_push(SIGPIPE, SIG_IGN);
      +
     @@ refs/refs-advertise.c (new)
      +	packet_reader_init(reader, proc->out, NULL, 0,
      +			   PACKET_READ_CHOMP_NEWLINE |
      +			   PACKET_READ_GENTLE_ON_EOF);
     -+	code = packet_write_fmt_gently(proc->in, "version=1%c%s", '\0', command);
     ++	code = packet_write_fmt_gently(proc->in, "version=1%c%s", '\0', hide_refs_section.buf);
      +	if (!code)
      +		code = packet_flush_gently(proc->in);
      +
      +	if (!code)
      +		for (;;) {
     -+			int linelen;
      +			enum packet_read_status status;
      +
      +			status = packet_reader_read(reader);
      +			if (status != PACKET_READ_NORMAL) {
     -+				/* Check whether refs-advertise exited abnormally */
     ++				/* Check whether hide-refs exited abnormally */
      +				if (status == PACKET_READ_EOF)
     -+					die("can not read version message from hook refs-advertise");
     ++					die("can not read version message from hook hide-refs");
      +				break;
      +			}
      +
      +			if (reader->pktlen > 8 && starts_with(reader->line, "version=")) {
      +				version = atoi(reader->line + 8);
     -+				linelen = strlen(reader->line);
     -+				if (linelen < reader->pktlen) {
     -+					const char *command_list = reader->line + linelen + 1;
     -+					if (parse_feature_request(command_list, command)) {
     -+						command_support = 1;
     -+					}
     -+				}
      +			}
      +		}
      +
      +	if (code)
     -+		die("can not read version message from hook refs-advertise");
     ++		die("can not read version message from hook hide-refs");
      +
      +	switch (version) {
      +	case 0:
     @@ refs/refs-advertise.c (new)
      +	case 1:
      +		break;
      +	default:
     -+		die(_("hook refs-advertise version '%d' is not supported"), version);
     ++		die(_("hook hide-refs version '%d' is not supported"), version);
      +	}
      +
      +	sigchain_pop(SIGPIPE);
      +
     -+	if (!command_support) {
     -+		close(proc->in);
     -+		close(proc->out);
     -+		kill(proc->pid, SIGTERM);
     -+		finish_command_in_signal(proc);
     -+		free(filter);
     -+
     -+		return;
     -+	}
     -+
     -+	hook_filter = filter;
     ++	hide_refs_proc = proc;
     ++	hide_refs_reader = reader;
      +	return;
      +}
      +
     -+void clean_advertise_refs_filter(void) {
     -+	struct child_process *proc;
     -+
     -+	if (!hook_filter) {
     -+		return;
     -+	}
     -+
     -+	proc = &hook_filter->proc;
     -+
     -+	close(proc->in);
     -+	close(proc->out);
     -+	kill(proc->pid, SIGTERM);
     -+	finish_command_in_signal(proc);
     -+	FREE_AND_NULL(hook_filter);
     -+}
     -+
     -+static int do_filter_advertise_ref(const char *refname, const struct object_id *oid) {
     -+	struct child_process *proc;
     -+	struct packet_reader *reader;
     ++static int ref_force_hidden_check(const char *refname, const char *refname_full)
     ++{
      +	struct strbuf buf = STRBUF_INIT;
     -+	char *oid_hex;
      +	int code;
     ++	int ret = 0;
     ++
     ++	if (!force_hide_refs) {
     ++		return 0;
     ++	}
      +
     -+	proc = &hook_filter->proc;
     -+	reader = &hook_filter->reader;
     -+	if (oid)
     -+		oid_hex = oid_to_hex(oid);
     -+	else
     -+		oid_hex = oid_to_hex(null_oid());
     ++	if (!hide_refs_proc) {
     ++		create_hide_refs_process();
     ++	}
      +
     -+	code = packet_write_fmt_gently(proc->in, "ref %s %s", refname, oid_hex);
     ++	sigchain_push(SIGPIPE, SIG_IGN);
     ++	code = packet_write_fmt_gently(hide_refs_proc->in, "ref %s:%s", refname, refname_full);
      +	if (code)
     -+		die("hook refs-advertise died abnormally");
     ++		die("hook hide-refs died abnormally");
      +
     -+	code = packet_flush_gently(proc->in);
     ++	code = packet_flush_gently(hide_refs_proc->in);
      +	if (code)
     -+		die("hook refs-advertise died abnormally");
     ++		die("hook hide-refs died abnormally");
      +
      +	for (;;) {
      +		enum packet_read_status status;
      +
     -+		status = packet_reader_read(reader);
     ++		status = packet_reader_read(hide_refs_reader);
      +		if (status != PACKET_READ_NORMAL) {
     -+			/* Check whether refs-advertise exited abnormally */
     ++			/* Check whether hide-refs exited abnormally */
      +			if (status == PACKET_READ_EOF)
     -+				die("hook refs-advertise died abnormally");
     ++				die("hook hide-refs died abnormally");
      +			break;
      +		}
      +
      +		strbuf_reset(&buf);
     -+		strbuf_addstr(&buf, reader->line);
     ++		strbuf_addstr(&buf, hide_refs_reader->line);
      +	}
      +
     -+	if (strncmp("ok ref ", buf.buf, 7))
     -+		return -1;
     ++	if (!strncmp("hide", buf.buf, 4))
     ++		ret = 1;
      +
     -+	if (strcmp(refname, buf.buf + 7))
     -+		return -1;
     -+
     -+	return 0;
     -+}
     -+
     -+int filter_advertise_ref(const char *refname, const struct object_id *oid) {
     -+	int result;
     -+
     -+	if (!hook_filter) {
     -+		return 0;
     -+	}
     -+
     -+	sigchain_push(SIGPIPE, SIG_IGN);
     -+	result = do_filter_advertise_ref(refname, oid);
      +	sigchain_pop(SIGPIPE);
     -+
     -+	return result;
     ++	return ret;
      +}
      +
     -+static int do_filter_advertise_object(const struct object_id *oid) {
     -+	struct child_process *proc;
     -+	struct packet_reader *reader;
     -+	struct strbuf buf = STRBUF_INIT;
     -+	char *oid_hex;
     -+	int code;
     -+
     -+	proc = &hook_filter->proc;
     -+	reader = &hook_filter->reader;
     -+	oid_hex = oid_to_hex(oid);
     -+
     -+	code = packet_write_fmt_gently(proc->in, "obj %s", oid_hex);
     -+	if (code)
     -+		die("hook refs-advertise died abnormally");
     -+
     -+	code = packet_flush_gently(proc->in);
     -+	if (code)
     -+		die("hook refs-advertise died abnormally");
     -+
     -+	for (;;) {
     -+		enum packet_read_status status;
     ++static int ref_hidden_check(const char *refname, const char *refname_full, int force)
     + {
     ++	struct string_list *hide_refs_list = hide_refs;
     + 	int i;
     + 
     +-	if (!hide_refs)
     ++	if (force)
     ++		hide_refs_list = force_hide_refs;
      +
     -+		status = packet_reader_read(reader);
     -+		if (status != PACKET_READ_NORMAL) {
     -+			/* Check whether refs-advertise exited abnormally */
     -+			if (status == PACKET_READ_EOF)
     -+				die("hook refs-advertise died abnormally");
     -+			break;
     ++	if (!hide_refs_list)
     + 		return 0;
     +-	for (i = hide_refs->nr - 1; i >= 0; i--) {
     +-		const char *match = hide_refs->items[i].string;
     ++	for (i = hide_refs_list->nr - 1; i >= 0; i--) {
     ++		const char *match = hide_refs_list->items[i].string;
     + 		const char *subject;
     + 		int neg = 0;
     + 		const char *p;
     +@@ refs.c: int ref_is_hidden(const char *refname, const char *refname_full)
     + 		/* refname can be NULL when namespaces are used. */
     + 		if (subject &&
     + 		    skip_prefix(subject, match, &p) &&
     +-		    (!*p || *p == '/'))
     +-			return !neg;
     ++		    (!*p || *p == '/')) {
     ++			if (neg)
     ++				return 0;
     ++			if (!force)
     ++				return 1;
     ++			return ref_force_hidden_check(refname, refname_full);
      +		}
     + 	}
     + 	return 0;
     + }
     + 
     ++int ref_is_hidden(const char *refname, const char *refname_full)
     ++{
     ++	return ref_hidden_check(refname, refname_full, 0);
     ++}
      +
     -+		strbuf_reset(&buf);
     -+		strbuf_addstr(&buf, reader->line);
     -+	}
     -+
     -+	if (strncmp("ok obj ", buf.buf, 7))
     -+		return -1;
     -+
     -+	if (strcmp(oid_hex, buf.buf + 7))
     -+		return -1;
     -+
     -+	return 0;
     ++int ref_is_force_hidden(const char *refname, const char *refname_full)
     ++{
     ++	return ref_hidden_check(refname, refname_full, 1);
      +}
      +
     -+int filter_advertise_object(const struct object_id *oid) {
     -+	int result;
     ++#define OUR_REF		(1u << 12)
     ++#define HIDDEN_REF	(1u << 19)
     ++#define HIDDEN_REF_FORCE	(1u << 20)
     ++static int has_force_hidden;
     ++int mark_our_ref(const char *refname, const char *refname_full,
     ++			const struct object_id *oid)
     ++{
     ++	struct object *o;
      +
     -+	if (!hook_filter || !oid) {
     ++	if (!oid || is_null_oid(oid)) {
      +		return 0;
      +	}
      +
     -+	sigchain_push(SIGPIPE, SIG_IGN);
     -+	result = do_filter_advertise_object(oid);
     -+	sigchain_pop(SIGPIPE);
     ++	o = lookup_unknown_object(the_repository, oid);
     ++	if (ref_is_force_hidden(refname, refname_full)) {
     ++		o->flags |= HIDDEN_REF_FORCE;
     ++		has_force_hidden = 1;
     ++		return 1;
     ++	}
     ++	if (ref_is_hidden(refname, refname_full)) {
     ++		o->flags |= HIDDEN_REF;
     ++		return 1;
     ++	}
     ++	o->flags |= OUR_REF;
     ++	return 0;
     ++}
      +
     -+	return result;
     ++int has_force_hidden_refs(void) {
     ++	return has_force_hidden;
      +}
     ++
     + const char *find_descendant_ref(const char *dirname,
     + 				const struct string_list *extras,
     + 				const struct string_list *skip)
     +
     + ## refs.h ##
     +@@ refs.h: int parse_hide_refs_config(const char *var, const char *value, const char *);
     +  * parameter always points to the full ref name.
     +  */
     + int ref_is_hidden(const char *, const char *);
     ++int ref_is_force_hidden(const char *, const char *);
     ++/* return non-zero if the ref is hidden, otherwise 0 */
     ++int mark_our_ref(const char *refname, const char *refname_full,
     ++			const struct object_id *oid);
     ++int has_force_hidden_refs(void);
     ++void lazy_load_hidden_refs(void);
     + 
     + enum ref_type {
     + 	REF_TYPE_PER_WORKTREE,	  /* refs inside refs/ but not shared       */
      
     - ## refs/refs-advertise.h (new) ##
     + ## serve.c ##
      @@
     -+#ifndef REFS_REFS_ADVERTISE_H
     -+#define REFS_REFS_ADVERTISE_H
     -+
     -+#include "../hash.h"
     -+
     -+void create_advertise_refs_filter(const char *command);
     -+void clean_advertise_refs_filter(void);
     -+int filter_advertise_ref(const char *refname, const struct object_id *oid);
     -+int filter_advertise_object(const struct object_id *oid);
     -+
     -+#endif
     + #include "cache.h"
     + #include "repository.h"
     + #include "config.h"
     ++#include "refs.h"
     + #include "pkt-line.h"
     + #include "version.h"
     + #include "ls-refs.h"
     +@@ serve.c: void protocol_v2_serve_loop(int stateless_rpc)
     + 	 * a single request/response exchange
     + 	 */
     + 	if (stateless_rpc) {
     ++		lazy_load_hidden_refs();
     + 		process_request();
     + 	} else {
     + 		for (;;)
      
     - ## t/helper/test-refs-advertise.c (new) ##
     + ## t/helper/test-hide-refs.c (new) ##
      @@
      +#include "cache.h"
      +#include "hash.h"
     @@ t/helper/test-refs-advertise.c (new)
      +#include "sigchain.h"
      +#include "test-tool.h"
      +
     -+static const char *refs_advertise_usage[] = {
     -+	"test-tool refs-advertise [<options>...]",
     ++static const char *hide_refs_usage[] = {
     ++	"test-tool hide-refs [<options>...]",
      +	NULL
      +};
      +
     -+static int can_upload_pack;
     -+static int can_receive_pack;
     -+static int can_ls_refs;
      +static int die_read_version;
      +static int die_write_version;
      +static int die_read_first_ref;
      +static int die_read_second_ref;
     -+static int die_filter_refs;
     -+static int upload_pack;
     -+static int receive_pack;
     -+static int ls_refs;
     ++static int die_after_proc_ref;
      +static int verbose;
      +static int version = 1;
      +static int first_ref;
     @@ t/helper/test-refs-advertise.c (new)
      +	char ref_name[FLEX_ARRAY]; /* more */
      +};
      +
     -+static void refs_advertise_verison(struct packet_reader *reader) {
     ++static void hide_refs_verison(struct packet_reader *reader) {
      +	int server_version = 0;
      +
      +	if (die_read_version)
      +		die("die with the --die-read-version option");
      +
      +	for (;;) {
     -+		int linelen;
     -+
      +		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
      +			break;
      +
     @@ t/helper/test-refs-advertise.c (new)
      +			server_version = atoi(reader->line+8);
      +			if (server_version != 1)
      +				die("bad protocol version: %d", server_version);
     -+			linelen = strlen(reader->line);
     -+			if (linelen < reader->pktlen) {
     -+				const char *feature_list = reader->line + linelen + 1;
     -+				if (parse_feature_request(feature_list, "git-upload-pack"))
     -+					upload_pack = 1;
     -+				if (parse_feature_request(feature_list, "git-receive-pack"))
     -+					receive_pack = 1;
     -+				if (parse_feature_request(feature_list, "ls-refs"))
     -+					ls_refs = 1;
     -+			}
      +		}
      +	}
      +
      +	if (die_write_version)
      +		die("die with the --die-write-version option");
      +
     -+	if (can_upload_pack || can_receive_pack || can_ls_refs)
     -+		packet_write_fmt(1, "version=%d%c%s%s%s\n",
     -+				version, '\0',
     -+				can_upload_pack ? "git-upload-pack ": "",
     -+				can_receive_pack? "git-receive-pack ": "",
     -+				can_ls_refs? "ls-refs ": "");
     -+	else
     -+		packet_write_fmt(1, "version=%d\n", version);
     -+
     ++	packet_write_fmt(1, "version=%d\n", version);
      +	packet_flush(1);
     -+
     -+	if ((upload_pack && !can_upload_pack) ||
     -+		(receive_pack && !can_receive_pack) ||
     -+		(ls_refs && !can_ls_refs)) {
     -+			exit(0);
     -+	}
      +}
      +
     -+static void refs_advertise_read_refs(struct packet_reader *reader)
     ++static void hide_refs_proc(struct packet_reader *reader)
      +{
      +	const char *p;
      +	struct strbuf buf = STRBUF_INIT;
      +	enum packet_read_status status;
     -+	int filter_ok = 0;
      +
      +	if (!first_ref) {
      +		if (die_read_first_ref)
     @@ t/helper/test-refs-advertise.c (new)
      +		strbuf_addstr(&buf, reader->line);
      +	}
      +
     -+	p = buf.buf;
     -+
     -+	if (unsorted_string_list_has_string(&returns, p)) {
     -+		filter_ok = 1;
     -+	}
     -+
     -+	// if it's a ref filter request, we response without the commit id
     -+	if ((buf.len > (hash_size + 1)) && (strncmp("obj ", buf.buf, 4)))
     -+		strbuf_setlen(&buf, buf.len - (hash_size + 1));
     -+
     -+	if (filter_ok) {
     -+		packet_write_fmt(1, "%s %s\n", "ok", p);
     -+	} else {
     -+		packet_write_fmt(1, "%s %s\n", "ng", p);
     ++	p = strchr(buf.buf, ':');
     ++	if (unsorted_string_list_has_string(&returns, p + 1)) {
     ++		packet_write_fmt(1, "hide");
      +	}
      +
     -+	if (die_filter_refs)
     -+		die("die with the --die-filter-refs option");
     ++	if (die_after_proc_ref)
     ++		die("die with the --die-after-proc-refs option");
      +
      +	packet_flush(1);
      +}
      +
     -+int cmd__refs_advertise(int argc, const char **argv) {
     ++int cmd__hide_refs(int argc, const char **argv) {
      +	int nongit_ok = 0;
      +	struct packet_reader reader;
      +	const char *value = NULL;
      +	struct option options[] = {
     -+		OPT_BOOL(0, "can-upload-pack", &can_upload_pack,
     -+			 "support upload-pack command"),
     -+		OPT_BOOL(0, "can-receive-pack", &can_receive_pack,
     -+			 "support upload-pack command"),
     -+		OPT_BOOL(0, "can-ls-refs", &can_ls_refs,
     -+			 "support ls-refs command"),
      +		OPT_BOOL(0, "die-read-version", &die_read_version,
      +			 "die when reading version"),
      +		OPT_BOOL(0, "die-write-version", &die_write_version,
     @@ t/helper/test-refs-advertise.c (new)
      +			 "die when reading first reference"),
      +		OPT_BOOL(0, "die-read-second-ref", &die_read_second_ref,
      +			 "die when reading second reference"),
     -+		OPT_BOOL(0, "die-filter-refs", &die_filter_refs,
     -+			 "die when filtering refs"),
     -+		OPT_STRING_LIST('r', "return", &returns, "ref<SP>$refname<SP>$oid|obj<SP>$oid",
     -+				"refs or objects that can advertise"),
     ++		OPT_BOOL(0, "die-after-proc-refs", &die_after_proc_ref,
     ++			 "die after proc ref"),
     ++		OPT_STRING_LIST('r', "reserved", &returns, "refs-to-force-hidden",
     ++				"refs that will force hide"),
      +		OPT__VERBOSE(&verbose, "be verbose"),
      +		OPT_INTEGER('V', "version", &version,
      +			    "use this protocol version number"),
     @@ t/helper/test-refs-advertise.c (new)
      +
      +	setup_git_directory_gently(&nongit_ok);
      +
     -+	argc = parse_options(argc, argv, "test-tools", options, refs_advertise_usage, 0);
     ++	argc = parse_options(argc, argv, "test-tools", options, hide_refs_usage, 0);
      +	if (argc > 0)
     -+		usage_msg_opt("Too many arguments.", refs_advertise_usage, options);
     ++		usage_msg_opt("Too many arguments.", hide_refs_usage, options);
      +
      +	packet_reader_init(&reader, 0, NULL, 0, PACKET_READ_CHOMP_NEWLINE | PACKET_READ_GENTLE_ON_EOF);
      +
     @@ t/helper/test-refs-advertise.c (new)
      +			hash_size = GIT_SHA256_HEXSZ;
      +	}
      +
     -+	refs_advertise_verison(&reader);
     ++	hide_refs_verison(&reader);
      +	for (;;) {
     -+		refs_advertise_read_refs(&reader);
     ++		hide_refs_proc(&reader);
      +	}
      +
      +	return 0;
     @@ t/helper/test-tool.c: static struct test_cmd cmds[] = {
       	{ "regex", cmd__regex },
       	{ "repository", cmd__repository },
       	{ "revision-walking", cmd__revision_walking },
     -+	{ "refs-advertise", cmd__refs_advertise },
     ++	{ "hide-refs", cmd__hide_refs },
       	{ "run-command", cmd__run_command },
       	{ "scrap-cache-tree", cmd__scrap_cache_tree },
       	{ "serve-v2", cmd__serve_v2 },
     @@ t/helper/test-tool.h: int cmd__reftable(int argc, const char **argv);
       int cmd__regex(int argc, const char **argv);
       int cmd__repository(int argc, const char **argv);
       int cmd__revision_walking(int argc, const char **argv);
     -+int cmd__refs_advertise(int argc, const char **argv);
     ++int cmd__hide_refs(int argc, const char **argv);
       int cmd__run_command(int argc, const char **argv);
       int cmd__scrap_cache_tree(int argc, const char **argv);
       int cmd__serve_v2(int argc, const char **argv);
      
       ## upload-pack.c ##
      @@
     - #include "commit-graph.h"
     - #include "commit-reach.h"
     - #include "shallow.h"
     -+#include "refs/refs-advertise.h"
     - 
     - /* Remember to update object flag allocation in object.h */
     - #define THEY_HAVE	(1u << 11)
     -@@ upload-pack.c: static void receive_needs(struct upload_pack_data *data,
     - 		}
     - 
     - 		o = parse_object(the_repository, &oid_buf);
     --		if (!o) {
     -+		if ((!o) || (filter_advertise_object(&oid_buf))) {
     - 			packet_writer_error(&data->writer,
     - 					    "upload-pack: not our ref %s",
     - 					    oid_to_hex(&oid_buf));
     -@@ upload-pack.c: static int mark_our_ref(const char *refname, const char *refname_full,
     - 		o->flags |= HIDDEN_REF;
     - 		return 1;
     - 	}
     -+
     -+	if (filter_advertise_ref(refname, oid)) {
     -+		o->flags |= HIDDEN_REF;
     -+		return 1;
     -+	}
     -+
     - 	o->flags |= OUR_REF;
     - 	return 0;
     - }
     -@@ upload-pack.c: static void format_symref_info(struct strbuf *buf, struct string_list *symref)
     + #define NOT_SHALLOW	(1u << 17)
     + #define CLIENT_SHALLOW	(1u << 18)
     + #define HIDDEN_REF	(1u << 19)
     ++#define HIDDEN_REF_FORCE	(1u << 20)
       
     - 	if (!symref->nr)
     - 		return;
     --	for_each_string_list_item(item, symref)
     --		strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
     -+	for_each_string_list_item(item, symref) {
     -+		if (!filter_advertise_ref((char *)item->util, NULL))
     -+			strbuf_addf(buf, " symref=%s:%s", item->string, (char *)item->util);
     -+	}
     - }
     - 
     - static void format_session_id(struct strbuf *buf, struct upload_pack_data *d) {
     -@@ upload-pack.c: static int send_ref(const char *refname, const struct object_id *oid,
     - 	static const char *capabilities = "multi_ack thin-pack side-band"
     - 		" side-band-64k ofs-delta shallow deepen-since deepen-not"
     - 		" deepen-relative no-progress include-tag multi_ack_detailed";
     --	const char *refname_nons = strip_namespace(refname);
     - 	struct object_id peeled;
     - 	struct upload_pack_data *data = cb_data;
     -+	const char *refname_nons = strip_namespace(refname);
     -+	const char *refname_to_filter = refname_nons;
     - 
     --	if (mark_our_ref(refname_nons, refname, oid))
     -+	if (!strcmp(refname_nons, "HEAD")) {
     -+		struct string_list_item *item = string_list_lookup(&data->symref, "HEAD");
     -+		if (item) {
     -+			refname_to_filter = (const char *)item->util;
     -+		}
     -+	}
     -+
     -+	if (mark_our_ref(refname_to_filter, refname, oid))
     - 		return 0;
     +-#define ALL_FLAGS (THEY_HAVE | OUR_REF | WANTED | COMMON_KNOWN | SHALLOW | \
     +-		NOT_SHALLOW | CLIENT_SHALLOW | HIDDEN_REF)
     ++#define ALL_FLAGS (THEY_HAVE |WANTED | COMMON_KNOWN | SHALLOW | \
     ++		NOT_SHALLOW | CLIENT_SHALLOW)
       
     - 	if (capabilities) {
     -@@ upload-pack.c: static void get_upload_pack_config(struct upload_pack_data *data)
     - 	git_protected_config(upload_pack_protected_config, data);
     + /* Enum for allowed unadvertised object request (UOR) */
     + enum allow_uor {
     +@@ upload-pack.c: static void receive_needs(struct upload_pack_data *data,
     + 		packet_flush(1);
       }
       
     -+static void clean_refs_advertise_filter(void) {
     -+	clean_advertise_refs_filter();
     -+}
     -+
     - void upload_pack(const int advertise_refs, const int stateless_rpc,
     - 		 const int timeout)
     +-/* return non-zero if the ref is hidden, otherwise 0 */
     +-static int mark_our_ref(const char *refname, const char *refname_full,
     +-			const struct object_id *oid)
     +-{
     +-	struct object *o = lookup_unknown_object(the_repository, oid);
     +-
     +-	if (ref_is_hidden(refname, refname_full)) {
     +-		o->flags |= HIDDEN_REF;
     +-		return 1;
     +-	}
     +-	o->flags |= OUR_REF;
     +-	return 0;
     +-}
     +-
     + static int check_ref(const char *refname_full, const struct object_id *oid,
     + 		     int flag, void *cb_data)
       {
     - 	struct packet_reader reader;
     - 	struct upload_pack_data data;
     - 
     -+	create_advertise_refs_filter("git-upload-pack");
     -+	atexit(clean_refs_advertise_filter);
     -+
     - 	upload_pack_data_init(&data);
     - 	get_upload_pack_config(&data);
     - 
     -@@ upload-pack.c: static int parse_want(struct packet_writer *writer, const char *line,
     - 		else
     - 			o = parse_object(the_repository, &oid);
     - 
     --		if (!o) {
     -+		if ((!o) || (filter_advertise_object(&oid))) {
     - 			packet_writer_error(writer,
     - 					    "upload-pack: not our ref %s",
     - 					    oid_to_hex(&oid));
      @@ upload-pack.c: static int parse_want_ref(struct packet_writer *writer, const char *line,
       
       		strbuf_addf(&refname, "%s%s", get_git_namespace(), refname_nons);
       		if (ref_is_hidden(refname_nons, refname.buf) ||
     --		    read_ref(refname.buf, &oid)) {
     -+		    read_ref(refname.buf, &oid) ||
     -+			filter_advertise_ref(refname_nons, &oid)) {
     ++			ref_is_force_hidden(refname_nons, refname.buf) ||
     + 		    read_ref(refname.buf, &oid)) {
       			packet_writer_error(writer, "unknown ref %s", refname_nons);
       			die("unknown ref %s", refname_nons);
     - 		}
     +@@ upload-pack.c: enum fetch_state {
     + 	FETCH_DONE,
     + };
     + 
     ++static int lazy_load_hidden = 0;
     ++// lazy load hidden refs for protocol V2
     ++void lazy_load_hidden_refs(void) {
     ++	lazy_load_hidden = 1;
     ++}
     ++
     + int upload_pack_v2(struct repository *r, struct packet_reader *request)
     + {
     + 	enum fetch_state state = FETCH_PROCESS_ARGS;
      @@ upload-pack.c: int upload_pack_v2(struct repository *r, struct packet_reader *request)
     + 				state = FETCH_DONE;
     + 			break;
     + 		case FETCH_SEND_PACK:
     ++			if (lazy_load_hidden) {
     ++				head_ref_namespaced(check_ref, NULL);
     ++				for_each_namespaced_ref(check_ref, NULL);
     ++			}
     ++			if (has_force_hidden_refs())
     ++				check_non_tip(&data);
     + 			send_wanted_ref_info(&data);
     + 			send_shallow_info(&data);
       
     - 	clear_object_flags(ALL_FLAGS);
     +
     + ## upload-pack.h ##
     +@@ upload-pack.h: struct strbuf;
     + int upload_pack_advertise(struct repository *r,
     + 			  struct strbuf *value);
       
     -+	create_advertise_refs_filter("git-upload-pack");
     -+	atexit(clean_refs_advertise_filter);
     -+
     - 	upload_pack_data_init(&data);
     - 	data.use_sideband = LARGE_PACKET_MAX;
     - 	get_upload_pack_config(&data);
     ++void lazy_load_hidden_refs(void);
     + #endif /* UPLOAD_PACK_H */
 2:  88504b3a08a ! 2:  72333c12c3f t1419: add test cases for refs-advertise hook
     @@ Metadata
      Author: Sun Chao <[email-masked]>
      
       ## Commit message ##
     -    t1419: add test cases for refs-advertise hook
     +    t1419: add test cases for hide-refs hook
      
     -    Add test cases for the new 'refs-advertise' hook which is used to
     -    filter the references during reference discovery phase and commit
     -    fetching phase.
     +    Add test cases for the new 'hide-refs' hook which is used to
     +    filter the references during reference discovery phase.
      
          Signed-off-by: Sun Chao <[email-masked]>
      
     - ## t/t1419-refs-advertise-hooks.sh (new) ##
     + ## t/t1419-hide-refs-hook.sh (new) ##
      @@
      +#!/bin/sh
      +#
      +# Copyright (c) 2022 Sun Chao
      +#
      +
     -+test_description='Test refs-advertise hook'
     ++test_description='Test hide-refs hook'
      +
      +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main
      +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME
     @@ t/t1419-refs-advertise-hooks.sh (new)
      +	'
      +}
      +
     -+run_refs_advertise_hook_tests() {
     ++run_hide_refs_hook_tests() {
      +	case $1 in
      +		http)
      +			PROTOCOL="HTTP protocol"
     @@ t/t1419-refs-advertise-hooks.sh (new)
      +
      +	GIT_TEST_PROTOCOL_VERSION=$2
      +
     -+	# Run test cases for 'refs-advertise' hook
     ++	# Run test cases for 'hide-refs' hook
      +	for t in  "$TEST_DIRECTORY"/t1419/test-*.sh
      +	do
      +		# Initialize the bare_repo repository and work_repo
     @@ t/t1419-refs-advertise-hooks.sh (new)
      +		cp -rf work_repo work_repo.dump
      +
      +		git -C bare_repo.git config --local http.receivepack true
     ++		git -C bare_repo.git config --add transfer.hiderefs force:HEAD
     ++		git -C bare_repo.git config --add transfer.hiderefs force:refs
      +		cp -rf bare_repo.git bare_repo.git.dump
      +
      +		if test "$1" = "http"; then
     @@ t/t1419-refs-advertise-hooks.sh (new)
      +# Load test cases that only need to be executed once.
      +for t in  "$TEST_DIRECTORY"/t1419/once-*.sh
      +do
     ++	git -C "$BAREREPO_GIT_DIR" config --add transfer.hiderefs force:HEAD
     ++	git -C "$BAREREPO_GIT_DIR" config --add transfer.hiderefs force:refs
      +	. "$t"
      +done
      +
      +for protocol in 1 2
      +do
     -+	# Run test cases for 'refs-advertise' hook on local file protocol.
     -+	run_refs_advertise_hook_tests local $protocol
     ++	# Run test cases for 'hide-refs' hook on local file protocol.
     ++	run_hide_refs_hook_tests local $protocol
      +done
      +
      +ROOT_PATH="$PWD"
     @@ t/t1419-refs-advertise-hooks.sh (new)
      +start_httpd
      +set_askpass user@host pass@host
      +
     -+# Run test cases for 'refs-advertise' hook on HTTP protocol.
     ++# Run test cases for 'hide-refs' hook on HTTP protocol.
      +for protocol in 1 2
      +do
     -+	run_refs_advertise_hook_tests http $protocol
     ++	run_hide_refs_hook_tests http $protocol
      +done
      +
      +test_done
     @@ t/t1419/common-functions.sh (new)
      +		-e "s#$BAREREPO_PREFIX/bare_repo.git#<URL/of/bare_repo.git>#"
      +}
      +
     -+filter_out_refs_advertise_output() {
     ++filter_out_hide_refs_output() {
      +	make_user_friendly_and_stable_output | sed 's/^[0-9a-f]\{4\}//g'
      +}
      +
     @@ t/t1419/common-functions.sh (new)
      +	test_cmp show-ref.expect show-ref.filtered
      +}
      
     - ## t/t1419/once-0000-abnormal-refs-advertise-hook.sh (new) ##
     + ## t/t1419/once-0000-abnormal-hide-refs-hook.sh (new) ##
      @@
     -+test_expect_success "builtin protocol (protocol: 1): setup refs-advertise hook which die when read version" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-version
     ++test_expect_success "builtin protocol (protocol: 1): setup hide-refs hook which die when read version" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs \
     ++			--die-read-version \
     ++			-r refs/heads/main
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while refs-advertise hook die when read version" '
     ++test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while hide-refs hook die when read version" '
      +	test_must_fail git -c protocol.version=1 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-version option
     -+		fatal: can not read version message from hook refs-advertise
     ++		fatal: can not read version message from hook hide-refs
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): setup refs-advertise hook which die when write version" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-write-version
     ++test_expect_success "builtin protocol (protocol: 1): setup hide-refs hook which die when write version" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-write-version
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while refs-advertise hook die when write version" '
     ++test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while hide-refs hook die when write version" '
      +	test_must_fail git -c protocol.version=1 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-write-version option
     -+		fatal: can not read version message from hook refs-advertise
     ++		fatal: can not read version message from hook hide-refs
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): setup refs-advertise hook which die when read first filter request" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-first-ref
     ++test_expect_success "builtin protocol (protocol: 1): setup hide-refs hook which die when read first filter request" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-read-first-ref
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while refs-advertise hook die when read first filter request" '
     ++test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while hide-refs hook die when read first filter request" '
      +	test_must_fail git -c protocol.version=1 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-first-ref option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): setup refs-advertise hook which die when read second filter request" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-second-ref
     ++test_expect_success "builtin protocol (protocol: 1): setup hide-refs hook which die when read second filter request" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-read-second-ref
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while refs-advertise hook die when read second filter request" '
     ++test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while hide-refs hook die when read second filter request" '
      +	test_must_fail git -c protocol.version=1 upload-pack --advertise-refs "$BAREREPO_URL"  >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-second-ref option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): setup refs-advertise hook which die while filtring refs" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-filter-refs
     ++test_expect_success "builtin protocol (protocol: 1): setup hide-refs hook which die while filtring refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-after-proc-refs
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while refs-advertise hook die while filtring refs" '
     ++test_expect_success "builtin protocol (protocol: 1): upload-pack --advertise-refs while hide-refs hook die while filtring refs" '
      +	test_must_fail git -c protocol.version=1 upload-pack --advertise-refs "$BAREREPO_URL"  >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		fatal: die with the --die-filter-refs option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: die with the --die-after-proc-refs option
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): setup refs-advertise hook which die when read version" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-version
     ++test_expect_success "builtin protocol (protocol: 2): setup hide-refs hook which die when read version" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-read-version
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while refs-advertise hook die when read version" '
     ++test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while hide-refs hook die when read version" '
      +	test_must_fail git -c protocol.version=2 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-version option
     -+		fatal: can not read version message from hook refs-advertise
     ++		fatal: can not read version message from hook hide-refs
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): setup refs-advertise hook which die when write version" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-write-version
     ++test_expect_success "builtin protocol (protocol: 2): setup hide-refs hook which die when write version" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-write-version
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while refs-advertise hook die when write version" '
     ++test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while hide-refs hook die when write version" '
      +	test_must_fail git -c protocol.version=2 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-write-version option
     -+		fatal: can not read version message from hook refs-advertise
     ++		fatal: can not read version message from hook hide-refs
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): setup refs-advertise hook which die when read first filter request" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-first-ref
     ++test_expect_success "builtin protocol (protocol: 2): setup hide-refs hook which die when read first filter request" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-read-first-ref
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while refs-advertise hook die when read first filter request" '
     ++test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while hide-refs hook die when read first filter request" '
      +	test_must_fail git -c protocol.version=2 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-first-ref option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): setup refs-advertise hook which die when read second filter request" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-read-second-ref
     ++test_expect_success "builtin protocol (protocol: 2): setup hide-refs hook which die when read second filter request" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-read-second-ref
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while refs-advertise hook die when read second filter request" '
     ++test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while hide-refs hook die when read second filter request" '
      +	test_must_fail git -c protocol.version=2 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
      +		fatal: die with the --die-read-second-ref option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): setup refs-advertise hook which die while filtring refs" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise \
     -+			--can-upload-pack \
     -+			--can-receive-pack \
     -+			--can-ls-refs \
     -+			--die-filter-refs
     ++test_expect_success "builtin protocol (protocol: 2): setup hide-refs hook which die while filtring refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs --die-after-proc-refs
      +	EOF
      +'
      +
     -+test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while refs-advertise hook die while filtring refs" '
     ++test_expect_success "builtin protocol (protocol: 2): upload-pack --advertise-refs while hide-refs hook die while filtring refs" '
      +	test_must_fail git -c protocol.version=2 upload-pack --advertise-refs "$BAREREPO_URL" >out 2>&1 &&
      +	cat out | grep "fatal: " | make_user_friendly_and_stable_output >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		fatal: die with the --die-filter-refs option
     -+		fatal: hook refs-advertise died abnormally
     ++		fatal: die with the --die-after-proc-refs option
     ++		fatal: hook hide-refs died abnormally
      +	EOF
      +	test_cmp expect actual
      +'
      
     - ## t/t1419/test-0000-standard-git-clone.sh (new) ##
     + ## t/t1419/test-0001-ls-remote-with-hide-refs-hook.sh (new) ##
      @@
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone" '
     -+	rm -rf local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone --mirror "$BAREREPO_URL" local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git show-ref -d >out 2>&1 &&
     -+	refs_num=$(cat out | wc -l) &&
     -+	make_user_friendly_and_stable_output <out >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		<COMMIT-A> refs/heads/dev
     -+		<COMMIT-B> refs/heads/main
     -+		<COMMIT-C> refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123> refs/tags/v123
     -+		<COMMIT-D> refs/tags/v123^{}
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide no refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs
      +	EOF
     -+	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote while hide-refs hook hide no refs" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION ls-remote "$BAREREPO_URL" >out 2>&1 &&
     -+	refs_num=$(cat out | wc -l) &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
      +		<COMMIT-B>	HEAD
     @@ t/t1419/test-0000-standard-git-clone.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): upload-pack" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION upload-pack --advertise-refs "$BAREREPO_GIT_DIR" >out 2>&1 &&
     -+	filter_out_refs_advertise_output <out >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		<COMMIT-B> HEAD
     -+		<COMMIT-A> refs/heads/dev
     -+		<COMMIT-B> refs/heads/main
     -+		<COMMIT-C> refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123> refs/tags/v123
     -+		<COMMIT-D> refs/tags/v123^{}
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): receive-pack" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION receive-pack --advertise-refs "$BAREREPO_GIT_DIR" >out 2>&1 &&
     -+	filter_out_refs_advertise_output <out >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		<COMMIT-A> refs/heads/dev
     -+		<COMMIT-B> refs/heads/main
     -+		<COMMIT-C> refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123> refs/tags/v123
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -
     - ## t/t1419/test-0001-standard-git-push.sh (new) ##
     -@@
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main" '
     -+	create_commits_in work_repo E &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:main >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <COMMIT-B> <COMMIT-E> refs/heads/main        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/main <COMMIT-B> <COMMIT-E>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <COMMIT-B> <COMMIT-E> refs/heads/main        Z
     -+		To <URL/of/bare_repo.git>
     -+		   <COMMIT-B>..<COMMIT-E>  HEAD -> main
     -+		EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin :dev >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/dev        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/dev <COMMIT-A> <ZERO-OID>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/dev        Z
     -+		To <URL/of/bare_repo.git>
     -+		 - [deleted]         dev
     -+		EOF
     -+	test_cmp expect actual
     -+'
     -
     - ## t/t1419/test-0002-ls-remote-with-refs-advertise-hook.sh (new) ##
     -@@
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which handle no command" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide all refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev" \
     ++		-r "refs/heads/main" \
     ++		-r "refs/pull-requests/1/head" \
     ++		-r "refs/tags/v123"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote while refs-advertise hook handle no command" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote while hide-refs hook hide all refs" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION ls-remote "$BAREREPO_URL" >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		<COMMIT-B>	HEAD
     -+		<COMMIT-A>	refs/heads/dev
     -+		<COMMIT-B>	refs/heads/main
     -+		<COMMIT-C>	refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123>	refs/tags/v123
     -+		<COMMIT-D>	refs/tags/v123^{}
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise all refs" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B" \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $D"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide branches" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev" \
     ++		-r "refs/heads/main"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote all refs" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote while hide-refs hook hide branches" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION ls-remote "$BAREREPO_URL" >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		<COMMIT-B>	HEAD
     -+		<COMMIT-A>	refs/heads/dev
     -+		<COMMIT-B>	refs/heads/main
      +		<COMMIT-C>	refs/pull-requests/1/head
      +		<COMMIT-TAG-v123>	refs/tags/v123
      +		<COMMIT-D>	refs/tags/v123^{}
     @@ t/t1419/test-0002-ls-remote-with-refs-advertise-hook.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise branches" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide pull refs and tags" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "refs/pull-requests/1/head" \
     ++		-r "refs/tags/v123"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote branches" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote while hide-refs hook hide pull refs and tags" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION ls-remote "$BAREREPO_URL" >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     @@ t/t1419/test-0002-ls-remote-with-refs-advertise-hook.sh (new)
      +		<COMMIT-B>	refs/heads/main
      +	EOF
      +	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise pull refs and tags" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs  \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $D"
     -+	EOF
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): ls-remote pull refs and tags" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION ls-remote "$BAREREPO_URL" >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		<COMMIT-C>	refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123>	refs/tags/v123
     -+		<COMMIT-D>	refs/tags/v123^{}
     -+	EOF
     -+	test_cmp expect actual
      +'
      
     - ## t/t1419/test-0003-upload-pack-with-refs-advertise-hook.sh (new) ##
     + ## t/t1419/test-0002-upload-pack-with-hide-refs-hook.sh (new) ##
      @@
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which handle no command" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide no refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone while refs-advertise hook handle no command" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone while hide-refs hook hide no refs" '
      +	rm -rf local.git &&
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone --mirror "$BAREREPO_URL" local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git show-ref -d >out 2>&1 &&
     ++	git -C local.git show-ref -d >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
      +		<COMMIT-A> refs/heads/dev
     @@ t/t1419/test-0003-upload-pack-with-refs-advertise-hook.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise all refs" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B" \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $TAG" \
     -+		-r "obj $D"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide all refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev" \
     ++		-r "refs/heads/main" \
     ++		-r "refs/pull-requests/1/head" \
     ++		-r "refs/tags/v123"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone all refs" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone while hide-refs hide all refs" '
      +	rm -rf local.git &&
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone --mirror "$BAREREPO_URL" local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git show-ref -d >out 2>&1 &&
     ++	test_must_fail git -C local.git show-ref -d >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		<COMMIT-A> refs/heads/dev
     -+		<COMMIT-B> refs/heads/main
     -+		<COMMIT-C> refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123> refs/tags/v123
     -+		<COMMIT-D> refs/tags/v123^{}
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise branches" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide branches" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev" \
     ++		-r "refs/heads/main"
      +	EOF
      +'
      +
      +test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone branches" '
      +	rm -rf local.git &&
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone --mirror "$BAREREPO_URL" local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git show-ref -d >out 2>&1 &&
     ++	git -C local.git show-ref -d >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		<COMMIT-A> refs/heads/dev
     -+		<COMMIT-B> refs/heads/main
     ++		<COMMIT-C> refs/pull-requests/1/head
     ++		<COMMIT-TAG-v123> refs/tags/v123
     ++		<COMMIT-D> refs/tags/v123^{}
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise branches" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which some branches" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): fetch a not advertised commit" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): fetch a tip commit which is not hidden" '
      +	rm -rf local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION init local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git remote add origin "$BAREREPO_URL" &&
     -+	test_must_fail git -C local.git fetch "$BAREREPO_URL" $D
     ++	git init local.git &&
     ++	git -C local.git remote add origin "$BAREREPO_URL" &&
     ++	git -C local.git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION fetch "$BAREREPO_URL" $B
     ++'
     ++
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): config allowAnySHA1InWant to true" '
     ++	git -C "$BAREREPO_GIT_DIR" config uploadpack.allowTipSHA1InWant true &&
     ++	git -C "$BAREREPO_GIT_DIR" config uploadpack.allowReachableSHA1InWant true
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise pull refs and tags" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-upload-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $TAG" \
     -+		-r "obj $D"
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): fetch a non-tip commit which is not hidden" '
     ++	rm -rf local.git &&
     ++	git init local.git &&
     ++	git -C local.git remote add origin "$BAREREPO_URL" &&
     ++	git -C local.git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION fetch "$BAREREPO_URL" $A
     ++'
     ++
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide pull refs and tags" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "refs/pull-requests/1/head" \
     ++		-r "refs/tags/v123"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone pull refs and tags" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): mirror clone while hide-refs hook hide pull refs and tags" '
      +	rm -rf local.git &&
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone --mirror "$BAREREPO_URL" local.git &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C local.git show-ref -d >out 2>&1 &&
     ++	git -C local.git show-ref -d >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >actual &&
      +	format_and_save_expect <<-EOF &&
     -+		<COMMIT-C> refs/pull-requests/1/head
     -+		<COMMIT-TAG-v123> refs/tags/v123
     -+		<COMMIT-D> refs/tags/v123^{}
     ++		<COMMIT-A> refs/heads/dev
     ++		<COMMIT-B> refs/heads/main
      +	EOF
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): clone a not advertised branch" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): config allowAnySHA1InWant to true" '
     ++	git -C "$BAREREPO_GIT_DIR" config uploadpack.allowTipSHA1InWant true &&
     ++	git -C "$BAREREPO_GIT_DIR" config uploadpack.allowReachableSHA1InWant true
     ++'
     ++
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): fetch a non-tip commit which is hidden" '
      +	rm -rf local.git &&
     -+	test_must_fail git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION clone "$BAREREPO_URL" local.git -b main &&
     -+	test ! -d local.git
     ++	git init local.git &&
     ++	git -C local.git remote add origin "$BAREREPO_URL" &&
     ++	test_must_fail git -C local.git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION fetch "$BAREREPO_URL" $C
      +'
      
     - ## t/t1419/test-0004-receive-pack-with-refs-advertise-hook.sh (new) ##
     + ## t/t1419/test-0003-receive-pack-with-hide-refs-hook.sh (new) ##
      @@
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which handle no command" '
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+		test-tool refs-advertise
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide no refs" '
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++		test-tool hide-refs
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while refs-advertise hook handle no command" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while hide-refs hook hide no refs" '
      +	create_commits_in work_repo E &&
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:main >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >out.tmp &&
     @@ t/t1419/test-0004-receive-pack-with-refs-advertise-hook.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): create ref while refs-advertise hook handle no command" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): create ref while hide-refs hook hide no refs" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:new >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >out.tmp &&
      +	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     @@ t/t1419/test-0004-receive-pack-with-refs-advertise-hook.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref while refs-advertise hook handle no command" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref while hide-refs hook hide no refs" '
      +	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin :dev >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >out.tmp &&
      +	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     @@ t/t1419/test-0004-receive-pack-with-refs-advertise-hook.sh (new)
      +	test_cmp expect actual
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise nothing" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup hide-refs hook which hide all refs" '
      +	rm -rf work_repo &&
      +	cp -rf work_repo.dump work_repo &&
      +	rm -rf "$BAREREPO_GIT_DIR" &&
      +	cp -rf bare_repo.git.dump "$BAREREPO_GIT_DIR" &&
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-receive-pack \
     -+		--can-ls-refs
     ++	write_script "$BAREREPO_GIT_DIR/hooks/hide-refs" <<-EOF
     ++	test-tool hide-refs \
     ++		-r "HEAD" \
     ++		-r "refs/heads/dev" \
     ++		-r "refs/heads/main" \
     ++		-r "refs/pull-requests/1/head" \
     ++		-r "refs/tags/v123"
      +	EOF
      +'
      +
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while refs-advertise hook advertise nothing" '
     ++test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while hide-refs hook hide all refs" '
      +	create_commits_in work_repo E &&
      +	test_must_fail git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:main >out 2>&1 &&
      +	make_user_friendly_and_stable_output <out >out.tmp &&
     @@ t/t1419/test-0004-receive-pack-with-refs-advertise-hook.sh (new)
      +	format_and_save_expect <<-EOF &&
      +		remote: # pre-receive hook        Z
      +		remote: pre-receive< <ZERO-OID> <COMMIT-E> refs/heads/main        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/main <ZERO-OID> <COMMIT-E>        Z
     -+		remote: error: cannot lock ref "refs/heads/main": reference already exists        Z
      +		To <URL/of/bare_repo.git>
     -+		 ! [remote rejected] HEAD -> main (failed to update ref)
     -+		error: failed to push some refs to "<URL/of/bare_repo.git>"
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): create ref while while refs-advertise hook advertise nothing" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:new >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/new <ZERO-OID> <COMMIT-E>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		To <URL/of/bare_repo.git>
     -+		 * [new branch]      HEAD -> new
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref while while refs-advertise hook advertise nothing" '
     -+	test_must_fail git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin :dev >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		error: unable to delete "dev": remote ref does not exist
     -+		error: failed to push some refs to "<URL/of/bare_repo.git>"
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise all refs" '
     -+	rm -rf work_repo &&
     -+	cp -rf work_repo.dump work_repo &&
     -+	rm -rf "$BAREREPO_GIT_DIR" &&
     -+	cp -rf bare_repo.git.dump "$BAREREPO_GIT_DIR" &&
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-receive-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/heads/dev $A" \
     -+		-r "obj $A" \
     -+		-r "ref refs/heads/main $B" \
     -+		-r "obj $B" \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $TAG" \
     -+		-r "obj $D"
     -+	EOF
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while refs-advertise hook advertise all refs" '
     -+	create_commits_in work_repo E &&
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:main >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <COMMIT-B> <COMMIT-E> refs/heads/main        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/main <COMMIT-B> <COMMIT-E>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <COMMIT-B> <COMMIT-E> refs/heads/main        Z
     -+		To <URL/of/bare_repo.git>
     -+		   <COMMIT-B>..<COMMIT-E>  HEAD -> main
     -+		EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): create ref while while refs-advertise hook advertise all refs" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:new >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/new <ZERO-OID> <COMMIT-E>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		To <URL/of/bare_repo.git>
     -+		 * [new branch]      HEAD -> new
     -+		EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref while while refs-advertise hook advertise all refs" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin :dev >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <COMMIT-A> <ZERO-OID> refs/heads/dev        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/dev <COMMIT-A> <ZERO-OID>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <COMMIT-A> <ZERO-OID> refs/heads/dev        Z
     -+		To <URL/of/bare_repo.git>
     -+		 - [deleted]         dev
     -+		EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): setup refs-advertise hook which advertise pull refs and tags" '
     -+	rm -rf work_repo &&
     -+	cp -rf work_repo.dump work_repo &&
     -+	rm -rf "$BAREREPO_GIT_DIR" &&
     -+	cp -rf bare_repo.git.dump "$BAREREPO_GIT_DIR" &&
     -+	write_script "$BAREREPO_GIT_DIR/hooks/refs-advertise" <<-EOF
     -+	test-tool refs-advertise \
     -+		--can-receive-pack \
     -+		--can-ls-refs \
     -+		-r "ref refs/pull-requests/1/head $C" \
     -+		-r "obj $C" \
     -+		-r "ref refs/tags/v123 $TAG" \
     -+		-r "obj $TAG" \
     -+		-r "obj $D"
     -+	EOF
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to main while refs-advertise hook advertise pull refs and tags" '
     -+	create_commits_in work_repo E &&
     -+	test_must_fail git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:main >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <ZERO-OID> <COMMIT-E> refs/heads/main        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/main <ZERO-OID> <COMMIT-E>        Z
     -+		remote: error: cannot lock ref "refs/heads/main": reference already exists        Z
     -+		To <URL/of/bare_repo.git>
     -+		 ! [remote rejected] HEAD -> main (failed to update ref)
     -+		error: failed to push some refs to "<URL/of/bare_repo.git>"
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): create ref while while refs-advertise hook advertise pull refs and tags" '
     -+	git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin HEAD:new >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		remote: # pre-receive hook        Z
     -+		remote: pre-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		remote: # update hook        Z
     -+		remote: update< refs/heads/new <ZERO-OID> <COMMIT-E>        Z
     -+		remote: # post-receive hook        Z
     -+		remote: post-receive< <ZERO-OID> <COMMIT-E> refs/heads/new        Z
     -+		To <URL/of/bare_repo.git>
     -+		 * [new branch]      HEAD -> new
     -+	EOF
     -+	test_cmp expect actual
     -+'
     -+
     -+test_expect_success "$PROTOCOL (protocol: $GIT_TEST_PROTOCOL_VERSION): push to delete ref while while refs-advertise hook advertise pull refs and tags" '
     -+	test_must_fail git -c protocol.version=$GIT_TEST_PROTOCOL_VERSION -C work_repo push origin :dev >out 2>&1 &&
     -+	make_user_friendly_and_stable_output <out >out.tmp &&
     -+	sed "s/$(get_abbrev_oid $E)[0-9a-f]*/<COMMIT-E>/g" <out.tmp >actual &&
     -+	format_and_save_expect <<-EOF &&
     -+		error: unable to delete "dev": remote ref does not exist
     ++		 ! [remote rejected] HEAD -> main (deny updating a hidden ref)
      +		error: failed to push some refs to "<URL/of/bare_repo.git>"
      +	EOF
      +	test_cmp expect actual
 3:  d98357d5f64 ! 3:  e737997eb31 doc: add documentation for the refs-advertise hook
     @@ Metadata
      Author: Sun Chao <[email-masked]>
      
       ## Commit message ##
     -    doc: add documentation for the refs-advertise hook
     +    doc: add documentation for the hide-refs hook
      
     -    "git upload-pack" or "git recevie-pack" can use "refs-advertise"
     -    hook to filter the references and commits during reference discovery
     -    phase and commit fetching phase.
     +    "git upload-pack" or "git recevie-pack" can use "hide-refs"
     +    hook to filter the references during reference discovery phase.
      
          Signed-off-by: Sun Chao <[email-masked]>
      
     @@ Documentation/githooks.txt: If this hook exits with a non-zero status, `git push
       pushing anything.  Information about why the push is rejected may be sent
       to the user by writing to standard error.
       
     -+[[refs-advertise]]
     -+refs-advertise
     -+~~~~~~~~~~~~
     ++[[hide-refs]]
     ++hide-refs
     ++~~~~~~~~~
      +
     -+This hook is invoked by 'git-receive-pack' and 'git-upload-pack' during the
     -+reference discovery phase and the commit fetching phase, each reference and
     -+commit to fetch will be filtered by this hook. The hook executes once with
     -+no arguments for each 'git-upload-pack' and 'git-receive-pack' process and
     -+if the exit status is non-zero, `git push` and `git fetch` will abort.
     ++This hook would be invoked by 'git-receive-pack' and 'git-upload-pack'
     ++during the reference discovery phase, each reference and will be filtered
     ++by this hook. The hook executes once with no arguments for each
     ++'git-upload-pack' and 'git-receive-pack' process. Once the hook is invoked,
     ++a version number and server process name ('uploadpack' or 'receive') will
     ++send to it in pkt-line format, followed by a flush-pkt. The hook should
     ++response with its version number.
      +
     -+Once the hook is invoked, a version number and server process name
     -+('git-upload-pack' or 'git-receive-pack' or 'ls-refs') should send to it in
     -+packet-line format, followed by a flush-pkt. The hook should response with
     -+its version number and process name list it support. If the list does not
     -+contains the server process name, the server will close the connection with
     -+the hook and keep going without the hook child process.
     ++During reference discovery phase, each reference will be filtered by this
     ++hook. In the following example, the letter 'G' stands for 'git-receive-pack'
     ++or 'git-upload-pack' and the letter 'H' stands for this hook. The hook
     ++decides if the reference will be hidden or not, it sends result back in
     ++pkt-line format protocol, a response "hide" the references will hide
     ++to the client and can not fetch it even in protocol V2.
      +
     -+During reference discovery phase, each reference will be filtered by this hook,
     -+In the following example, the letter 'G' stands for 'git-receive-pack' or
     -+'git-upload-pack' and the letter 'H' stands for this hook. The hook decides if
     -+the reference will be advertised or not, it sends result back with pkt-line format
     -+protocol, a response in "ok ref <ref>" format followed by a flush-pkt means
     -+the references "<ref>" can be advertised, and "ng ref <ref>" means not.
     ++	# Version negotiation
     ++	G: PKT-LINE(version=1\0uploadpack)
     ++	G: flush-pkt
     ++	H: PKT-LINE(version=1)
     ++	H: flush-pkt
      +
     -+    # Version negotiation
     -+    G: PKT-LINE(version=1\0git-upload-pack)
     -+    G: flush-pkt
     -+    H: PKT-LINE(version=1\0git-upload-pack git-receive-pack ls-refs)
     -+    H: flush-pkt
     ++	# Send reference filter request to hook
     ++	G: PKT-LINE(ref <refname>:<refnamefull>)
     ++	G: flush-pkt
      +
     -+    # Send reference filter request to hook
     -+    G: PKT-LINE(ref <ref> <oid>)
     -+    G: flush-pkt
     ++	# Receive result from the hook.
     ++	# Case 1: this reference is hidden
     ++	H: PKT-LINE(hide)
     ++	H: flush-pkt
      +
     -+    # Receive result from the hook.
     -+    # Case 1: this reference is valid
     -+    H: PKT-LINE(ok ref <ref>)
     -+    H: flush-pkt
     -+    # Case 2: this reference is filtered out
     -+    H: PKT-LINE(ng ref <ref>)
     -+    H: flush-pkt
     ++	# Case 2: this reference can be advertised
     ++	H: flush-pkt
      +
     -+During commit fetch phase of 'git-upload-pack' process, git client may send `want <oid>`
     -+requests and 'git-upload-pack' will send object filter requests to the hook to check if
     -+the object "<oid>" will be sent to the client or not. In the following example, the letter
     -+'G' stands for 'git-upload-pack' and the letter 'H' stands for this hook.
     ++To enable the `hide-refs` hook, we should config hiderefs with `force:`
     ++option, eg:
      +
     -+The hook will decides if a commit will be sent to the client or not, it sends result with
     -+pkt-line format protocol to `git-upload-pack`, a response in "ok obj <oid>" format
     -+followed by a flush-pkt means the object "<oid>" can be sent to client, and "ng obj <oid>"
     -+means not.
     ++	git config --add transfer.hiderefs force:refs/prefix1/
     ++	git config --add uploadpack.hiderefs force:!refs/prefix2/
      +
     -+    # Version negotiation
     -+    G: PKT-LINE(version=1\0ls-refs)
     -+    G: flush-pkt
     -+    H: PKT-LINE(version=1\0git-upload-pack git-receive-pack ls-refs)
     -+    H: flush-pkt
     -+
     -+    # Send commit filter request to hook
     -+    G: PKT-LINE(obj <oid>)
     -+    G: flush-pkt
     -+
     -+    # Receive result from the hook.
     -+    # Case 1: this object is valid
     -+    H: PKT-LINE(ok obj <oid>)
     -+    H: flush-pkt
     -+    # Case 2: this object is filtered out
     -+    H: PKT-LINE(ng obj <oid>)
     -+    H: flush-pkt
     ++the `hide-refs` will be called during reference discovery phase and
     ++check each matched reference, a 'hide' reponse means the reference will
     ++be hidden for its private data and even the `allowTipSHA1InWant` and
     ++`allowReachableSHA1InWant` is set to true.
      +
       [[pre-receive]]
       pre-receive

-- 
gitgitgadget