Commit Diff


commit - a0275e4081617f9d84038b640e26fd0af5bbfb49
commit + 09f5bd908b0b901f2123f462e4800e76a3d1b10e
blob - 52d7a09962ece61d245acc31d840a4b52ab3d012
blob + 640ad150c74800a6bd02833ddf62dbb7d2c07004
--- include/got_error.h
+++ 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" },
 };
 
 /*
blob - e384ebc955c80bfb8377f98004ea75fd12568b56
blob + 45237270ff29274f7f93126f03f903dc7e87bbea
--- lib/worktree.c
+++ lib/worktree.c
@@ -2738,8 +2738,11 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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_
 	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,17 +2825,36 @@ got_worktree_commit(struct got_object_id **new_commit_
 	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)
 		goto done;
@@ -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;
 }