Subject: [PATCH 5/8] refs/files-backend: add support for symref updates

From: Karthik Nayak <[email-masked]>
Date: Sat, 30 Mar 2024 23:46:20 +0100

From: Karthik Nayak <[email-masked]>

Add support for transactional symbolic reference updates in the files
backend. This also adheres to the config of using symlinks for symbolic
references.

While this commit is setting up the files-backend to support symrefs in
transaction's. It will only be used in a consequent commit, when we wire
up the `update-symref` option for `git-update-ref`.

Signed-off-by: Karthik Nayak <[email-masked]>
---
 refs/files-backend.c | 45 +++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 42 insertions(+), 3 deletions(-)

diff --git a/refs/files-backend.c b/refs/files-backend.c
index 4dbe73c106..6b4cc80843 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -2323,7 +2323,7 @@ static int split_head_update(struct ref_update *update,
 			transaction, "HEAD",
 			update->flags | REF_LOG_ONLY | REF_NO_DEREF,
 			&update->new_oid, &update->old_oid,
-			update->msg, NULL);
+			update->msg, update->symref_target);
 
 	/*
 	 * Add "HEAD". This insertion is O(N) in the transaction
@@ -2386,7 +2386,7 @@ static int split_symref_update(struct ref_update *update,
 	new_update = ref_transaction_add_update(
 			transaction, referent, new_flags,
 			&update->new_oid, &update->old_oid,
-			update->msg, NULL);
+			update->msg, update->symref_target);
 
 	new_update->parent_update = update;
 
@@ -2396,7 +2396,7 @@ static int split_symref_update(struct ref_update *update,
 	 * done when new_update is processed.
 	 */
 	update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
-	update->flags &= ~REF_HAVE_OLD;
+	update->flags &= ~(REF_HAVE_OLD|REF_UPDATE_SYMREF);
 
 	/*
 	 * Add the referent. This insertion is O(N) in the transaction
@@ -2567,6 +2567,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
 		}
 	}
 
+	if (update->flags & REF_UPDATE_SYMREF) {
+		if (create_symref_lock(refs, lock, update->refname, update->symref_target)) {
+			ret = TRANSACTION_GENERIC_ERROR;
+			goto out;
+		}
+
+		if (close_ref_gently(lock)) {
+			strbuf_addf(err, "couldn't close '%s.lock'",
+				    update->refname);
+			ret = TRANSACTION_GENERIC_ERROR;
+			goto out;
+		}
+
+		/*
+		 * Once we have created the symref lock, the commit
+		 * phase of the transaction only needs to commit the lock.
+		 */
+		if (update->flags & REF_UPDATE_SYMREF)
+			update->flags |= REF_NEEDS_COMMIT;
+	}
+
 	if ((update->flags & REF_HAVE_NEW) &&
 	    !(update->flags & REF_DELETING) &&
 	    !(update->flags & REF_LOG_ONLY)) {
@@ -2862,6 +2883,14 @@ static int files_transaction_finish(struct ref_store *ref_store,
 
 		if (update->flags & REF_NEEDS_COMMIT ||
 		    update->flags & REF_LOG_ONLY) {
+			if (update->flags & REF_UPDATE_SYMREF) {
+				if (!refs_resolve_ref_unsafe(&refs->base, update->symref_target,
+							     RESOLVE_REF_READING, &update->new_oid, NULL)) {
+					strbuf_addf(err, "refname %s not found", update->symref_target);
+					goto cleanup;
+				}
+			}
+
 			if (files_log_ref_write(refs,
 						lock->ref_name,
 						&lock->old_oid,
@@ -2879,6 +2908,16 @@ static int files_transaction_finish(struct ref_store *ref_store,
 				goto cleanup;
 			}
 		}
+
+		/*
+		 * We try creating a symlink, if that succeeds we continue to the
+		 * next updated. If not, we try and create a regular symref.
+		 */
+		if (update->flags & REF_UPDATE_SYMREF && prefer_symlink_refs)
+			if (!create_ref_symlink(lock, update->symref_target))
+				continue;
+
+
 		if (update->flags & REF_NEEDS_COMMIT) {
 			clear_loose_ref_cache(refs);
 			if (commit_ref(lock)) {
-- 
2.43.GIT