refs: atomically record overwritten ref in update_symref

When updating a symref with update_symref it's currently not possible to know for sure what was the previous value that was overwritten. Extend refs_update_symref under a new function name, to record the value after the ref has been locked if the caller of refs_update_symref_extended requests it via a new variable in the function call. Make the return value of the function notify the caller, if the previous value was actually not a symbolic reference. Keep the original refs_update_symref function with the same signature, but now as a wrapper around refs_update_symref_extended. Signed-off-by: Bence Ferdinandy <bence@ferdinandy.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Bence Ferdinandy committed Nov 22, 2024 at 13:28 UTC d842cd130122c6f3d3019d467040570d7d001f7a
2 files changed +24 -2
refs.c
+20 -2
@@ -2115,6 +2115,13 @@ int peel_iterated_oid(struct repository *r, const struct object_id *base, struct
2115
2116 int refs_update_symref(struct ref_store *refs, const char *ref,
2117 const char *target, const char *logmsg)
2118 +{
2119 + return refs_update_symref_extended(refs, ref, target, logmsg, NULL);
2120 +}
2121 +
2122 +int refs_update_symref_extended(struct ref_store *refs, const char *ref,
2123 + const char *target, const char *logmsg,
2124 + struct strbuf *referent)
2125 {
2126 struct ref_transaction *transaction;
2127 struct strbuf err = STRBUF_INIT;
@@ -2125,10 +2132,22 @@ int refs_update_symref(struct ref_store *refs, const char *ref,
2132 ref_transaction_update(transaction, ref, NULL, NULL,
2133 target, NULL, REF_NO_DEREF,
2134 logmsg, &err) ||
2128 - ref_transaction_commit(transaction, &err)) {
2135 + ref_transaction_prepare(transaction, &err)) {
2136 ret = error("%s", err.buf);
2137 + goto cleanup;
2138 }
2139 + if (referent && refs_read_symbolic_ref(refs, ref, referent) == NOT_A_SYMREF) {
2140 + struct object_id oid;
2141 + if (!refs_read_ref(refs, ref, &oid)) {
2142 + strbuf_addstr(referent, oid_to_hex(&oid));
2143 + ret = NOT_A_SYMREF;
2144 + }
2145 + }
2146 +
2147 + if (ref_transaction_commit(transaction, &err))
2148 + ret = error("%s", err.buf);
2149
2150 +cleanup:
2151 strbuf_release(&err);
2152 if (transaction)
2153 ref_transaction_free(transaction);
@@ -2948,4 +2967,3 @@ int ref_update_expects_existing_old_ref(struct ref_update *update)
2967 return (update->flags & REF_HAVE_OLD) &&
2968 (!is_null_oid(&update->old_oid) || update->old_target);
2969 }
2951 -
refs.h
+4
@@ -584,6 +584,10 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
584 int refs_update_symref(struct ref_store *refs, const char *refname,
585 const char *target, const char *logmsg);
586
587 +int refs_update_symref_extended(struct ref_store *refs, const char *refname,
588 + const char *target, const char *logmsg,
589 + struct strbuf *referent);
590 +
591 enum action_on_err {
592 UPDATE_REFS_MSG_ON_ERR,
593 UPDATE_REFS_DIE_ON_ERR,