commit - 78fb09675bee22e8ae24eb8bd9a86d8a04b17a7e
commit + 257add310e4b16ae43b467b91f66b773da39f470
blob - 19122e372a158d8a95216bb854924e9a25225a76
blob + fc2c1967ab5a100d7afd85ed536d8bec33d4c31f
--- got/got.1
+++ got/got.1
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
.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
+files of the cloned repository to store the
.Ar repository-url
for future use by
.Cm got fetch
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
.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 .
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
.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
+The
.Dv user.name
and
.Dv user.email
configuration settings contained in Git's global
.Pa ~/.gitconfig
-configuration file will be used only if the
+configuration file will only be used if neither
+.Xr got.conf 5
+nor the
.Ev GOT_AUTHOR
-environment variable is
-.Em not
-set.
+environment variable provide author information.
.It Ev VISUAL , EDITOR
The editor spawned by
.Cm got commit ,
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
.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 + 45468e19e41e08dc67438153100b3e1883d44e32
--- got/got.c
+++ got/got.c
*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);
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;
}
}
- /* 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");
}
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)
free(server_path);
free(repo_name);
free(default_destdir);
+ free(gotconfig);
+ free(gitconfig);
+ free(gotconfig_path);
free(gitconfig_path);
free(git_url);
return error;
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,
blob - /dev/null
blob + d6d11e44ab3797c4469bc5d41a882b8be638c18d (mode 644)
--- /dev/null
+++ got/got.conf.5
+.\"
+.\" 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
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
#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
{ 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
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).
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
#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
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 \
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. */
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
#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"
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 - 5c21a195ee0e8cf21767c0bf8ec2efb2c841e1d1
blob + 63a8b0da31d4514f9839334c64e7108fa41ab78b
--- lib/privsep.c
+++ lib/privsep.c
*nremotes = 0;
}
return err;
+}
+
+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)
{
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 - c2ee66c55abf30b586e88619a4a81d4be0c443bf
blob + b120196a4d400ea2d257c39efc48145d0508855e
--- lib/repository.c
+++ lib/repository.c
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)
*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)
{
goto done;
done:
free(repo_gitconfig_path);
+ 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;
}
}
} while (path);
+ err = read_gotconfig(repo);
+ if (err)
+ goto done;
+
err = read_gitconfig(repo, global_gitconfig_path);
if (err)
goto done;
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
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
+.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 + e3bcc200c6b5f1d8417e2926319744311bb40275 (mode 644)
--- /dev/null
+++ libexec/got-read-gotconfig/got-read-gotconfig.c
+/*
+ * 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
+/*
+ * 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 + 4bba6446d36f1aeb5246780345b255b6a26153e7 (mode 644)
--- /dev/null
+++ libexec/got-read-gotconfig/parse.y
+/*
+ * 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
# Let git-fsck verify the newly written tree to make sure Git is happy
(cd $testroot/repo && git fsck --strict \
> $testroot/fsck.stdout 2> $testroot/fsck.stderr)
+ ret="$?"
+ 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"
}
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