"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Martijn van Duren <openbsd+got@list.imperialat.at>
Subject:
gotd: allow repository directives in global scope
To:
gameoftrees@openbsd.org
Date:
Fri, 03 Nov 2023 21:19:23 +0100

Download raw body.

Thread
Hello,

I just started playing with got{,d} for the first time and after
wrapping my head around the fact that the repository needs to live
outside the working tree I'm starting to like it.

In setting up a gotd server, I quickly became bored with the copy-paste
work of the different per repository settings.

Diff below allows to declare repository directives in the global scope
and are stored in a repo_template. If a directive is not specified in
the repository it will be copied over from the template. The path
directive will be used as a root-directory and the name of the
repository will be appended to complete the per repository path.

thoughts?

martijn@

diff /home/martijn/src/got
commit - 14eb0fefd04d63b1a8d626e72c953a811a403f7d
path + /home/martijn/src/got
blob - 09928aa29395cb1acfaff6303c26cda1adfaf34a
file + gotd/gotd.conf.5
--- gotd/gotd.conf.5
+++ gotd/gotd.conf.5
@@ -143,6 +143,8 @@ may contain path-separators,
 .Dq / ,
 to expose repositories as part of a virtual client-visible directory hierarchy.
 .Pp
+The repository directives can be specified in the global scope, which can be
+overwritten per repository.
 The available repository configuration directives are as follows:
 .Bl -tag -width Ds
 .It Ic deny Ar identity
@@ -155,7 +157,13 @@ to
 Numeric IDs are also accepted.
 .It Ic path Ar path
 Set the path to the Git repository.
-Must be specified.
+If set in the global scope this will be used as the root directory and the
+.Ic repository
+.Ar name
+will be appended for the full path.
+A
+.Ar path
+must be specified.
 .It Ic permit Ar mode Ar identity
 Permit repository access to users with the username
 .Ar identity .
blob - acb40dee8cd351b48669c3c3247c42ed5f44501b
file + gotd/gotd.h
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -76,6 +76,9 @@ struct gotd_repo {
 	char name[NAME_MAX];
 	char path[PATH_MAX];
 
+	int rules_set;
+	int protect_set;
+
 	struct gotd_access_rule_list rules;
 	struct got_pathlist_head protected_tag_namespaces;
 	struct got_pathlist_head protected_branch_namespaces;
@@ -119,6 +122,7 @@ struct gotd {
 	char user_name[32];
 	struct gotd_repolist repos;
 	int nrepos;
+	struct gotd_repo repo_template;
 	struct gotd_child_proc *listen_proc;
 	struct timeval request_timeout;
 	struct timeval auth_timeout;
blob - cc7231514a823861b662e419be141a4492c833fd
file + gotd/parse.y
--- gotd/parse.y
+++ gotd/parse.y
@@ -91,6 +91,9 @@ static int		 errors;
 static struct gotd		*gotd;
 static struct gotd_repo		*new_repo;
 static int			 conf_limit_user_connections(const char *, int);
+static void			 conf_repo_fill_from_template(
+				    struct gotd_repo *, struct gotd_repo *);
+static void			 conf_repo_init(struct gotd_repo *);
 static struct gotd_repo		*conf_new_repo(const char *);
 static void			 conf_new_access_rule(struct gotd_repo *,
 				    enum gotd_access, int, char *);
@@ -117,7 +120,7 @@ typedef struct {
 
 %token	PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
 %token	RO RW CONNECTION LIMIT REQUEST TIMEOUT
-%token	PROTECT NAMESPACE BRANCH TAG
+%token	PROTECT NAMESPACE BRANCH TAG NONE
 
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
@@ -213,6 +216,7 @@ main		: LISTEN ON STRING {
 			free($2);
 		}
 		| connection
+		| repoopts1
 		;
 
 connection	: CONNECTION '{' optnl conflags_l '}'
@@ -240,8 +244,19 @@ conflags	: REQUEST TIMEOUT timeout		{
 		}
 		;
 
-protect		: PROTECT '{' optnl protectflags_l '}'
-		| PROTECT protectflags
+protect		: PROTECT '{' optnl protectflags_l '}' {
+			new_repo->protect_set = 1;
+		}
+		| PROTECT protectflags {
+			new_repo->protect_set = 1;
+		}
+		| PROTECT {
+			new_repo->protect_set = 1;
+		}
+		| PROTECT '{' optnl '}' {
+			new_repo->protect_set = 1;
+		}
+		;
 
 protectflags_l	: protectflags optnl protectflags_l
 		| protectflags optnl
@@ -298,7 +313,8 @@ repository	: REPOSITORY STRING {
 				new_repo = conf_new_repo($2);
 			}
 			free($2);
-		} '{' optnl repoopts2 '}' {
+		} repoopts3 {
+			new_repo = &gotd->repo_template;
 		}
 		;
 
@@ -372,6 +388,11 @@ repoopts2	: repoopts2 repoopts1 nl
 		| repoopts1 optnl
 		;
 
+repoopts3	: '{' optnl repoopts2 '}'
+		| '{' optnl '}'
+		| /* empty */
+		;
+
 nl		: '\n' optnl
 		;
 
@@ -765,6 +786,8 @@ parse_config(const char *filename, enum gotd_procid pr
 	gotd = env;
 	gotd_proc_id = proc_id;
 	TAILQ_INIT(&gotd->repos);
+	conf_repo_init(&gotd->repo_template);
+	new_repo = &gotd->repo_template;
 
 	/* Apply default values. */
 	if (strlcpy(gotd->unix_socket_path, GOTD_UNIX_SOCKET,
@@ -806,6 +829,7 @@ parse_config(const char *filename, enum gotd_procid pr
 		return (-1);
 
 	TAILQ_FOREACH(repo, &gotd->repos, entry) {
+		conf_repo_fill_from_template(repo, &gotd->repo_template);
 		if (repo->path[0] == '\0') {
 			log_warnx("repository \"%s\": no path provided in "
 			    "configuration file", repo->name);
@@ -879,6 +903,79 @@ conf_limit_user_connections(const char *user, int maxi
 	return 0;
 }
 
+static void
+conf_repo_fill_from_template(struct gotd_repo *repo, struct gotd_repo *template)
+{
+	struct gotd_access_rule *rule;
+	const struct got_error *error;
+	struct got_pathlist_entry *pe, *npe;
+	char *s;
+
+	if (repo->path[0] == '\0' && template->path[0] != '\0') {
+		if (snprintf(repo->path, sizeof(repo->path), "%s/%s",
+		    template->path, repo->name) >= sizeof(repo->path)) {
+			log_warnx("repository \"%s\": %s", repo->name,
+			    strerror(ENAMETOOLONG));
+			repo->path[0] = '\0';
+		}
+	}
+	if (!repo->rules_set) {
+		STAILQ_FOREACH(rule, &template->rules, entry) {
+			s = strdup(rule->identifier);
+			if (s == NULL)
+				fatal("strdup");
+			conf_new_access_rule(repo, rule->access,
+			    rule->authorization, s);
+		}
+	}
+	if (!repo->protect_set) {
+		TAILQ_FOREACH(pe, &template->protected_tag_namespaces, entry) {
+			s = strdup(pe->path);
+			if (s == NULL)
+				fatal("strdup");
+			error = got_pathlist_insert(&npe,
+			    &repo->protected_tag_namespaces, s, NULL);
+			if (error || pe == NULL) {
+				/* duplicate shouldn't be possible */
+				fatalx("got_pathlist_insert: %s",
+				    error == NULL ? "???" : error->msg);
+			}
+		}
+		TAILQ_FOREACH(pe,
+		    &template->protected_branch_namespaces, entry) {
+			s = strdup(pe->path);
+			if (s == NULL)
+				fatal("strdup");
+			error = got_pathlist_insert(&npe,
+			    &repo->protected_branch_namespaces, s, NULL);
+			if (error || pe == NULL) {
+				fatalx("got_pathlist_insert: %s",
+				    error == NULL ? "???" : error->msg);
+			}
+		}
+		TAILQ_FOREACH(pe, &template->protected_branches, entry) {
+			s = strdup(pe->path);
+			if (s == NULL)
+				fatal("strdup");
+			error = got_pathlist_insert(&npe,
+			    &repo->protected_branches, s, NULL);
+			if (error || pe == NULL) {
+				fatalx("got_pathlist_insert: %s",
+				    error == NULL ? "???" : error->msg);
+			}
+		}
+	}
+}
+
+static void
+conf_repo_init(struct gotd_repo *repo)
+{
+	STAILQ_INIT(&repo->rules);
+	TAILQ_INIT(&repo->protected_tag_namespaces);
+	TAILQ_INIT(&repo->protected_branch_namespaces);
+	TAILQ_INIT(&repo->protected_branches);
+}
+
 static struct gotd_repo *
 conf_new_repo(const char *name)
 {
@@ -896,10 +993,7 @@ conf_new_repo(const char *name)
 	if (repo == NULL)
 		fatalx("%s: calloc", __func__);
 
-	STAILQ_INIT(&repo->rules);
-	TAILQ_INIT(&repo->protected_tag_namespaces);
-	TAILQ_INIT(&repo->protected_branch_namespaces);
-	TAILQ_INIT(&repo->protected_branches);
+	conf_repo_init(repo);
 
 	if (strlcpy(repo->name, name, sizeof(repo->name)) >=
 	    sizeof(repo->name))
@@ -926,6 +1020,8 @@ conf_new_access_rule(struct gotd_repo *repo, enum gotd
 	rule->identifier = identifier;
 
 	STAILQ_INSERT_TAIL(&repo->rules, rule, entry);
+
+	repo->rules_set = 1;
 }
 
 static int