Commit 47531f47334d8b14f6f3ad30a780565c657e4698

Sebastian Henke 2019-10-11T12:44:09

refs: unlock unmodified refs on transaction commit Refs which are locked in a transaction without an altered target, still should to be unlocked on `git_transaction_commit`. `git_transaction_free` also unlocks refs but the moment of calling of `git_transaction_free` cannot be controlled in all situations. Some binding libs call `git_transaction_free` on garbage collection or not at all if the application exits before and don't provide public access to `git_transaction_free`. It is better to release locks as soon as possible.

diff --git a/src/transaction.c b/src/transaction.c
index 5dd7f42..7367d12 100644
--- a/src/transaction.c
+++ b/src/transaction.c
@@ -334,7 +334,13 @@ int git_transaction_commit(git_transaction *tx)
 				return error;
 		}
 
-		if (node->ref_type != GIT_REFERENCE_INVALID) {
+		if (node->ref_type == GIT_REFERENCE_INVALID) {
+			/* ref was locked but not modified */
+			if ((error = git_refdb_unlock(tx->db, node->payload, false, false, NULL, NULL, NULL)) < 0) {
+				return error;
+			}
+			node->committed = true;
+		} else {
 			if ((error = update_target(tx->db, node)) < 0)
 				return error;
 		}
diff --git a/tests/refs/transactions.c b/tests/refs/transactions.c
index d4ddf45..50c102a 100644
--- a/tests/refs/transactions.c
+++ b/tests/refs/transactions.c
@@ -129,3 +129,29 @@ void test_refs_transactions__error_on_locking_locked_ref(void)
 	git_transaction_free(g_tx_with_lock);
 	git_repository_free(g_repo_with_locking_tx);
 }
+
+void test_refs_transactions__commit_unlocks_unmodified_ref(void)
+{
+	git_transaction *second_tx;
+
+	cl_git_pass(git_transaction_new(&second_tx, g_repo));
+	cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master"));
+	cl_git_pass(git_transaction_commit(second_tx));
+
+	/* a transaction must now be able to get the lock */
+	cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
+
+	git_transaction_free(second_tx);
+}
+
+void test_refs_transactions__free_unlocks_unmodified_ref(void)
+{
+	git_transaction *second_tx;
+
+	cl_git_pass(git_transaction_new(&second_tx, g_repo));
+	cl_git_pass(git_transaction_lock_ref(second_tx, "refs/heads/master"));
+	git_transaction_free(second_tx);
+
+	/* a transaction must now be able to get the lock */
+	cl_git_pass(git_transaction_lock_ref(g_tx, "refs/heads/master"));
+}