Commit 09f5bd908b0b901f2123f462e4800e76a3d1b10e

Stefan Sperling 2019-05-09T15:33:57

try to detect concurrent commits; not perfect yet (see comments)

diff --git a/include/got_error.h b/include/got_error.h
index 52d7a09..640ad15 100644
--- a/include/got_error.h
+++ b/include/got_error.h
@@ -85,6 +85,7 @@
 #define GOT_ERR_COMMIT_CONFLICT	69
 #define GOT_ERR_BAD_REF_TYPE	70
 #define GOT_ERR_COMMIT_NO_AUTHOR 71
+#define GOT_ERR_COMMIT_HEAD_CHANGED 72
 
 static const struct got_error {
 	int code;
@@ -161,6 +162,8 @@ static const struct got_error {
 	{ GOT_ERR_COMMIT_CONFLICT,"cannot commit file in conflicted status" },
 	{ GOT_ERR_BAD_REF_TYPE,	"bad reference type" },
 	{ GOT_ERR_COMMIT_NO_AUTHOR,"GOT_AUTHOR environment variable is not set" },
+	{ GOT_ERR_COMMIT_HEAD_CHANGED, "branch head in repository has changed "
+	    "while commit was in progress" },
 };
 
 /*
diff --git a/lib/worktree.c b/lib/worktree.c
index e384ebc..4523727 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -2738,8 +2738,11 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	struct collect_commitables_arg cc_arg;
 	struct got_pathlist_head commitable_paths;
 	struct got_pathlist_entry *pe;
-	char *relpath = NULL;
+	char *relpath = NULL, *head_ref_str = NULL;
 	struct got_commit_object *base_commit = NULL;
+	struct got_object_id *head_commit_id = NULL;
+	struct got_reference *head_ref2 = NULL;
+	struct got_object_id *head_commit_id2 = NULL;
 	struct got_tree_object *base_tree = NULL;
 	struct got_object_id *new_tree_id = NULL;
 	struct got_object_id_queue parent_ids;
@@ -2761,6 +2764,11 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	if (err)
 		goto done;
 
+	/* XXX should re-read head ref here now that work tree is locked */
+	err = got_ref_resolve(&head_commit_id, repo, worktree->head_ref);
+	if (err)
+		goto done;
+
 	cc_arg.commitable_paths = &commitable_paths;
 	cc_arg.worktree = worktree;
 	cc_arg.repo = repo;
@@ -2817,16 +2825,35 @@ got_worktree_commit(struct got_object_id **new_commit_id,
 	if (err)
 		goto done;
 
-	err = got_worktree_set_base_commit_id(worktree, repo, *new_commit_id);
+	/* Check if a concurrent commit to our branch has occurred. */
+	/* XXX ideally we'd lock the reference file here to avoid a race */
+	head_ref_str = got_ref_to_str(worktree->head_ref);
+	if (head_ref_str == NULL) {
+		err = got_error_from_errno();
+		goto done;
+	}
+	err = got_ref_open(&head_ref2, repo, head_ref_str);
 	if (err)
 		goto done;
-
+	err = got_ref_resolve(&head_commit_id2, repo, head_ref2);
+	if (err)
+		goto done;
+	if (got_object_id_cmp(head_commit_id, head_commit_id2) != 0) {
+		err = got_error(GOT_ERR_COMMIT_HEAD_CHANGED);
+		goto done;
+	}
+	/* Update branch head in repository. */
 	err = got_ref_change_ref(worktree->head_ref, *new_commit_id);
 	if (err)
 		goto done;
 	err = got_ref_write(worktree->head_ref, repo);
 	if (err)
 		goto done;
+	/* XXX race has ended here */
+
+	err = got_worktree_set_base_commit_id(worktree, repo, *new_commit_id);
+	if (err)
+		goto done;
 
 	err = ref_base_commit(worktree, repo);
 	if (err)
@@ -2850,5 +2877,10 @@ done:
 	if (base_commit)
 		got_object_commit_close(base_commit);
 	free(relpath);
+	free(head_commit_id);
+	free(head_commit_id2);
+	free(head_ref_str);
+	if (head_ref2)
+		got_ref_close(head_ref2);
 	return err;
 }