From: Tracey Emery Subject: Re: got import -> gotadmin import To: gameoftrees@openbsd.org Date: Wed, 6 Jul 2022 08:05:04 -0600 On Wed, Jul 06, 2022 at 08:58:44AM +0200, Stefan Sperling wrote: > This patch moves 'got import' into gotadmin. > > Having written the patch I am not quite sure if this is really > a good idea. The stated purpose of gotadmin is "inspecting and > manipulating the on-disk state of Git repositories". I am not > sure if creating new root commits (vendor branches) should be > part of this scope. Perhaps 'got import' is indeed the better > place for this functionality? > > We would also have to move some code from got.c into lib/ so it could > be shared with gotadmin, in particular for the creation of log messages. > This creates a lot of code churn. > > Does anyone very much prefer gotadmin import? Or should I drop the patch? I prefer both got init and got import, but have no strong opinion. It feels weird to me moving those to gotadmin. There are cases where I would never have a server-side component for several repos, if I remember to got init move argument. > > diff 230e1f1bfa1d7bbd0dee0217e22bc2817e848ad0 3866985167c3ce83469b78e6553bf975cab3abbb > commit - 230e1f1bfa1d7bbd0dee0217e22bc2817e848ad0 > commit + 3866985167c3ce83469b78e6553bf975cab3abbb > blob - 1b45b53a4efff9977dcd3c2e2e33c499adc94533 > blob + 9b8612199aff6a53330920d1cac9ef819e02703e > --- got/Makefile > +++ got/Makefile > @@ -13,7 +13,8 @@ SRCS= got.c blame.c commit_graph.c delta.c diff.c \ > diff_myers.c diff_output.c diff_output_plain.c \ > diff_output_unidiff.c diff_output_edscript.c \ > diff_patience.c send.c deltify.c pack_create.c dial.c \ > - bloom.c murmurhash2.c ratelimit.c patch.c sigs.c date.c > + bloom.c murmurhash2.c ratelimit.c patch.c sigs.c date.c \ > + logmsg.c > > MAN = ${PROG}.1 got-worktree.5 git-repository.5 got.conf.5 > > blob - 5c511f97ce933f13c1d706a8c3cc7ce271ca6cb5 > blob + 549e764508b315e8fc0e6c338daa2ae76e2d9f82 > --- got/got.1 > +++ got/got.1 > @@ -62,67 +62,6 @@ The commands for > .Nm > are as follows: > .Bl -tag -width checkout > -.Tg im > -.It Cm import Oo Fl b Ar branch Oc Oo Fl m Ar message Oc Oo Fl r Ar repository-path Oc Oo Fl I Ar pattern Oc Ar directory > -.Dl Pq alias: Cm im > -Create an initial commit in a repository from the file hierarchy > -within the specified > -.Ar directory . > -The created commit will not have any parent commits, i.e. it will be a > -root commit. > -Also create a new reference which provides a branch name for the newly > -created commit. > -Show the path of each imported file to indicate progress. > -.Pp > -The > -.Cm got import > -command requires the > -.Ev GOT_AUTHOR > -environment variable to be set, > -unless an author has been configured in > -.Xr got.conf 5 > -or Git's > -.Dv user.name > -and > -.Dv user.email > -configuration settings can be obtained from the repository's > -.Pa .git/config > -file or from Git's global > -.Pa ~/.gitconfig > -configuration file. > -.Pp > -The options for > -.Cm got import > -are as follows: > -.Bl -tag -width Ds > -.It Fl b Ar branch > -Create the specified > -.Ar branch > -instead of creating the default branch > -.Dq main . > -Use of this option is required if the > -.Dq main > -branch already exists. > -.It Fl m Ar message > -Use the specified log message when creating the new commit. > -Without the > -.Fl m > -option, > -.Cm got import > -opens a temporary file in an editor where a log message can be written. > -.It Fl r Ar repository-path > -Use the repository at the specified path. > -If not specified, assume the repository is located at or above the current > -working directory. > -.It Fl I Ar pattern > -Ignore files or directories with a name which matches the specified > -.Ar pattern . > -This option may be specified multiple times to build a list of ignore patterns. > -The > -.Ar pattern > -follows the globbing rules documented in > -.Xr glob 7 . > -.El > .Tg cl > .It Cm clone Oo Fl a Oc Oo Fl b Ar branch Oc Oo Fl l Oc Oo Fl m Oc Oo Fl q Oc Oo Fl v Oc Oo Fl R Ar reference Oc Ar repository-URL Op Ar directory > .Dl Pq alias: Cm cl > @@ -2354,7 +2293,7 @@ should be preferred over > However, even strictly linear projects may require merge commits in order > to merge in new versions of third-party code stored on vendor branches > created with > -.Cm got import . > +.Cm gotadmin import . > .Pp > Merge commits are commits based on multiple parent commits. > The tip commit of the work tree's current branch, which must be set with > @@ -2699,7 +2638,7 @@ for all tracked files. > The author's name and email address for > .Cm got commit > and > -.Cm got import , > +.Cm gotadmin import , > for example: > .Dq An Flan Hacker Aq Mt flan_hacker@openbsd.org . > Because > @@ -2734,7 +2673,7 @@ environment variable provide author information. > The editor spawned by > .Cm got commit , > .Cm got histedit , > -.Cm got import , > +.Cm gotadmin import , > or > .Cm got tag . > If not set, the > @@ -2803,7 +2742,7 @@ e.g. from a temporary CVS checkout located at > .Pa /tmp/src : > .Pp > .Dl $ gotadmin init /var/git/src.git > -.Dl $ got import -r /var/git/src.git -I CVS -I obj /tmp/src > +.Dl $ gotadmin import -r /var/git/src.git -I CVS -I obj /tmp/src > .Pp > Check out a work tree from the Git repository to /usr/src: > .Pp > blob - 77ee3290c2b299ba86499c5b196115d3f54e96b6 > blob + 80f9c5e10dd970222405707e8ead1d7bfe4aa971 > --- got/got.c > +++ got/got.c > @@ -61,6 +61,7 @@ > #include "got_patch.h" > #include "got_sigs.h" > #include "got_date.h" > +#include "got_logmsg.h" > > #ifndef nitems > #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) > @@ -90,7 +91,6 @@ struct got_cmd { > }; > > __dead static void usage(int, int); > -__dead static void usage_import(void); > __dead static void usage_clone(void); > __dead static void usage_fetch(void); > __dead static void usage_checkout(void); > @@ -120,7 +120,6 @@ __dead static void usage_unstage(void); > __dead static void usage_cat(void); > __dead static void usage_info(void); > > -static const struct got_error* cmd_import(int, char *[]); > static const struct got_error* cmd_clone(int, char *[]); > static const struct got_error* cmd_fetch(int, char *[]); > static const struct got_error* cmd_checkout(int, char *[]); > @@ -151,7 +150,6 @@ static const struct got_error* cmd_cat(int, char *[]) > static const struct got_error* cmd_info(int, char *[]); > > static const struct got_cmd got_commands[] = { > - { "import", cmd_import, usage_import, "im" }, > { "clone", cmd_clone, usage_clone, "cl" }, > { "fetch", cmd_fetch, usage_fetch, "fe" }, > { "checkout", cmd_checkout, usage_checkout, "co" }, > @@ -289,33 +287,6 @@ usage(int hflag, int status) > } > > static const struct got_error * > -get_editor(char **abspath) > -{ > - const struct got_error *err = NULL; > - const char *editor; > - > - *abspath = NULL; > - > - editor = getenv("VISUAL"); > - if (editor == NULL) > - editor = getenv("EDITOR"); > - > - if (editor) { > - err = got_path_find_prog(abspath, editor); > - if (err) > - return err; > - } > - > - if (*abspath == NULL) { > - *abspath = strdup("/bin/ed"); > - if (*abspath == NULL) > - return got_error_from_errno("strdup"); > - } > - > - return NULL; > -} > - > -static const struct got_error * > apply_unveil(const char *repo_path, int repo_read_only, > const char *worktree_path) > { > @@ -344,296 +315,7 @@ apply_unveil(const char *repo_path, int repo_read_only > return NULL; > } > > -__dead static void > -usage_import(void) > -{ > - fprintf(stderr, "usage: %s import [-b branch] [-m message] " > - "[-r repository-path] [-I pattern] path\n", getprogname()); > - exit(1); > -} > - > -static int > -spawn_editor(const char *editor, const char *file) > -{ > - pid_t pid; > - sig_t sighup, sigint, sigquit; > - int st = -1; > - > - sighup = signal(SIGHUP, SIG_IGN); > - sigint = signal(SIGINT, SIG_IGN); > - sigquit = signal(SIGQUIT, SIG_IGN); > - > - switch (pid = fork()) { > - case -1: > - goto doneediting; > - case 0: > - execl(editor, editor, file, (char *)NULL); > - _exit(127); > - } > - > - while (waitpid(pid, &st, 0) == -1) > - if (errno != EINTR) > - break; > - > -doneediting: > - (void)signal(SIGHUP, sighup); > - (void)signal(SIGINT, sigint); > - (void)signal(SIGQUIT, sigquit); > - > - if (!WIFEXITED(st)) { > - errno = EINTR; > - return -1; > - } > - > - return WEXITSTATUS(st); > -} > - > static const struct got_error * > -edit_logmsg(char **logmsg, const char *editor, const char *logmsg_path, > - const char *initial_content, size_t initial_content_len, > - int require_modification) > -{ > - const struct got_error *err = NULL; > - char *line = NULL; > - size_t linesize = 0; > - ssize_t linelen; > - struct stat st, st2; > - FILE *fp = NULL; > - size_t len, logmsg_len; > - char *initial_content_stripped = NULL, *buf = NULL, *s; > - > - *logmsg = NULL; > - > - if (stat(logmsg_path, &st) == -1) > - return got_error_from_errno2("stat", logmsg_path); > - > - if (spawn_editor(editor, logmsg_path) == -1) > - return got_error_from_errno("failed spawning editor"); > - > - if (stat(logmsg_path, &st2) == -1) > - return got_error_from_errno("stat"); > - > - if (require_modification && > - st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) > - return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > - "no changes made to commit message, aborting"); > - > - /* > - * Set up a stripped version of the initial content without comments > - * and blank lines. We need this in order to check if the message > - * has in fact been edited. > - */ > - initial_content_stripped = malloc(initial_content_len + 1); > - if (initial_content_stripped == NULL) > - return got_error_from_errno("malloc"); > - initial_content_stripped[0] = '\0'; > - > - buf = strdup(initial_content); > - if (buf == NULL) { > - err = got_error_from_errno("strdup"); > - goto done; > - } > - s = buf; > - len = 0; > - while ((line = strsep(&s, "\n")) != NULL) { > - if ((line[0] == '#' || (len == 0 && line[0] == '\n'))) > - continue; /* remove comments and leading empty lines */ > - len = strlcat(initial_content_stripped, line, > - initial_content_len + 1); > - if (len >= initial_content_len + 1) { > - err = got_error(GOT_ERR_NO_SPACE); > - goto done; > - } > - } > - while (len > 0 && initial_content_stripped[len - 1] == '\n') { > - initial_content_stripped[len - 1] = '\0'; > - len--; > - } > - > - logmsg_len = st2.st_size; > - *logmsg = malloc(logmsg_len + 1); > - if (*logmsg == NULL) > - return got_error_from_errno("malloc"); > - (*logmsg)[0] = '\0'; > - > - fp = fopen(logmsg_path, "re"); > - if (fp == NULL) { > - err = got_error_from_errno("fopen"); > - goto done; > - } > - > - len = 0; > - while ((linelen = getline(&line, &linesize, fp)) != -1) { > - if ((line[0] == '#' || (len == 0 && line[0] == '\n'))) > - continue; /* remove comments and leading empty lines */ > - len = strlcat(*logmsg, line, logmsg_len + 1); > - if (len >= logmsg_len + 1) { > - err = got_error(GOT_ERR_NO_SPACE); > - goto done; > - } > - } > - free(line); > - if (ferror(fp)) { > - err = got_ferror(fp, GOT_ERR_IO); > - goto done; > - } > - while (len > 0 && (*logmsg)[len - 1] == '\n') { > - (*logmsg)[len - 1] = '\0'; > - len--; > - } > - > - if (len == 0) { > - err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > - "commit message cannot be empty, aborting"); > - goto done; > - } > - if (require_modification && > - strcmp(*logmsg, initial_content_stripped) == 0) > - err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > - "no changes made to commit message, aborting"); > -done: > - free(initial_content_stripped); > - free(buf); > - if (fp && fclose(fp) == EOF && err == NULL) > - err = got_error_from_errno("fclose"); > - if (err) { > - free(*logmsg); > - *logmsg = NULL; > - } > - return err; > -} > - > -static const struct got_error * > -collect_import_msg(char **logmsg, char **logmsg_path, const char *editor, > - const char *path_dir, const char *branch_name) > -{ > - char *initial_content = NULL; > - const struct got_error *err = NULL; > - int initial_content_len; > - int fd = -1; > - > - initial_content_len = asprintf(&initial_content, > - "\n# %s to be imported to branch %s\n", path_dir, > - branch_name); > - if (initial_content_len == -1) > - return got_error_from_errno("asprintf"); > - > - err = got_opentemp_named_fd(logmsg_path, &fd, > - GOT_TMPDIR_STR "/got-importmsg"); > - if (err) > - goto done; > - > - if (write(fd, initial_content, initial_content_len) == -1) { > - err = got_error_from_errno2("write", *logmsg_path); > - goto done; > - } > - > - err = edit_logmsg(logmsg, editor, *logmsg_path, initial_content, > - initial_content_len, 1); > -done: > - if (fd != -1 && close(fd) == -1 && err == NULL) > - err = got_error_from_errno2("close", *logmsg_path); > - free(initial_content); > - if (err) { > - free(*logmsg_path); > - *logmsg_path = NULL; > - } > - return err; > -} > - > -static const struct got_error * > -import_progress(void *arg, const char *path) > -{ > - printf("A %s\n", path); > - return NULL; > -} > - > -static int > -valid_author(const char *author) > -{ > - /* > - * Really dumb email address check; we're only doing this to > - * avoid git's object parser breaking on commits we create. > - */ > - while (*author && *author != '<') > - author++; > - if (*author != '<') > - return 0; > - while (*author && *author != '@') > - author++; > - if (*author != '@') > - return 0; > - while (*author && *author != '>') > - author++; > - return *author == '>'; > -} > - > -static const struct got_error * > -get_author(char **author, struct got_repository *repo, > - struct got_worktree *worktree) > -{ > - const struct got_error *err = NULL; > - const char *got_author = NULL, *name, *email; > - const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; > - > - *author = NULL; > - > - if (worktree) > - worktree_conf = got_worktree_get_gotconfig(worktree); > - repo_conf = got_repo_get_gotconfig(repo); > - > - /* > - * Priority of potential author information sources, from most > - * significant to least significant: > - * 1) work tree's .got/got.conf file > - * 2) repository's got.conf file > - * 3) repository's git config file > - * 4) environment variables > - * 5) global git config files (in user's home directory or /etc) > - */ > - > - if (worktree_conf) > - got_author = got_gotconfig_get_author(worktree_conf); > - if (got_author == NULL) > - got_author = got_gotconfig_get_author(repo_conf); > - if (got_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"); > - 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); > - if (*author == NULL) > - return got_error_from_errno("strdup"); > - > - if (!valid_author(*author)) { > - err = got_error_fmt(GOT_ERR_COMMIT_NO_EMAIL, "%s", *author); > - free(*author); > - *author = NULL; > - } > - return err; > -} > - > -static const struct got_error * > get_allowed_signers(char **allowed_signers, struct got_repository *repo, > struct got_worktree *worktree) > { > @@ -736,251 +418,6 @@ get_signer_id(char **signer_id, struct got_repository > return NULL; > } > > -static const struct got_error * > -get_gitconfig_path(char **gitconfig_path) > -{ > - const char *homedir = getenv("HOME"); > - > - *gitconfig_path = NULL; > - if (homedir) { > - if (asprintf(gitconfig_path, "%s/.gitconfig", homedir) == -1) > - return got_error_from_errno("asprintf"); > - > - } > - return NULL; > -} > - > -static const struct got_error * > -cmd_import(int argc, char *argv[]) > -{ > - const struct got_error *error = NULL; > - char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; > - char *gitconfig_path = NULL, *editor = NULL, *author = NULL; > - const char *branch_name = "main"; > - char *refname = NULL, *id_str = NULL, *logmsg_path = NULL; > - struct got_repository *repo = NULL; > - struct got_reference *branch_ref = NULL, *head_ref = NULL; > - struct got_object_id *new_commit_id = NULL; > - int ch; > - struct got_pathlist_head ignores; > - struct got_pathlist_entry *pe; > - int preserve_logmsg = 0; > - int *pack_fds = NULL; > - > - TAILQ_INIT(&ignores); > - > - while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { > - switch (ch) { > - case 'b': > - branch_name = optarg; > - break; > - case 'm': > - logmsg = strdup(optarg); > - if (logmsg == NULL) { > - error = got_error_from_errno("strdup"); > - goto done; > - } > - break; > - case 'r': > - repo_path = realpath(optarg, NULL); > - if (repo_path == NULL) { > - error = got_error_from_errno2("realpath", > - optarg); > - goto done; > - } > - break; > - case 'I': > - if (optarg[0] == '\0') > - break; > - error = got_pathlist_insert(&pe, &ignores, optarg, > - NULL); > - if (error) > - goto done; > - break; > - default: > - usage_import(); > - /* NOTREACHED */ > - } > - } > - > - argc -= optind; > - argv += optind; > - > -#ifndef PROFILE > - if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " > - "unveil", > - NULL) == -1) > - err(1, "pledge"); > -#endif > - if (argc != 1) > - usage_import(); > - > - if (repo_path == NULL) { > - repo_path = getcwd(NULL, 0); > - if (repo_path == NULL) > - return got_error_from_errno("getcwd"); > - } > - got_path_strip_trailing_slashes(repo_path); > - error = get_gitconfig_path(&gitconfig_path); > - if (error) > - goto done; > - error = got_repo_pack_fds_open(&pack_fds); > - if (error != NULL) > - goto done; > - error = got_repo_open(&repo, repo_path, gitconfig_path, pack_fds); > - if (error) > - goto done; > - > - error = get_author(&author, repo, NULL); > - if (error) > - return error; > - > - /* > - * Don't let the user create a branch name with a leading '-'. > - * While technically a valid reference name, this case is usually > - * an unintended typo. > - */ > - if (branch_name[0] == '-') > - return got_error_path(branch_name, GOT_ERR_REF_NAME_MINUS); > - > - if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { > - error = got_error_from_errno("asprintf"); > - goto done; > - } > - > - error = got_ref_open(&branch_ref, repo, refname, 0); > - if (error) { > - if (error->code != GOT_ERR_NOT_REF) > - goto done; > - } else { > - error = got_error_msg(GOT_ERR_BRANCH_EXISTS, > - "import target branch already exists"); > - goto done; > - } > - > - path_dir = realpath(argv[0], NULL); > - if (path_dir == NULL) { > - error = got_error_from_errno2("realpath", argv[0]); > - goto done; > - } > - got_path_strip_trailing_slashes(path_dir); > - > - /* > - * unveil(2) traverses exec(2); if an editor is used we have > - * to apply unveil after the log message has been written. > - */ > - if (logmsg == NULL || strlen(logmsg) == 0) { > - error = get_editor(&editor); > - if (error) > - goto done; > - free(logmsg); > - error = collect_import_msg(&logmsg, &logmsg_path, editor, > - path_dir, refname); > - if (error) { > - if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && > - logmsg_path != NULL) > - preserve_logmsg = 1; > - goto done; > - } > - } > - > - if (unveil(path_dir, "r") != 0) { > - error = got_error_from_errno2("unveil", path_dir); > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = apply_unveil(got_repo_get_path(repo), 0, NULL); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_repo_import(&new_commit_id, path_dir, logmsg, > - author, &ignores, repo, import_progress, NULL); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_ref_alloc(&branch_ref, refname, new_commit_id); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_ref_write(branch_ref, repo); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_object_id_str(&id_str, new_commit_id); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); > - if (error) { > - if (error->code != GOT_ERR_NOT_REF) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, > - branch_ref); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - > - error = got_ref_write(head_ref, repo); > - if (error) { > - if (logmsg_path) > - preserve_logmsg = 1; > - goto done; > - } > - } > - > - printf("Created branch %s with commit %s\n", > - got_ref_get_name(branch_ref), id_str); > -done: > - if (pack_fds) { > - const struct got_error *pack_err = > - got_repo_pack_fds_close(pack_fds); > - if (error == NULL) > - error = pack_err; > - } > - if (preserve_logmsg) { > - fprintf(stderr, "%s: log message preserved in %s\n", > - getprogname(), logmsg_path); > - } else if (logmsg_path && unlink(logmsg_path) == -1 && error == NULL) > - error = got_error_from_errno2("unlink", logmsg_path); > - free(logmsg); > - free(logmsg_path); > - free(repo_path); > - free(editor); > - free(refname); > - free(new_commit_id); > - free(id_str); > - free(author); > - free(gitconfig_path); > - if (branch_ref) > - got_ref_close(branch_ref); > - if (head_ref) > - got_ref_close(head_ref); > - return error; > -} > - > __dead static void > usage_clone(void) > { > @@ -7169,10 +6606,10 @@ get_tag_message(char **tagmsg, char **tagmsg_path, con > goto done; > } > > - err = get_editor(&editor); > + err = got_logmsg_get_editor(&editor); > if (err) > goto done; > - err = edit_logmsg(tagmsg, editor, *tagmsg_path, initial_content, > + err = got_logmsg_edit(tagmsg, editor, *tagmsg_path, initial_content, > initial_content_len, 1); > done: > free(initial_content); > @@ -7460,7 +6897,7 @@ cmd_tag(int argc, char *argv[]) > error = list_tags(repo, tag_name, verify_tags, allowed_signers, > revoked_signers, verbosity); > } else { > - error = get_gitconfig_path(&gitconfig_path); > + error = got_repo_get_gitconfig_path(&gitconfig_path); > if (error) > goto done; > error = got_repo_open(&repo, repo_path, gitconfig_path, > @@ -7468,7 +6905,7 @@ cmd_tag(int argc, char *argv[]) > if (error != NULL) > goto done; > > - error = get_author(&tagger, repo, worktree); > + error = got_logmsg_get_author(&tagger, repo, worktree); > if (error) > goto done; > if (signer_id == NULL) { > @@ -8446,8 +7883,8 @@ collect_commit_logmsg(struct got_pathlist_head *commit > got_commitable_get_path(ct)); > } > > - err = edit_logmsg(logmsg, a->editor, a->logmsg_path, initial_content, > - initial_content_len, a->prepared_log ? 0 : 1); > + err = got_logmsg_edit(logmsg, a->editor, a->logmsg_path, > + initial_content, initial_content_len, a->prepared_log ? 0 : 1); > done: > free(initial_content); > free(template); > @@ -8550,7 +7987,7 @@ cmd_commit(int argc, char *argv[]) > if (error) > goto done; > > - error = get_gitconfig_path(&gitconfig_path); > + error = got_repo_get_gitconfig_path(&gitconfig_path); > if (error) > goto done; > error = got_repo_open(&repo, got_worktree_get_repo_path(worktree), > @@ -8566,7 +8003,7 @@ cmd_commit(int argc, char *argv[]) > goto done; > } > > - error = get_author(&author, repo, worktree); > + error = got_logmsg_get_author(&author, repo, worktree); > if (error) > return error; > > @@ -8575,7 +8012,7 @@ cmd_commit(int argc, char *argv[]) > * to apply unveil after the log message has been written. > */ > if (logmsg == NULL || strlen(logmsg) == 0) > - error = get_editor(&editor); > + error = got_logmsg_get_editor(&editor); > else > error = apply_unveil(got_repo_get_path(repo), 0, > got_worktree_get_root_path(worktree)); > @@ -10665,11 +10102,11 @@ histedit_edit_logmsg(struct got_histedit_list_entry *h > write(fd, logmsg, logmsg_len); > close(fd); > > - err = get_editor(&editor); > + err = got_logmsg_get_editor(&editor); > if (err) > goto done; > > - err = edit_logmsg(&hle->logmsg, editor, logmsg_path, logmsg, > + err = got_logmsg_edit(&hle->logmsg, editor, logmsg_path, logmsg, > logmsg_len, 0); > if (err) { > if (err->code != GOT_ERR_COMMIT_MSG_EMPTY) > @@ -10860,11 +10297,11 @@ histedit_run_editor(struct got_histedit_list *histedit > char *editor; > FILE *f = NULL; > > - err = get_editor(&editor); > + err = got_logmsg_get_editor(&editor); > if (err) > return err; > > - if (spawn_editor(editor, path) == -1) { > + if (got_logmsg_spawn_editor(editor, path) == -1) { > err = got_error_from_errno("failed spawning editor"); > goto done; > } > @@ -12057,7 +11494,7 @@ cmd_merge(int argc, char *argv[]) > goto done; /* nothing else to do */ > } > > - error = get_author(&author, repo, worktree); > + error = got_logmsg_get_author(&author, repo, worktree); > if (error) > goto done; > > blob - bf9729a9216142455edfd253fb05cd98c0b4b1f1 > blob + 183759b3c11df3e317479aeda4517a504cb470a1 > --- gotadmin/Makefile > +++ gotadmin/Makefile > @@ -9,7 +9,7 @@ SRCS= gotadmin.c \ > object_idset.c object_parse.c opentemp.c pack.c pack_create.c \ > path.c privsep.c reference.c repository.c repository_admin.c \ > worktree_open.c sha1.c bloom.c murmurhash2.c ratelimit.c \ > - sigs.c buf.c date.c > + sigs.c buf.c date.c logmsg.c > MAN = ${PROG}.1 > > CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib > blob - 6205dfc5bd0505463ccfca983492ba58b3ac92fe > blob + a8978bd21c811ade17a8000ebef9ab34d1e82d91 > --- gotadmin/gotadmin.1 > +++ gotadmin/gotadmin.1 > @@ -60,7 +60,7 @@ Create a new empty repository at the specified > After > .Cm gotadmin init , > the > -.Cm got import > +.Cm gotadmin import > command must be used to populate the empty repository before > .Cm got checkout > can be used. > @@ -83,6 +83,67 @@ If this directory is a > .Xr got 1 > work tree, use the repository path associated with this work tree. > .El > +.Tg im > +.It Cm import Oo Fl b Ar branch Oc Oo Fl m Ar message Oc Oo Fl r Ar repository-path Oc Oo Fl I Ar pattern Oc Ar directory > +.Dl Pq alias: Cm im > +Create an initial commit in a repository from the file hierarchy > +within the specified > +.Ar directory . > +The created commit will not have any parent commits, i.e. it will be a > +root commit. > +Also create a new reference which provides a branch name for the newly > +created commit. > +Show the path of each imported file to indicate progress. > +.Pp > +The > +.Cm gotadmin import > +command requires the > +.Ev GOT_AUTHOR > +environment variable to be set, > +unless an author has been configured in > +.Xr got.conf 5 > +or Git's > +.Dv user.name > +and > +.Dv user.email > +configuration settings can be obtained from the repository's > +.Pa .git/config > +file or from Git's global > +.Pa ~/.gitconfig > +configuration file. > +.Pp > +The options for > +.Cm gotadmin import > +are as follows: > +.Bl -tag -width Ds > +.It Fl b Ar branch > +Create the specified > +.Ar branch > +instead of creating the default branch > +.Dq main . > +Use of this option is required if the > +.Dq main > +branch already exists. > +.It Fl m Ar message > +Use the specified log message when creating the new commit. > +Without the > +.Fl m > +option, > +.Cm gotadmin import > +opens a temporary file in an editor where a log message can be written. > +.It Fl r Ar repository-path > +Use the repository at the specified path. > +If not specified, assume the repository is located at or above the current > +working directory. > +.It Fl I Ar pattern > +Ignore files or directories with a name which matches the specified > +.Ar pattern . > +This option may be specified multiple times to build a list of ignore patterns. > +The > +.Ar pattern > +follows the globbing rules documented in > +.Xr glob 7 . > +.El > .It Cm pack Oo Fl a Oc Oo Fl r Ar repository-path Oc Oo Fl x Ar reference Oc Oo Fl q Oc Op Ar reference ... > Generate a new pack file and a corresponding pack file index. > By default, add any loose objects which are reachable via any references > blob - f4d2b3013d6751cf1612acb25b8552e3910f05f5 > blob + 4c3cddb38fc5ed35941da801a1d3e0f057bd0b80 > --- gotadmin/gotadmin.c > +++ gotadmin/gotadmin.c > @@ -43,6 +43,7 @@ > #include "got_privsep.h" > #include "got_opentemp.h" > #include "got_worktree.h" > +#include "got_logmsg.h" > > #ifndef nitems > #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0])) > @@ -80,6 +81,7 @@ struct gotadmin_cmd { > > __dead static void usage(int, int); > __dead static void usage_init(void); > +__dead static void usage_import(void); > __dead static void usage_info(void); > __dead static void usage_pack(void); > __dead static void usage_indexpack(void); > @@ -87,6 +89,7 @@ __dead static void usage_listpack(void); > __dead static void usage_cleanup(void); > > static const struct got_error* cmd_init(int, char *[]); > +static const struct got_error* cmd_import(int, char *[]); > static const struct got_error* cmd_info(int, char *[]); > static const struct got_error* cmd_pack(int, char *[]); > static const struct got_error* cmd_indexpack(int, char *[]); > @@ -95,6 +98,7 @@ static const struct got_error* cmd_cleanup(int, char > > static const struct gotadmin_cmd gotadmin_commands[] = { > { "init", cmd_init, usage_init, "" }, > + { "import", cmd_import, usage_import, "im" }, > { "info", cmd_info, usage_info, "" }, > { "pack", cmd_pack, usage_pack, "" }, > { "indexpack", cmd_indexpack, usage_indexpack,"ix" }, > @@ -321,7 +325,291 @@ done: > return error; > } > > +__dead static void > +usage_import(void) > +{ > + fprintf(stderr, "usage: %s import [-b branch] [-m message] " > + "[-r repository-path] [-I pattern] path\n", getprogname()); > + exit(1); > +} > + > static const struct got_error * > +collect_import_msg(char **logmsg, char **logmsg_path, const char *editor, > + const char *path_dir, const char *branch_name) > +{ > + char *initial_content = NULL; > + const struct got_error *err = NULL; > + int initial_content_len; > + int fd = -1; > + > + initial_content_len = asprintf(&initial_content, > + "\n# %s to be imported to branch %s\n", path_dir, > + branch_name); > + if (initial_content_len == -1) > + return got_error_from_errno("asprintf"); > + > + err = got_opentemp_named_fd(logmsg_path, &fd, > + GOT_TMPDIR_STR "/got-importmsg"); > + if (err) > + goto done; > + > + if (write(fd, initial_content, initial_content_len) == -1) { > + err = got_error_from_errno2("write", *logmsg_path); > + goto done; > + } > + > + err = got_logmsg_edit(logmsg, editor, *logmsg_path, initial_content, > + initial_content_len, 1); > +done: > + if (fd != -1 && close(fd) == -1 && err == NULL) > + err = got_error_from_errno2("close", *logmsg_path); > + free(initial_content); > + if (err) { > + free(*logmsg_path); > + *logmsg_path = NULL; > + } > + return err; > +} > + > +static const struct got_error * > +import_progress(void *arg, const char *path) > +{ > + printf("A %s\n", path); > + return NULL; > +} > + > +static const struct got_error * > +cmd_import(int argc, char *argv[]) > +{ > + const struct got_error *error = NULL; > + char *path_dir = NULL, *repo_path = NULL, *logmsg = NULL; > + char *gitconfig_path = NULL, *editor = NULL, *author = NULL; > + const char *branch_name = "main"; > + char *refname = NULL, *id_str = NULL, *logmsg_path = NULL; > + struct got_repository *repo = NULL; > + struct got_reference *branch_ref = NULL, *head_ref = NULL; > + struct got_object_id *new_commit_id = NULL; > + int ch; > + struct got_pathlist_head ignores; > + struct got_pathlist_entry *pe; > + int preserve_logmsg = 0; > + int *pack_fds = NULL; > + > + TAILQ_INIT(&ignores); > + > + while ((ch = getopt(argc, argv, "b:m:r:I:")) != -1) { > + switch (ch) { > + case 'b': > + branch_name = optarg; > + break; > + case 'm': > + logmsg = strdup(optarg); > + if (logmsg == NULL) { > + error = got_error_from_errno("strdup"); > + goto done; > + } > + break; > + case 'r': > + repo_path = realpath(optarg, NULL); > + if (repo_path == NULL) { > + error = got_error_from_errno2("realpath", > + optarg); > + goto done; > + } > + break; > + case 'I': > + if (optarg[0] == '\0') > + break; > + error = got_pathlist_insert(&pe, &ignores, optarg, > + NULL); > + if (error) > + goto done; > + break; > + default: > + usage_import(); > + /* NOTREACHED */ > + } > + } > + > + argc -= optind; > + argv += optind; > + > +#ifndef PROFILE > + if (pledge("stdio rpath wpath cpath fattr flock proc exec sendfd " > + "unveil", > + NULL) == -1) > + err(1, "pledge"); > +#endif > + if (argc != 1) > + usage_import(); > + > + if (repo_path == NULL) { > + repo_path = getcwd(NULL, 0); > + if (repo_path == NULL) > + return got_error_from_errno("getcwd"); > + } > + got_path_strip_trailing_slashes(repo_path); > + error = got_repo_get_gitconfig_path(&gitconfig_path); > + if (error) > + goto done; > + error = got_repo_pack_fds_open(&pack_fds); > + if (error != NULL) > + goto done; > + error = got_repo_open(&repo, repo_path, gitconfig_path, pack_fds); > + if (error) > + goto done; > + > + error = got_logmsg_get_author(&author, repo, NULL); > + if (error) > + return error; > + > + /* > + * Don't let the user create a branch name with a leading '-'. > + * While technically a valid reference name, this case is usually > + * an unintended typo. > + */ > + if (branch_name[0] == '-') > + return got_error_path(branch_name, GOT_ERR_REF_NAME_MINUS); > + > + if (asprintf(&refname, "refs/heads/%s", branch_name) == -1) { > + error = got_error_from_errno("asprintf"); > + goto done; > + } > + > + error = got_ref_open(&branch_ref, repo, refname, 0); > + if (error) { > + if (error->code != GOT_ERR_NOT_REF) > + goto done; > + } else { > + error = got_error_msg(GOT_ERR_BRANCH_EXISTS, > + "import target branch already exists"); > + goto done; > + } > + > + path_dir = realpath(argv[0], NULL); > + if (path_dir == NULL) { > + error = got_error_from_errno2("realpath", argv[0]); > + goto done; > + } > + got_path_strip_trailing_slashes(path_dir); > + > + /* > + * unveil(2) traverses exec(2); if an editor is used we have > + * to apply unveil after the log message has been written. > + */ > + if (logmsg == NULL || strlen(logmsg) == 0) { > + error = got_logmsg_get_editor(&editor); > + if (error) > + goto done; > + free(logmsg); > + error = collect_import_msg(&logmsg, &logmsg_path, editor, > + path_dir, refname); > + if (error) { > + if (error->code != GOT_ERR_COMMIT_MSG_EMPTY && > + logmsg_path != NULL) > + preserve_logmsg = 1; > + goto done; > + } > + } > + > + if (unveil(path_dir, "r") != 0) { > + error = got_error_from_errno2("unveil", path_dir); > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = apply_unveil(got_repo_get_path(repo), 0); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_repo_import(&new_commit_id, path_dir, logmsg, > + author, &ignores, repo, import_progress, NULL); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_ref_alloc(&branch_ref, refname, new_commit_id); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_ref_write(branch_ref, repo); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_object_id_str(&id_str, new_commit_id); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_ref_open(&head_ref, repo, GOT_REF_HEAD, 0); > + if (error) { > + if (error->code != GOT_ERR_NOT_REF) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_ref_alloc_symref(&head_ref, GOT_REF_HEAD, > + branch_ref); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + > + error = got_ref_write(head_ref, repo); > + if (error) { > + if (logmsg_path) > + preserve_logmsg = 1; > + goto done; > + } > + } > + > + printf("Created branch %s with commit %s\n", > + got_ref_get_name(branch_ref), id_str); > +done: > + if (pack_fds) { > + const struct got_error *pack_err = > + got_repo_pack_fds_close(pack_fds); > + if (error == NULL) > + error = pack_err; > + } > + if (preserve_logmsg) { > + fprintf(stderr, "%s: log message preserved in %s\n", > + getprogname(), logmsg_path); > + } else if (logmsg_path && unlink(logmsg_path) == -1 && error == NULL) > + error = got_error_from_errno2("unlink", logmsg_path); > + free(logmsg); > + free(logmsg_path); > + free(repo_path); > + free(editor); > + free(refname); > + free(new_commit_id); > + free(id_str); > + free(author); > + free(gitconfig_path); > + if (branch_ref) > + got_ref_close(branch_ref); > + if (head_ref) > + got_ref_close(head_ref); > + return error; > +} > + > +static const struct got_error * > cmd_info(int argc, char *argv[]) > { > const struct got_error *error = NULL; > blob - /dev/null > blob + 2764cd763f3cdb2cf63c52f1c4e7e6d1fa83af98 (mode 644) > --- /dev/null > +++ include/got_logmsg.h > @@ -0,0 +1,25 @@ > +/* > + * Copyright (c) 2019 Stefan Sperling > + * > + * 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. > + */ > + > +const struct got_error *got_logmsg_get_author(char **author, > + struct got_repository *repo, struct got_worktree *worktree); > + > +const struct got_error *got_logmsg_get_editor(char **); > +int got_logmsg_spawn_editor(const char *editor, const char *file); > + > +const struct got_error *got_logmsg_edit(char **logmsg, const char *editor, > + const char *logmsg_path, const char *initial_content, > + size_t initial_content_len, int require_modification); > blob - dea6dd81d267dfa92571a33f5c7559726bab8d8b > blob + 48c19f5f94255edff5e2bd2f72f5f7cb6251f264 > --- include/got_repository.h > +++ include/got_repository.h > @@ -54,6 +54,9 @@ const char *got_repo_get_gitconfig_owner(struct got_re > void got_repo_get_gitconfig_extensions(char ***, int *, > struct got_repository *); > > +/* Obtain the path to the user's .gitconfig file. Caller must free(3). */ > +const struct got_error *got_repo_get_gitconfig_path(char **); > + > /* Information about one remote repository. */ > struct got_remote_repo { > char *name; > blob - /dev/null > blob + e5910e4fa816e28ec67a826ee5aea57fe7f43e6f (mode 644) > --- /dev/null > +++ lib/logmsg.c > @@ -0,0 +1,297 @@ > +/* > + * Copyright (c) 2019 Stefan Sperling > + * > + * 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 > +#include > +#include > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include "got_error.h" > +#include "got_cancel.h" > +#include "got_repository.h" > +#include "got_worktree.h" > +#include "got_logmsg.h" > +#include "got_path.h" > +#include "got_gotconfig.h" > + > +static int > +valid_author(const char *author) > +{ > + /* > + * Really dumb email address check; we're only doing this to > + * avoid git's object parser breaking on commits we create. > + */ > + while (*author && *author != '<') > + author++; > + if (*author != '<') > + return 0; > + while (*author && *author != '@') > + author++; > + if (*author != '@') > + return 0; > + while (*author && *author != '>') > + author++; > + return *author == '>'; > +} > + > +const struct got_error * > +got_logmsg_get_author(char **author, struct got_repository *repo, > + struct got_worktree *worktree) > +{ > + const struct got_error *err = NULL; > + const char *got_author = NULL, *name, *email; > + const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL; > + > + *author = NULL; > + > + if (worktree) > + worktree_conf = got_worktree_get_gotconfig(worktree); > + repo_conf = got_repo_get_gotconfig(repo); > + > + /* > + * Priority of potential author information sources, from most > + * significant to least significant: > + * 1) work tree's .got/got.conf file > + * 2) repository's got.conf file > + * 3) repository's git config file > + * 4) environment variables > + * 5) global git config files (in user's home directory or /etc) > + */ > + > + if (worktree_conf) > + got_author = got_gotconfig_get_author(worktree_conf); > + if (got_author == NULL) > + got_author = got_gotconfig_get_author(repo_conf); > + if (got_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"); > + 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); > + if (*author == NULL) > + return got_error_from_errno("strdup"); > + > + if (!valid_author(*author)) { > + err = got_error_fmt(GOT_ERR_COMMIT_NO_EMAIL, "%s", *author); > + free(*author); > + *author = NULL; > + } > + return err; > +} > + > +const struct got_error * > +got_logmsg_get_editor(char **abspath) > +{ > + const struct got_error *err = NULL; > + const char *editor; > + > + *abspath = NULL; > + > + editor = getenv("VISUAL"); > + if (editor == NULL) > + editor = getenv("EDITOR"); > + > + if (editor) { > + err = got_path_find_prog(abspath, editor); > + if (err) > + return err; > + } > + > + if (*abspath == NULL) { > + *abspath = strdup("/bin/ed"); > + if (*abspath == NULL) > + return got_error_from_errno("strdup"); > + } > + > + return NULL; > +} > + > +int > +got_logmsg_spawn_editor(const char *editor, const char *file) > +{ > + pid_t pid; > + sig_t sighup, sigint, sigquit; > + int st = -1; > + > + sighup = signal(SIGHUP, SIG_IGN); > + sigint = signal(SIGINT, SIG_IGN); > + sigquit = signal(SIGQUIT, SIG_IGN); > + > + switch (pid = fork()) { > + case -1: > + goto doneediting; > + case 0: > + execl(editor, editor, file, (char *)NULL); > + _exit(127); > + } > + > + while (waitpid(pid, &st, 0) == -1) > + if (errno != EINTR) > + break; > + > +doneediting: > + (void)signal(SIGHUP, sighup); > + (void)signal(SIGINT, sigint); > + (void)signal(SIGQUIT, sigquit); > + > + if (!WIFEXITED(st)) { > + errno = EINTR; > + return -1; > + } > + > + return WEXITSTATUS(st); > +} > + > +const struct got_error * > +got_logmsg_edit(char **logmsg, const char *editor, const char *logmsg_path, > + const char *initial_content, size_t initial_content_len, > + int require_modification) > +{ > + const struct got_error *err = NULL; > + char *line = NULL; > + size_t linesize = 0; > + ssize_t linelen; > + struct stat st, st2; > + FILE *fp = NULL; > + size_t len, logmsg_len; > + char *initial_content_stripped = NULL, *buf = NULL, *s; > + > + *logmsg = NULL; > + > + if (stat(logmsg_path, &st) == -1) > + return got_error_from_errno2("stat", logmsg_path); > + > + if (got_logmsg_spawn_editor(editor, logmsg_path) == -1) > + return got_error_from_errno("failed spawning editor"); > + > + if (stat(logmsg_path, &st2) == -1) > + return got_error_from_errno("stat"); > + > + if (require_modification && > + st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) > + return got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > + "no changes made to commit message, aborting"); > + > + /* > + * Set up a stripped version of the initial content without comments > + * and blank lines. We need this in order to check if the message > + * has in fact been edited. > + */ > + initial_content_stripped = malloc(initial_content_len + 1); > + if (initial_content_stripped == NULL) > + return got_error_from_errno("malloc"); > + initial_content_stripped[0] = '\0'; > + > + buf = strdup(initial_content); > + if (buf == NULL) { > + err = got_error_from_errno("strdup"); > + goto done; > + } > + s = buf; > + len = 0; > + while ((line = strsep(&s, "\n")) != NULL) { > + if ((line[0] == '#' || (len == 0 && line[0] == '\n'))) > + continue; /* remove comments and leading empty lines */ > + len = strlcat(initial_content_stripped, line, > + initial_content_len + 1); > + if (len >= initial_content_len + 1) { > + err = got_error(GOT_ERR_NO_SPACE); > + goto done; > + } > + } > + while (len > 0 && initial_content_stripped[len - 1] == '\n') { > + initial_content_stripped[len - 1] = '\0'; > + len--; > + } > + > + logmsg_len = st2.st_size; > + *logmsg = malloc(logmsg_len + 1); > + if (*logmsg == NULL) > + return got_error_from_errno("malloc"); > + (*logmsg)[0] = '\0'; > + > + fp = fopen(logmsg_path, "re"); > + if (fp == NULL) { > + err = got_error_from_errno("fopen"); > + goto done; > + } > + > + len = 0; > + while ((linelen = getline(&line, &linesize, fp)) != -1) { > + if ((line[0] == '#' || (len == 0 && line[0] == '\n'))) > + continue; /* remove comments and leading empty lines */ > + len = strlcat(*logmsg, line, logmsg_len + 1); > + if (len >= logmsg_len + 1) { > + err = got_error(GOT_ERR_NO_SPACE); > + goto done; > + } > + } > + free(line); > + if (ferror(fp)) { > + err = got_ferror(fp, GOT_ERR_IO); > + goto done; > + } > + while (len > 0 && (*logmsg)[len - 1] == '\n') { > + (*logmsg)[len - 1] = '\0'; > + len--; > + } > + > + if (len == 0) { > + err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > + "commit message cannot be empty, aborting"); > + goto done; > + } > + if (require_modification && > + strcmp(*logmsg, initial_content_stripped) == 0) > + err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY, > + "no changes made to commit message, aborting"); > +done: > + free(initial_content_stripped); > + free(buf); > + if (fp && fclose(fp) == EOF && err == NULL) > + err = got_error_from_errno("fclose"); > + if (err) { > + free(*logmsg); > + *logmsg = NULL; > + } > + return err; > +} > blob - 4c93c601016c47cab8439703f5925c65095b4b7e > blob + 9befcb7dc09ce5e8891cdb64f3541975366933c5 > --- lib/repository.c > +++ lib/repository.c > @@ -129,6 +129,20 @@ got_repo_get_gitconfig_extensions(char ***extensions, > *nextensions = repo->nextensions; > } > > +const struct got_error * > +got_repo_get_gitconfig_path(char **gitconfig_path) > +{ > + const char *homedir = getenv("HOME"); > + > + *gitconfig_path = NULL; > + if (homedir) { > + if (asprintf(gitconfig_path, "%s/.gitconfig", homedir) == -1) > + return got_error_from_errno("asprintf"); > + > + } > + return NULL; > +} > + > int > got_repo_is_bare(struct got_repository *repo) > { > blob - 585486aa9bd3b5620681a9e4ccad89befbbaf36b > blob + d506cf8b1f1ddc3c2631bbbc5e3ffa67314c3aec > --- lib/worktree.c > +++ lib/worktree.c > @@ -364,12 +364,6 @@ done: > return err; > } > > -const struct got_gotconfig * > -got_worktree_get_gotconfig(struct got_worktree *worktree) > -{ > - return worktree->gotconfig; > -} > - > static const struct got_error * > lock_worktree(struct got_worktree *worktree, int operation) > { > blob - 529f4b6767b84a31e24d93b402c6a3c8226cdd74 > blob + f82e12b4fe0053656a74f49f11d00bbd552b4e77 > --- lib/worktree_open.c > +++ lib/worktree_open.c > @@ -335,3 +335,9 @@ got_worktree_get_path_prefix(struct got_worktree *work > { > return worktree->path_prefix; > } > + > +const struct got_gotconfig * > +got_worktree_get_gotconfig(struct got_worktree *worktree) > +{ > + return worktree->gotconfig; > +} > blob - 71d20c61b9666f8571b8996f1bb477f6e01a3c1f > blob + e70021a604b6b1a96aad6f74843827f344d8671a > --- regress/cmdline/import.sh > +++ regress/cmdline/import.sh > @@ -25,7 +25,7 @@ test_import_basic() { > mkdir $testroot/tree > make_test_tree $testroot/tree > > - got import -m 'init' -r $testroot/repo $testroot/tree \ > + gotadmin import -m 'init' -r $testroot/repo $testroot/tree \ > > $testroot/stdout > ret=$? > if [ $ret -ne 0 ]; then > @@ -146,7 +146,7 @@ test_import_requires_new_branch() { > mkdir $testroot/tree > make_test_tree $testroot/tree > > - got import -b master -m 'init' -r $testroot/repo $testroot/tree \ > + gotadmin import -b master -m 'init' -r $testroot/repo $testroot/tree \ > > $testroot/stdout 2> $testroot/stderr > ret=$? > if [ $ret -eq 0 ]; then > @@ -155,7 +155,7 @@ test_import_requires_new_branch() { > return 1 > fi > > - echo "got: import target branch already exists" \ > + echo "gotadmin: import target branch already exists" \ > > $testroot/stderr.expected > cmp -s $testroot/stderr.expected $testroot/stderr > ret=$? > @@ -165,8 +165,8 @@ test_import_requires_new_branch() { > return 1 > fi > > - got import -b newbranch -m 'init' -r $testroot/repo $testroot/tree \ > - > $testroot/stdout > + gotadmin import -b newbranch -m 'init' -r $testroot/repo \ > + $testroot/tree > $testroot/stdout > ret=$? > test_done "$testroot" "$ret" > > @@ -181,7 +181,7 @@ test_import_ignores() { > mkdir $testroot/tree > make_test_tree $testroot/tree > > - got import -I alpha -I '*lta*' -I '*silon' \ > + gotadmin import -I alpha -I '*lta*' -I '*silon' \ > -m 'init' -r $testroot/repo $testroot/tree > $testroot/stdout > ret=$? > if [ $ret -ne 0 ]; then > @@ -212,7 +212,8 @@ test_import_empty_dir() { > mkdir -p $testroot/tree/empty $testroot/tree/notempty > echo "alpha" > $testroot/tree/notempty/alpha > > - got import -m 'init' -r $testroot/repo $testroot/tree > $testroot/stdout > + gotadmin import -m 'init' -r $testroot/repo $testroot/tree \ > + > $testroot/stdout > ret=$? > if [ $ret -ne 0 ]; then > test_done "$testroot" "$ret" > @@ -255,7 +256,7 @@ test_import_symlink() { > echo 'this is file alpha' > $testroot/tree/alpha > ln -s alpha $testroot/tree/alpha.link > > - got import -m 'init' -r $testroot/repo $testroot/tree \ > + gotadmin import -m 'init' -r $testroot/repo $testroot/tree \ > > $testroot/stdout > ret=$? > if [ $ret -ne 0 ]; then > blob - fc218eff521a465dc36ca25aad4973aa104fbb4c > blob + 761b9de05dbc0b3614182f2090c3ab0910ee63dc > --- regress/cmdline/log.sh > +++ regress/cmdline/log.sh > @@ -673,7 +673,8 @@ test_log_in_worktree_different_repo() { > gotadmin init $testroot/other-repo > mkdir -p $testroot/tree > make_test_tree $testroot/tree > - got import -mm -b foo -r $testroot/other-repo $testroot/tree >/dev/null > + gotadmin import -mm -b foo -r $testroot/other-repo $testroot/tree \ > + > /dev/null > got checkout -b foo $testroot/other-repo $testroot/wt > /dev/null > ret=$? > if [ $ret -ne 0 ]; then > blob - 0d03d0c83a725429e9a8d665b4e26dcbfe287e8c > blob + e46030af3bd1a83b5c011e791d330e41b0254b70 > --- regress/cmdline/merge.sh > +++ regress/cmdline/merge.sh > @@ -1155,7 +1155,7 @@ test_merge_imported_branch() { > echo "there should" > $testroot/tree/there/should > echo "be lots of" > $testroot/tree/be/lots/of > echo "files here" > $testroot/tree/files/here > - got import -r $testroot/repo -b files -m 'import files' \ > + gotadmin import -r $testroot/repo -b files -m 'import files' \ > $testroot/tree > /dev/null > > got checkout -b master $testroot/repo $testroot/wt > /dev/null > > -- Tracey Emery