Commit de18fc635cbce498d8f11d0b994a9de1821760bb

Stefan Sperling 2019-05-08T18:37:37

write commit objects

diff --git a/got/got.c b/got/got.c
index f48a524..a39387a 100644
--- a/got/got.c
+++ b/got/got.c
@@ -2132,7 +2132,8 @@ cmd_commit(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	error = got_worktree_commit(&id, worktree, path, logmsg, repo);
+	error = got_worktree_commit(&id, worktree, path,
+	    "Stefan Sperling <stsp@stsp.name>", NULL, logmsg, repo);
 	if (error)
 		goto done;
 
diff --git a/include/got_worktree.h b/include/got_worktree.h
index debcb30..27d9c4b 100644
--- a/include/got_worktree.h
+++ b/include/got_worktree.h
@@ -171,10 +171,10 @@ const struct got_error *got_worktree_revert(struct got_worktree *,
  * The worktree's base commit will be set to this new commit.
  * Files unaffected by this commit operation will retain their
  * current base commit.
- * A non-empty log message must be specified.
+ * An author and a non-empty log message must be specified.
+ * The name of the committer is optional (may be NULL).
  * If an on-disk path is specified, only commit changes at or within this path.
  */
-const struct got_error *
-got_worktree_commit(struct got_object_id **,
-    struct got_worktree *, const char *, const char *,
-    struct got_repository *);
+const struct got_error *got_worktree_commit(struct got_object_id **,
+    struct got_worktree *, const char *, const char *, const char *,
+    const char *, struct got_repository *);
diff --git a/lib/got_lib_object_create.h b/lib/got_lib_object_create.h
index 4d00479..011e79e 100644
--- a/lib/got_lib_object_create.h
+++ b/lib/got_lib_object_create.h
@@ -18,3 +18,7 @@ const struct got_error *got_object_blob_create(struct got_object_id **,
     const char *, struct got_repository *);
 const struct got_error *got_object_tree_create(struct got_object_id **,
     struct got_tree_entries *, struct got_repository *);
+const struct got_error *got_object_commit_create(struct got_object_id **,
+    struct got_object_id *, struct got_object_id_queue *, int,
+    const char *, time_t, const char *, time_t, const char *,
+    struct got_repository *);
diff --git a/lib/object_create.c b/lib/object_create.c
index 8c94148..6e0d389 100644
--- a/lib/object_create.c
+++ b/lib/object_create.c
@@ -304,3 +304,165 @@ done:
 	}
 	return err;
 }
+
+const struct got_error *
+got_object_commit_create(struct got_object_id **id,
+    struct got_object_id *tree_id, struct got_object_id_queue *parent_ids,
+    int nparents, const char *author, time_t author_time,
+    const char *committer, time_t committer_time,
+    const char *logmsg, struct got_repository *repo)
+{
+	const struct got_error *err = NULL;
+	SHA1_CTX sha1_ctx;
+	char *header = NULL, *tree_str = NULL;
+	char *author_str = NULL, *committer_str = NULL;
+	char *id_str = NULL;
+	size_t headerlen, len = 0, n;
+	FILE *commitfile = NULL;
+	struct got_object_qid *qid;
+
+	*id = NULL;
+
+	SHA1Init(&sha1_ctx);
+
+	if (asprintf(&author_str, "%s%s %lld +0000\n",
+	    GOT_COMMIT_LABEL_AUTHOR, author, author_time) == -1)
+		return got_error_from_errno();
+
+	if (asprintf(&committer_str, "%s%s %lld +0000\n",
+	    GOT_COMMIT_LABEL_COMMITTER, committer ? committer : author,
+	    committer ? committer_time : author_time)
+	    == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	len = strlen(GOT_COMMIT_LABEL_TREE) + SHA1_DIGEST_STRING_LENGTH +
+	    nparents *
+	    (strlen(GOT_COMMIT_LABEL_PARENT) + SHA1_DIGEST_STRING_LENGTH) +
+	    + strlen(author_str) + strlen(committer_str) + 2 + strlen(logmsg);
+
+	if (asprintf(&header, "%s %zd", GOT_OBJ_LABEL_COMMIT, len) == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	headerlen = strlen(header) + 1;
+	SHA1Update(&sha1_ctx, header, headerlen);
+
+	commitfile = got_opentemp();
+	if (commitfile == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	n = fwrite(header, 1, headerlen, commitfile);
+	if (n != headerlen) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	err = got_object_id_str(&id_str, tree_id);
+	if (err)
+		goto done;
+	if (asprintf(&tree_str, "%s%s\n", GOT_COMMIT_LABEL_TREE, id_str)
+	    == -1) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	len = strlen(tree_str);
+	SHA1Update(&sha1_ctx, tree_str, len);
+	n = fwrite(tree_str, 1, len, commitfile);
+	if (n != len) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	SIMPLEQ_FOREACH(qid, parent_ids, entry) {
+		char *parent_str = NULL;
+
+		free(id_str);
+
+		err = got_object_id_str(&id_str, qid->id);
+		if (err)
+			goto done;
+		if (asprintf(&parent_str, "%s%s\n", GOT_COMMIT_LABEL_PARENT,
+		    id_str) == -1) {
+			err = got_error_from_errno();
+			goto done;
+		}
+		len = strlen(parent_str);
+		SHA1Update(&sha1_ctx, parent_str, len);
+		n = fwrite(parent_str, 1, len, commitfile);
+		if (n != len) {
+			err = got_ferror(commitfile, GOT_ERR_IO);
+			free(parent_str);
+			goto done;
+		}
+		free(parent_str);
+	}
+
+	len = strlen(author_str);
+	SHA1Update(&sha1_ctx, author_str, len);
+	n = fwrite(author_str, 1, len, commitfile);
+	if (n != len) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	len = strlen(committer_str);
+	SHA1Update(&sha1_ctx, committer_str, len);
+	n = fwrite(committer_str, 1, len, commitfile);
+	if (n != len) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	SHA1Update(&sha1_ctx, "\n", 1);
+	n = fwrite("\n", 1, 1, commitfile);
+	if (n != 1) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	len = strlen(logmsg);
+	SHA1Update(&sha1_ctx, logmsg, len);
+	n = fwrite(logmsg, 1, len, commitfile);
+	if (n != len) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	SHA1Update(&sha1_ctx, "\n", 1);
+	n = fwrite("\n", 1, 1, commitfile);
+	if (n != 1) {
+		err = got_ferror(commitfile, GOT_ERR_IO);
+		goto done;
+	}
+
+	*id = malloc(sizeof(**id));
+	if (*id == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	SHA1Final((*id)->sha1, &sha1_ctx);
+
+	if (fflush(commitfile) != 0) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	rewind(commitfile);
+
+	err = create_object_file(*id, commitfile, repo);
+done:
+	free(header);
+	free(tree_str);
+	free(author_str);
+	free(committer_str);
+	if (commitfile && fclose(commitfile) != 0 && err == NULL)
+		err = got_error_from_errno();
+	if (err) {
+		free(*id);
+		*id = NULL;
+	}
+	return err;
+}
diff --git a/lib/worktree.c b/lib/worktree.c
index 90a9746..1bd890d 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -2598,6 +2598,7 @@ done:
 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, struct got_repository *repo)
 {
 	const struct got_error *err = NULL, *unlockerr = NULL;
@@ -2608,10 +2609,13 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	struct got_commit_object *base_commit = NULL;
 	struct got_tree_object *base_tree = NULL;
 	struct got_object_id *new_tree_id = NULL;
+	struct got_object_id_queue parent_ids;
+	struct got_object_qid *pid = NULL;
 
 	*new_commit_id = NULL;
 
 	TAILQ_INIT(&commitable_paths);
+	SIMPLEQ_INIT(&parent_ids);
 
 	if (ondisk_path) {
 		err = got_path_skip_common_ancestor(&relpath,
@@ -2632,6 +2636,8 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	if (err)
 		goto done;
 
+	/* TODO: out-of-dateness check */
+
 	/* TODO: collect commit message if not specified */
 
 	/* Create blobs from added and modified files and record their IDs. */
@@ -2667,8 +2673,13 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	if (err)
 		goto done;
 
-	/* TODO: Write new commit. */
-
+	err = got_object_qid_alloc(&pid, worktree->base_commit_id);
+	if (err)
+		goto done;
+	SIMPLEQ_INSERT_TAIL(&parent_ids, pid, entry);
+	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);
 done:
 	unlockerr = lock_worktree(worktree, LOCK_SH);
 	if (unlockerr && err == NULL)