git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v5 1/5] hiderefs: add hide-refs hook to hide refs dynamically

From
Sun Chao via GitGitGadget <gitgitgadget@gmail.com>
Date
Sep 9, 2022, 15:06 UTC
Message-ID
<278bd185aec26285f8c00aca838f89e5f3877748.1662735985.git.gitgitgadget@gmail.com>
In-Reply-To
<pull.1301.v5.git.git.1662735985.gitgitgadget@gmail.com>
From: Sun Chao <sunchao9@huawei.com>

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 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 respond 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" means the references will be hidden to the client.

        # 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 `hook:` option, eg:

        git config --add transfer.hiderefs hook:refs/prefix1/
        git config --add uploadpack.hiderefs hook:!refs/prefix2/
Signed-off-by: Sun Chao <sunchao9@huawei.com>
---
 refs.c | 229 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 219 insertions(+), 10 deletions(-)
diff --git a/refs.c b/refs.c
index 92819732ab7..a99734fedcd 100644
--- a/refs.c
+++ b/refs.c
@@ -8,6 +8,7 @@
 #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"
@@ -1384,10 +1385,14 @@ char *shorten_unambiguous_ref(const char *refname, int strict)
 }
 
 static struct string_list *hide_refs;
+static struct string_list *hook_hide_refs;
+static struct strbuf hide_refs_section = STRBUF_INIT;
 
 int parse_hide_refs_config(const char *var, const char *value, const char *section)
 {
 	const char *key;
+	int hook = 0;
+
 	if (!strcmp("transfer.hiderefs", var) ||
 	    (!parse_config_key(var, section, NULL, NULL, &key) &&
 	     !strcmp(key, "hiderefs"))) {
@@ -1396,27 +1401,218 @@ int parse_hide_refs_config(const char *var, const char *value, const char *secti
 
 		if (!value)
 			return config_error_nonbool(var);
+
+		/*
+		 * the prefix 'hook:' means that the matched refs will be
+		 * checked by the hide-refs hook dynamically, we need to put
+		 * the 'ref' string to the hook_hide_refs list
+		 */
+		if (skip_prefix(value, "hook:", &value)) {
+			if (!strlen(value))
+				return error(_("missing value for '%s' after hook option"), var);
+			hook = 1;
+		}
+
 		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 (hook) {
+			if (!hook_hide_refs) {
+				CALLOC_ARRAY(hook_hide_refs, 1);
+				hook_hide_refs->strdup_strings = 1;
+			}
+			string_list_append(hook_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);
 	}
+
+	/*
+	 * Once hide-refs hook is invoked, Git need to do version negotiation,
+	 * with it, version number and process name ('uploadpack' or 'receive')
+	 * will send to it in pkt-line format, the proccess name is recorded
+	 * by hide_refs_section
+	 */
+	if (hook && hide_refs_section.len == 0)
+		strbuf_addstr(&hide_refs_section, section);
+
 	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;
+
+/*
+ * Create the hide-refs hook child process and complete version negotiation,
+ * return non-zero upon success, otherwise 0
+ */
+static int create_hide_refs_process(void)
+{
+	struct child_process *proc;
+	struct packet_reader *reader;
+	const char *hook_path;
+	int version = 0;
+	int err;
+
+	hook_path = find_hook("hide-refs");
+	if (!hook_path)
+		return 0;
+
+	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 = "hide-refs";
+	proc->err = 0;
+
+	err = start_command(proc);
+	if (err)
+		goto cleanup;
+
+	sigchain_push(SIGPIPE, SIG_IGN);
+
+	/* Version negotiaton */
+	packet_reader_init(reader, proc->out, NULL, 0,
+			   PACKET_READ_CHOMP_NEWLINE | PACKET_READ_GENTLE_ON_EOF);
+	err = packet_write_fmt_gently(proc->in, "version=1%c%s", '\0', hide_refs_section.buf);
+	if (!err)
+		err = packet_flush_gently(proc->in);
+
+	if (!err)
+		for (;;) {
+			enum packet_read_status status;
+
+			status = packet_reader_read(reader);
+			if (status != PACKET_READ_NORMAL) {
+				/* Check whether hide-refs exited abnormally */
+				if (status == PACKET_READ_EOF)
+					goto failure;
+				break;
+			}
+
+			if (reader->pktlen > 8 && starts_with(reader->line, "version=")) {
+				version = atoi(reader->line + 8);
+			}
+		}
+
+	if (err)
+		goto failure;
+
+	switch (version) {
+	case 0:
+		/* fallthrough */
+	case 1:
+		break;
+	default:
+		trace_printf(_("hook hide-refs version '%d' is not supported"), version);
+		goto failure;
+	}
+
+	sigchain_pop(SIGPIPE);
+
+	hide_refs_proc = proc;
+	hide_refs_reader = reader;
+	return 1;
+
+failure:
+	close(proc->in);
+	close(proc->out);
+	kill(proc->pid, SIGTERM);
+	finish_command_in_signal(proc);
+
+cleanup:
+	free(proc);
+	free(reader);
+	sigchain_pop(SIGPIPE);
+	return 0;
+}
+
+/* If hide-refs child process start failed, set skip_hide_refs_proc to true */
+static int skip_hide_refs_proc;
+
+/*
+ * Return non-zero if hide-refs hook want to hide the ref and 0 otherwise,
+ * and return 0 if hide-refs child proccess start failed or exit abnormally
+ */
+static int ref_hidden_check_by_hook(const char *refname, const char *refname_full)
+{
+	struct strbuf buf = STRBUF_INIT;
+	int err;
+	int ret = 0;
+
+	if (skip_hide_refs_proc)
+		return 0;
+
+	if (!hide_refs_proc)
+		if (!create_hide_refs_process()) {
+			skip_hide_refs_proc = 1;
+			return 0;
+		}
+
+	sigchain_push(SIGPIPE, SIG_IGN);
+	err = packet_write_fmt_gently(hide_refs_proc->in, "ref %s:%s", refname, refname_full);
+	if (err)
+		goto cleanup;
+
+	err = packet_flush_gently(hide_refs_proc->in);
+	if (err)
+		goto cleanup;
+
+	for (;;) {
+		enum packet_read_status status;
+
+		status = packet_reader_read(hide_refs_reader);
+		if (status != PACKET_READ_NORMAL) {
+			/* Check whether hide-refs exited abnormally */
+			if (status == PACKET_READ_EOF)
+				goto cleanup;
+			break;
+		}
+
+		strbuf_addstr(&buf, hide_refs_reader->line);
+	}
+
+	if (!strncmp("hide", buf.buf, 4))
+		ret = 1;
+
+	sigchain_pop(SIGPIPE);
+	return ret;
+
+cleanup:
+	close(hide_refs_proc->in);
+	close(hide_refs_proc->out);
+	kill(hide_refs_proc->pid, SIGTERM);
+	finish_command_in_signal(hide_refs_proc);
+
+	free(hide_refs_proc);
+	free(hide_refs_reader);
+	sigchain_pop(SIGPIPE);
+
+	skip_hide_refs_proc = 1;
+	return 0;
+}
+
+static int ref_hidden_check(const char *refname, const char *refname_full, int hook)
 {
+	struct string_list *hide_refs_list = hide_refs;
 	int i;
 
-	if (!hide_refs)
+	if (hook)
+		hide_refs_list = hook_hide_refs;
+
+	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;
@@ -1436,12 +1632,25 @@ 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 (!hook)
+				return 1;
+			return ref_hidden_check_by_hook(refname, refname_full);
+		}
 	}
 	return 0;
 }
 
+int ref_is_hidden(const char *refname, const char *refname_full)
+{
+	if (ref_hidden_check(refname, refname_full, 0) ||
+	    ref_hidden_check(refname, refname_full, 1))
+		return 1;
+	return 0;
+}
+
 const char *find_descendant_ref(const char *dirname,
 				const struct string_list *extras,
 				const struct string_list *skip)
-- 
gitgitgadget
Previous: Sun Chao via GitGitGadgetNext: Junio C Hamano
Message 29 of 42 in “refs-advertise: add hook to filter advertised refs”
  1. 0/3 refs-advertise: add hook to filter advertised refsSun Chao via GitGitGadget, Aug 3, 2022
  2. 1/3 refs-advertise: add hook to filter advertised refsSun Chao via GitGitGadget, Aug 3, 2022
  3. 3/3 doc: add documentation for the refs-advertise hookSun Chao via GitGitGadget, Aug 3, 2022
  4. 2/3 t1419: add test cases for refs-advertise hookSun Chao via GitGitGadget, Aug 3, 2022
  5. Junio C HamanoAug 3, 2022
  6. 孙超Aug 4, 2022
  7. Jiang XinAug 10, 2022
  8. 孙超Aug 10, 2022
  9. 0/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  10. 1/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  11. 2/3 t1419: add test cases for hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  12. 3/3 doc: add documentation for the hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  13. Eric SunshineAug 15, 2022
  14. 孙超Aug 15, 2022
  15. Junio C HamanoAug 15, 2022
  16. 0/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  17. 1/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  18. 3/3 doc: add documentation for the hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  19. 2/3 t1419: add test cases for hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  20. 0/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  21. 3/3 doc: add documentation for the hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  22. 1/3 hide-refs: add hook to force hide refsSun Chao via GitGitGadget, Aug 15, 2022
  23. Junio C HamanoAug 15, 2022
  24. 孙超Aug 16, 2022
  25. Calvin WanAug 18, 2022
  26. 孙超Aug 19, 2022
  27. 2/3 t1419: add test cases for hide-refs hookSun Chao via GitGitGadget, Aug 15, 2022
  28. 0/5 hiderefs: add hide-refs hook to hide refs dynamicallySun Chao via GitGitGadget, Sep 9, 2022
  29. 1/5 hiderefs: add hide-refs hook to hide refs dynamicallySun Chao via GitGitGadget, Sep 9, 2022
  30. Junio C HamanoSep 13, 2022
  31. Junio C HamanoSep 16, 2022
  32. 孙超Sep 17, 2022
  33. 2/5 hiderefs: use new flag to mark force hidden refsSun Chao via GitGitGadget, Sep 9, 2022
  34. 3/5 hiderefs: hornor hide flags in wire protocol V2Sun Chao via GitGitGadget, Sep 9, 2022
  35. 4/5 test: add test cases for hide-refs hookSun Chao via GitGitGadget, Sep 9, 2022
  36. 5/5 doc: add documentation for the hide-refs hookSun Chao via GitGitGadget, Sep 9, 2022
  37. 0/5 hiderefs: add hide-refs hook to hide refs dynamicallySun Chao via GitGitGadget, Sep 20, 2022
  38. 1/5 hiderefs: add hide-refs hook to hide refs dynamicallySun Chao via GitGitGadget, Sep 20, 2022
  39. 3/5 hiderefs: hornor hide flags in wire protocol V2Sun Chao via GitGitGadget, Sep 20, 2022
  40. 2/5 hiderefs: use a new flag to mark force hidden refsSun Chao via GitGitGadget, Sep 20, 2022
  41. 5/5 doc: add documentation for the hide-refs hookSun Chao via GitGitGadget, Sep 20, 2022
  42. 4/5 test: add test cases for hide-refs hookSun Chao via GitGitGadget, Sep 20, 2022

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.