From: Martijn van Duren Subject: gotd: allow repository directives in global scope To: gameoftrees@openbsd.org Date: Fri, 03 Nov 2023 21:19:23 +0100 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 STRING %token 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