Commit Diff


commit - bd14628f607b07c5bb661e33c69e4079811b4514
commit + 33ad4cbe5926c7fe36929934d68a000fe19dafa3
blob - 035d5aa5cd59668ae07b24cb317ccd24a40929f2
blob + 9f023e7c97fd57dec07e2583bf5cc55a3e12417b
--- got/got.c
+++ got/got.c
@@ -20,6 +20,7 @@
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/param.h>
+#include <sys/wait.h>
 
 #include <err.h>
 #include <errno.h>
@@ -31,6 +32,7 @@
 #include <unistd.h>
 #include <libgen.h>
 #include <time.h>
+#include <paths.h>
 
 #include "got_error.h"
 #include "got_object.h"
@@ -2144,6 +2146,160 @@ usage_commit(void)
 {
 	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 *
@@ -2154,7 +2310,7 @@ cmd_commit(int argc, char *argv[])
 	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;
 
@@ -2201,13 +2357,15 @@ cmd_commit(int argc, char *argv[])
 	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
@@ -87,6 +87,9 @@
 #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;
@@ -167,6 +170,8 @@ static const struct got_error {
 	    "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
@@ -30,6 +30,16 @@ struct got_worktree;
 #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
@@ -160,6 +170,15 @@ const struct got_error *got_worktree_revert(struct got
     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.
@@ -171,4 +190,5 @@ const struct got_error *got_worktree_revert(struct got
  */
 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
@@ -100,7 +100,7 @@ enum got_imsg_type {
 	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
@@ -2223,19 +2223,9 @@ done:
 		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);
@@ -2257,7 +2247,7 @@ collect_commitables(void *arg, unsigned char status, c
 {
 	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;
@@ -2366,7 +2356,7 @@ write_subtree(struct got_object_id **new_subtree_id,
 }
 
 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;
@@ -2387,14 +2377,14 @@ match_ct_parent_path(int *match, struct commitable *ct
 }
 
 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;
 
@@ -2422,7 +2412,7 @@ done:
 
 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;
@@ -2475,7 +2465,7 @@ insert_tree_entry(struct got_tree_entry *new_te,
 }
 
 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;
@@ -2500,7 +2490,7 @@ match_modified_subtree(int *modified, struct got_tree_
 		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)
@@ -2512,7 +2502,7 @@ match_modified_subtree(int *modified, struct got_tree_
 }
 
 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)
 {
@@ -2522,7 +2512,7 @@ match_deleted_or_modified_ct(struct commitable **ctp,
 	*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;
 
@@ -2573,7 +2563,7 @@ write_tree(struct got_object_id **new_tree_id,
 
 	/* 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)
@@ -2646,7 +2636,7 @@ write_tree(struct got_object_id **new_tree_id,
 		/* 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;
@@ -2734,7 +2724,7 @@ update_fileindex_after_commit(struct got_pathlist_head
 
 	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) {
@@ -2783,7 +2773,7 @@ done:
 }
 
 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;
@@ -2818,7 +2808,8 @@ check_ct_out_of_date(struct commitable *ct, struct got
 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)
 {
@@ -2837,6 +2828,7 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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;
 
@@ -2869,12 +2861,17 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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;
@@ -2884,11 +2881,20 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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 &&
@@ -2919,6 +2925,8 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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;
 
@@ -2964,7 +2972,7 @@ 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);