try to detect concurrent commits; not perfect yet (see comments)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
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;
}