Download raw body.
add got.conf(5)
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?
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;
+ }
+ }
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));
+
+ 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);
+ }
+ }
+
+ 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
+ ;
+remoteopts1 : REPOSITORY STRING {
+ remote->repository = strdup($2);
+ if (remote->repository == NULL) {
+ free($2);
+ yyerror("strdup");
+ YYERROR;
+ }
+ free($2);
+ }
+ | SERVER STRING {
+ remote->server = strdup($2);
+ if (remote->server == NULL) {
+ free($2);
+ yyerror("strdup");
+ YYERROR;
+ }
+ free($2);
+ }
+ | PROTOCOL STRING {
+ remote->protocol = strdup($2);
+ if (remote->protocol == NULL) {
+ free($2);
+ yyerror("strdup");
+ YYERROR;
+ }
+ free($2);
+ }
+ | MIRROR_REFERENCES boolean {
+ remote->mirror_references = $2;
+ }
+ | PORT portplain {
+ remote->port = $2;
+ }
+ ;
+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);
+ 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
add got.conf(5)