Skip to content

Commit 644daf7

Browse files
KarthikNayakgitster
authored andcommitted
refs: add support for transactional symref updates
The reference backends currently support transactional reference updates. While this is exposed to users via 'git-update-ref' and its '--stdin' mode, it is also used internally within various commands. However, we do not support transactional updates of symrefs. This commit adds support for symrefs in both the 'files' and the 'reftable' backend. Here, we add and use `ref_update_has_null_new_value()`, a helper function which is used to check if there is a new_value in a reference update. The new value could either be a symref target `new_target` or a OID `new_oid`. We also add another common function `ref_update_check_old_target` which will be used to check if the update's old_target corresponds to a reference's current target. Now transactional updates (verify, create, delete, update) can be used for: - regular refs - symbolic refs - conversion of regular to symbolic refs and vice versa This also allows us to expose this to users via new commands in 'git-update-ref' in the future. Note that a dangling symref update does not record a new reflog entry, which is unchanged before and after this commit. Signed-off-by: Karthik Nayak <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent e9965ba commit 644daf7

File tree

4 files changed

+197
-40
lines changed

4 files changed

+197
-40
lines changed

refs.c

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)
12171217

12181218
for (i = 0; i < transaction->nr; i++) {
12191219
free(transaction->updates[i]->msg);
1220+
free((char *)transaction->updates[i]->new_target);
1221+
free((char *)transaction->updates[i]->old_target);
12201222
free(transaction->updates[i]);
12211223
}
12221224
free(transaction->updates);
@@ -1247,10 +1249,13 @@ struct ref_update *ref_transaction_add_update(
12471249

12481250
update->flags = flags;
12491251

1250-
if (flags & REF_HAVE_NEW)
1252+
update->new_target = xstrdup_or_null(new_target);
1253+
update->old_target = xstrdup_or_null(old_target);
1254+
if ((flags & REF_HAVE_NEW) && new_oid)
12511255
oidcpy(&update->new_oid, new_oid);
1252-
if (flags & REF_HAVE_OLD)
1256+
if ((flags & REF_HAVE_OLD) && old_oid)
12531257
oidcpy(&update->old_oid, old_oid);
1258+
12541259
update->msg = normalize_reflog_message(msg);
12551260
return update;
12561261
}
@@ -1286,6 +1291,7 @@ int ref_transaction_update(struct ref_transaction *transaction,
12861291
flags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;
12871292

12881293
flags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);
1294+
flags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);
12891295

12901296
ref_transaction_add_update(transaction, refname, flags,
12911297
new_oid, old_oid, new_target,
@@ -2822,3 +2828,30 @@ const char *ref_update_original_update_refname(struct ref_update *update)
28222828

28232829
return update->refname;
28242830
}
2831+
2832+
int ref_update_has_null_new_value(struct ref_update *update)
2833+
{
2834+
return !update->new_target && is_null_oid(&update->new_oid);
2835+
}
2836+
2837+
int ref_update_check_old_target(const char *referent, struct ref_update *update,
2838+
struct strbuf *err)
2839+
{
2840+
if (!update->old_target)
2841+
BUG("called without old_target set");
2842+
2843+
if (!strcmp(referent, update->old_target))
2844+
return 0;
2845+
2846+
if (!strcmp(referent, ""))
2847+
strbuf_addf(err, "verifying symref target: '%s': "
2848+
"reference is missing but expected %s",
2849+
ref_update_original_update_refname(update),
2850+
update->old_target);
2851+
else
2852+
strbuf_addf(err, "verifying symref target: '%s': "
2853+
"is at %s but expected %s",
2854+
ref_update_original_update_refname(update),
2855+
referent, update->old_target);
2856+
return -1;
2857+
}

refs/files-backend.c

Lines changed: 92 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2394,8 +2394,9 @@ static int split_symref_update(struct ref_update *update,
23942394

23952395
new_update = ref_transaction_add_update(
23962396
transaction, referent, new_flags,
2397-
&update->new_oid, &update->old_oid,
2398-
NULL, NULL, update->msg);
2397+
update->new_target ? NULL : &update->new_oid,
2398+
update->old_target ? NULL : &update->old_oid,
2399+
update->new_target, update->old_target, update->msg);
23992400

24002401
new_update->parent_update = update;
24012402

@@ -2483,7 +2484,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
24832484

24842485
files_assert_main_repository(refs, "lock_ref_for_update");
24852486

2486-
if ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))
2487+
if ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))
24872488
update->flags |= REF_DELETING;
24882489

24892490
if (head_ref) {
@@ -2526,7 +2527,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
25262527
ret = TRANSACTION_GENERIC_ERROR;
25272528
goto out;
25282529
}
2529-
} else if (check_old_oid(update, &lock->old_oid, err)) {
2530+
}
2531+
2532+
if (update->old_target) {
2533+
if (ref_update_check_old_target(referent.buf, update, err)) {
2534+
ret = TRANSACTION_GENERIC_ERROR;
2535+
goto out;
2536+
}
2537+
} else if (check_old_oid(update, &lock->old_oid, err)) {
25302538
ret = TRANSACTION_GENERIC_ERROR;
25312539
goto out;
25322540
}
@@ -2547,7 +2555,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
25472555
} else {
25482556
struct ref_update *parent_update;
25492557

2550-
if (check_old_oid(update, &lock->old_oid, err)) {
2558+
/*
2559+
* Even if the ref is a regular ref, if `old_target` is set, we
2560+
* check the referent value. Ideally `old_target` should only
2561+
* be set for symrefs, but we're strict about its usage.
2562+
*/
2563+
if (update->old_target) {
2564+
if (ref_update_check_old_target(referent.buf, update, err)) {
2565+
ret = TRANSACTION_GENERIC_ERROR;
2566+
goto out;
2567+
}
2568+
} else if (check_old_oid(update, &lock->old_oid, err)) {
25512569
ret = TRANSACTION_GENERIC_ERROR;
25522570
goto out;
25532571
}
@@ -2565,9 +2583,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,
25652583
}
25662584
}
25672585

2568-
if ((update->flags & REF_HAVE_NEW) &&
2569-
!(update->flags & REF_DELETING) &&
2570-
!(update->flags & REF_LOG_ONLY)) {
2586+
if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
2587+
if (create_symref_lock(refs, lock, update->refname,
2588+
update->new_target, err)) {
2589+
ret = TRANSACTION_GENERIC_ERROR;
2590+
goto out;
2591+
}
2592+
2593+
if (close_ref_gently(lock)) {
2594+
strbuf_addf(err, "couldn't close '%s.lock'",
2595+
update->refname);
2596+
ret = TRANSACTION_GENERIC_ERROR;
2597+
goto out;
2598+
}
2599+
2600+
/*
2601+
* Once we have created the symref lock, the commit
2602+
* phase of the transaction only needs to commit the lock.
2603+
*/
2604+
update->flags |= REF_NEEDS_COMMIT;
2605+
} else if ((update->flags & REF_HAVE_NEW) &&
2606+
!(update->flags & REF_DELETING) &&
2607+
!(update->flags & REF_LOG_ONLY)) {
25712608
if (!(update->type & REF_ISSYMREF) &&
25722609
oideq(&lock->old_oid, &update->new_oid)) {
25732610
/*
@@ -2830,6 +2867,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,
28302867
return ret;
28312868
}
28322869

2870+
static int parse_and_write_reflog(struct files_ref_store *refs,
2871+
struct ref_update *update,
2872+
struct ref_lock *lock,
2873+
struct strbuf *err)
2874+
{
2875+
if (update->new_target) {
2876+
/*
2877+
* We want to get the resolved OID for the target, to ensure
2878+
* that the correct value is added to the reflog.
2879+
*/
2880+
if (!refs_resolve_ref_unsafe(&refs->base, update->new_target,
2881+
RESOLVE_REF_READING,
2882+
&update->new_oid, NULL)) {
2883+
/*
2884+
* TODO: currently we skip creating reflogs for dangling
2885+
* symref updates. It would be nice to capture this as
2886+
* zero oid updates however.
2887+
*/
2888+
return 0;
2889+
}
2890+
}
2891+
2892+
if (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,
2893+
&update->new_oid, update->msg, update->flags, err)) {
2894+
char *old_msg = strbuf_detach(err, NULL);
2895+
2896+
strbuf_addf(err, "cannot update the ref '%s': %s",
2897+
lock->ref_name, old_msg);
2898+
free(old_msg);
2899+
unlock_ref(lock);
2900+
update->backend_data = NULL;
2901+
return -1;
2902+
}
2903+
2904+
return 0;
2905+
}
2906+
28332907
static int files_transaction_finish(struct ref_store *ref_store,
28342908
struct ref_transaction *transaction,
28352909
struct strbuf *err)
@@ -2860,23 +2934,20 @@ static int files_transaction_finish(struct ref_store *ref_store,
28602934

28612935
if (update->flags & REF_NEEDS_COMMIT ||
28622936
update->flags & REF_LOG_ONLY) {
2863-
if (files_log_ref_write(refs,
2864-
lock->ref_name,
2865-
&lock->old_oid,
2866-
&update->new_oid,
2867-
update->msg, update->flags,
2868-
err)) {
2869-
char *old_msg = strbuf_detach(err, NULL);
2870-
2871-
strbuf_addf(err, "cannot update the ref '%s': %s",
2872-
lock->ref_name, old_msg);
2873-
free(old_msg);
2874-
unlock_ref(lock);
2875-
update->backend_data = NULL;
2937+
if (parse_and_write_reflog(refs, update, lock, err)) {
28762938
ret = TRANSACTION_GENERIC_ERROR;
28772939
goto cleanup;
28782940
}
28792941
}
2942+
2943+
/*
2944+
* We try creating a symlink, if that succeeds we continue to the
2945+
* next update. If not, we try and create a regular symref.
2946+
*/
2947+
if (update->new_target && prefer_symlink_refs)
2948+
if (!create_ref_symlink(lock, update->new_target))
2949+
continue;
2950+
28802951
if (update->flags & REF_NEEDS_COMMIT) {
28812952
clear_loose_ref_cache(refs);
28822953
if (commit_ref(lock)) {

refs/refs-internal.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,4 +754,20 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor
754754
*/
755755
const char *ref_update_original_update_refname(struct ref_update *update);
756756

757+
/*
758+
* Helper function to check if the new value is null, this
759+
* takes into consideration that the update could be a regular
760+
* ref or a symbolic ref.
761+
*/
762+
int ref_update_has_null_new_value(struct ref_update *update);
763+
764+
/*
765+
* Check whether the old_target values stored in update are consistent
766+
* with the referent, which is the symbolic reference's current value.
767+
* If everything is OK, return 0; otherwise, write an error message to
768+
* err and return -1.
769+
*/
770+
int ref_update_check_old_target(const char *referent, struct ref_update *update,
771+
struct strbuf *err);
772+
757773
#endif /* REFS_REFS_INTERNAL_H */

refs/reftable-backend.c

Lines changed: 54 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -843,7 +843,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
843843
* There is no need to write the reference deletion
844844
* when the reference in question doesn't exist.
845845
*/
846-
if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {
846+
if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
847847
ret = queue_transaction_update(refs, tx_data, u,
848848
&current_oid, err);
849849
if (ret)
@@ -894,8 +894,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
894894
* intertwined with the locking in files-backend.c.
895895
*/
896896
new_update = ref_transaction_add_update(
897-
transaction, referent.buf, new_flags,
898-
&u->new_oid, &u->old_oid, NULL, NULL, u->msg);
897+
transaction, referent.buf, new_flags,
898+
&u->new_oid, &u->old_oid, u->new_target,
899+
u->old_target, u->msg);
900+
899901
new_update->parent_update = u;
900902

901903
/*
@@ -925,7 +927,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
925927
* individual refs. But the error messages match what the files
926928
* backend returns, which keeps our tests happy.
927929
*/
928-
if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {
930+
if (u->old_target) {
931+
if (ref_update_check_old_target(referent.buf, u, err)) {
932+
ret = -1;
933+
goto done;
934+
}
935+
} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
929936
if (is_null_oid(&u->old_oid))
930937
strbuf_addf(err, _("cannot lock ref '%s': "
931938
"reference already exists"),
@@ -1030,7 +1037,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
10301037
* - `core.logAllRefUpdates` tells us to create the reflog for
10311038
* the given ref.
10321039
*/
1033-
if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {
1040+
if ((u->flags & REF_HAVE_NEW) &&
1041+
!(u->type & REF_ISSYMREF) &&
1042+
ref_update_has_null_new_value(u)) {
10341043
struct reftable_log_record log = {0};
10351044
struct reftable_iterator it = {0};
10361045

@@ -1071,24 +1080,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
10711080
(u->flags & REF_FORCE_CREATE_REFLOG ||
10721081
should_write_log(&arg->refs->base, u->refname))) {
10731082
struct reftable_log_record *log;
1083+
int create_reflog = 1;
1084+
1085+
if (u->new_target) {
1086+
if (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,
1087+
RESOLVE_REF_READING, &u->new_oid, NULL)) {
1088+
/*
1089+
* TODO: currently we skip creating reflogs for dangling
1090+
* symref updates. It would be nice to capture this as
1091+
* zero oid updates however.
1092+
*/
1093+
create_reflog = 0;
1094+
}
1095+
}
10741096

1075-
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
1076-
log = &logs[logs_nr++];
1077-
memset(log, 0, sizeof(*log));
1078-
1079-
fill_reftable_log_record(log);
1080-
log->update_index = ts;
1081-
log->refname = xstrdup(u->refname);
1082-
memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);
1083-
memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);
1084-
log->value.update.message =
1085-
xstrndup(u->msg, arg->refs->write_options.block_size / 2);
1097+
if (create_reflog) {
1098+
ALLOC_GROW(logs, logs_nr + 1, logs_alloc);
1099+
log = &logs[logs_nr++];
1100+
memset(log, 0, sizeof(*log));
1101+
1102+
fill_reftable_log_record(log);
1103+
log->update_index = ts;
1104+
log->refname = xstrdup(u->refname);
1105+
memcpy(log->value.update.new_hash,
1106+
u->new_oid.hash, GIT_MAX_RAWSZ);
1107+
memcpy(log->value.update.old_hash,
1108+
tx_update->current_oid.hash, GIT_MAX_RAWSZ);
1109+
log->value.update.message =
1110+
xstrndup(u->msg, arg->refs->write_options.block_size / 2);
1111+
}
10861112
}
10871113

10881114
if (u->flags & REF_LOG_ONLY)
10891115
continue;
10901116

1091-
if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {
1117+
if (u->new_target) {
1118+
struct reftable_ref_record ref = {
1119+
.refname = (char *)u->refname,
1120+
.value_type = REFTABLE_REF_SYMREF,
1121+
.value.symref = (char *)u->new_target,
1122+
.update_index = ts,
1123+
};
1124+
1125+
ret = reftable_writer_add_ref(writer, &ref);
1126+
if (ret < 0)
1127+
goto done;
1128+
} else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {
10921129
struct reftable_ref_record ref = {
10931130
.refname = (char *)u->refname,
10941131
.update_index = ts,

0 commit comments

Comments
 (0)