Download raw body.
add got.conf(5)
On Sun, Sep 06, 2020 at 06:09:44PM +0200, Stefan Sperling wrote:
> This adds a got.conf(5) configuration file, based on parse.y code
> added by Tracey in the got/ subdirectory.
>
> The new got.conf file is parsed by a new libexec helper when a repository
> is opened. This is not done because we really need privsep, but because
> yacc generates code with global variables that cannot be used in a library
> context. We can work around this by running our parse.y code in a separate
> address space.
>
> At present, only author information (for got commit/import) and remote
> repositories (for got fetch) can be configured. We can expand this scope
> later as needed.
>
> ok?
Ok. There are some whitespace problems pointed out below, but those can
be handled after the commit. Also, this diff doesn't take care of
removing got/parse.y. Were you going to handle that after the commit?
>
> diff refs/heads/master refs/heads/gotconfig
> blob - 19122e372a158d8a95216bb854924e9a25225a76
> blob + f74cc058dc06317e6752bfcbebd77f4e23bccba8
> --- got/got.1
> +++ got/got.1
> @@ -88,7 +88,9 @@ The
> command requires the
> .Ev GOT_AUTHOR
> environment variable to be set,
> -unless Git's
> +unless an author has been configured in
> +.Xr got.conf 5
> +or Git's
> .Dv user.name
> and
> .Dv user.email
> @@ -186,6 +188,8 @@ More details about the pack file format are documented
> .Pp
> .Cm got clone
> creates a remote repository entry in the
> +.Xr got.conf 5
> +and
> .Pa config
> file of the cloned repository to store the
> .Ar repository-url
> @@ -234,8 +238,10 @@ This is useful if the cloned repository will not be us
> locally created commits.
> .Pp
> The repository's
> +.Xr got.conf 5
> +and
> .Pa config
> -file will be set up with the
> +files will be set up with the
> .Dq mirror
> option enabled, such that
> .Cm got fetch
> @@ -310,6 +316,8 @@ is specified,
> .Dq origin
> will be used.
> The remote repository's URL is obtained from the corresponding entry in the
> +.Xr got.conf 5
> +or
> .Pa config
> file of the local repository, as created by
> .Cm got clone .
> @@ -1228,7 +1236,9 @@ The
> command requires the
> .Ev GOT_AUTHOR
> environment variable to be set,
> -unless Git's
> +unless an author has been configured in
> +.Xr got.conf 5
> +or Git's
> .Dv user.name
> and
> .Dv user.email
> @@ -1920,19 +1930,25 @@ attempts to reject
> .Ev GOT_AUTHOR
> environment variables with a missing email address.
> .Pp
> -If present, Git's
> +If present,
> +configuration settings in
> +.Xr got.conf 5 ,
> +or Git's
> .Dv user.name
> and
> .Dv user.email
> configuration settings in the repository's
> .Pa .git/config
> -file will override the value of
> +file,
> +will override the value of
> .Ev GOT_AUTHOR .
> However, the
> .Dv user.name
> and
> .Dv user.email
> -configuration settings contained in Git's global
> +configuration settings contained in
> +.Xr got.conf 5
> +or Git's global
> .Pa ~/.gitconfig
> configuration file will be used only if the
> .Ev GOT_AUTHOR
> @@ -1951,6 +1967,16 @@ The default limit on the number of commits traversed b
> If set to zero, the limit is unbounded.
> This variable will be silently ignored if it is set to a non-numeric value.
> .El
> +.Sh FILES
> +.Bl -tag -width packed-refs -compact
> +.It Pa got.conf
> +Repository-wide configuration settings for
> +.Nm .
> +If present, this configuration file is located in the root directory
> +of a Git repository and supersedes any relevant settings in Git's
> +.Pa config
> +file.
> +.El
> .Sh EXIT STATUS
> .Ex -std got
> .Sh EXAMPLES
> @@ -2257,7 +2283,8 @@ repository with
> .Sh SEE ALSO
> .Xr tog 1 ,
> .Xr git-repository 5 ,
> -.Xr got-worktree 5
> +.Xr got-worktree 5 ,
> +.Xr got.conf 5
> .Sh AUTHORS
> .An Stefan Sperling Aq Mt stsp@openbsd.org
> .An Martin Pieuchot Aq Mt mpi@openbsd.org
> blob - 2b4d757744e69bb5461dd333ec37c8351cdeaae0
> blob + ee68786d7a1608cf5b225ba31744511639efc4f6
> --- got/got.c
> +++ got/got.c
> @@ -523,25 +523,30 @@ get_author(char **author, struct got_repository *repo)
>
> *author = NULL;
>
> - name = got_repo_get_gitconfig_author_name(repo);
> - email = got_repo_get_gitconfig_author_email(repo);
> - if (name && email) {
> - if (asprintf(author, "%s <%s>", name, email) == -1)
> - return got_error_from_errno("asprintf");
> - return NULL;
> - }
> -
> - got_author = getenv("GOT_AUTHOR");
> + got_author = got_repo_get_gotconfig_author(repo);
> if (got_author == NULL) {
> - name = got_repo_get_global_gitconfig_author_name(repo);
> - email = got_repo_get_global_gitconfig_author_email(repo);
> + name = got_repo_get_gitconfig_author_name(repo);
> + email = got_repo_get_gitconfig_author_email(repo);
> if (name && email) {
> if (asprintf(author, "%s <%s>", name, email) == -1)
> return got_error_from_errno("asprintf");
> return NULL;
> }
> - /* TODO: Look up user in password database? */
> - return got_error(GOT_ERR_COMMIT_NO_AUTHOR);
> +
> + got_author = getenv("GOT_AUTHOR");
> + if (got_author == NULL) {
> + name = got_repo_get_global_gitconfig_author_name(repo);
> + email = got_repo_get_global_gitconfig_author_email(
> + repo);
> + if (name && email) {
> + if (asprintf(author, "%s <%s>", name, email)
> + == -1)
> + return got_error_from_errno("asprintf");
> + return NULL;
> + }
> + /* TODO: Look up user in password database? */
> + return got_error(GOT_ERR_COMMIT_NO_AUTHOR);
> + }
> }
>
> *author = strdup(got_author);
> @@ -1037,9 +1042,9 @@ cmd_clone(int argc, char *argv[])
> pid_t fetchpid = -1;
> struct got_fetch_progress_arg fpa;
> char *git_url = NULL;
> - char *gitconfig_path = NULL;
> - char *gitconfig = NULL;
> - FILE *gitconfig_file = NULL;
> + char *gitconfig_path = NULL, *gotconfig_path = NULL;
> + char *gitconfig = NULL, *gotconfig = NULL;
> + FILE *gitconfig_file = NULL, *gotconfig_file = NULL;
> ssize_t n;
> int verbosity = 0, fetch_all_branches = 0, mirror_references = 0;
> int list_refs_only = 0;
> @@ -1340,7 +1345,40 @@ cmd_clone(int argc, char *argv[])
> }
> }
>
> - /* Create a config file git-fetch(1) can understand. */
> + /* Create got.conf(5). */
> + gotconfig_path = got_repo_get_path_gotconfig(repo);
> + if (gotconfig_path == NULL) {
> + error = got_error_from_errno("got_repo_get_path_gotconfig");
> + goto done;
> + }
> + gotconfig_file = fopen(gotconfig_path, "a");
> + if (gotconfig_file == NULL) {
> + error = got_error_from_errno2("fopen", gotconfig_path);
> + goto done;
> + }
> + got_path_strip_trailing_slashes(server_path);
> + if (asprintf(&gotconfig,
> + "remote \"%s\" {\n"
> + "\tserver %s\n"
> + "\tprotocol %s\n"
> + "%s%s%s"
> + "\trepository \"%s\"\n"
> + "%s"
> + "}\n",
> + GOT_FETCH_DEFAULT_REMOTE_NAME, host, proto,
> + port ? "\tport " : "", port ? port : "", port ? "\n" : "",
> + server_path,
> + mirror_references ? "\tmirror-references yes\n" : "") == -1) {
> + error = got_error_from_errno("asprintf");
> + goto done;
> + }
> + n = fwrite(gotconfig, 1, strlen(gotconfig), gotconfig_file);
> + if (n != strlen(gotconfig)) {
> + error = got_ferror(gotconfig_file, GOT_ERR_IO);
> + goto done;
> + }
> +
> + /* Create a config file Git can understand. */
> gitconfig_path = got_repo_get_path_gitconfig(repo);
> if (gitconfig_path == NULL) {
> error = got_error_from_errno("got_repo_get_path_gitconfig");
> @@ -1413,6 +1451,8 @@ done:
> }
> if (fetchfd != -1 && close(fetchfd) == -1 && error == NULL)
> error = got_error_from_errno("close");
> + if (gotconfig_file && fclose(gotconfig_file) == EOF && error == NULL)
> + error = got_error_from_errno("fclose");
> if (gitconfig_file && fclose(gitconfig_file) == EOF && error == NULL)
> error = got_error_from_errno("fclose");
> if (repo)
> @@ -1438,6 +1478,9 @@ done:
> free(server_path);
> free(repo_name);
> free(default_destdir);
> + free(gotconfig);
> + free(gitconfig);
> + free(gotconfig_path);
> free(gitconfig_path);
> free(git_url);
> return error;
> @@ -1858,16 +1901,24 @@ cmd_fetch(int argc, char *argv[])
> if (error)
> goto done;
>
> - got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo);
> + got_repo_get_gotconfig_remotes(&nremotes, &remotes, repo);
> for (i = 0; i < nremotes; i++) {
> remote = &remotes[i];
> if (strcmp(remote->name, remote_name) == 0)
> break;
> }
> if (i == nremotes) {
> - error = got_error_path(remote_name, GOT_ERR_NO_REMOTE);
> - goto done;
> - }
> + got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo);
> + for (i = 0; i < nremotes; i++) {
> + remote = &remotes[i];
> + if (strcmp(remote->name, remote_name) == 0)
> + break;
> + }
> + if (i == nremotes) {
> + error = got_error_path(remote_name, GOT_ERR_NO_REMOTE);
> + goto done;
> + }
> + }
extra end tab
>
> error = got_fetch_parse_uri(&proto, &host, &port, &server_path,
> &repo_name, remote->url);
> blob - /dev/null
> blob + d6d11e44ab3797c4469bc5d41a882b8be638c18d (mode 644)
> --- /dev/null
> +++ got/got.conf.5
> @@ -0,0 +1,156 @@
> +.\"
> +.\" Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
> +.\"
> +.\" Permission to use, copy, modify, and distribute this software for any
> +.\" purpose with or without fee is hereby granted, provided that the above
> +.\" copyright notice and this permission notice appear in all copies.
> +.\"
> +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> +.\"
> +.Dd $Mdocdate$
> +.Dt GOT.CONF 5
> +.Os
> +.Sh NAME
> +.Nm got.conf
> +.Nd Game of Trees configuration file
> +.Sh DESCRIPTION
> +.Nm
> +is the run-time configuration file for
> +.Xr got 1 .
> +.Pp
> +The file format is line-based, with one configuration directive per line.
> +Any lines beginning with a
> +.Sq #
> +are treated as comments and ignored.
> +.Pp
> +The available configuration directives are as follows:
> +.Bl -tag -width Ds
> +.It Ic author Dq Real Name <email address>
> +Configure the author's name and email address for
> +.Cm got commit
> +and
> +.Cm got import
> +when operating on this repository.
> +Author information specified here overrides the
> +.Ev GOT_AUTHOR
> +environment variable.
> +.Pp
> +Because
> +.Xr git 1
> +may fail to parse commits without an email address in author data,
> +.Xr got 1
> +attempts to reject author information with a missing email address.
> +.It Ic remote Ar name Brq ...
> +Define a remote repository.
> +The specified
> +.Ar name
> +can be used to refer to the remote repository on the command line of
> +.Cm got fetch .
> +.Pp
> +Information about this repository is declared in a block of options
> +enclosed in curly brackets:
> +.Bl -tag -width Ds
> +.It Ic server Ar hostname
> +Defines the hostname to use for contacting the remote repository's server.
> +.It Ic repository Ar path
> +Defines the path to the repository on the remote repository's server.
> +.It Ic protocol Ar scheme
> +Defines the protocol to use for communicating with the remote repository's
> +server.
> +.Pp
> +The following protocol schemes are supported:
> +.Bl -tag -width git+ssh
> +.It git
> +The Git protocol as implemented by the
> +.Xr git-daemon 1
> +server.
> +Use of this protocol is discouraged since it supports neither authentication
> +nor encryption.
> +.It git+ssh
> +The Git protocol wrapped in an authenticated and encrypted
> +.Xr ssh 1
> +tunnel.
> +With this protocol the hostname may contain an embedded username for
> +.Xr ssh 1
> +to use:
> +.Mt user@hostname
> +.It ssh
> +Short alias for git+ssh.
> +.El
> +.It Ic port Ar port
> +Defines the port to use for connecting to the remote repository's server.
> +The
> +.Ar port
> +can be specified by number or name.
> +The port name to number mappings are found in the file
> +.Pa /etc/services ;
> +see
> +.Xr services 5
> +for details.
> +If not specified, the default port of the specified
> +.Cm protocol
> +will be used.
> +.It Ic mirror-references Ar yes | no
> +This option controls the behaviour of
> +.Cm got fetch
> +when updating references.
> +.Sy Enabling this option can lead to the loss of local commits.
> +Maintaining custom changes in a mirror repository is therefore discouraged.
> +.Pp
> +If this option is not specified or set to
> +.Ar no ,
> +.Cm got fetch
> +will map references of the remote repository into the local repository's
> +.Dq refs/remotes/
> +namespace.
> +.Pp
> +If this option is set to
> +.Ar yes ,
> +all branches in the
> +.Dq refs/heads/
> +namespace will be updated directly to match the corresponding branches in
> +the remote repository.
> +.El
> +.Sh EXAMPLES
> +Configure author information:
> +.Bd -literal -offset indent
> +author "Flan Hacker <flan_hacker@openbsd.org>"
> +.Ed
> +.Pp
> +Remote repository specification for the Game of Trees repository:
> +.Bd -literal -offset indent
> +remote "origin" {
> + server git.gameoftrees.org
> + protocol git
> + repository got
> +}
> +.Ed
> +.Pp
> +Mirror the OpenBSD src repository from Github:
> +.Bd -literal -offset indent
> +remote "origin" {
> + repository "openbsd/src"
> + server git@github.com
> + protocol git+ssh
> + mirror-references yes
> +}
> +.Ed
> +.Sh FILES
> +.Bl -tag -width Ds -compact
> +.It Pa got.conf
> +If present, the
> +.Nm
> +configuration file is located in the root directory of a Git repository
> +and supersedes any relevant settings in Git's
> +.Pa config
> +file.
> +.El
> +.Sh SEE ALSO
> +.Xr got 1 ,
> +.Xr git-repository 5
> blob - 93dc3d73083ef892787d94815eeedb416e7fc4e3
> blob + 93c6e03f94ed0176787d92976b2a68085ae10de3
> --- gotweb/parse.y
> +++ gotweb/parse.y
> @@ -234,7 +234,7 @@ yyerror(const char *fmt, ...)
> gerror = got_error_from_errno("asprintf");
> return(0);
> }
> - gerror = got_error_msg(GOT_ERR_PARSE_Y_YY, strdup(err));
> + gerror = got_error_msg(GOT_ERR_PARSE_CONFIG, strdup(err));
> free(msg);
> free(err);
> return(0);
> blob - b3805d39c06efa63932243754b6f446fa0218eda
> blob + 0f3e07a914451fb4ee60661753010efd60284036
> --- include/got_error.h
> +++ include/got_error.h
> @@ -141,7 +141,7 @@
> #define GOT_ERR_FETCH_NO_BRANCH 124
> #define GOT_ERR_FETCH_BAD_REF 125
> #define GOT_ERR_TREE_ENTRY_TYPE 126
> -#define GOT_ERR_PARSE_Y_YY 127
> +#define GOT_ERR_PARSE_CONFIG 127
> #define GOT_ERR_NO_CONFIG_FILE 128
> #define GOT_ERR_BAD_SYMLINK 129
>
> @@ -291,7 +291,7 @@ static const struct got_error {
> { GOT_ERR_FETCH_NO_BRANCH, "could not find any branches to fetch" },
> { GOT_ERR_FETCH_BAD_REF, "reference cannot be fetched" },
> { GOT_ERR_TREE_ENTRY_TYPE, "unexpected tree entry type" },
> - { GOT_ERR_PARSE_Y_YY, "yyerror error" },
> + { GOT_ERR_PARSE_CONFIG, "configuration file syntax error" },
> { GOT_ERR_NO_CONFIG_FILE, "configuration file doesn't exit" },
> { GOT_ERR_BAD_SYMLINK, "symbolic link points outside of paths under "
> "version control" },
> blob - 1e0ca3b4ba3d818cceefe6c470559274e954788c
> blob + bc9bddc1aebed32b0412d1f87f93b3ec0b753271
> --- include/got_repository.h
> +++ include/got_repository.h
> @@ -59,10 +59,17 @@ struct got_remote_repo {
> int mirror_references;
> };
>
> +/* Obtain the commit author if parsed from got.conf, else NULL. */
> +const char *got_repo_get_gotconfig_author(struct got_repository *);
> +
> /* Obtain the list of remote repositories parsed from gitconfig. */
> void got_repo_get_gitconfig_remotes(int *, struct got_remote_repo **,
> struct got_repository *);
>
> +/* Obtain the list of remote repositories parsed from got.conf. */
> +void got_repo_get_gotconfig_remotes(int *, struct got_remote_repo **,
> + struct got_repository *);
> +
> /*
> * Obtain paths to various directories within a repository.
> * The caller must dispose of a path with free(3).
> @@ -72,6 +79,7 @@ char *got_repo_get_path_objects_pack(struct got_reposi
> char *got_repo_get_path_refs(struct got_repository *);
> char *got_repo_get_path_packed_refs(struct got_repository *);
> char *got_repo_get_path_gitconfig(struct got_repository *);
> +char *got_repo_get_path_gotconfig(struct got_repository *);
>
> struct got_reference;
>
> blob - 950723fdd3cab55b86ef0eb87c5739432be13b7c
> blob + 51acfb2fde45abd2e9073411ad36ae9a24fed30e
> --- lib/got_lib_privsep.h
> +++ lib/got_lib_privsep.h
> @@ -43,6 +43,7 @@
> #define GOT_PROG_READ_TAG got-read-tag
> #define GOT_PROG_READ_PACK got-read-pack
> #define GOT_PROG_READ_GITCONFIG got-read-gitconfig
> +#define GOT_PROG_READ_GOTCONFIG got-read-gotconfig
> #define GOT_PROG_FETCH_PACK got-fetch-pack
> #define GOT_PROG_INDEX_PACK got-index-pack
> #define GOT_PROG_SEND_PACK got-send-pack
> @@ -65,6 +66,8 @@
> GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_PACK)
> #define GOT_PATH_PROG_READ_GITCONFIG \
> GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GITCONFIG)
> +#define GOT_PATH_PROG_READ_GOTCONFIG \
> + GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_READ_GOTCONFIG)
> #define GOT_PATH_PROG_FETCH_PACK \
> GOT_STRINGVAL(GOT_LIBEXECDIR) "/" GOT_STRINGVAL(GOT_PROG_FETCH_PACK)
> #define GOT_PATH_PROG_SEND_PACK \
> @@ -147,6 +150,15 @@ enum got_imsg_type {
> GOT_IMSG_GITCONFIG_REMOTE,
> GOT_IMSG_GITCONFIG_OWNER_REQUEST,
> GOT_IMSG_GITCONFIG_OWNER,
> +
> + /* Messages related to gotconfig files. */
> + GOT_IMSG_GOTCONFIG_PARSE_REQUEST,
> + GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST,
> + GOT_IMSG_GOTCONFIG_REMOTES_REQUEST,
> + GOT_IMSG_GOTCONFIG_INT_VAL,
> + GOT_IMSG_GOTCONFIG_STR_VAL,
> + GOT_IMSG_GOTCONFIG_REMOTES,
> + GOT_IMSG_GOTCONFIG_REMOTE,
> };
>
> /* Structure for GOT_IMSG_ERROR. */
> @@ -457,6 +469,16 @@ const struct got_error *got_privsep_recv_gitconfig_str
> struct imsgbuf *);
> const struct got_error *got_privsep_recv_gitconfig_int(int *, struct imsgbuf *);
> const struct got_error *got_privsep_recv_gitconfig_remotes(
> + struct got_remote_repo **, int *, struct imsgbuf *);
> +
> +const struct got_error *got_privsep_send_gotconfig_parse_req(struct imsgbuf *,
> + int);
> +const struct got_error *got_privsep_send_gotconfig_author_req(struct imsgbuf *);
> +const struct got_error *got_privsep_send_gotconfig_remotes_req(
> + struct imsgbuf *);
> +const struct got_error *got_privsep_recv_gotconfig_str(char **,
> + struct imsgbuf *);
> +const struct got_error *got_privsep_recv_gotconfig_remotes(
> struct got_remote_repo **, int *, struct imsgbuf *);
>
> const struct got_error *got_privsep_send_commit_traversal_request(
> blob - df440da3c28c0038eb1d0ecb1cf3af9501734493
> blob + 53390eff4e10f3f105c870ac188b9a0db1df2416
> --- lib/got_lib_repository.h
> +++ lib/got_lib_repository.h
> @@ -21,6 +21,7 @@
> #define GOT_REFS_DIR "refs"
> #define GOT_HEAD_FILE "HEAD"
> #define GOT_GITCONFIG "config"
> +#define GOT_GOTCONFIG "got.conf"
>
> /* Other files and directories inside the git directory. */
> #define GOT_FETCH_HEAD_FILE "FETCH_HEAD"
> @@ -64,6 +65,11 @@ struct got_repository {
> int ngitconfig_remotes;
> struct got_remote_repo *gitconfig_remotes;
> char *gitconfig_owner;
> +
> + /* Settings read from got.conf. */
> + char *gotconfig_author;
> + int ngotconfig_remotes;
> + struct got_remote_repo *gotconfig_remotes;
> };
>
> const struct got_error*got_repo_cache_object(struct got_repository *,
> blob - 44f5cc811353036797f501a79e4fb1ee0f11dcf1
> blob + b91bda4fa54fee65a9eb14a87a3662af9a624a11
> --- lib/privsep.c
> +++ lib/privsep.c
> @@ -1887,6 +1887,208 @@ got_privsep_recv_gitconfig_remotes(struct got_remote_r
> }
>
> const struct got_error *
> +got_privsep_send_gotconfig_parse_req(struct imsgbuf *ibuf, int fd)
> +{
> + const struct got_error *err = NULL;
> +
> + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_PARSE_REQUEST, 0, 0, fd,
> + NULL, 0) == -1) {
> + err = got_error_from_errno("imsg_compose "
> + "GOTCONFIG_PARSE_REQUEST");
> + close(fd);
> + return err;
> + }
> +
> + return flush_imsg(ibuf);
> +}
> +
> +const struct got_error *
> +got_privsep_send_gotconfig_author_req(struct imsgbuf *ibuf)
> +{
> + if (imsg_compose(ibuf,
> + GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST, 0, 0, -1, NULL, 0) == -1)
> + return got_error_from_errno("imsg_compose "
> + "GOTCONFIG_AUTHOR_REQUEST");
> +
> + return flush_imsg(ibuf);
> +}
> +
> +const struct got_error *
> +got_privsep_send_gotconfig_remotes_req(struct imsgbuf *ibuf)
> +{
> + if (imsg_compose(ibuf,
> + GOT_IMSG_GOTCONFIG_REMOTES_REQUEST, 0, 0, -1, NULL, 0) == -1)
> + return got_error_from_errno("imsg_compose "
> + "GOTCONFIG_REMOTE_REQUEST");
> +
> + return flush_imsg(ibuf);
> +}
> +
> +const struct got_error *
> +got_privsep_recv_gotconfig_str(char **str, struct imsgbuf *ibuf)
> +{
> + const struct got_error *err = NULL;
> + struct imsg imsg;
> + size_t datalen;
> + const size_t min_datalen = 0;
> +
> + *str = NULL;
> +
> + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
> + if (err)
> + return err;
> + datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
> +
> + switch (imsg.hdr.type) {
> + case GOT_IMSG_ERROR:
> + if (datalen < sizeof(struct got_imsg_error)) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + err = recv_imsg_error(&imsg, datalen);
> + break;
> + case GOT_IMSG_GOTCONFIG_STR_VAL:
> + if (datalen == 0)
> + break;
> + *str = malloc(datalen);
> + if (*str == NULL) {
> + err = got_error_from_errno("malloc");
> + break;
> + }
> + if (strlcpy(*str, imsg.data, datalen) >= datalen)
> + err = got_error(GOT_ERR_NO_SPACE);
> + break;
> + default:
> + err = got_error(GOT_ERR_PRIVSEP_MSG);
> + break;
> + }
> +
> + imsg_free(&imsg);
> + return err;
> +}
> +
> +const struct got_error *
> +got_privsep_recv_gotconfig_remotes(struct got_remote_repo **remotes,
> + int *nremotes, struct imsgbuf *ibuf)
> +{
> + const struct got_error *err = NULL;
> + struct imsg imsg;
> + size_t datalen;
> + struct got_imsg_remotes iremotes;
> + struct got_imsg_remote iremote;
> + const size_t min_datalen =
> + MIN(sizeof(struct got_imsg_error), sizeof(iremotes));
> +
> + *remotes = NULL;
> + *nremotes = 0;
> + iremotes.nremotes = 0;
> +
> + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
> + if (err)
> + return err;
> + datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
> +
> + switch (imsg.hdr.type) {
> + case GOT_IMSG_ERROR:
> + if (datalen < sizeof(struct got_imsg_error)) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + err = recv_imsg_error(&imsg, datalen);
> + break;
> + case GOT_IMSG_GOTCONFIG_REMOTES:
> + if (datalen != sizeof(iremotes)) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + memcpy(&iremotes, imsg.data, sizeof(iremotes));
> + if (iremotes.nremotes == 0) {
> + imsg_free(&imsg);
> + return NULL;
> + }
> + break;
> + default:
> + imsg_free(&imsg);
> + return got_error(GOT_ERR_PRIVSEP_MSG);
> + }
> +
> + imsg_free(&imsg);
> +
> + *remotes = recallocarray(NULL, 0, iremotes.nremotes, sizeof(**remotes));
> + if (*remotes == NULL)
> + return got_error_from_errno("recallocarray");
> +
> + while (*nremotes < iremotes.nremotes) {
> + struct got_remote_repo *remote;
> + const size_t min_datalen =
> + MIN(sizeof(struct got_imsg_error), sizeof(iremote));
> +
extra tab
> + err = got_privsep_recv_imsg(&imsg, ibuf, min_datalen);
> + if (err)
> + break;
> + datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
> +
> + switch (imsg.hdr.type) {
> + case GOT_IMSG_ERROR:
> + if (datalen < sizeof(struct got_imsg_error)) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + err = recv_imsg_error(&imsg, datalen);
> + break;
> + case GOT_IMSG_GOTCONFIG_REMOTE:
> + remote = &(*remotes)[*nremotes];
> + if (datalen < sizeof(iremote)) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + memcpy(&iremote, imsg.data, sizeof(iremote));
> + if (iremote.name_len == 0 || iremote.url_len == 0 ||
> + (sizeof(iremote) + iremote.name_len +
> + iremote.url_len) > datalen) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + remote->name = strndup(imsg.data + sizeof(iremote),
> + iremote.name_len);
> + if (remote->name == NULL) {
> + err = got_error_from_errno("strndup");
> + break;
> + }
> + remote->url = strndup(imsg.data + sizeof(iremote) +
> + iremote.name_len, iremote.url_len);
> + if (remote->url == NULL) {
> + err = got_error_from_errno("strndup");
> + free(remote->name);
> + break;
> + }
> + remote->mirror_references = iremote.mirror_references;
> + (*nremotes)++;
> + break;
> + default:
> + err = got_error(GOT_ERR_PRIVSEP_MSG);
> + break;
> + }
> +
> + imsg_free(&imsg);
> + if (err)
> + break;
> + }
> +
> + if (err) {
> + int i;
> + for (i = 0; i < *nremotes; i++) {
> + free((*remotes)[i].name);
> + free((*remotes)[i].url);
> + }
> + free(*remotes);
> + *remotes = NULL;
> + *nremotes = 0;
> + }
> + return err;
> +}
> +
> +const struct got_error *
> got_privsep_send_commit_traversal_request(struct imsgbuf *ibuf,
> struct got_object_id *id, int idx, const char *path)
> {
> @@ -2008,6 +2210,7 @@ got_privsep_unveil_exec_helpers(void)
> GOT_PATH_PROG_READ_BLOB,
> GOT_PATH_PROG_READ_TAG,
> GOT_PATH_PROG_READ_GITCONFIG,
> + GOT_PATH_PROG_READ_GOTCONFIG,
> GOT_PATH_PROG_FETCH_PACK,
> GOT_PATH_PROG_INDEX_PACK,
> };
> blob - b8408a749c9e5ff2c1983eb5d006ed171fdfb6e8
> blob + 794725948774f714efc8876112a3a0da332e47be
> --- lib/repository.c
> +++ lib/repository.c
> @@ -159,6 +159,12 @@ got_repo_get_path_gitconfig(struct got_repository *rep
> return get_path_git_child(repo, GOT_GITCONFIG);
> }
>
> +char *
> +got_repo_get_path_gotconfig(struct got_repository *repo)
> +{
> + return get_path_git_child(repo, GOT_GOTCONFIG);
> +}
> +
> void
> got_repo_get_gitconfig_remotes(int *nremotes, struct got_remote_repo **remotes,
> struct got_repository *repo)
> @@ -167,6 +173,20 @@ got_repo_get_gitconfig_remotes(int *nremotes, struct g
> *remotes = repo->gitconfig_remotes;
> }
>
> +const char *
> +got_repo_get_gotconfig_author(struct got_repository *repo)
> +{
> + return repo->gotconfig_author;
> +}
> +
> +void
> +got_repo_get_gotconfig_remotes(int *nremotes, struct got_remote_repo **remotes,
> + struct got_repository *repo)
> +{
> + *nremotes = repo->ngotconfig_remotes;
> + *remotes = repo->gotconfig_remotes;
> +}
> +
> static int
> is_git_repo(struct got_repository *repo)
> {
> @@ -516,6 +536,124 @@ done:
> return err;
> }
>
> +static const struct got_error *
> +parse_gotconfig_file(char **author,
> + struct got_remote_repo **remotes, int *nremotes,
> + const char *gotconfig_path)
> +{
> + const struct got_error *err = NULL, *child_err = NULL;
> + int fd = -1;
> + int imsg_fds[2] = { -1, -1 };
> + pid_t pid;
> + struct imsgbuf *ibuf;
> +
> + if (author)
> + *author = NULL;
> + if (remotes)
> + *remotes = NULL;
> + if (nremotes)
> + *nremotes = 0;
> +
> + fd = open(gotconfig_path, O_RDONLY);
> + if (fd == -1) {
> + if (errno == ENOENT)
> + return NULL;
> + return got_error_from_errno2("open", gotconfig_path);
> + }
> +
> + ibuf = calloc(1, sizeof(*ibuf));
> + if (ibuf == NULL) {
> + err = got_error_from_errno("calloc");
> + goto done;
> + }
> +
> + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
> + err = got_error_from_errno("socketpair");
> + goto done;
> + }
> +
> + pid = fork();
> + if (pid == -1) {
> + err = got_error_from_errno("fork");
> + goto done;
> + } else if (pid == 0) {
> + got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_GOTCONFIG,
> + gotconfig_path);
> + /* not reached */
> + }
> +
> + if (close(imsg_fds[1]) == -1) {
> + err = got_error_from_errno("close");
> + goto done;
> + }
> + imsg_fds[1] = -1;
> + imsg_init(ibuf, imsg_fds[0]);
> +
> + err = got_privsep_send_gotconfig_parse_req(ibuf, fd);
> + if (err)
> + goto done;
> + fd = -1;
> +
> + if (author) {
> + err = got_privsep_send_gotconfig_author_req(ibuf);
> + if (err)
> + goto done;
> +
> + err = got_privsep_recv_gotconfig_str(author, ibuf);
> + if (err)
> + goto done;
> + }
> +
> + if (remotes && nremotes) {
> + err = got_privsep_send_gotconfig_remotes_req(ibuf);
> + if (err)
> + goto done;
> +
> + err = got_privsep_recv_gotconfig_remotes(remotes,
> + nremotes, ibuf);
> + if (err)
> + goto done;
> + }
> +
> + imsg_clear(ibuf);
> + err = got_privsep_send_stop(imsg_fds[0]);
> + child_err = got_privsep_wait_for_child(pid);
> + if (child_err && err == NULL)
> + err = child_err;
> +done:
> + if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
> + err = got_error_from_errno("close");
> + if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
> + err = got_error_from_errno("close");
> + if (fd != -1 && close(fd) == -1 && err == NULL)
> + err = got_error_from_errno2("close", gotconfig_path);
> + if (err) {
> + if (author) {
> + free(*author);
> + *author = NULL;
> + }
> + }
> + free(ibuf);
> + return err;
> +}
> +
> +static const struct got_error *
> +read_gotconfig(struct got_repository *repo)
> +{
> + const struct got_error *err = NULL;
> + char *gotconfig_path;
> +
> + gotconfig_path = got_repo_get_path_gotconfig(repo);
> + if (gotconfig_path == NULL)
> + return got_error_from_errno("got_repo_get_path_gotconfig");
> +
> + err = parse_gotconfig_file(&repo->gotconfig_author,
> + &repo->gotconfig_remotes, &repo->ngotconfig_remotes,
> + gotconfig_path);
> + free(gotconfig_path);
> + return err;
> +}
> +
> const struct got_error *
> got_repo_open(struct got_repository **repop, const char *path,
> const char *global_gitconfig_path)
> @@ -589,6 +727,10 @@ got_repo_open(struct got_repository **repop, const cha
> }
> } while (path);
>
> + err = read_gotconfig(repo);
> + if (err)
> + goto done;
> +
> err = read_gitconfig(repo, global_gitconfig_path);
> if (err)
> goto done;
> @@ -644,6 +786,12 @@ got_repo_close(struct got_repository *repo)
> err = got_error_from_errno("close");
> }
>
> + free(repo->gotconfig_author);
> + for (i = 0; i < repo->ngotconfig_remotes; i++) {
> + free(repo->gotconfig_remotes[i].name);
> + free(repo->gotconfig_remotes[i].url);
> + }
> + free(repo->gotconfig_remotes);
> free(repo->gitconfig_author_name);
> free(repo->gitconfig_author_email);
> for (i = 0; i < repo->ngitconfig_remotes; i++) {
> blob - 41c5a86903979f5cc3495303157b082ea384ee7f
> blob + 1e55c9808beb7f0984445d4aae36eaddd0ceb5d4
> --- libexec/Makefile
> +++ libexec/Makefile
> @@ -1,5 +1,5 @@
> SUBDIR = got-read-blob got-read-commit got-read-object got-read-tree \
> got-read-tag got-fetch-pack got-index-pack got-read-pack \
> - got-read-gitconfig
> + got-read-gitconfig got-read-gotconfig
>
> .include <bsd.subdir.mk>
> blob - /dev/null
> blob + a683cf2ee1b6cf0753a4a7f2b006bfc29f853a8c (mode 644)
> --- /dev/null
> +++ libexec/got-read-gotconfig/Makefile
> @@ -0,0 +1,13 @@
> +.PATH:${.CURDIR}/../../lib
> +
> +.include "../../got-version.mk"
> +
> +PROG= got-read-gotconfig
> +SRCS= got-read-gotconfig.c error.c inflate.c object_parse.c \
> + path.c privsep.c sha1.c parse.y
> +
> +CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib -I${.CURDIR}
> +LDADD = -lutil -lz
> +DPADD = ${LIBZ} ${LIBUTIL}
> +
> +.include <bsd.prog.mk>
> blob - /dev/null
> blob + ff7e52b3c971f60ef8eea16eaa9fb51f78f337bb (mode 644)
> --- /dev/null
> +++ libexec/got-read-gotconfig/got-read-gotconfig.c
> @@ -0,0 +1,358 @@
> +/*
> + * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +#include <sys/types.h>
> +#include <sys/queue.h>
> +#include <sys/uio.h>
> +#include <sys/time.h>
> +#include <sys/syslimits.h>
> +
> +#include <stdint.h>
> +#include <imsg.h>
> +#include <limits.h>
> +#include <signal.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sha1.h>
> +#include <zlib.h>
> +
> +#include "got_error.h"
> +#include "got_object.h"
> +#include "got_repository.h"
> +
> +#include "got_lib_delta.h"
> +#include "got_lib_object.h"
> +#include "got_lib_privsep.h"
> +
> +#include "gotconfig.h"
> +
> +/* parse.y */
> +static volatile sig_atomic_t sigint_received;
> +
> +static void
> +catch_sigint(int signo)
> +{
> + sigint_received = 1;
> +}
> +
> +static const struct got_error *
> +make_repo_url(char **url, struct gotconfig_remote_repo *repo)
> +{
> + const struct got_error *err = NULL;
> + char *s = NULL, *p = NULL;
> +
> + *url = NULL;
> +
> + if (asprintf(&s, "%s://", repo->protocol) == -1)
> + return got_error_from_errno("asprintf");
> +
> + if (repo->server) {
> + p = s;
> + s = NULL;
> + if (asprintf(&s, "%s%s", p, repo->server) == -1) {
> + err = got_error_from_errno("asprintf");
> + goto done;
> + }
> + free(p);
> + p = NULL;
> + }
> +
> + if (repo->port) {
> + p = s;
> + s = NULL;
> + if (asprintf(&s, "%s:%d", p, repo->port) == -1) {
> + err = got_error_from_errno("asprintf");
> + goto done;
> + }
> + free(p);
> + p = NULL;
> + }
> +
> + if (repo->repository) {
> + p = s;
> + s = NULL;
> + if (asprintf(&s, "%s/%s", p, repo->repository) == -1) {
> + err = got_error_from_errno("asprintf");
> + goto done;
> + }
> + free(p);
> + p = NULL;
> + }
> +done:
> + if (err) {
> + free(s);
> + free(p);
> + } else
> + *url = s;
> + return err;
> +}
> +
> +static const struct got_error *
> +send_gotconfig_str(struct imsgbuf *ibuf, const char *value)
> +{
> + size_t len = value ? strlen(value) + 1 : 0;
> +
> + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_STR_VAL, 0, 0, -1,
> + value, len) == -1)
> + return got_error_from_errno("imsg_compose GOTCONFIG_STR_VAL");
> +
> + return got_privsep_flush_imsg(ibuf);
> +}
> +
> +static const struct got_error *
> +send_gotconfig_remotes(struct imsgbuf *ibuf,
> + struct gotconfig_remote_repo_list *remotes, int nremotes)
> +{
> + const struct got_error *err = NULL;
> + struct got_imsg_remotes iremotes;
> + struct gotconfig_remote_repo *repo;
> + char *url = NULL;
> +
> + iremotes.nremotes = nremotes;
> + if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_REMOTES, 0, 0, -1,
> + &iremotes, sizeof(iremotes)) == -1)
> + return got_error_from_errno("imsg_compose GOTCONFIG_REMOTES");
> +
> + err = got_privsep_flush_imsg(ibuf);
> + imsg_clear(ibuf);
> + if (err)
> + return err;
> +
> + TAILQ_FOREACH(repo, remotes, entry) {
> + struct got_imsg_remote iremote;
> + size_t len = sizeof(iremote);
> + struct ibuf *wbuf;
> +
> + iremote.mirror_references = repo->mirror_references;
> +
> + iremote.name_len = strlen(repo->name);
> + len += iremote.name_len;
> +
> + err = make_repo_url(&url, repo);
> + if (err)
> + break;
> + iremote.url_len = strlen(url);
> + len += iremote.url_len;
> +
> + wbuf = imsg_create(ibuf, GOT_IMSG_GOTCONFIG_REMOTE, 0, 0, len);
> + if (wbuf == NULL) {
> + err = got_error_from_errno(
> + "imsg_create GOTCONFIG_REMOTE");
> + break;
> + }
> +
> + if (imsg_add(wbuf, &iremote, sizeof(iremote)) == -1) {
> + err = got_error_from_errno(
> + "imsg_add GOTCONFIG_REMOTE");
> + ibuf_free(wbuf);
> + break;
> + }
> +
> + if (imsg_add(wbuf, repo->name, iremote.name_len) == -1) {
> + err = got_error_from_errno(
> + "imsg_add GOTCONFIG_REMOTE");
> + ibuf_free(wbuf);
> + break;
> + }
> + if (imsg_add(wbuf, url, iremote.url_len) == -1) {
> + err = got_error_from_errno(
> + "imsg_add GOTCONFIG_REMOTE");
> + ibuf_free(wbuf);
> + break;
> + }
> +
> + wbuf->fd = -1;
> + imsg_close(ibuf, wbuf);
> + err = got_privsep_flush_imsg(ibuf);
> + if (err)
> + break;
> +
> + free(url);
> + url = NULL;
> + }
> +
> + free(url);
> + return err;
> +}
> +
> +static const struct got_error *
> +validate_config(struct gotconfig *gotconfig)
> +{
> + struct gotconfig_remote_repo *repo, *repo2;
> + static char msg[512];
> +
> + TAILQ_FOREACH(repo, &gotconfig->remotes, entry) {
> + if (repo->name == NULL) {
> + return got_error_msg(GOT_ERR_PARSE_CONFIG,
> + "name required for remote repository");
> + }
> +
> + TAILQ_FOREACH(repo2, &gotconfig->remotes, entry) {
> + if (repo == repo2 ||
> + strcmp(repo->name, repo2->name) != 0)
> + continue;
> + snprintf(msg, sizeof(msg),
> + "duplicate remote repository name '%s'",
> + repo->name);
> + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);
> + }
> +
> + if (repo->server == NULL) {
> + snprintf(msg, sizeof(msg),
> + "server required for remote repository \"%s\"",
> + repo->name);
> + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);
> + }
> +
> + if (repo->protocol == NULL) {
> + snprintf(msg, sizeof(msg),
> + "protocol required for remote repository \"%s\"",
> + repo->name);
> + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);
> + }
> + if (strcmp(repo->protocol, "ssh") != 0 &&
> + strcmp(repo->protocol, "git+ssh") != 0 &&
> + strcmp(repo->protocol, "git") != 0) {
> + snprintf(msg, sizeof(msg),"unknown protocol \"%s\" "
> + "for remote repository \"%s\"", repo->protocol,
> + repo->name);
> + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);
> + }
> +
> + if (repo->repository == NULL) {
> + snprintf(msg, sizeof(msg),
> + "repository path required for remote "
> + "repository \"%s\"", repo->name);
> + return got_error_msg(GOT_ERR_PARSE_CONFIG, msg);
> + }
> + }
extra tab
> +
> + return NULL;
> +}
> +
> +int
> +main(int argc, char *argv[])
> +{
> + const struct got_error *err = NULL;
> + struct imsgbuf ibuf;
> + struct gotconfig *gotconfig;
> + size_t datalen;
> + const char *filename = "got.conf";
> +#if 0
> + static int attached;
> +
> + while (!attached)
> + sleep(1);
> +#endif
> + signal(SIGINT, catch_sigint);
> +
> + imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
> +
> +#ifndef PROFILE
> + /* revoke access to most system calls */
> + if (pledge("stdio recvfd", NULL) == -1) {
> + err = got_error_from_errno("pledge");
> + got_privsep_send_error(&ibuf, err);
> + return 1;
> + }
> +#endif
> +
> + if (argc > 1)
> + filename = argv[1];
> +
> + for (;;) {
> + struct imsg imsg;
> +
> + memset(&imsg, 0, sizeof(imsg));
> + imsg.fd = -1;
> +
> + if (sigint_received) {
> + err = got_error(GOT_ERR_CANCELLED);
> + break;
> + }
> +
> + err = got_privsep_recv_imsg(&imsg, &ibuf, 0);
> + if (err) {
> + if (err->code == GOT_ERR_PRIVSEP_PIPE)
> + err = NULL;
> + break;
> + }
> +
> + if (imsg.hdr.type == GOT_IMSG_STOP)
> + break;
> +
> + switch (imsg.hdr.type) {
> + case GOT_IMSG_GOTCONFIG_PARSE_REQUEST:
> + datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
> + if (datalen != 0) {
> + err = got_error(GOT_ERR_PRIVSEP_LEN);
> + break;
> + }
> + if (imsg.fd == -1){
> + err = got_error(GOT_ERR_PRIVSEP_NO_FD);
> + break;
> + }
> +
> + if (gotconfig)
> + gotconfig_free(gotconfig);
> + err = gotconfig_parse(&gotconfig, filename, &imsg.fd);
> + if (err)
> + break;
> + err = validate_config(gotconfig);
> + break;
> + case GOT_IMSG_GOTCONFIG_AUTHOR_REQUEST:
> + if (gotconfig == NULL) {
> + err = got_error(GOT_ERR_PRIVSEP_MSG);
> + break;
> + }
> + err = send_gotconfig_str(&ibuf,
> + gotconfig->author ? gotconfig->author : "");
> + break;
> + case GOT_IMSG_GOTCONFIG_REMOTES_REQUEST:
> + if (gotconfig == NULL) {
> + err = got_error(GOT_ERR_PRIVSEP_MSG);
> + break;
> + }
> + err = send_gotconfig_remotes(&ibuf,
> + &gotconfig->remotes, gotconfig->nremotes);
> + break;
> + default:
> + err = got_error(GOT_ERR_PRIVSEP_MSG);
> + break;
> + }
> +
> + if (imsg.fd != -1) {
> + if (close(imsg.fd) == -1 && err == NULL)
> + err = got_error_from_errno("close");
> + }
> +
> + imsg_free(&imsg);
> + if (err)
> + break;
> + }
> +
> + imsg_clear(&ibuf);
> + if (err) {
> + if (!sigint_received && err->code != GOT_ERR_PRIVSEP_PIPE) {
> + fprintf(stderr, "%s: %s\n", getprogname(), err->msg);
> + got_privsep_send_error(&ibuf, err);
> + }
> + }
> + if (close(GOT_IMSG_FD_CHILD) != 0 && err == NULL)
> + err = got_error_from_errno("close");
> + return err ? 1 : 0;
> +}
> blob - /dev/null
> blob + ab55bd31f17ddbcdf0a483b21903f7e00a588c20 (mode 644)
> --- /dev/null
> +++ libexec/got-read-gotconfig/gotconfig.h
> @@ -0,0 +1,40 @@
> +/*
> + * Copyright (c) 2020 Tracey Emery <tracey@openbsd.org>
> + * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +struct gotconfig_remote_repo {
> + TAILQ_ENTRY(gotconfig_remote_repo) entry;
> + char *name;
> + char *repository;
> + char *server;
> + char *protocol;
> + int port;
> + int mirror_references;
> +};
> +TAILQ_HEAD(gotconfig_remote_repo_list, gotconfig_remote_repo);
> +
> +struct gotconfig {
> + char *author;
> + struct gotconfig_remote_repo_list remotes;
> + int nremotes;
> +};
> +
> +/*
> + * Parse individual gotconfig repository files
> + */
> +const struct got_error *gotconfig_parse(struct gotconfig **, const char *,
> + int *);
> +void gotconfig_free(struct gotconfig *);
> blob - /dev/null
> blob + ea6e5db706853103c807b6c22841c8aba8770b71 (mode 644)
> --- /dev/null
> +++ libexec/got-read-gotconfig/parse.y
> @@ -0,0 +1,762 @@
> +/*
> + * Copyright (c) 2020 Tracey Emery <tracey@openbsd.org>
> + * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
> + * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
> + * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
> + * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
> + * Copyright (c) 2001 Markus Friedl. All rights reserved.
> + * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
> + * Copyright (c) 2001 Theo de Raadt. All rights reserved.
> + *
> + * Permission to use, copy, modify, and distribute this software for any
> + * purpose with or without fee is hereby granted, provided that the above
> + * copyright notice and this permission notice appear in all copies.
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
> + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
> + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
> + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
> + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
> + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
> + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
> + */
> +
> +%{
> +#include <sys/types.h>
> +#include <sys/queue.h>
> +#include <sys/socket.h>
> +#include <sys/stat.h>
> +
> +#include <netinet/in.h>
> +
> +#include <arpa/inet.h>
> +
> +#include <netdb.h>
> +
> +#include <ctype.h>
> +#include <err.h>
> +#include <errno.h>
> +#include <event.h>
> +#include <ifaddrs.h>
> +#include <imsg.h>
> +#include <limits.h>
> +#include <stdarg.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <syslog.h>
> +#include <unistd.h>
> +
> +#include "got_error.h"
> +#include "gotconfig.h"
> +
> +static struct file {
> + FILE *stream;
> + const char *name;
> + size_t ungetpos;
> + size_t ungetsize;
> + u_char *ungetbuf;
> + int eof_reached;
> + int lineno;
> +} *file;
> +static const struct got_error* newfile(struct file**, const char *, int *);
> +static void closefile(struct file *);
> +int yyparse(void);
> +int yylex(void);
> +int yyerror(const char *, ...)
> + __attribute__((__format__ (printf, 1, 2)))
> + __attribute__((__nonnull__ (1)));
> +int kw_cmp(const void *, const void *);
> +int lookup(char *);
> +int igetc(void);
> +int lgetc(int);
> +void lungetc(int);
> +int findeol(void);
> +static int parseport(char *, long long *);
> +
> +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead);
> +struct sym {
> + TAILQ_ENTRY(sym) entry;
> + int used;
> + int persist;
> + char *nam;
> + char *val;
> +};
> +
> +int symset(const char *, const char *, int);
> +char *symget(const char *);
> +
> +static int atoul(char *, u_long *);
> +
> +static const struct got_error* gerror;
> +static struct gotconfig_remote_repo *remote;
> +static struct gotconfig gotconfig;
> +static const struct got_error* new_remote(struct gotconfig_remote_repo **);
> +
> +typedef struct {
> + union {
> + int64_t number;
> + char *string;
> + } v;
> + int lineno;
> +} YYSTYPE;
> +
> +%}
> +
> +%token ERROR
> +%token REMOTE REPOSITORY SERVER PORT PROTOCOL MIRROR_REFERENCES AUTHOR
> +%token <v.string> STRING
> +%token <v.number> NUMBER
> +%type <v.number> boolean portplain
> +%type <v.string> numberstring
> +
> +%%
> +
> +grammar : /* empty */
> + | grammar '\n'
> + | grammar author '\n'
> + | grammar remote '\n'
> + ;
> +boolean : STRING {
> + if (strcasecmp($1, "true") == 0 ||
> + strcasecmp($1, "yes") == 0)
> + $$ = 1;
> + else if (strcasecmp($1, "false") == 0 ||
> + strcasecmp($1, "no") == 0)
> + $$ = 0;
> + else {
> + yyerror("invalid boolean value '%s'", $1);
> + free($1);
> + YYERROR;
> + }
> + free($1);
> + }
> + ;
> +numberstring : NUMBER {
> + char *s;
> + if (asprintf(&s, "%lld", $1) == -1) {
> + yyerror("string: asprintf");
> + YYERROR;
> + }
> + $$ = s;
> + }
> + | STRING
> + ;
> +portplain : numberstring {
> + if (parseport($1, &$$) == -1) {
> + free($1);
> + YYERROR;
> + }
> + free($1);
> + }
> + ;
> +remoteopts2 : remoteopts2 remoteopts1 nl
> + | remoteopts1 optnl
4x spaces
> + ;
> +remoteopts1 : REPOSITORY STRING {
> + remote->repository = strdup($2);
4x spaces
> + if (remote->repository == NULL) {
> + free($2);
> + yyerror("strdup");
> + YYERROR;
> + }
> + free($2);
> + }
4x spaces
> + | SERVER STRING {
4x spaces
> + remote->server = strdup($2);
4x spaces
> + if (remote->server == NULL) {
> + free($2);
> + yyerror("strdup");
> + YYERROR;
> + }
> + free($2);
> + }
> + | PROTOCOL STRING {
> + remote->protocol = strdup($2);
4x spaces
> + if (remote->protocol == NULL) {
> + free($2);
> + yyerror("strdup");
> + YYERROR;
> + }
> + free($2);
> + }
> + | MIRROR_REFERENCES boolean {
> + remote->mirror_references = $2;
> + }
> + | PORT portplain {
> + remote->port = $2;
> + }
> + ;
4x spaces
> +remote : REMOTE STRING {
> + static const struct got_error* error;
> +
> + error = new_remote(&remote);
> + if (error) {
> + free($2);
> + yyerror("%s", error->msg);
> + YYERROR;
> + }
> + remote->name = strdup($2);
> + if (remote->name == NULL) {
> + free($2);
> + yyerror("strdup");
> + YYERROR;
> + }
> + free($2);
> + } '{' optnl remoteopts2 '}' {
> + TAILQ_INSERT_TAIL(&gotconfig.remotes, remote, entry);
> + gotconfig.nremotes++;
> + }
> + ;
> +author : AUTHOR STRING {
> + gotconfig.author = strdup($2);
4x spaces
> + if (gotconfig.author == NULL) {
> + free($2);
> + yyerror("strdup");
> + YYERROR;
> + }
> + free($2);
> + }
> + ;
> +optnl : '\n' optnl
> + | /* empty */
> + ;
> +nl : '\n' optnl
> + ;
> +%%
> +
> +struct keywords {
> + const char *k_name;
> + int k_val;
> +};
> +
> +int
> +yyerror(const char *fmt, ...)
> +{
> + va_list ap;
> + char *msg;
> + char *err = NULL;
> +
> + va_start(ap, fmt);
> + if (vasprintf(&msg, fmt, ap) == -1) {
> + gerror = got_error_from_errno("vasprintf");
> + return 0;
> + }
> + va_end(ap);
> + if (asprintf(&err, "%s: line %d: %s", file->name, yylval.lineno,
> + msg) == -1) {
> + gerror = got_error_from_errno("asprintf");
> + return(0);
> + }
> + gerror = got_error_msg(GOT_ERR_PARSE_CONFIG, strdup(err));
> + free(msg);
> + free(err);
> + return(0);
> +}
> +int
> +kw_cmp(const void *k, const void *e)
> +{
> + return (strcmp(k, ((const struct keywords *)e)->k_name));
> +}
> +
> +int
> +lookup(char *s)
> +{
> + /* This has to be sorted always. */
> + static const struct keywords keywords[] = {
> + {"author", AUTHOR},
> + {"mirror-references", MIRROR_REFERENCES},
> + {"port", PORT},
> + {"protocol", PROTOCOL},
> + {"remote", REMOTE},
> + {"repository", REPOSITORY},
> + {"server", SERVER},
> + };
> + const struct keywords *p;
> +
> + p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
> + sizeof(keywords[0]), kw_cmp);
> +
> + if (p)
> + return (p->k_val);
> + else
> + return (STRING);
> +}
> +
> +#define START_EXPAND 1
> +#define DONE_EXPAND 2
> +
> +static int expanding;
> +
> +int
> +igetc(void)
> +{
> + int c;
> +
> + while (1) {
> + if (file->ungetpos > 0)
> + c = file->ungetbuf[--file->ungetpos];
> + else
> + c = getc(file->stream);
> +
> + if (c == START_EXPAND)
> + expanding = 1;
> + else if (c == DONE_EXPAND)
> + expanding = 0;
> + else
> + break;
> + }
> + return (c);
> +}
> +
> +int
> +lgetc(int quotec)
> +{
> + int c, next;
> +
> + if (quotec) {
> + c = igetc();
> + if (c == EOF) {
> + yyerror("reached end of file while parsing "
> + "quoted string");
> + }
> + return (c);
> + }
> +
> + c = igetc();
> + while (c == '\\') {
> + next = igetc();
> + if (next != '\n') {
> + c = next;
> + break;
> + }
> + yylval.lineno = file->lineno;
> + file->lineno++;
> + }
> +
> + return (c);
> +}
> +
> +void
> +lungetc(int c)
> +{
> + if (c == EOF)
> + return;
> +
> + if (file->ungetpos >= file->ungetsize) {
> + void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
> + if (p == NULL)
> + err(1, "%s", __func__);
> + file->ungetbuf = p;
> + file->ungetsize *= 2;
> + }
> + file->ungetbuf[file->ungetpos++] = c;
> +}
> +
> +int
> +findeol(void)
> +{
> + int c;
> +
> + /* Skip to either EOF or the first real EOL. */
> + while (1) {
> + c = lgetc(0);
> + if (c == '\n') {
> + file->lineno++;
> + break;
> + }
> + if (c == EOF)
> + break;
> + }
> + return (ERROR);
> +}
> +
> +static long long
> +getservice(char *n)
> +{
> + struct servent *s;
> + u_long ulval;
> +
> + if (atoul(n, &ulval) == 0) {
> + if (ulval > 65535) {
> + yyerror("illegal port value %lu", ulval);
> + return (-1);
> + }
> + return ulval;
> + } else {
> + s = getservbyname(n, "tcp");
> + if (s == NULL)
> + s = getservbyname(n, "udp");
> + if (s == NULL) {
> + yyerror("unknown port %s", n);
> + return (-1);
> + }
> + return (s->s_port);
> + }
> +}
> +
> +static int
> +parseport(char *port, long long *pn)
> +{
> + if ((*pn = getservice(port)) == -1) {
> + *pn = 0LL;
> + return (-1);
> + }
> + return (0);
> +}
> +
> +
> +int
> +yylex(void)
> +{
> + unsigned char buf[8096];
> + unsigned char *p, *val;
> + int quotec, next, c;
> + int token;
> +
> +top:
> + p = buf;
> + c = lgetc(0);
> + while (c == ' ' || c == '\t')
> + c = lgetc(0); /* nothing */
> +
> + yylval.lineno = file->lineno;
> + if (c == '#') {
> + c = lgetc(0);
> + while (c != '\n' && c != EOF)
> + c = lgetc(0); /* nothing */
> + }
> + if (c == '$' && !expanding) {
> + while (1) {
> + c = lgetc(0);
> + if (c == EOF)
> + return (0);
> +
> + if (p + 1 >= buf + sizeof(buf) - 1) {
> + yyerror("string too long");
> + return (findeol());
> + }
> + if (isalnum(c) || c == '_') {
> + *p++ = c;
> + continue;
> + }
> + *p = '\0';
> + lungetc(c);
> + break;
> + }
> + val = symget(buf);
> + if (val == NULL) {
> + yyerror("macro '%s' not defined", buf);
> + return (findeol());
> + }
> + p = val + strlen(val) - 1;
> + lungetc(DONE_EXPAND);
> + while (p >= val) {
> + lungetc(*p);
> + p--;
> + }
> + lungetc(START_EXPAND);
> + goto top;
> + }
> +
> + switch (c) {
> + case '\'':
> + case '"':
> + quotec = c;
> + while (1) {
> + c = lgetc(quotec);
> + if (c == EOF)
> + return (0);
> + if (c == '\n') {
> + file->lineno++;
> + continue;
> + } else if (c == '\\') {
> + next = lgetc(quotec);
> + if (next == EOF)
> + return (0);
> + if (next == quotec || c == ' ' || c == '\t')
> + c = next;
> + else if (next == '\n') {
> + file->lineno++;
> + continue;
> + } else
> + lungetc(next);
> + } else if (c == quotec) {
> + *p = '\0';
> + break;
> + } else if (c == '\0') {
> + yyerror("syntax error");
> + return (findeol());
> + }
> + if (p + 1 >= buf + sizeof(buf) - 1) {
> + yyerror("string too long");
> + return (findeol());
> + }
> + *p++ = c;
> + }
> + yylval.v.string = strdup(buf);
> + if (yylval.v.string == NULL)
> + err(1, "%s", __func__);
> + return (STRING);
> + }
> +
> +#define allowed_to_end_number(x) \
> + (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
> +
> + if (c == '-' || isdigit(c)) {
> + do {
> + *p++ = c;
> + if ((unsigned)(p-buf) >= sizeof(buf)) {
> + yyerror("string too long");
> + return (findeol());
> + }
> + c = lgetc(0);
> + } while (c != EOF && isdigit(c));
> + lungetc(c);
> + if (p == buf + 1 && buf[0] == '-')
> + goto nodigits;
> + if (c == EOF || allowed_to_end_number(c)) {
> + const char *errstr = NULL;
> +
> + *p = '\0';
> + yylval.v.number = strtonum(buf, LLONG_MIN,
> + LLONG_MAX, &errstr);
> + if (errstr) {
> + yyerror("\"%s\" invalid number: %s",
> + buf, errstr);
> + return (findeol());
> + }
> + return (NUMBER);
> + } else {
> +nodigits:
> + while (p > buf + 1)
> + lungetc(*--p);
> + c = *--p;
> + if (c == '-')
> + return (c);
> + }
> + }
> +
> +#define allowed_in_string(x) \
> + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
> + x != '{' && x != '}' && \
> + x != '!' && x != '=' && x != '#' && \
> + x != ','))
> +
> + if (isalnum(c) || c == ':' || c == '_') {
> + do {
> + *p++ = c;
> + if ((unsigned)(p-buf) >= sizeof(buf)) {
> + yyerror("string too long");
> + return (findeol());
> + }
> + c = lgetc(0);
> + } while (c != EOF && (allowed_in_string(c)));
> + lungetc(c);
> + *p = '\0';
> + token = lookup(buf);
> + if (token == STRING) {
> + yylval.v.string = strdup(buf);
> + if (yylval.v.string == NULL)
> + err(1, "%s", __func__);
> + }
> + return (token);
> + }
> + if (c == '\n') {
> + yylval.lineno = file->lineno;
> + file->lineno++;
> + }
> + if (c == EOF)
> + return (0);
> + return (c);
> +}
> +
> +static const struct got_error*
> +newfile(struct file **nfile, const char *filename, int *fd)
> +{
> + const struct got_error* error = NULL;
> +
> + (*nfile) = calloc(1, sizeof(struct file));
> + if ((*nfile) == NULL)
> + return got_error_from_errno("calloc");
> + (*nfile)->stream = fdopen(*fd, "r");
> + if ((*nfile)->stream == NULL) {
> + error = got_error_from_errno("fdopen");
> + free((*nfile));
> + return error;
> + }
> + *fd = -1; /* Stream owns the file descriptor now. */
> + (*nfile)->name = filename;
> + (*nfile)->lineno = 1;
> + (*nfile)->ungetsize = 16;
> + (*nfile)->ungetbuf = malloc((*nfile)->ungetsize);
> + if ((*nfile)->ungetbuf == NULL) {
> + error = got_error_from_errno("malloc");
> + fclose((*nfile)->stream);
> + free((*nfile));
> + return error;
> + }
> + return NULL;
> +}
> +
> +static const struct got_error*
> +new_remote(struct gotconfig_remote_repo **remote)
> +{
> + const struct got_error *error = NULL;
> +
> + *remote = calloc(1, sizeof(**remote));
> + if (*remote == NULL)
> + error = got_error_from_errno("calloc");
> + return error;
> +}
> +
> +static void
> +closefile(struct file *file)
> +{
> + fclose(file->stream);
> + free(file->ungetbuf);
> + free(file);
> +}
> +
> +const struct got_error *
> +gotconfig_parse(struct gotconfig **conf, const char *filename, int *fd)
> +{
> + const struct got_error *err = NULL;
> + struct sym *sym, *next;
> +
> + *conf = NULL;
> +
> + err = newfile(&file, filename, fd);
> + if (err)
> + return err;
> +
> + TAILQ_INIT(&gotconfig.remotes);
> +
> + yyparse();
> + closefile(file);
> +
> + /* Free macros and check which have not been used. */
> + TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
> + if (!sym->persist) {
> + free(sym->nam);
> + free(sym->val);
> + TAILQ_REMOVE(&symhead, sym, entry);
> + free(sym);
> + }
> + }
> +
> + if (gerror == NULL)
> + *conf = &gotconfig;
> + return gerror;
> +}
> +
> +void
> +gotconfig_free(struct gotconfig *conf)
> +{
> + struct gotconfig_remote_repo *remote;
> +
> + free(conf->author);
> + while (!TAILQ_EMPTY(&conf->remotes)) {
> + remote = TAILQ_FIRST(&conf->remotes);
> + TAILQ_REMOVE(&conf->remotes, remote, entry);
> + free(remote->name);
> + free(remote->repository);
> + free(remote->server);
> + free(remote->protocol);
> + free(remote);
> + }
> +}
> +
> +int
> +symset(const char *nam, const char *val, int persist)
> +{
> + struct sym *sym;
> +
> + TAILQ_FOREACH(sym, &symhead, entry) {
> + if (strcmp(nam, sym->nam) == 0)
> + break;
> + }
> +
> + if (sym != NULL) {
> + if (sym->persist == 1)
> + return (0);
> + else {
> + free(sym->nam);
> + free(sym->val);
> + TAILQ_REMOVE(&symhead, sym, entry);
> + free(sym);
> + }
> + }
> + sym = calloc(1, sizeof(*sym));
> + if (sym == NULL)
> + return (-1);
> +
> + sym->nam = strdup(nam);
> + if (sym->nam == NULL) {
> + free(sym);
> + return (-1);
> + }
> + sym->val = strdup(val);
> + if (sym->val == NULL) {
> + free(sym->nam);
> + free(sym);
> + return (-1);
> + }
> + sym->used = 0;
> + sym->persist = persist;
> + TAILQ_INSERT_TAIL(&symhead, sym, entry);
> + return (0);
> +}
> +
> +int
> +cmdline_symset(char *s)
> +{
> + char *sym, *val;
> + int ret;
> + size_t len;
> +
> + val = strrchr(s, '=');
> + if (val == NULL)
> + return (-1);
> +
> + len = strlen(s) - strlen(val) + 1;
> + sym = malloc(len);
> + if (sym == NULL)
> + errx(1, "cmdline_symset: malloc");
> +
> + strlcpy(sym, s, len);
> +
> + ret = symset(sym, val + 1, 1);
> + free(sym);
> +
> + return (ret);
> +}
> +
> +char *
> +symget(const char *nam)
> +{
> + struct sym *sym;
> +
> + TAILQ_FOREACH(sym, &symhead, entry) {
> + if (strcmp(nam, sym->nam) == 0) {
> + sym->used = 1;
> + return (sym->val);
> + }
> + }
> + return (NULL);
> +}
> +
> +static int
> +atoul(char *s, u_long *ulvalp)
> +{
> + u_long ulval;
> + char *ep;
> +
> + errno = 0;
> + ulval = strtoul(s, &ep, 0);
> + if (s[0] == '\0' || *ep != '\0')
> + return (-1);
> + if (errno == ERANGE && ulval == ULONG_MAX)
> + return (-1);
> + *ulvalp = ulval;
> + return (0);
> +}
> blob - 34abaa95cade02c6d2fc7fe82a0cae32a089e962
> blob + 13a4e58bcc3693cd4eb419089a8e29ea5e377524
> --- regress/cmdline/commit.sh
> +++ regress/cmdline/commit.sh
> @@ -699,6 +699,43 @@ function test_commit_tree_entry_sorting {
> test_done "$testroot" "$ret"
> }
>
> +function test_commit_gotconfig_author {
> + local testroot=`test_init commit_gotconfig_author`
> +
> + got checkout $testroot/repo $testroot/wt > /dev/null
> + ret="$?"
> + if [ "$ret" != "0" ]; then
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> + echo 'author "Flan Luck <flan_luck@openbsd.org>"' \
> + > $testroot/repo/.git/got.conf
> +
> + echo "modified alpha" > $testroot/wt/alpha
> + (cd $testroot/wt && got commit -m 'test gotconfig author' > /dev/null)
> + ret="$?"
> + if [ "$ret" != "0" ]; then
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> +
> + (cd $testroot/repo && got log -l1 | grep ^from: > $testroot/stdout)
> + ret="$?"
> + if [ "$ret" != "0" ]; then
> + test_done "$testroot" "$ret"
> + return 1
> + fi
> +
> + echo "from: Flan Luck <flan_luck@openbsd.org>" \
> + > $testroot/stdout.expected
> + cmp -s $testroot/stdout.expected $testroot/stdout
> + ret="$?"
> + if [ "$ret" != "0" ]; then
> + diff -u $testroot/stdout.expected $testroot/stdout
> + fi
> + test_done "$testroot" "$ret"
> +}
> +
> function test_commit_gitconfig_author {
> local testroot=`test_init commit_gitconfig_author`
>
> @@ -1297,6 +1334,7 @@ run_test test_commit_selected_paths
> run_test test_commit_outside_refs_heads
> run_test test_commit_no_email
> run_test test_commit_tree_entry_sorting
> +run_test test_commit_gotconfig_author
> run_test test_commit_gitconfig_author
> run_test test_commit_xbit_change
> run_test test_commit_normalizes_filemodes
--
Tracey Emery
add got.conf(5)