commit - bd14628f607b07c5bb661e33c69e4079811b4514
commit + 33ad4cbe5926c7fe36929934d68a000fe19dafa3
blob - 035d5aa5cd59668ae07b24cb317ccd24a40929f2
blob + 9f023e7c97fd57dec07e2583bf5cc55a3e12417b
--- got/got.c
+++ got/got.c
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/param.h>
+#include <sys/wait.h>
#include <err.h>
#include <errno.h>
#include <unistd.h>
#include <libgen.h>
#include <time.h>
+#include <paths.h>
#include "got_error.h"
#include "got_object.h"
{
fprintf(stderr, "usage: %s commit [-m msg] file-path\n", getprogname());
exit(1);
+}
+
+int
+spawn_editor(const char *file)
+{
+ char *argp[] = { "sh", "-c", NULL, NULL };
+ char *editor, *editp;
+ pid_t pid;
+ sig_t sighup, sigint, sigquit;
+ int st = -1;
+
+ editor = getenv("VISUAL");
+ if (editor == NULL)
+ editor = getenv("EDITOR");
+ if (editor == NULL)
+ editor = "ed";
+
+ if (asprintf(&editp, "%s %s", editor, file) == -1)
+ return -1;
+
+ argp[2] = editp;
+
+ sighup = signal(SIGHUP, SIG_IGN);
+ sigint = signal(SIGINT, SIG_IGN);
+ sigquit = signal(SIGQUIT, SIG_IGN);
+
+ switch (pid = fork()) {
+ case -1:
+ goto doneediting;
+ case 0:
+ execv(_PATH_BSHELL, argp);
+ _exit(127);
+ }
+
+ free(editp);
+
+ 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 *
+collect_commit_logmsg(struct got_pathlist_head *commitable_paths, char **logmsg,
+ void *arg)
+{
+ struct got_pathlist_entry *pe;
+ const struct got_error *err = NULL;
+ char *tmppath = NULL;
+ char *tmpfile = NULL;
+ char *cmdline_log = (char *)arg;
+ char buf[1024];
+ struct stat st, st2;
+ FILE *fp;
+ size_t len;
+ int fd;
+
+ /* if a message was specified on the command line, just use it */
+ if (cmdline_log != NULL && strlen(cmdline_log) != 0) {
+ len = strlen(cmdline_log) + 1;
+ *logmsg = malloc(len + 1);
+ strlcpy(*logmsg, cmdline_log, len);
+ return NULL;
+ }
+
+ tmppath = getenv("TMPDIR");
+ if (tmppath == NULL || strlen(tmppath) == 0)
+ tmppath = "/tmp";
+
+ if (asprintf(&tmpfile, "%s/got-XXXXXXXXXX", tmppath) == -1) {
+ err = got_error_prefix_errno("asprintf");
+ return err;
+ }
+
+ fd = mkstemp(tmpfile);
+ if (fd < 0) {
+ err = got_error_prefix_errno("mktemp");
+ free(tmpfile);
+ return err;
+ }
+
+ dprintf(fd, "\n"
+ "# changes to be committed:\n");
+
+ TAILQ_FOREACH(pe, commitable_paths, entry) {
+ struct got_commitable *ct = pe->data;
+ dprintf(fd, "# %c %s\n", ct->status, pe->path);
+ }
+ close(fd);
+
+ if (stat(tmpfile, &st) == -1) {
+ err = got_error_prefix_errno2("stat", tmpfile);
+ goto done;
+ }
+
+ if (spawn_editor(tmpfile) == -1) {
+ err = got_error_prefix_errno("failed spawning editor");
+ goto done;
+ }
+
+ if (stat(tmpfile, &st2) == -1) {
+ err = got_error_prefix_errno("stat");
+ goto done;
+ }
+
+ if (st.st_mtime == st2.st_mtime && st.st_size == st2.st_size) {
+ err = got_error_msg(GOT_ERR_COMMIT_MSG_EMPTY,
+ "no changes made to commit message, aborting");
+ goto done;
+ }
+
+ /* remove comments */
+ *logmsg = malloc(st2.st_size + 1);
+ len = 0;
+
+ fp = fopen(tmpfile, "r");
+ while (fgets(buf, sizeof(buf), fp) != NULL) {
+ if (buf[0] == '#' || (len == 0 && buf[0] == '\n'))
+ continue;
+ len = strlcat(*logmsg, buf, st2.st_size);
+ }
+ fclose(fp);
+
+ 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;
+ }
+
+ goto done;
+
+done:
+ if (tmpfile) {
+ unlink(tmpfile);
+ free(tmpfile);
+ }
+
+ return err;
}
static const struct got_error *
struct got_repository *repo = NULL;
char *cwd = NULL, *path = NULL, *id_str = NULL;
struct got_object_id *id = NULL;
- const char *logmsg = "<no log message was specified>";
+ const char *logmsg = NULL;
const char *got_author = getenv("GOT_AUTHOR");
int ch;
if (error != NULL)
goto done;
+#if 0
error = apply_unveil(got_repo_get_path(repo), 0,
got_worktree_get_root_path(worktree), 0);
if (error)
goto done;
+#endif
error = got_worktree_commit(&id, worktree, path, got_author, NULL,
- logmsg, print_status, NULL, repo);
+ collect_commit_logmsg, (void *)logmsg, print_status, NULL, repo);
if (error)
goto done;
blob - 604b1544720d90d5e86a3b3cd55f6ab9a7c01ede
blob + 2d5f5bdc1070d54afafaa832ad1ba9e483d82233
--- include/got_error.h
+++ include/got_error.h
#define GOT_ERR_COMMIT_NO_AUTHOR 71
#define GOT_ERR_COMMIT_HEAD_CHANGED 72
#define GOT_ERR_COMMIT_OUT_OF_DATE 73
+#define GOT_ERR_COMMIT_MSG_EMPTY 74
+#define GOT_ERR_DIR_NOT_EMPTY 75
+#define GOT_ERR_COMMIT_NO_CHANGES 76
static const struct got_error {
int code;
"while commit was in progress" },
{ GOT_ERR_COMMIT_OUT_OF_DATE, "work tree must be updated before these "
"changes can be committed" },
+ { GOT_ERR_COMMIT_MSG_EMPTY, "commit message cannot be empty" },
+ { GOT_ERR_COMMIT_NO_CHANGES, "no changes to commit" },
};
/*
blob - 3c54627d7d7a81f4bdda04d9700580311324fe9e
blob + 7103aa2afce020c27d8adc980b95aaa232035522
--- include/got_worktree.h
+++ include/got_worktree.h
#define GOT_STATUS_OBSTRUCTED '~'
#define GOT_STATUS_REVERT 'R'
+struct got_commitable {
+ char *path;
+ char *in_repo_path;
+ char *ondisk_path;
+ unsigned char status;
+ struct got_object_id *blob_id;
+ struct got_object_id *base_id;
+ mode_t mode;
+};
+
/*
* Attempt to initialize a new work tree on disk.
* The first argument is the path to a directory where the work tree
const char *, got_worktree_checkout_cb, void *, struct got_repository *);
/*
+ * A callback function which is invoked when a commit message is requested.
+ * Passes a list of modified paths being committed to, a pointer to the log
+ * message that must be set by the callback and will be freed after committing,
+ * and an argument passed through to the callback.
+ */
+typedef const struct got_error *(*got_worktree_commit_msg_cb)(
+ struct got_pathlist_head *, char **, void *);
+
+/*
* Create a new commit from changes in the work tree.
* Return the ID of the newly created commit.
* The worktree's base commit will be set to this new commit.
*/
const struct got_error *got_worktree_commit(struct got_object_id **,
struct got_worktree *, const char *, const char *, const char *,
- const char *, got_worktree_status_cb, void *, struct got_repository *);
+ got_worktree_commit_msg_cb, void *,
+ got_worktree_status_cb, void *, struct got_repository *);
blob - eec689d0aeb000d58fe1ede8d80a700c0a2d4d55
blob + 1ad8832b391a09f83bd2201d0c1400a6c47b6f39
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
GOT_IMSG_PACK,
GOT_IMSG_PACKED_OBJECT_REQUEST,
- /* Message sending file desciprtor to a temporary file. */
+ /* Message sending file descriptor to a temporary file. */
GOT_IMSG_TMPFD,
};
blob - ea806c36531cbc279e1c607c5ff3a998ccf0ac9a
blob + 3f36aa40237c2d0e4882f875245fa2ff09640633
--- lib/worktree.c
+++ lib/worktree.c
err = unlockerr;
return err;
}
-
-struct commitable {
- char *path;
- char *in_repo_path;
- char *ondisk_path;
- unsigned char status;
- struct got_object_id *blob_id;
- struct got_object_id *base_id;
- mode_t mode;
-};
static void
-free_commitable(struct commitable *ct)
+free_commitable(struct got_commitable *ct)
{
free(ct->path);
free(ct->in_repo_path);
{
struct collect_commitables_arg *a = arg;
const struct got_error *err = NULL;
- struct commitable *ct = NULL;
+ struct got_commitable *ct = NULL;
struct got_pathlist_entry *new = NULL;
char *parent_path = NULL, *path = NULL;
struct stat sb;
}
static const struct got_error *
-match_ct_parent_path(int *match, struct commitable *ct, const char *path)
+match_ct_parent_path(int *match, struct got_commitable *ct, const char *path)
{
const struct got_error *err = NULL;
char *ct_parent_path = NULL;
}
static mode_t
-get_ct_file_mode(struct commitable *ct)
+get_ct_file_mode(struct got_commitable *ct)
{
return S_IFREG | (ct->mode & ((S_IRWXU | S_IRWXG | S_IRWXO)));
}
static const struct got_error *
alloc_modified_blob_tree_entry(struct got_tree_entry **new_te,
- struct got_tree_entry *te, struct commitable *ct)
+ struct got_tree_entry *te, struct got_commitable *ct)
{
const struct got_error *err = NULL;
static const struct got_error *
alloc_added_blob_tree_entry(struct got_tree_entry **new_te,
- struct commitable *ct)
+ struct got_commitable *ct)
{
const struct got_error *err = NULL;
char *ct_name;
}
static const struct got_error *
-report_ct_status(struct commitable *ct,
+report_ct_status(struct got_commitable *ct,
got_worktree_status_cb status_cb, void *status_arg)
{
const char *ct_path = ct->path;
return got_error_prefix_errno("asprintf");
TAILQ_FOREACH(pe, commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
*modified = got_path_is_child(ct->in_repo_path, te_path,
strlen(te_path));
if (*modified)
}
static const struct got_error *
-match_deleted_or_modified_ct(struct commitable **ctp,
+match_deleted_or_modified_ct(struct got_commitable **ctp,
struct got_tree_entry *te, const char *base_tree_path,
struct got_pathlist_head *commitable_paths)
{
*ctp = NULL;
TAILQ_FOREACH(pe, commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
char *ct_name = NULL;
int path_matches;
/* Insert, and recurse into, newly added entries first. */
TAILQ_FOREACH(pe, commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
char *child_path = NULL, *slash;
if (ct->status != GOT_STATUS_ADD)
/* Handle modified and deleted entries. */
base_entries = got_object_tree_get_entries(base_tree);
SIMPLEQ_FOREACH(te, &base_entries->head, entry) {
- struct commitable *ct = NULL;
+ struct got_commitable *ct = NULL;
if (S_ISDIR(te->mode)) {
int modified;
TAILQ_FOREACH(pe, commitable_paths, entry) {
struct got_fileindex_entry *ie;
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
ie = got_fileindex_entry_get(fileindex, pe->path);
if (ie) {
}
static const struct got_error *
-check_ct_out_of_date(struct commitable *ct, struct got_repository *repo,
+check_ct_out_of_date(struct got_commitable *ct, struct got_repository *repo,
struct got_object_id *head_commit_id)
{
const struct got_error *err = NULL;
const struct got_error *
got_worktree_commit(struct got_object_id **new_commit_id,
struct got_worktree *worktree, const char *ondisk_path,
- const char *author, const char *committer, const char *logmsg,
+ const char *author, const char *committer,
+ got_worktree_commit_msg_cb commit_msg_cb, void *commit_arg,
got_worktree_status_cb status_cb, void *status_arg,
struct got_repository *repo)
{
struct got_object_id *new_tree_id = NULL;
struct got_object_id_queue parent_ids;
struct got_object_qid *pid = NULL;
+ char *logmsg = NULL;
*new_commit_id = NULL;
if (err)
goto done;
+ if (TAILQ_EMPTY(&commitable_paths)) {
+ err = got_error(GOT_ERR_COMMIT_NO_CHANGES);
+ goto done;
+ }
+
err = got_object_open_as_commit(&head_commit, repo, head_commit_id);
if (err)
goto done;
TAILQ_FOREACH(pe, &commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
err = check_ct_out_of_date(ct, repo, head_commit_id);
if (err)
goto done;
if (err)
goto done;
- /* TODO: collect commit message if not specified */
+ if (commit_msg_cb != NULL) {
+ err = commit_msg_cb(&commitable_paths, &logmsg, commit_arg);
+ if (err)
+ goto done;
+ }
+ if (logmsg == NULL || strlen(logmsg) == 0) {
+ err = got_error(GOT_ERR_COMMIT_MSG_EMPTY);
+ goto done;
+ }
+
/* Create blobs from added and modified files and record their IDs. */
TAILQ_FOREACH(pe, &commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
char *ondisk_path;
if (ct->status != GOT_STATUS_ADD &&
err = got_object_commit_create(new_commit_id, new_tree_id, &parent_ids,
1, author, time(NULL), committer, time(NULL), logmsg, repo);
got_object_qid_free(pid);
+ if (logmsg != NULL)
+ free(logmsg);
if (err)
goto done;
if (unlockerr && err == NULL)
err = unlockerr;
TAILQ_FOREACH(pe, &commitable_paths, entry) {
- struct commitable *ct = pe->data;
+ struct got_commitable *ct = pe->data;
free_commitable(ct);
}
got_pathlist_free(&commitable_paths);