{"thread":{"id":"61235","subject":"[PATCH 0/8] update-ref: add support for update-symref option","startedAt":"2024-03-30T22:46:28Z","lastAt":"2024-05-13T17:15:58Z","messageCount":194,"participants":["Karthik Nayak","Junio C Hamano","Chris Torek","Patrick Steinhardt","Christian Couder","Phillip Wood","Jeff King","phillip.wood123@gmail.com"],"isPatch":true,"patchVersion":1,"patchTotal":8},"messages":[{"id":"491902","messageId":"20240330224623.579457-1-knayak@gitlab.com","threadId":"61235","inReplyTo":null,"subject":"[PATCH 0/8] update-ref: add support for update-symref option","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:15Z","receivedAt":"2024-03-30T22:46:28Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'git-update-ref(1)' command allows transactional reference updates.\nBut currently only supports regular reference updates. Meaning, if one\nwants to update HEAD (symbolic ref) in a transaction, there is no tool\nto do so.\n\nOne option to obtain transactional updates for the HEAD ref is to\nmanually create the HEAD.lock file and commit. This is intrusive, where\nthe user needs to mimic internal git behavior. Also, this only works\nwhen using the files backend.\n\nAt GitLab, we've been using the manual process till date, to allow users\nto set and change their default branch. But with the introduction of\nreftables as a reference backend, this becomes a necessity to be solved\nwithin git.\n\nThis patch series goes about introducing 'update-symref' command for\n'git-update-ref(1)'. This command allows the user to create symref in a\ntransaction similar to the 'update' command of 'git-update-ref(1)'. This\ncommand also works well with the existing 'no-deref' option.\n\nWe don't create a dedicated 'create-symref' since 'update-symref' can\nalso create symrefs. We don't create 'delete-symref', since the 'delete'\ncommand can also delete symrefs.\n\nKarthik Nayak (8):\n  files-backend: extract out `create_symref_lock`\n  reftable-backend: extract out `write_symref_with_log`\n  reftable-backend: move `write_symref_with_log` up\n  refs: accept symref in `ref_transaction_add_update`\n  refs/files-backend: add support for symref updates\n  refs/reftable-backend: add support for symref updates\n  refs: add 'update-symref' command to 'update-ref'\n  refs: support symrefs in 'reference-transaction' hook\n\n Documentation/git-update-ref.txt |  11 ++-\n Documentation/githooks.txt       |  13 ++-\n branch.c                         |   2 +-\n builtin/fast-import.c            |   6 +-\n builtin/fetch.c                  |   2 +-\n builtin/receive-pack.c           |   2 +-\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   2 +-\n builtin/update-ref.c             |  63 +++++++++++---\n refs.c                           |  37 ++++++---\n refs.h                           |   9 +-\n refs/files-backend.c             |  95 ++++++++++++++++-----\n refs/refs-internal.h             |  12 ++-\n refs/reftable-backend.c          | 136 ++++++++++++++++++-------------\n sequencer.c                      |   6 +-\n t/t0600-reffiles-backend.sh      |  30 +++++++\n t/t1400-update-ref.sh            | 127 +++++++++++++++++++++++++++++\n t/t1416-ref-transaction-hooks.sh |  27 ++++++\n walker.c                         |   2 +-\n 19 files changed, 464 insertions(+), 120 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"491903","messageId":"20240330224623.579457-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 1/8] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:16Z","receivedAt":"2024-03-30T22:46:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nSplit this into two individual functions `create_and_commit_symref` and\n`create_symref_locked`. This way we can create the symref lock and\ncommit it at different times. This will be used to provide symref\nsupport in `git-update-ref(1)`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..3f0f9521cb 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n+\t\treturn error(\"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\n+\t/* no error check; commit_ref will check ferror */\n+\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n \treturn 0;\n }\n \n@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"491904","messageId":"20240330224623.579457-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 2/8] reftable-backend: extract out `write_symref_with_log`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:17Z","receivedAt":"2024-03-30T22:46:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `write_create_symref_table`, creates a\n`reftable_ref_record` for a symref and adds it to the writer. Then it\nalso creates a log entry for the symref. It does all of this while also\nobtaining and using a new update index.\n\nWe extract out `write_symref_with_log` from this to provide the\nfunctionality of creating a symref without making changes to the update\nindex. This will be used to add `update-symref` option to the\n`git-update-ref` command.\n\nRename the `create` field to `arg` while we're here, as `create` is a\nbit misleading.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/reftable-backend.c | 41 +++++++++++++++++++++++++----------------\n 1 file changed, 25 insertions(+), 16 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e206d5a073..282a08e3cb 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1222,23 +1222,22 @@ struct write_create_symref_arg {\n \tconst char *logmsg;\n };\n \n-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n+static int write_symref_with_log(struct reftable_writer *writer,\n+\t\t\t\t struct write_create_symref_arg *arg,\n+\t\t\t\t uint64_t update_index)\n {\n-\tstruct write_create_symref_arg *create = cb_data;\n-\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n \tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)create->refname,\n+\t\t.refname = (char *)arg->refname,\n \t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)create->target,\n-\t\t.update_index = ts,\n+\t\t.value.symref = (char *)arg->target,\n+\t\t.update_index = update_index,\n \t};\n+\n \tstruct reftable_log_record log = {0};\n \tstruct object_id new_oid;\n \tstruct object_id old_oid;\n \tint ret;\n \n-\treftable_writer_set_limits(writer, ts, ts);\n-\n \tret = reftable_writer_add_ref(writer, &ref);\n \tif (ret)\n \t\treturn ret;\n@@ -1251,25 +1250,35 @@ static int write_create_symref_table(struct reftable_writer *writer, void *cb_da\n \t * not resolve for new repositories this ordering will ensure that this\n \t * never happens.\n \t */\n-\tif (!create->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n+\tif (!arg->logmsg ||\n+\t    !refs_resolve_ref_unsafe(&arg->refs->base, arg->target,\n \t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&create->refs->base, create->refname))\n+\t    !should_write_log(&arg->refs->base, arg->refname))\n \t\treturn 0;\n \n \tfill_reftable_log_record(&log);\n-\tlog.refname = xstrdup(create->refname);\n-\tlog.update_index = ts;\n-\tlog.value.update.message = xstrndup(create->logmsg,\n-\t\t\t\t\t    create->refs->write_options.block_size / 2);\n+\tlog.refname = xstrdup(arg->refname);\n+\tlog.update_index = update_index;\n+\tlog.value.update.message = xstrndup(arg->logmsg,\n+\t\t\t\t\t    arg->refs->write_options.block_size / 2);\n \tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n+\tif (refs_resolve_ref_unsafe(&arg->refs->base, arg->refname,\n \t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n \t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n \n \tret = reftable_writer_add_log(writer, &log);\n \treftable_log_record_release(&log);\n \treturn ret;\n+\n+}\n+\n+static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n+{\n+\tstruct write_create_symref_arg *arg = cb_data;\n+\tuint64_t ts = reftable_stack_next_update_index(arg->stack);\n+\treftable_writer_set_limits(writer, ts, ts);\n+\n+\treturn write_symref_with_log(writer, arg, ts);\n }\n \n static int reftable_be_create_symref(struct ref_store *ref_store,\n-- \n2.43.GIT\n\n"},{"id":"491905","messageId":"20240330224623.579457-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 3/8] reftable-backend: move `write_symref_with_log` up","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:18Z","receivedAt":"2024-03-30T22:46:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `write_symref_with_log` code will be consequently used to provide\nsymref creation functionality in transactions. To do this, we move the\ndeclaration up so it can be used accordingly.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/reftable-backend.c | 116 ++++++++++++++++++++--------------------\n 1 file changed, 58 insertions(+), 58 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 282a08e3cb..9b53d42541 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -588,6 +588,64 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+struct write_create_symref_arg {\n+\tstruct reftable_ref_store *refs;\n+\tstruct reftable_stack *stack;\n+\tconst char *refname;\n+\tconst char *target;\n+\tconst char *logmsg;\n+};\n+\n+static int write_symref_with_log(struct reftable_writer *writer,\n+\t\t\t\t struct write_create_symref_arg *arg,\n+\t\t\t\t uint64_t update_index)\n+{\n+\tstruct reftable_ref_record ref = {\n+\t\t.refname = (char *)arg->refname,\n+\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t.value.symref = (char *)arg->target,\n+\t\t.update_index = update_index,\n+\t};\n+\n+\tstruct reftable_log_record log = {0};\n+\tstruct object_id new_oid;\n+\tstruct object_id old_oid;\n+\tint ret;\n+\n+\tret = reftable_writer_add_ref(writer, &ref);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * Note that it is important to try and resolve the reference before we\n+\t * write the log entry. This is because `should_write_log()` will munge\n+\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n+\t * repository because it would be written into the config. As HEAD will\n+\t * not resolve for new repositories this ordering will ensure that this\n+\t * never happens.\n+\t */\n+\tif (!arg->logmsg ||\n+\t    !refs_resolve_ref_unsafe(&arg->refs->base, arg->target,\n+\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n+\t    !should_write_log(&arg->refs->base, arg->refname))\n+\t\treturn 0;\n+\n+\tfill_reftable_log_record(&log);\n+\tlog.refname = xstrdup(arg->refname);\n+\tlog.update_index = update_index;\n+\tlog.value.update.message = xstrndup(arg->logmsg,\n+\t\t\t\t\t    arg->refs->write_options.block_size / 2);\n+\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n+\tif (refs_resolve_ref_unsafe(&arg->refs->base, arg->refname,\n+\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n+\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n+\n+\tret = reftable_writer_add_log(writer, &log);\n+\treftable_log_record_release(&log);\n+\treturn ret;\n+\n+}\n+\n struct reftable_transaction_update {\n \tstruct ref_update *update;\n \tstruct object_id current_oid;\n@@ -1214,64 +1272,6 @@ static int reftable_be_pack_refs(struct ref_store *ref_store,\n \treturn ret;\n }\n \n-struct write_create_symref_arg {\n-\tstruct reftable_ref_store *refs;\n-\tstruct reftable_stack *stack;\n-\tconst char *refname;\n-\tconst char *target;\n-\tconst char *logmsg;\n-};\n-\n-static int write_symref_with_log(struct reftable_writer *writer,\n-\t\t\t\t struct write_create_symref_arg *arg,\n-\t\t\t\t uint64_t update_index)\n-{\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)arg->refname,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)arg->target,\n-\t\t.update_index = update_index,\n-\t};\n-\n-\tstruct reftable_log_record log = {0};\n-\tstruct object_id new_oid;\n-\tstruct object_id old_oid;\n-\tint ret;\n-\n-\tret = reftable_writer_add_ref(writer, &ref);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Note that it is important to try and resolve the reference before we\n-\t * write the log entry. This is because `should_write_log()` will munge\n-\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n-\t * repository because it would be written into the config. As HEAD will\n-\t * not resolve for new repositories this ordering will ensure that this\n-\t * never happens.\n-\t */\n-\tif (!arg->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&arg->refs->base, arg->target,\n-\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&arg->refs->base, arg->refname))\n-\t\treturn 0;\n-\n-\tfill_reftable_log_record(&log);\n-\tlog.refname = xstrdup(arg->refname);\n-\tlog.update_index = update_index;\n-\tlog.value.update.message = xstrndup(arg->logmsg,\n-\t\t\t\t\t    arg->refs->write_options.block_size / 2);\n-\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&arg->refs->base, arg->refname,\n-\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n-\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n-\n-\tret = reftable_writer_add_log(writer, &log);\n-\treftable_log_record_release(&log);\n-\treturn ret;\n-\n-}\n-\n static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n {\n \tstruct write_create_symref_arg *arg = cb_data;\n-- \n2.43.GIT\n\n"},{"id":"491906","messageId":"20240330224623.579457-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 4/8] refs: accept symref in `ref_transaction_add_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:19Z","receivedAt":"2024-03-30T22:46:32Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `ref_transaction_add_update` function obtains ref information and\nflags to create a `ref_update` and add it to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nsymref and process it. While we handle the processing in upcoming\ncommits. In this commit, let's add the paramater to the function.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  6 +++---\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  2 +-\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  2 +-\n builtin/update-ref.c    |  2 +-\n refs.c                  | 16 ++++++++--------\n refs.h                  |  9 +++++++--\n refs/files-backend.c    | 14 +++++++-------\n refs/refs-internal.h    | 12 +++++++++---\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  6 +++---\n walker.c                |  2 +-\n 14 files changed, 46 insertions(+), 35 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 621019fcf4..266dcb6a89 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\t0, msg, NULL, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 71a195ca22..96c3fd5a30 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1648,7 +1648,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   0, msg, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1688,8 +1688,8 @@ static void dump_tags(void)\n \t\tstrbuf_reset(&ref_name);\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n-\t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\tif (ref_transaction_update(transaction, ref_name.buf, &t->oid,\n+\t\t\t\t\t   NULL, 0, msg, NULL, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 46a793411a..5a8a58b6fa 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -667,7 +667,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     0, msg, NULL, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 56d8a77ed7..5318bc4b58 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1596,7 +1596,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n \t\t\t\t\t   0, \"push\",\n-\t\t\t\t\t   &err)) {\n+\t\t\t\t\t   NULL, &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tret = \"failed to update ref\";\n \t\t} else {\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..b3281758d0 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   0, NULL, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 19a7e06bf4..8de32535de 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -624,7 +624,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n-\t\t\t\t   reflog_msg.buf, &err) ||\n+\t\t\t\t   reflog_msg.buf, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tif (path)\n \t\t\tfprintf(stderr,\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 61338a01ec..3807cf4106 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -205,7 +205,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n-\t\t\t\t   msg, &err))\n+\t\t\t\t   msg, NULL, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..69b89a1aa2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,7 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n-\t\tconst char *msg)\n+\t\tconst char *msg, const char *symref)\n {\n \tstruct ref_update *update;\n \n@@ -1254,7 +1254,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n \t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err)\n+\t\t\t   const char *symref, struct strbuf *err)\n {\n \tassert(err);\n \n@@ -1280,7 +1280,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, msg, symref);\n \treturn 0;\n }\n \n@@ -1295,7 +1295,7 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), flags, msg, NULL, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1308,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      flags, msg, NULL, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1320,8 +1320,8 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \tif (!old_oid)\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n-\t\t\t\t      NULL, old_oid,\n-\t\t\t\t      flags, NULL, err);\n+\t\t\t\t      NULL, old_oid, flags,\n+\t\t\t\t      NULL, NULL, err);\n }\n \n int refs_update_ref(struct ref_store *refs, const char *msg,\n@@ -1336,7 +1336,7 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n \t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t\t\t\t   NULL, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex 298caf6c61..073653d1a4 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -694,13 +694,18 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  */\n #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n \n+/*\n+ * Used to denote a symbolic reference update.\n+ */\n+#define REF_UPDATE_SYMREF (1 << 12)\n+\n /*\n  * Bitmask of all of the flags that are allowed to be passed in to\n  * ref_transaction_update() and friends:\n  */\n #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n-\t REF_SKIP_REFNAME_VERIFICATION)\n+\t REF_SKIP_REFNAME_VERIFICATION | REF_UPDATE_SYMREF)\n \n /*\n  * Add a reference update to transaction. `new_oid` is the value that\n@@ -721,7 +726,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n \t\t\t   unsigned int flags, const char *msg,\n-\t\t\t   struct strbuf *err);\n+\t\t\t   const char *symref, struct strbuf *err);\n \n /*\n  * Add a reference creation to transaction. new_oid is the value that\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 3f0f9521cb..4dbe73c106 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,8 +1292,8 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n-\t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n+\t\t\t\t\t   iter->oid, NULL, REF_NO_DEREF,\n+\t\t\t\t\t   NULL, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n \n@@ -2323,7 +2323,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tupdate->msg, NULL);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2386,7 +2386,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tupdate->msg, NULL);\n \n \tnew_update->parent_update = update;\n \n@@ -2777,7 +2777,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL);\n \t\t}\n \t}\n \n@@ -3062,7 +3062,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..3fccf784d4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -134,6 +134,12 @@ struct ref_update {\n \tunsigned int type;\n \tchar *msg;\n \n+\t/*\n+\t * If (flags & REF_UPDATE_SYMREF), we update the reference to be a\n+\t * symbolic reference and the value is taken from this field.\n+\t */\n+\tchar *symref_target;\n+\n \t/*\n \t * If this ref_update was split off of a symref update via\n \t * split_symref_update(), then this member points at that\n@@ -164,8 +170,8 @@ int ref_update_reject_duplicates(struct string_list *refnames,\n /*\n  * Add a ref_update with the specified properties to transaction, and\n  * return a pointer to the new object. This function does not verify\n- * that refname is well-formed. new_oid and old_oid are only\n- * dereferenced if the REF_HAVE_NEW and REF_HAVE_OLD bits,\n+ * that refname is well-formed. new_oid, old_oid, symref are only\n+ * dereferenced if the REF_HAVE_NEW, REF_HAVE_OLD and REF_UPDATE_SYMREF bits,\n  * respectively, are set in flags.\n  */\n struct ref_update *ref_transaction_add_update(\n@@ -173,7 +179,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n-\t\tconst char *msg);\n+\t\tconst char *msg, const char *symref);\n \n /*\n  * Transaction states.\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 9b53d42541..92f2803e90 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -884,7 +884,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -963,7 +963,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 4e14fa6541..668ea683ee 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -614,7 +614,7 @@ static int fast_forward_to(struct repository *r,\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n \t\t\t\t   null_oid() : from,\n-\t\t\t\t   0, sb.buf, &err) ||\n+\t\t\t\t   0, sb.buf, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1232,7 +1232,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   0, sb.buf, NULL, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3750,7 +3750,7 @@ static int do_label(struct repository *r, const char *name, int len)\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n \t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t\t\t\t\t  NULL, 0, msg.buf, NULL, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex 65002a7220..33eef703ce 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -326,7 +326,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tif (ref_transaction_update(transaction, refname.buf,\n \t\t\t\t\t   oids + i, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n-\t\t\t\t\t   &err)) {\n+\t\t\t\t\t   NULL, &err)) {\n \t\t\terror(\"%s\", err.buf);\n \t\t\tgoto done;\n \t\t}\n-- \n2.43.GIT\n\n"},{"id":"491907","messageId":"20240330224623.579457-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 5/8] refs/files-backend: add support for symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:20Z","receivedAt":"2024-03-30T22:46:33Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd support for transactional symbolic reference updates in the files\nbackend. This also adheres to the config of using symlinks for symbolic\nreferences.\n\nWhile this commit is setting up the files-backend to support symrefs in\ntransaction's. It will only be used in a consequent commit, when we wire\nup the `update-symref` option for `git-update-ref`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 45 +++++++++++++++++++++++++++++++++++++++++---\n 1 file changed, 42 insertions(+), 3 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 4dbe73c106..6b4cc80843 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2323,7 +2323,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg, NULL);\n+\t\t\tupdate->msg, update->symref_target);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2386,7 +2386,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg, NULL);\n+\t\t\tupdate->msg, update->symref_target);\n \n \tnew_update->parent_update = update;\n \n@@ -2396,7 +2396,7 @@ static int split_symref_update(struct ref_update *update,\n \t * done when new_update is processed.\n \t */\n \tupdate->flags |= REF_LOG_ONLY | REF_NO_DEREF;\n-\tupdate->flags &= ~REF_HAVE_OLD;\n+\tupdate->flags &= ~(REF_HAVE_OLD|REF_UPDATE_SYMREF);\n \n \t/*\n \t * Add the referent. This insertion is O(N) in the transaction\n@@ -2567,6 +2567,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n+\tif (update->flags & REF_UPDATE_SYMREF) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->symref_target)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tif (update->flags & REF_UPDATE_SYMREF)\n+\t\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t}\n+\n \tif ((update->flags & REF_HAVE_NEW) &&\n \t    !(update->flags & REF_DELETING) &&\n \t    !(update->flags & REF_LOG_ONLY)) {\n@@ -2862,6 +2883,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n+\t\t\tif (update->flags & REF_UPDATE_SYMREF) {\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->symref_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n+\t\t\t\t\tstrbuf_addf(err, \"refname %s not found\", update->symref_target);\n+\t\t\t\t\tgoto cleanup;\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\tif (files_log_ref_write(refs,\n \t\t\t\t\t\tlock->ref_name,\n \t\t\t\t\t\t&lock->old_oid,\n@@ -2879,6 +2908,16 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next updated. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->flags & REF_UPDATE_SYMREF && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->symref_target))\n+\t\t\t\tcontinue;\n+\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\n-- \n2.43.GIT\n\n"},{"id":"491908","messageId":"20240330224623.579457-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 6/8] refs/reftable-backend: add support for symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:21Z","receivedAt":"2024-03-30T22:46:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd support for transactional symbolic reference updates in the reftable\nbackend.\n\nWhile this commit is setting up the reftable-backend to support symrefs\nin transaction's. It will only be used in a consequent commit, when we\nwire up the `update-symref` option for `git-update-ref`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/reftable-backend.c | 23 +++++++++++++++++++----\n 1 file changed, 19 insertions(+), 4 deletions(-)\n\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 92f2803e90..35f2e8e050 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -884,7 +884,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, u->symref_target);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -909,9 +909,11 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \n \t\t\t/*\n \t\t\t * There is no need to write the reference deletion\n-\t\t\t * when the reference in question doesn't exist.\n+\t\t\t * when the reference in question doesn't exist except\n+\t\t\t * when we want to create new symrefs.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\tif ((u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) ||\n+\t\t\t    u->flags & REF_UPDATE_SYMREF) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -963,7 +965,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, u->symref_target);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -1026,6 +1028,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t */\n \t\tif ((u->type & REF_ISSYMREF) ||\n \t\t    (u->flags & REF_LOG_ONLY) ||\n+\t\t    (u->flags & REF_UPDATE_SYMREF) ||\n \t\t    (u->flags & REF_HAVE_NEW && !oideq(&current_oid, &u->new_oid))) {\n \t\t\tret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t       &current_oid, err);\n@@ -1187,6 +1190,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\tret = reftable_writer_add_ref(writer, &ref);\n \t\t\tif (ret < 0)\n \t\t\t\tgoto done;\n+\t\t} else if (u->flags & REF_UPDATE_SYMREF) {\n+\t\t\tstruct write_create_symref_arg create  = {\n+\t\t\t\t.refs = arg->refs,\n+\t\t\t\t.stack = arg->stack,\n+\t\t\t\t.refname = u->refname,\n+\t\t\t\t.target = u->symref_target,\n+\t\t\t\t.logmsg = u->msg,\n+\t\t\t};\n+\n+\t\t\twrite_symref_with_log(writer, &create, ts);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n \t\t}\n \t}\n \n-- \n2.43.GIT\n\n"},{"id":"491909","messageId":"20240330224623.579457-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:22Z","receivedAt":"2024-03-30T22:46:35Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'git-update-ref(1)' command allows transactional reference updates.\nBut currently only supports regular reference updates. Meaning, if one\nwants to update HEAD (symbolic ref) in a transaction, there is no tool\nto do so.\n\nOne option to obtain transactional updates for the HEAD ref is to\nmanually create the HEAD.lock file and commit. This is intrusive, where\nthe user needs to mimic internal git behavior. Also, this only works\nwhen using the files backend.\n\nTo allow users to update the HEAD ref in a transaction, we introduce\n'update-symref' command for 'git-update-ref(1)'. This command allows the\nuser to create symref in a transaction similar to the 'update' command\nof 'git-update-ref(1)'. This command also works well with the existing\n'no-deref' option.\n\nThe option can also be used to create new symrefs too. This means we\ndon't need a dedicated `create-symref` option. This is also because we\ndon't verify the old symref value when updating a symref. So in this\ncase update and create hold the same meaning.\n\nThe regular `delete` option can also be used to delete symrefs. So we\ndon't add a dedicated `delete-symref` option.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  11 ++-\n builtin/update-ref.c             |  61 ++++++++++++---\n refs.c                           |  10 +++\n t/t0600-reffiles-backend.sh      |  30 ++++++++\n t/t1400-update-ref.sh            | 127 +++++++++++++++++++++++++++++++\n 5 files changed, 229 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 0561808cca..2ea8bc8167 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <newvalue> LF\n \tdelete SP <ref> [SP <oldvalue>] LF\n \tverify SP <ref> [SP <oldvalue>] LF\n+\tupdate-symref SP <ref> SP <newvalue> LF\n \toption SP <opt> LF\n \tstart LF\n \tprepare LF\n@@ -86,6 +87,7 @@ quoting:\n \tcreate SP <ref> NUL <newvalue> NUL\n \tdelete SP <ref> NUL [<oldvalue>] NUL\n \tverify SP <ref> NUL [<oldvalue>] NUL\n+\tupdate-symref NUL <ref> NUL <newvalue> NUL\n \toption SP <opt> NUL\n \tstart NUL\n \tprepare NUL\n@@ -111,12 +113,19 @@ create::\n \n delete::\n \tDelete <ref> after verifying it exists with <oldvalue>, if\n-\tgiven.  If given, <oldvalue> may not be zero.\n+\tgiven.  If given, <oldvalue> may not be zero. Can also delete\n+\tsymrefs.\n \n verify::\n \tVerify <ref> against <oldvalue> but do not change it.  If\n \t<oldvalue> is zero or missing, the ref must not exist.\n \n+update-symref::\n+\tUpdate <ref> as a symbolic reference to point to the given\n+\treference <newvalue>. By default, <ref> will be recursively\n+\tde-referenced, unless the `no-deref` option is used. Can also\n+\tbe used to create new symrefs.\n+\n option::\n \tModify the behavior of the next command naming a <ref>.\n \tThe only valid option is `no-deref` to avoid dereferencing\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 3807cf4106..357daf31b8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -213,6 +213,48 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+static void parse_cmd_update_symref(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *symref;\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"update-symref: missing <ref>\");\n+\n+\tif (line_termination) {\n+\t\t/* Without -z, consume SP and use next argument */\n+\t\tif (!*next || *next == line_termination)\n+\t\t\tdie(\"update-symref %s: missing <newvalue>\", refname);\n+\t\tif (*next != ' ')\n+\t\t\tdie(\"update-symref %s: expected SP but got: %s\",\n+\t\t\t    refname, next);\n+\t} else {\n+\t\t/* With -z, read the next NUL-terminated line */\n+\t\tif (*next)\n+\t\t\tdie(\"update-symref %s: missing <newvalue>\", refname);\n+\t}\n+\tnext++;\n+\n+\tsymref = parse_refname(&next);\n+\tif (!symref)\n+\t\tdie(\"update-symref %s: missing <newvalue>\", refname);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"update-symref %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_update(transaction, refname, NULL, NULL,\n+\t\t\t\t   update_flags | create_reflog_flag | REF_UPDATE_SYMREF,\n+\t\t\t\t   msg, symref, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(symref);\n+\tfree(refname);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_create(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -379,15 +421,16 @@ static const struct parse_cmd {\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n-\t{ \"update\",  parse_cmd_update,  3, UPDATE_REFS_OPEN },\n-\t{ \"create\",  parse_cmd_create,  2, UPDATE_REFS_OPEN },\n-\t{ \"delete\",  parse_cmd_delete,  2, UPDATE_REFS_OPEN },\n-\t{ \"verify\",  parse_cmd_verify,  2, UPDATE_REFS_OPEN },\n-\t{ \"option\",  parse_cmd_option,  1, UPDATE_REFS_OPEN },\n-\t{ \"start\",   parse_cmd_start,   0, UPDATE_REFS_STARTED },\n-\t{ \"prepare\", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },\n-\t{ \"abort\",   parse_cmd_abort,   0, UPDATE_REFS_CLOSED },\n-\t{ \"commit\",  parse_cmd_commit,  0, UPDATE_REFS_CLOSED },\n+\t{ \"update\",        parse_cmd_update,        3, UPDATE_REFS_OPEN },\n+\t{ \"update-symref\", parse_cmd_update_symref, 2, UPDATE_REFS_OPEN },\n+\t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n+\t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n+\t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n+\t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\n+\t{ \"prepare\",       parse_cmd_prepare,       0, UPDATE_REFS_PREPARED },\n+\t{ \"abort\",         parse_cmd_abort,         0, UPDATE_REFS_CLOSED },\n+\t{ \"commit\",        parse_cmd_commit,        0, UPDATE_REFS_CLOSED },\n };\n \n static void update_refs_stdin(void)\ndiff --git a/refs.c b/refs.c\nindex 69b89a1aa2..706dcd6deb 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1216,6 +1216,7 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \t}\n \n \tfor (i = 0; i < transaction->nr; i++) {\n+\t\tfree(transaction->updates[i]->symref_target);\n \t\tfree(transaction->updates[i]->msg);\n \t\tfree(transaction->updates[i]);\n \t}\n@@ -1235,6 +1236,9 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif ((flags & (REF_HAVE_NEW | REF_UPDATE_SYMREF)) == (REF_HAVE_NEW | REF_UPDATE_SYMREF))\n+\t\tBUG(\"cannot create regular ref and symref at once\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1245,6 +1249,8 @@ struct ref_update *ref_transaction_add_update(\n \t\toidcpy(&update->new_oid, new_oid);\n \tif (flags & REF_HAVE_OLD)\n \t\toidcpy(&update->old_oid, old_oid);\n+\tif (flags & REF_UPDATE_SYMREF)\n+\t\tupdate->symref_target = xstrdup(symref);\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -2337,6 +2343,10 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \n+\t\t// Reference transaction does not support symbolic updates.\n+\t\tif (update->flags & REF_UPDATE_SYMREF)\n+\t\t\tcontinue;\n+\n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n \t\t\t    oid_to_hex(&update->old_oid),\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 64214340e7..6d334cb477 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -472,4 +472,34 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '\n \tesac\n '\n \n+test_expect_success SYMLINKS 'symref transaction supports symlinks' '\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs true &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tupdate-symref TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_symlink .git/TESTSYMREFONE &&\n+\ttest \"$(test_readlink .git/TESTSYMREFONE)\" = refs/heads/new\n+'\n+\n+test_expect_success 'symref transaction supports false symlink config' '\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs false &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tupdate-symref TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_file .git/TESTSYMREFONE &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\techo refs/heads/new >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 6ebc3ef945..2a6036471b 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -868,6 +868,105 @@ test_expect_success 'stdin delete symref works flag --no-deref' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'stdin update-symref creates symref with --no-deref' '\n+\t# ensure that the symref does not already exist \n+\ttest_must_fail git symbolic-ref --no-recurse refs/heads/symref &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/symref $b\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\techo $b >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$Z $(git rev-parse $b)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref updates symref with --no-deref' '\n+\t# ensure that the symref already exists\n+\tgit symbolic-ref --no-recurse refs/heads/symref &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/symref $a\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $b) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref noop update with --no-deref' '\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\techo $a >expect &&\n+\ttest_cmp expect actual &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/symref $a\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref regular ref with --no-deref' '\n+\tgit update-ref refs/heads/regularref $a &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/regularref $a\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref creates symref' '\n+\t# delete the ref since it already exists from previous tests\n+\tgit update-ref --no-deref -d refs/heads/symref &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/symref $b\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\techo $b >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$Z $(git rev-parse $b)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref updates symref' '\n+\tgit update-ref refs/heads/symref2 $b &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/symref $a\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref2 >actual &&\n+\ttest_cmp expect actual &&\n+\techo refs/heads/symref2 >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $b) $(git rev-parse $b)\" actual\n+'\n+\n+test_expect_success 'stdin update-symref regular ref' '\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\tcat >stdin <<-EOF &&\n+\tupdate-symref refs/heads/regularref $a\n+\tEOF\n+\tgit update-ref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n test_expect_success 'stdin delete ref works with right old value' '\n \techo \"delete $b $m~1\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n@@ -1641,4 +1740,32 @@ test_expect_success PIPE 'transaction flushes status updates' '\n \ttest_cmp expected actual\n '\n \n+test_expect_success 'transaction can commit symref update' '\n+\tgit symbolic-ref TESTSYMREFONE $a &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tupdate-symref TESTSYMREFONE refs/heads/branch\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\techo refs/heads/branch >expect &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'transaction can abort symref update' '\n+\tgit symbolic-ref TESTSYMREFONE $a &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tupdate-symref TESTSYMREFONE refs/heads/branch\n+\tprepare\n+\tabort\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"491910","messageId":"20240330224623.579457-9-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH 8/8] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-03-30T22:46:23Z","receivedAt":"2024-03-30T22:46:36Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In the previous commit, we added support for the\n`update-symref` command in `git-update-ref`. While it allowed us to now\ncreate symbolic refs via `git-update-ref`, it didn't activate the\n'reference-transaction' hook.\n\nLet's activate the hook for symbolic reference updates too. This takes\nthe form `<symref-target> SP <ref-name> LF`, which deviates from the\nform for regular updates since this only contains two fields.\n\nWhile this seems to be backward incompatible, it is okay, since the only\nway the `reference-transaction` hook outputs this syntax is when\n`git-update-ref` is used with `update-symref` command. The command was\nonly introduced in the previous commit and hence only users of this\ncommand will face this incompatibility.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt       | 13 +++++++++++--\n refs.c                           | 17 +++++++++--------\n t/t1416-ref-transaction-hooks.sh | 27 +++++++++++++++++++++++++++\n 3 files changed, 47 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex 37f91d5b50..ae9f02974d 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -485,8 +485,7 @@ reference-transaction\n \n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n-committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+committed or aborted and may thus get called multiple times.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -513,6 +512,16 @@ to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For each symbolic reference update that was added to the transaction,\n+the hook receives on standard input a line of the format:\n+\n+  <symref-target> SP <ref-name> LF\n+\n+where `<symref-target>` is the target of the symbolic reference update\n+passed into the reference transaction, `<ref-name>` is the full name of\n+the ref being updated. To distinguish from the regular updates, we can\n+note that there are only two fields.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 706dcd6deb..d0929c5684 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2342,16 +2342,17 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tstrbuf_reset(&buf);\n \n-\t\t// Reference transaction does not support symbolic updates.\n \t\tif (update->flags & REF_UPDATE_SYMREF)\n-\t\t\tcontinue;\n-\n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\t\t\tstrbuf_addf(&buf, \"%s %s\\n\",\n+\t\t\t\t    update->symref_target,\n+\t\t\t\t    update->refname);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n+\t\t\t\t    oid_to_hex(&update->old_oid),\n+\t\t\t\t    oid_to_hex(&update->new_oid),\n+\t\t\t\t    update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..fac5d5fc6d 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -83,6 +83,33 @@ test_expect_success 'hook gets all queued updates in committed state' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'hook gets all queued symref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\tcat >expect <<-EOF &&\n+\t\tprepared\n+\t\trefs/heads/test TESTSYMREF\n+\t\trefs/heads/test refs/heads/symref\n+\t\tcommitted\n+\t\trefs/heads/test TESTSYMREF\n+\t\trefs/heads/test refs/heads/symref\n+\tEOF\n+\tgit update-ref --no-deref --stdin <<-EOF &&\n+\t\tstart\n+\t\tupdate-symref TESTSYMREF refs/heads/test\n+\t\tupdate-symref refs/heads/symref refs/heads/test\n+\t\tprepare\n+\t\tcommit\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'hook gets all queued updates in aborted state' '\n \ttest_when_finished \"rm actual\" &&\n \tgit reset --hard PRE &&\n-- \n2.43.GIT\n\n"},{"id":"491929","messageId":"xmqqy19yf40l.fsf@gitster.g","threadId":"61235","inReplyTo":"20240330224623.579457-8-knayak@gitlab.com","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-31T22:08:42Z","receivedAt":"2024-03-31T22:08:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 0561808cca..2ea8bc8167 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n\nIn pre-context, there is this entry.\n\n\tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n\nUnrelated to this patch, we probably should say <new-value> and\n<old-value> to follow the (recent) documentation guideline to write\nmultiple words concatenated with hyphen.\n\nIf we are updating these newvalue and oldvalue anyway, we may want\nto update them to <new-oid> and <old-oid>, as the existing commands\non refs are about object names, and what we are adding here are not.\n\nI would prefer to see such an update of existing documentation as a\nseparate preliminary clean-up patch, so that we can cleanly add the\n\"update-symref\" that takes <new-symref-target> on top of a\ncleaned-up base.\n\nThe <newvalue> ...\n\n>  \tcreate SP <ref> SP <newvalue> LF\n>  \tdelete SP <ref> [SP <oldvalue>] LF\n>  \tverify SP <ref> [SP <oldvalue>] LF\n> +\tupdate-symref SP <ref> SP <newvalue> LF\n\n... we are giving to \"update-symref\" is not an object name, but a\nrefname (i.e. the target of the symref at <ref> points at), so\n\"newvalue\" -> \"new-ref\" or something is needed.\n\nThe semantics of this new command is not quite in line with the\nexisting commands.  Existing \"update\" and \"delete\" allow you to\noptionally specify <old-oid> to make sure you do not overwrite\nsomebody else's update in the meantime.  Your \"update-symref\" lacks\nsuch safety completely, which I doubt is a good idea in the context\nof adding it to an existing set that has such safety features.  We\nshould at least offer the same \"optional\" safety.  That makes the\nsyntax more like\n\n\tupdate-symref SP <ref> SP <new-ref> [SP <old-ref>] LF\n\nprobably.\n\nExisting \"update\" command can be used to create (by having \"zero\"\n<old-oid>) and to delete (by having \"zero\" <new-oid>), but we still\nhave \"create\" and \"delete\" separately.  Given that we are teaching\nthe command to also work with symrefs by adding command(s) for\nsymrefs to an existing vocabulary, we should offer \"create-symref\"\nand \"delete-symref\" for consistency between refs and symrefs.\n\nIt probably is a good idea to allow \"update-symref\" to also create\nand delete a symref in similar ways with the existing \"update\"\nallows creation and deletion of a ref.  An \"zero\" <old-ref> may be\nan update that can happen only when the <ref> does not exist, i.e.,\ncreation, and an \"zero\" <new-ref> may be an update that makes <ref>\ndisappear.\n\n\"zero\" value in the context of refs is a 0{40} object name (side\nnote: we have an explicit mention of 40 in the doc, which needs to\nbe updated eventually, probably outside this series).  But in the\nnew context of symrefs, a safe \"zero\" value needs to be picked\ncarefully.  An empty string may not work well syntactically\nespecially in the `-z` format, because you cannot tell if\n\n\tupdate-symref NUL HEAD NUL refs/heads/main NUL NUL\n\nwants to say that <old-ref> is an empty string, or if it is missing.\nAs a refname cannot have a path component that begins with a dot,\na usable \"zero\" value for <new-ref> and <old-ref> may be a string\nlike \".missing\", \".detached\", and perhaps \".detached-f78d7...f2d4\".\n\nTo summarize:\n\n\tupdate-symref SP HEAD SP refs/heads/main LF\n\nforcibly sets HEAD to point at refs/heads/main without regard to the\ncurrent state of HEAD.\n\n\tupdate-symref SP HEAD SP .missing LF\n\nforcibly removes HEAD symref without regard to the current state of\nHEAD.\n\n\tupdate-symref SP HEAD SP refs/heads/main SP .missing LF\n\ncreates HEAD symref to point at the 'main' branch but fails if HEAD\nalready exists.\n\n\tupdate-symref SP HEAD SP refs/heads/main SP .detached LF\n\tupdate-symref SP HEAD SP refs/heads/main SP .detached-f78d7...f2d4 LF\n\ncreates HEAD symref to point at the 'main' branch but fails if HEAD\nis not detached (or detached at the specified commit).\n\n\tupdate-symref SP HEAD SP refs/heads/main SP refs/heads/next LF\n\ncreates HEAD symref to point at the 'main' branch but fails if HEAD\nis not pointing at the 'next' branch.\n\nHmm?\n"},{"id":"491932","messageId":"CAPx1GvdXdH3OdY1nC2ijVSdpWfg8jn5=j0KB+Wgv70wWeCNH5g@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqy19yf40l.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Chris Torek","fromEmail":"chris.torek@gmail.com","sentAt":"2024-03-31T22:27:24Z","receivedAt":"2024-03-31T22:27:37Z","isPatch":true,"sender":{"key":"chris.torek@gmail.com","avatar":"https://avatars.githubusercontent.com/u/16826774?v=4"},"body":"On Sun, Mar 31, 2024 at 3:09 PM Junio C Hamano <gitster@pobox.com> wrote:\n> ... we are giving to \"update-symref\" is not an object name, but a\n> refname (i.e. the target of the symref at <ref> points at), so\n> \"newvalue\" -> \"new-ref\" or something is needed.\n\nI kept this line just to say, I agree on all of the above ...\n\n> Existing \"update\" command can be used to create (by having \"zero\"\n> <old-oid>) and to delete (by having \"zero\" <new-oid>), but we still\n> have \"create\" and \"delete\" separately.  Given that we are teaching\n> the command to also work with symrefs by adding command(s) for\n> symrefs to an existing vocabulary, we should offer \"create-symref\"\n> and \"delete-symref\" for consistency between refs and symrefs.\n\n.. and on this, but:\n\n> It probably is a good idea to allow \"update-symref\" to also create\n> and delete a symref in similar ways with the existing \"update\"\n> allows creation and deletion of a ref.  An \"zero\" <old-ref> may be\n> an update that can happen only when the <ref> does not exist, i.e.,\n> creation, and an \"zero\" <new-ref> may be an update that makes <ref>\n> disappear.\n>\n> \"zero\" value in the context of refs is a 0{40} object name (side\n> note: we have an explicit mention of 40 in the doc, which needs to\n> be updated eventually, probably outside this series).  But in the\n> new context of symrefs, a safe \"zero\" value needs to be picked\n> carefully.  An empty string may not work well syntactically\n> especially in the `-z` format, because you cannot tell if\n>\n>         update-symref NUL HEAD NUL refs/heads/main NUL NUL\n>\n> wants to say that <old-ref> is an empty string, or if it is missing.\n\nFor these reasons, I'd suggest that the all-zero hash be officially\ndeprecated in favor of create/delete and of course create-symref\nand delete-symref. Of course, compatibility requires some sort\nof support for the old system for some time.  As to whether that\nmeans something like the suggestion of \".missing\" etc, I have no\nparticular opinion -- but since the symref options are new, they\nwould not *need* symmetric options, if we just say that \"update-symref\"\ncannot create or delete a symref.\n\nMeanwhile, for testing purposes I was curious as to what happens\nif you ask `git update-ref` to delete an existing symref, so after\ncreating a test repository:\n\n$ git branch\n* main\n  symref -> main\n$ git update-ref --stdin\ndelete refs/heads/symref\n$ git branch\n\nWhoops, this doesn't look good...\n\nRestoring the branch name (I had saved the hash ID Just In Case):\n\n$ echo d88ee82e6a5c29c95f712030f5efc9d43116ae79 > .git/refs/heads/main\n\nbrings things back, after which this works properly:\n\n$ git branch -d symref\nDeleted branch symref (was refs/heads/main).\n$ git branch\n* main\n\nIf this (deleting the target of the symref when using \"delete\")\nis a bug, and I think it is, that's a separate topic of course...\n\nChris\n"},{"id":"491936","messageId":"xmqqplvadmeq.fsf@gitster.g","threadId":"61235","inReplyTo":"CAPx1GvdXdH3OdY1nC2ijVSdpWfg8jn5=j0KB+Wgv70wWeCNH5g@mail.gmail.com","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-03-31T23:14:21Z","receivedAt":"2024-03-31T23:14:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Chris Torek <chris.torek@gmail.com> writes:\n\n> For these reasons, I'd suggest that the all-zero hash be officially\n> deprecated in favor of create/delete and of course create-symref\n> and delete-symref. Of course, compatibility requires some sort\n> of support for the old system for some time.  As to whether that\n> means something like the suggestion of \".missing\" etc, I have no\n> particular opinion -- but since the symref options are new, they\n> would not *need* symmetric options, if we just say that \"update-symref\"\n> cannot create or delete a symref.\n\nI love that simplicity.\n\n> If this (deleting the target of the symref when using \"delete\")\n> is a bug, and I think it is, that's a separate topic of course...\n\n\"git blame\" may find those who can give us answers, but my gut\nfeeling is that they weren't thinking too deeply about what should\nhappen, and the code does what it happens to do.\n\nWhen you update, you'd want the update go through a symref (i.e.,\nupdate-ref HEAD would update your current branch unless you are\ndetached), so they must have been aware of the fact that they need\nto deal with symrefs.\n\nBut when you delete a symref, what do you want that deletion affect?\nI do not have a good answer offhand, and they probably didn't even\nthink of the need to deal with symrefs, as the most common symref\nyou see, HEAD, is not something you'd ever want to delete anyway.\n"},{"id":"491937","messageId":"xmqqle5xeun1.fsf@gitster.g","threadId":"61235","inReplyTo":"xmqqplvadmeq.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-01T01:31:14Z","receivedAt":"2024-04-01T01:31:19Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Chris Torek <chris.torek@gmail.com> writes:\n>\n>> For these reasons, I'd suggest that the all-zero hash be officially\n>> deprecated in favor of create/delete and of course create-symref\n>> and delete-symref. Of course, compatibility requires some sort\n>> of support for the old system for some time.  As to whether that\n>> means something like the suggestion of \".missing\" etc, I have no\n>> particular opinion -- but since the symref options are new, they\n>> would not *need* symmetric options, if we just say that \"update-symref\"\n>> cannot create or delete a symref.\n>\n> I love that simplicity.\n\nHaving said that, the loose \"update that can create or delete\" may\nactually be used by applications that do not care about overwriting\ncompeting operation, so I am not sure if we can easily deprecate\nthat mode of operation.  Saying \"update refs/heads/next to point at\nthis object\" and have it created if it does not exist may be handy\nfor some loosely written applications.\n\nSo perhaps we can say \"update with a concrete <old-oid> will ensure\nthat the <ref> poitns at <old-oid> before proceeding, but update\nwith 0{40} as <old-oid> to ensure creation is deprecated.  update\nwith 0{40} as <new-oid> as deletion is also deprecated.  Use create\nand delete for these two deprecated operation modes\".\n\nThis assumes that create and delete currently ensures that what is\nasked to be created does not exist, and what is asked to be deleted\ndoes exist, before the operation.  If we are loosely doing these two\noperations, then we cannot easily deprecate the checking-update,\nwithout breaking existing users.\n\nAs {create,update,delete,verify}-symref do not exist yet, we can\nstart with the right semantics from day one.  \"update-symref\" will\naccept a <old-ref> only to ensure that the symref is pointing to\nthat ref and there is no \"zero\" value based creation/deletion\nvalidation offered via \"update-symref\".  \"create-symref\" will error\nout if the ref asked to be created already exists, \"delete-symref\"\nwill error out if the ref asked to be deleted does not exist.\n\n"},{"id":"491955","messageId":"CAOLa=ZRiTVGs9k5O0+RcNSnu=yyVVS8F2TF+VZOtPGg6EaDDLQ@mail.gmail.com","threadId":"61235","inReplyTo":"CAPx1GvdXdH3OdY1nC2ijVSdpWfg8jn5=j0KB+Wgv70wWeCNH5g@mail.gmail.com","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-01T10:38:25Z","receivedAt":"2024-04-01T10:38:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Chris Torek <chris.torek@gmail.com> writes:\n\n> Meanwhile, for testing purposes I was curious as to what happens\n> if you ask `git update-ref` to delete an existing symref, so after\n> creating a test repository:\n>\n> $ git branch\n> * main\n>   symref -> main\n> $ git update-ref --stdin\n> delete refs/heads/symref\n> $ git branch\n>\n> Whoops, this doesn't look good...\n>\n\nThis is expected though. Remember that `git-update-ref(1)` by default\ndoes dereferencing of symlinks. So in your case, 'refs/heads/symref' is\ndereferenced to 'refs/heads/main' and when a delete command is issued,\n'main' is deleted.\n\nTry the same with the `git update-ref --no-deref --stdin` instead.\n\n> Restoring the branch name (I had saved the hash ID Just In Case):\n>\n> $ echo d88ee82e6a5c29c95f712030f5efc9d43116ae79 > .git/refs/heads/main\n>\n> brings things back, after which this works properly:\n>\n> $ git branch -d symref\n> Deleted branch symref (was refs/heads/main).\n> $ git branch\n> * main\n>\n\nHere you switch to using 'git-branch(1)' and that doesn't dereference by\ndefault. So there is a difference in the two attempts.\n"},{"id":"491958","messageId":"CAOLa=ZTLv39b4Q=AAUA39tXKgOSuu54xk3-r9OUenzxR-6qcag@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqy19yf40l.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-01T11:48:02Z","receivedAt":"2024-04-01T11:48:04Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n>> index 0561808cca..2ea8bc8167 100644\n>> --- a/Documentation/git-update-ref.txt\n>> +++ b/Documentation/git-update-ref.txt\n>> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n>\n> In pre-context, there is this entry.\n>\n> \tupdate SP <ref> SP <newvalue> [SP <oldvalue>] LF\n>\n> Unrelated to this patch, we probably should say <new-value> and\n> <old-value> to follow the (recent) documentation guideline to write\n> multiple words concatenated with hyphen.\n>\n> If we are updating these newvalue and oldvalue anyway, we may want\n> to update them to <new-oid> and <old-oid>, as the existing commands\n> on refs are about object names, and what we are adding here are not.\n>\n> I would prefer to see such an update of existing documentation as a\n> separate preliminary clean-up patch, so that we can cleanly add the\n> \"update-symref\" that takes <new-symref-target> on top of a\n> cleaned-up base.\n>\n> The <newvalue> ...\n>\n\nI agree with your synopsis here, I'll send in a patch for this,\nindependent of these changes as a precursor, while we discuss the final\ndesign of this series.\n\n>> Chris Torek <chris.torek@gmail.com> writes:\n>>\n>>> For these reasons, I'd suggest that the all-zero hash be officially\n>>> deprecated in favor of create/delete and of course create-symref\n>>> and delete-symref. Of course, compatibility requires some sort\n>>> of support for the old system for some time.  As to whether that\n>>> means something like the suggestion of \".missing\" etc, I have no\n>>> particular opinion -- but since the symref options are new, they\n>>> would not *need* symmetric options, if we just say that \"update-symref\"\n>>> cannot create or delete a symref.\n>>\n>> I love that simplicity.\n>\n> Having said that, the loose \"update that can create or delete\" may\n> actually be used by applications that do not care about overwriting\n> competing operation, so I am not sure if we can easily deprecate\n> that mode of operation.  Saying \"update refs/heads/next to point at\n> this object\" and have it created if it does not exist may be handy\n> for some loosely written applications.\n>\n> So perhaps we can say \"update with a concrete <old-oid> will ensure\n> that the <ref> poitns at <old-oid> before proceeding, but update\n> with 0{40} as <old-oid> to ensure creation is deprecated.  update\n> with 0{40} as <new-oid> as deletion is also deprecated.  Use create\n> and delete for these two deprecated operation modes\".\n>\n> This assumes that create and delete currently ensures that what is\n> asked to be created does not exist, and what is asked to be deleted\n> does exist, before the operation.  If we are loosely doing these two\n> operations, then we cannot easily deprecate the checking-update,\n> without breaking existing users.\n>\n> As {create,update,delete,verify}-symref do not exist yet, we can\n> start with the right semantics from day one.  \"update-symref\" will\n> accept a <old-ref> only to ensure that the symref is pointing to\n> that ref and there is no \"zero\" value based creation/deletion\n> validation offered via \"update-symref\".  \"create-symref\" will error\n> out if the ref asked to be created already exists, \"delete-symref\"\n> will error out if the ref asked to be deleted does not exist.\n\nThis seems very reasonable to me. This ensures that each of the commands\ndo not spill over to the other and most importantly we don't have to\ndeal with \"zero\" values.\n\nBut this still means we need to think of the best output for the\nreference transaction hook (following commit).\n\nMy current implementation of:\n   <symref-target> SP <ref-name> LF\nShould be changed to:\n   <old-ref> SP <new-ref> LF\n\nBut this means, for creation of symrefs <old-ref> needs to be \"zero\"\nvalue. Also there is no way for clients to differentiate between regular\nrefs and symrefs here. I wonder if it makes sense to do something like:\n\n   symref SP <old-ref> SP <new-ref> LF\n\nWhere symref is a fixed string at the start, used as a differentiator.\nThis however still would leave us to deal with \"zero\" values for\ncreation and deletion.\n\nPerhaps the best way here to actually be a lot more verbose and have the\nhook output the following:\n\n   symref-create SP <new-ref> LF\n   symref-delete SP <old-ref> LF\n   symref-update SP <old-ref> SP <new-ref> LF\n   symref-update-forced <new-ref> LF\n\nI lean towards the latter, because its also much easier to extend in the\nfuture and we don't have to deal with the \"zero\" values.\n\nI'm against the \"zero\" values mostly cause there is no nice way to\ndescribe a zero value for a ref unlike OIDs, which is inherently baked\ninto the type of data.\n"},{"id":"491965","messageId":"xmqqv851awgq.fsf@gitster.g","threadId":"61235","inReplyTo":"CAOLa=ZTLv39b4Q=AAUA39tXKgOSuu54xk3-r9OUenzxR-6qcag@mail.gmail.com","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-01T16:17:41Z","receivedAt":"2024-04-01T16:17:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n>> So perhaps we can say \"update with a concrete <old-oid> will ensure\n>> that the <ref> poitns at <old-oid> before proceeding, but update\n>> with 0{40} as <old-oid> to ensure creation is deprecated.  update\n>> with 0{40} as <new-oid> as deletion is also deprecated.  Use create\n>> and delete for these two deprecated operation modes\".\n>>\n>> This assumes that create and delete currently ensures that what is\n>> asked to be created does not exist, and what is asked to be deleted\n>> does exist, before the operation.  If we are loosely doing these two\n>> operations, then we cannot easily deprecate the checking-update,\n>> without breaking existing users.\n\nNote that I did not (and do not) know if \"create\" and \"delete\" have\nsuch checks; I expected somebody (other than me) to check before\ngoing forward.\n\n> But this still means we need to think of the best output for the\n> reference transaction hook (following commit).\n>\n> My current implementation of:\n>    <symref-target> SP <ref-name> LF\n> Should be changed to:\n>    <old-ref> SP <new-ref> LF\n>\n> But this means, for creation of symrefs <old-ref> needs to be \"zero\"\n> value. Also there is no way for clients to differentiate between regular\n> refs and symrefs here. I wonder if it makes sense to do something like:\n>\n>    symref SP <old-ref> SP <new-ref> LF\n\nWhat do clients see for a regular ref?  \"<old-oid> SP <new-oid> LF\"?\nThat would be OK, as \"symref\" cannot be an object name, I guess?\n\n> Where symref is a fixed string at the start, used as a differentiator.\n> This however still would leave us to deal with \"zero\" values for\n> creation and deletion.\n\nAre these two <old-ref> and <new-ref> values optional in the context\nof your discussion?  The review comment was on input from the end-user\nthat made it optional to validate the precondition, but this is what\nyou produce as a result---if these are not optional, then an empty\nstring can be a reasonable \"zero\" value.  I am confused...\n\n> Perhaps the best way here to actually be a lot more verbose and have the\n> hook output the following:\n>\n>    symref-create SP <new-ref> LF\n>    symref-delete SP <old-ref> LF\n>    symref-update SP <old-ref> SP <new-ref> LF\n>    symref-update-forced <new-ref> LF\n\nIt is unfortunate that the input to the hook for a normal reference\nupdate uses syntax different from the \"--stdin\" input format, i.e.,\n\n    <old-oid> SP <new-oid> SP <ref> LF\n\nbut it is way too late to change it now.  So to be consistent,\n\n    symref-create SP <new-ref> SP <ref> LF\n    symref-delete SP <old-ref> SP <ref> LF\n    symref-update SP <old-ref> SP <new-ref> SP <ref> LF\n\nwould be the three operations.\n\nBut this is not an end-user input that tells Git \"I do not care\nabout precondition, I did not even bother to learn the current state\nto give you as <old-something>, just force it\".  The input to hook\nis what we tell the hook what we are planning to do (so that it can\ndecline), and we do not need the ability to say \"I do not know what\nthe current state is\".  So I do not think you need any \"zero\" value\nin the input to the reference-transaction hook.  And I do not see a\nneed for the \"symref-update-forced\" variant, either.\n\nBy the way, if we were to use symref-{create,delete,update} here,\nwouldn't it make more sense to name the command on the \"--stdin\"\nside the same, i.e., not \"update-symref\" but \"symref-update\"?\n\nWhat I suspect that needs more thought is what should happen when\nyou request via \"--stdin\" to create, update, or delete a symref,\nbut <ref> is a regular ref, e.g., \"symref-delete <ref>\".  For\n\"symref-create <ref> <new-ref>\", we would fail if <ref> exists,\nwhether it is a symref or a normal ref, so that is OK.  For\n\"symref-delete <ref> <old-ref>\", we would fail if <ref> is *not*\na symref to <old-ref>, so the case where <ref> is a normal ref\nis already covered. \n\nShould we support \"symref-update <ref> <new-ref> <old-oid>\" that\nmakes <ref> a symref that points at <new-ref>, but ensures that\n<ref> before the operation is a normal ref that points at <old-oid>?\n\nOr should \"symref-update\" work only on <ref> that is an existing\nsymref?\n\nI think I am OK if the answer was \"You can only give a precondition\nin the form of <old-ref>, which means you can only turn an existing\nsymref to point at a different ref with precondition protection. If\nyou want to turn a normal ref into a symref, you have to force it by\nnot having a precondition, or delete the ref and then (re)create it\nas a symref\".  But we need to decide the semantics and document it.\n\nThanks.\n\n\n"},{"id":"491981","messageId":"xmqqsf0495q1.fsf@gitster.g","threadId":"61235","inReplyTo":"xmqqv851awgq.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-01T20:40:38Z","receivedAt":"2024-04-01T20:40:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n>> But this still means we need to think of the best output for the\n>> reference transaction hook (following commit).\n>> ...\n\nOne thing I missed.  We are not currently reporting symref updates\nto these hooks.  Are they prepared to take such extra input?  If not,\nare they going to barf when they see \"symref-update\" while expecting\nto see <old-oid>?\n\nWe may need to make it possible for Git to tell which variant of the\nhook script it was given somehow (the easiest cop-out is to introduce\nref-transaction-hook-v2 as a separate hook, and use it if exists, or\nfall back to the reference-transaction hook, and report symref updates\nonly when we are using v2, but there may be better approaches).\n\n> But this is not an end-user input that tells Git \"I do not care\n> about precondition, I did not even bother to learn the current state\n> to give you as <old-something>, just force it\".  The input to hook\n> is what we tell the hook what we are planning to do (so that it can\n> decline), and we do not need the ability to say \"I do not know what\n> the current state is\".  So I do not think you need any \"zero\" value\n> in the input to the reference-transaction hook.  And I do not see a\n> need for the \"symref-update-forced\" variant, either.\n\nI misspoke here.  We do need \"zero\" value to indicate that \"this\nupdate is a creation event\" and \"this update is a deletion event\".\nWhat I meant to say is that there is no need to make the \"zero\"\nvalue distinguishable from a \"missing optional\" value, which was a\nproblem on the \"--stdin\" side with \"-z\" format, where each command\nis in a format with fixed number of parameters, unlike the textual\nformat, where a missing optional argument can be expressed by\nomitting SP before the value and the value itself and it can be\ndifferentiated from an empty string as an optional value that is not\nmissing.\n\nThanks.\n"},{"id":"492001","messageId":"CAOLa=ZSo7ngBUxkfR+EEojhr4a-mM+3=f-P1H36hnhJukEqGVA@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqv851awgq.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-01T22:37:17Z","receivedAt":"2024-04-01T22:37:18Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>>> So perhaps we can say \"update with a concrete <old-oid> will ensure\n>>> that the <ref> poitns at <old-oid> before proceeding, but update\n>>> with 0{40} as <old-oid> to ensure creation is deprecated.  update\n>>> with 0{40} as <new-oid> as deletion is also deprecated.  Use create\n>>> and delete for these two deprecated operation modes\".\n>>>\n>>> This assumes that create and delete currently ensures that what is\n>>> asked to be created does not exist, and what is asked to be deleted\n>>> does exist, before the operation.  If we are loosely doing these two\n>>> operations, then we cannot easily deprecate the checking-update,\n>>> without breaking existing users.\n>\n> Note that I did not (and do not) know if \"create\" and \"delete\" have\n> such checks; I expected somebody (other than me) to check before\n> going forward.\n>\n\ncreate does:\n\n$ git update-ref --stdin\ncreate refs/heads/a 53eb33454ce4f3db4d8c79e9c15640c2dffc4a37\nfatal: cannot lock ref 'refs/heads/a': reference already exists\n\ndelete doesn't:\n\n$ git update-ref --stdin\ndelete refs/heads/b\n% echo $?\n0\n$ ls .git/refs/heads/\na  master\n\n>> But this still means we need to think of the best output for the\n>> reference transaction hook (following commit).\n>>\n>> My current implementation of:\n>>    <symref-target> SP <ref-name> LF\n>> Should be changed to:\n>>    <old-ref> SP <new-ref> LF\n>>\n>> But this means, for creation of symrefs <old-ref> needs to be \"zero\"\n>> value. Also there is no way for clients to differentiate between regular\n>> refs and symrefs here. I wonder if it makes sense to do something like:\n>>\n>>    symref SP <old-ref> SP <new-ref> LF\n>\n> What do clients see for a regular ref?  \"<old-oid> SP <new-oid> LF\"?\n> That would be OK, as \"symref\" cannot be an object name, I guess?\n>\n\n<old-value> SP <new-value> SP <ref-name> LF\n\nYeah, 'symref' should work.\n\n>> Where symref is a fixed string at the start, used as a differentiator.\n>> This however still would leave us to deal with \"zero\" values for\n>> creation and deletion.\n>\n> Are these two <old-ref> and <new-ref> values optional in the context\n> of your discussion?  The review comment was on input from the end-user\n> that made it optional to validate the precondition, but this is what\n> you produce as a result---if these are not optional, then an empty\n> string can be a reasonable \"zero\" value.  I am confused...\n>> Perhaps the best way here to actually be a lot more verbose and have the\n>> hook output the following:\n>>\n>>    symref-create SP <new-ref> LF\n>>    symref-delete SP <old-ref> LF\n>>    symref-update SP <old-ref> SP <new-ref> LF\n>>    symref-update-forced <new-ref> LF\n>\n> It is unfortunate that the input to the hook for a normal reference\n> update uses syntax different from the \"--stdin\" input format, i.e.,\n>\n>     <old-oid> SP <new-oid> SP <ref> LF\n>\n> but it is way too late to change it now.  So to be consistent,\n>\n>     symref-create SP <new-ref> SP <ref> LF\n>     symref-delete SP <old-ref> SP <ref> LF\n>     symref-update SP <old-ref> SP <new-ref> SP <ref> LF\n>\n> would be the three operations.\n>\n> But this is not an end-user input that tells Git \"I do not care\n> about precondition, I did not even bother to learn the current state\n> to give you as <old-something>, just force it\".  The input to hook\n> is what we tell the hook what we are planning to do (so that it can\n> decline), and we do not need the ability to say \"I do not know what\n> the current state is\".  So I do not think you need any \"zero\" value\n> in the input to the reference-transaction hook.  And I do not see a\n> need for the \"symref-update-forced\" variant, either.\n\n... also from your latest email ...\n\n>> But this is not an end-user input that tells Git \"I do not care\n>> about precondition, I did not even bother to learn the current state\n>> to give you as <old-something>, just force it\".  The input to hook\n>> is what we tell the hook what we are planning to do (so that it can\n>> decline), and we do not need the ability to say \"I do not know what\n>> the current state is\".  So I do not think you need any \"zero\" value\n>> in the input to the reference-transaction hook.  And I do not see a\n>> need for the \"symref-update-forced\" variant, either.\n>\n> I misspoke here.  We do need \"zero\" value to indicate that \"this\n> update is a creation event\" and \"this update is a deletion event\".\n> What I meant to say is that there is no need to make the \"zero\"\n> value distinguishable from a \"missing optional\" value, which was a\n> problem on the \"--stdin\" side with \"-z\" format, where each command\n> is in a format with fixed number of parameters, unlike the textual\n> format, where a missing optional argument can be expressed by\n> omitting SP before the value and the value itself and it can be\n> differentiated from an empty string as an optional value that is not\n> missing.\n>\n> Thanks.\n\nYup. There is a slight subtlety here though, currently with the\nreference-transaction hook:\n\n    When force updating the reference regardless of its current value or\n    when the reference is to be created anew, <old-value> is the\n    all-zeroes object name. To distinguish these cases, you can inspect\n    the current value of <ref-name> via git rev-parse.\n\nI'll keep the same behavior with the symref updates.\n\n> By the way, if we were to use symref-{create,delete,update} here,\n> wouldn't it make more sense to name the command on the \"--stdin\"\n> side the same, i.e., not \"update-symref\" but \"symref-update\"?\n\nIf we were to use symref-*, definitely.\n\n> What I suspect that needs more thought is what should happen when\n> you request via \"--stdin\" to create, update, or delete a symref,\n> but <ref> is a regular ref, e.g., \"symref-delete <ref>\".  For\n> \"symref-create <ref> <new-ref>\", we would fail if <ref> exists,\n> whether it is a symref or a normal ref, so that is OK.  For\n> \"symref-delete <ref> <old-ref>\", we would fail if <ref> is *not*\n> a symref to <old-ref>, so the case where <ref> is a normal ref\n> is already covered.\n>\n> Should we support \"symref-update <ref> <new-ref> <old-oid>\" that\n> makes <ref> a symref that points at <new-ref>, but ensures that\n> <ref> before the operation is a normal ref that points at <old-oid>?\n>\n> Or should \"symref-update\" work only on <ref> that is an existing\n> symref?\n>\n> I think I am OK if the answer was \"You can only give a precondition\n> in the form of <old-ref>, which means you can only turn an existing\n> symref to point at a different ref with precondition protection. If\n> you want to turn a normal ref into a symref, you have to force it by\n> not having a precondition, or delete the ref and then (re)create it\n> as a symref\".  But we need to decide the semantics and document it.\n\nI would think that doing what you mentioned in the last para to be the\nway to go, unless someone thinks otherwise. This allows the ugly mess of\nparsing an value as a ref and then as a oid and provides some structure\nto what the input cases are.\n\nOne more case I'd add is that the <ref> argument for \"symref-delete\"\nshould be optional, similar to having forced update, we'd also want to\nsupport forced deletion.\n\n> Junio C Hamano <gitster@pobox.com> writes:\n>\n>>> But this still means we need to think of the best output for the\n>>> reference transaction hook (following commit).\n>>> ...\n>\n> One thing I missed.  We are not currently reporting symref updates\n> to these hooks.  Are they prepared to take such extra input?  If not,\n> are they going to barf when they see \"symref-update\" while expecting\n> to see <old-oid>?\n>\n> We may need to make it possible for Git to tell which variant of the\n> hook script it was given somehow (the easiest cop-out is to introduce\n> ref-transaction-hook-v2 as a separate hook, and use it if exists, or\n> fall back to the reference-transaction hook, and report symref updates\n> only when we are using v2, but there may be better approaches).\n>\n\nWell I was hoping this is okay since reference-transaction and\nupdate-ref never supported symrefs. So after this change:\n1. If a user never updates the hook to support symrefs, but doesn't use\nsymref feature of update-ref, they would be fine.\n2. If a user starts using symref features of update-ref, they would see\nthat reference transaction needs to be updated too.\n\nThis especially since the hook's documentation always claimed that\nsymref support might be added in the future.\n\n   The hook does not cover symbolic references (but that may change in\n   the future).\n\n---\n\nIn summary the plan going forward from my side would be to:\n\nImplement the following in update-ref:\n\n    symref-create SP <ref> SP <new-ref> LF\n    symref-update SP <ref> SP <new-ref> [SP <old-ref>] LF\n    symref-delete SP <ref> [SP <old-ref>] LF\n    symref-verify SP <ref> [SP <old-ref>] LF\n\nAlso on the reference transaction hook, we'll be adding the following\nnew inputs to the hook:\n\n    symref-create SP <new-ref> SP <ref> LF\n    symref-delete SP <old-ref> SP <ref> LF\n    symref-update SP <old-ref> SP <new-ref> SP <ref> LF\n\nThis either will be added to the existing hook or we would support a new\nhook (v2).\n"},{"id":"492048","messageId":"Zgv3_K_gCkPyZvRQ@tanuki","threadId":"61235","inReplyTo":"20240330224623.579457-2-knayak@gitlab.com","subject":"Re: [PATCH 1/8] files-backend: extract out `create_symref_lock`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:12Z","receivedAt":"2024-04-02T12:20:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Mar 30, 2024 at 11:46:16PM +0100, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The function `create_symref_locked` creates a symref by creating a\n> '<symref>.lock' file and then committing the symref lock, which creates\n> the final symref.\n> \n> Split this into two individual functions `create_and_commit_symref` and\n> `create_symref_locked`. This way we can create the symref lock and\n> commit it at different times. This will be used to provide symref\n> support in `git-update-ref(1)`.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------\n>  1 file changed, 27 insertions(+), 13 deletions(-)\n> \n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index a098d14ea0..3f0f9521cb 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,\n>  \t}\n>  }\n>  \n> -static int create_symref_locked(struct files_ref_store *refs,\n> -\t\t\t\tstruct ref_lock *lock, const char *refname,\n> -\t\t\t\tconst char *target, const char *logmsg)\n> +static int create_symref_lock(struct files_ref_store *refs,\n> +\t\t\t      struct ref_lock *lock, const char *refname,\n> +\t\t\t      const char *target)\n>  {\n> +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> +\t\treturn error(\"unable to fdopen %s: %s\",\n> +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> +\n> +\t/* no error check; commit_ref will check ferror */\n> +\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n> +\treturn 0;\n> +}\n> +\n> +static int create_and_commit_symref(struct files_ref_store *refs,\n> +\t\t\t\t    struct ref_lock *lock, const char *refname,\n> +\t\t\t\t    const char *target, const char *logmsg)\n> +{\n> +\tint ret;\n> +\n>  \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n>  \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>  \t\treturn 0;\n>  \t}\n>  \n> -\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> -\t\treturn error(\"unable to fdopen %s: %s\",\n> -\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> +\tret = create_symref_lock(refs, lock, refname, target);\n> +\tif (!ret) {\n> +\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n\nI feel like the resulting code here is a bit hard to read because the\nsuccessful path is now nested into the condition. This does not really\nconform to our typical coding style. Exiting early in case the function\nreturns an error would be easier to read.\n\n> -\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n> +\t\tif (commit_ref(lock) < 0)\n> +\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n> +\t\t\t\t     strerror(errno));\n> +\t}\n>  \n> -\t/* no error check; commit_ref will check ferror */\n> -\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n> -\tif (commit_ref(lock) < 0)\n> -\t\treturn error(\"unable to write symref for %s: %s\", refname,\n> -\t\t\t     strerror(errno));\n>  \treturn 0;\n\nAlso, is it correct to `return 0` here in case `create_symref_lock()`\nreturns an error? If so it certainly requires an in-code comment to\nexplain what is going on. If this is a bug I feel like we have\nidentified a test gap that we might want to plug.\n\nPatrick\n\n>  }\n>  \n> @@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n>  \t\treturn -1;\n>  \t}\n>  \n> -\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n> +\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n> +\n>  \tunlock_ref(lock);\n>  \treturn ret;\n>  }\n> -- \n> 2.43.GIT\n> \n"},{"id":"492049","messageId":"Zgv4B7DI9g0vmxea@tanuki","threadId":"61235","inReplyTo":"20240330224623.579457-3-knayak@gitlab.com","subject":"Re: [PATCH 2/8] reftable-backend: extract out `write_symref_with_log`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:23Z","receivedAt":"2024-04-02T12:20:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Mar 30, 2024 at 11:46:17PM +0100, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The function `write_create_symref_table`, creates a\n> `reftable_ref_record` for a symref and adds it to the writer. Then it\n> also creates a log entry for the symref. It does all of this while also\n> obtaining and using a new update index.\n> \n> We extract out `write_symref_with_log` from this to provide the\n> functionality of creating a symref without making changes to the update\n> index. This will be used to add `update-symref` option to the\n> `git-update-ref` command.\n> \n> Rename the `create` field to `arg` while we're here, as `create` is a\n> bit misleading.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs/reftable-backend.c | 41 +++++++++++++++++++++++++----------------\n>  1 file changed, 25 insertions(+), 16 deletions(-)\n> \n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index e206d5a073..282a08e3cb 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -1222,23 +1222,22 @@ struct write_create_symref_arg {\n>  \tconst char *logmsg;\n>  };\n>  \n> -static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n> +static int write_symref_with_log(struct reftable_writer *writer,\n> +\t\t\t\t struct write_create_symref_arg *arg,\n> +\t\t\t\t uint64_t update_index)\n>  {\n> -\tstruct write_create_symref_arg *create = cb_data;\n> -\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n>  \tstruct reftable_ref_record ref = {\n> -\t\t.refname = (char *)create->refname,\n> +\t\t.refname = (char *)arg->refname,\n>  \t\t.value_type = REFTABLE_REF_SYMREF,\n> -\t\t.value.symref = (char *)create->target,\n> -\t\t.update_index = ts,\n> +\t\t.value.symref = (char *)arg->target,\n> +\t\t.update_index = update_index,\n>  \t};\n> +\n\nNit: let's remove this superfluous newline.\n\nPatrick\n\n>  \tstruct reftable_log_record log = {0};\n>  \tstruct object_id new_oid;\n>  \tstruct object_id old_oid;\n>  \tint ret;\n>  \n> -\treftable_writer_set_limits(writer, ts, ts);\n> -\n>  \tret = reftable_writer_add_ref(writer, &ref);\n>  \tif (ret)\n>  \t\treturn ret;\n> @@ -1251,25 +1250,35 @@ static int write_create_symref_table(struct reftable_writer *writer, void *cb_da\n>  \t * not resolve for new repositories this ordering will ensure that this\n>  \t * never happens.\n>  \t */\n> -\tif (!create->logmsg ||\n> -\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n> +\tif (!arg->logmsg ||\n> +\t    !refs_resolve_ref_unsafe(&arg->refs->base, arg->target,\n>  \t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n> -\t    !should_write_log(&create->refs->base, create->refname))\n> +\t    !should_write_log(&arg->refs->base, arg->refname))\n>  \t\treturn 0;\n>  \n>  \tfill_reftable_log_record(&log);\n> -\tlog.refname = xstrdup(create->refname);\n> -\tlog.update_index = ts;\n> -\tlog.value.update.message = xstrndup(create->logmsg,\n> -\t\t\t\t\t    create->refs->write_options.block_size / 2);\n> +\tlog.refname = xstrdup(arg->refname);\n> +\tlog.update_index = update_index;\n> +\tlog.value.update.message = xstrndup(arg->logmsg,\n> +\t\t\t\t\t    arg->refs->write_options.block_size / 2);\n>  \tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n> -\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n> +\tif (refs_resolve_ref_unsafe(&arg->refs->base, arg->refname,\n>  \t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n>  \t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n>  \n>  \tret = reftable_writer_add_log(writer, &log);\n>  \treftable_log_record_release(&log);\n>  \treturn ret;\n> +\n> +}\n> +\n> +static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n> +{\n> +\tstruct write_create_symref_arg *arg = cb_data;\n> +\tuint64_t ts = reftable_stack_next_update_index(arg->stack);\n> +\treftable_writer_set_limits(writer, ts, ts);\n> +\n> +\treturn write_symref_with_log(writer, arg, ts);\n>  }\n>  \n>  static int reftable_be_create_symref(struct ref_store *ref_store,\n> -- \n> 2.43.GIT\n> \n"},{"id":"492050","messageId":"Zgv4DShrInEE30GA@tanuki","threadId":"61235","inReplyTo":"20240330224623.579457-6-knayak@gitlab.com","subject":"Re: [PATCH 5/8] refs/files-backend: add support for symref updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:29Z","receivedAt":"2024-04-02T12:20:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Mar 30, 2024 at 11:46:20PM +0100, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> Add support for transactional symbolic reference updates in the files\n> backend. This also adheres to the config of using symlinks for symbolic\n> references.\n> \n> While this commit is setting up the files-backend to support symrefs in\n> transaction's. It will only be used in a consequent commit, when we wire\n> up the `update-symref` option for `git-update-ref`.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs/files-backend.c | 45 +++++++++++++++++++++++++++++++++++++++++---\n>  1 file changed, 42 insertions(+), 3 deletions(-)\n> \n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 4dbe73c106..6b4cc80843 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2323,7 +2323,7 @@ static int split_head_update(struct ref_update *update,\n>  \t\t\ttransaction, \"HEAD\",\n>  \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n>  \t\t\t&update->new_oid, &update->old_oid,\n> -\t\t\tupdate->msg, NULL);\n> +\t\t\tupdate->msg, update->symref_target);\n>  \n>  \t/*\n>  \t * Add \"HEAD\". This insertion is O(N) in the transaction\n> @@ -2386,7 +2386,7 @@ static int split_symref_update(struct ref_update *update,\n>  \tnew_update = ref_transaction_add_update(\n>  \t\t\ttransaction, referent, new_flags,\n>  \t\t\t&update->new_oid, &update->old_oid,\n> -\t\t\tupdate->msg, NULL);\n> +\t\t\tupdate->msg, update->symref_target);\n>  \n>  \tnew_update->parent_update = update;\n>  \n> @@ -2396,7 +2396,7 @@ static int split_symref_update(struct ref_update *update,\n>  \t * done when new_update is processed.\n>  \t */\n>  \tupdate->flags |= REF_LOG_ONLY | REF_NO_DEREF;\n> -\tupdate->flags &= ~REF_HAVE_OLD;\n> +\tupdate->flags &= ~(REF_HAVE_OLD|REF_UPDATE_SYMREF);\n>  \n>  \t/*\n>  \t * Add the referent. This insertion is O(N) in the transaction\n> @@ -2567,6 +2567,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t}\n>  \t}\n>  \n> +\tif (update->flags & REF_UPDATE_SYMREF) {\n> +\t\tif (create_symref_lock(refs, lock, update->refname, update->symref_target)) {\n> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tif (close_ref_gently(lock)) {\n> +\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n> +\t\t\t\t    update->refname);\n> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * Once we have created the symref lock, the commit\n> +\t\t * phase of the transaction only needs to commit the lock.\n> +\t\t */\n> +\t\tif (update->flags & REF_UPDATE_SYMREF)\n> +\t\t\tupdate->flags |= REF_NEEDS_COMMIT;\n\nAs far as I can see the `update->flags` aren't ever modified in this\nblock, which already is guarded via `update->flags = REF_UPDATE_SYMREF`.\nThis condition should thus be superfluous, and we can instead set\n`REF_NEEDS_COMMIT` unconditionally here.\n\n> +\t}\n> +\n>  \tif ((update->flags & REF_HAVE_NEW) &&\n>  \t    !(update->flags & REF_DELETING) &&\n>  \t    !(update->flags & REF_LOG_ONLY)) {\n> @@ -2862,6 +2883,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>  \n>  \t\tif (update->flags & REF_NEEDS_COMMIT ||\n>  \t\t    update->flags & REF_LOG_ONLY) {\n> +\t\t\tif (update->flags & REF_UPDATE_SYMREF) {\n> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->symref_target,\n> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n> +\t\t\t\t\tstrbuf_addf(err, \"refname %s not found\", update->symref_target);\n> +\t\t\t\t\tgoto cleanup;\n> +\t\t\t\t}\n> +\t\t\t}\n\nSo we try to resolve the symref target here so that we can provide a\nproper new object ID for the reflog entry. What happens though when the\ncaller tries to create a dangling symref where the target ref does not\nexist? Wouldn't we raise an error and abort in that case?\n\nI think we should handle this case gracefully and set the new object ID\nto the zero OID. Which is also what happens when deleting the target of\ne.g. the `HEAD` symref.\n\n>  \t\t\tif (files_log_ref_write(refs,\n>  \t\t\t\t\t\tlock->ref_name,\n>  \t\t\t\t\t\t&lock->old_oid,\n> @@ -2879,6 +2908,16 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>  \t\t\t\tgoto cleanup;\n>  \t\t\t}\n>  \t\t}\n> +\n> +\t\t/*\n> +\t\t * We try creating a symlink, if that succeeds we continue to the\n> +\t\t * next updated. If not, we try and create a regular symref.\n> +\t\t */\n> +\t\tif (update->flags & REF_UPDATE_SYMREF && prefer_symlink_refs)\n> +\t\t\tif (!create_ref_symlink(lock, update->symref_target))\n> +\t\t\t\tcontinue;\n> +\n> +\n\nThere's a superfluous newline here.\n\n>  \t\tif (update->flags & REF_NEEDS_COMMIT) {\n>  \t\t\tclear_loose_ref_cache(refs);\n>  \t\t\tif (commit_ref(lock)) {\n\nWhat is the purpose of `clear_loose_ref_cache()` here, and do we need to\ncall it when updating symrefs, too?\n\nPatrick\n\n> -- \n> 2.43.GIT\n> \n"},{"id":"492051","messageId":"Zgv4FvntLkmm5mQI@tanuki","threadId":"61235","inReplyTo":"20240330224623.579457-7-knayak@gitlab.com","subject":"Re: [PATCH 6/8] refs/reftable-backend: add support for symref updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:38Z","receivedAt":"2024-04-02T12:20:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Mar 30, 2024 at 11:46:21PM +0100, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> Add support for transactional symbolic reference updates in the reftable\n> backend.\n> \n> While this commit is setting up the reftable-backend to support symrefs\n> in transaction's. It will only be used in a consequent commit, when we\n> wire up the `update-symref` option for `git-update-ref`.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  refs/reftable-backend.c | 23 +++++++++++++++++++----\n>  1 file changed, 19 insertions(+), 4 deletions(-)\n> \n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 92f2803e90..35f2e8e050 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -884,7 +884,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t\tnew_update = ref_transaction_add_update(\n>  \t\t\t\t\ttransaction, \"HEAD\",\n>  \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n> -\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n> +\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, u->symref_target);\n>  \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n>  \t\t}\n>  \n> @@ -909,9 +909,11 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \n>  \t\t\t/*\n>  \t\t\t * There is no need to write the reference deletion\n> -\t\t\t * when the reference in question doesn't exist.\n> +\t\t\t * when the reference in question doesn't exist except\n> +\t\t\t * when we want to create new symrefs.\n\nNit: it's not really an exception because it's not a reference deletion\nin the first place when we write a symref.\n\nPatrick\n\n>  \t\t\t */\n> -\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n> +\t\t\tif ((u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) ||\n> +\t\t\t    u->flags & REF_UPDATE_SYMREF) {\n>  \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n>  \t\t\t\t\t\t\t\t&current_oid, err);\n>  \t\t\t\t if (ret)\n> @@ -963,7 +965,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t\t\t */\n>  \t\t\t\tnew_update = ref_transaction_add_update(\n>  \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n> -\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, NULL);\n> +\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg, u->symref_target);\n>  \t\t\t\tnew_update->parent_update = u;\n>  \n>  \t\t\t\t/*\n> @@ -1026,6 +1028,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t */\n>  \t\tif ((u->type & REF_ISSYMREF) ||\n>  \t\t    (u->flags & REF_LOG_ONLY) ||\n> +\t\t    (u->flags & REF_UPDATE_SYMREF) ||\n>  \t\t    (u->flags & REF_HAVE_NEW && !oideq(&current_oid, &u->new_oid))) {\n>  \t\t\tret = queue_transaction_update(refs, tx_data, u,\n>  \t\t\t\t\t\t       &current_oid, err);\n> @@ -1187,6 +1190,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n>  \t\t\tret = reftable_writer_add_ref(writer, &ref);\n>  \t\t\tif (ret < 0)\n>  \t\t\t\tgoto done;\n> +\t\t} else if (u->flags & REF_UPDATE_SYMREF) {\n> +\t\t\tstruct write_create_symref_arg create  = {\n> +\t\t\t\t.refs = arg->refs,\n> +\t\t\t\t.stack = arg->stack,\n> +\t\t\t\t.refname = u->refname,\n> +\t\t\t\t.target = u->symref_target,\n> +\t\t\t\t.logmsg = u->msg,\n> +\t\t\t};\n> +\n> +\t\t\twrite_symref_with_log(writer, &create, ts);\n> +\t\t\tif (ret < 0)\n> +\t\t\t\tgoto done;\n>  \t\t}\n>  \t}\n>  \n> -- \n> 2.43.GIT\n> \n"},{"id":"492052","messageId":"Zgv4H66NmLZ_o1IC@tanuki","threadId":"61235","inReplyTo":"xmqqle5xeun1.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:47Z","receivedAt":"2024-04-02T12:20:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Mar 31, 2024 at 06:31:14PM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Chris Torek <chris.torek@gmail.com> writes:\n> >\n> >> For these reasons, I'd suggest that the all-zero hash be officially\n> >> deprecated in favor of create/delete and of course create-symref\n> >> and delete-symref. Of course, compatibility requires some sort\n> >> of support for the old system for some time.  As to whether that\n> >> means something like the suggestion of \".missing\" etc, I have no\n> >> particular opinion -- but since the symref options are new, they\n> >> would not *need* symmetric options, if we just say that \"update-symref\"\n> >> cannot create or delete a symref.\n> >\n> > I love that simplicity.\n> \n> Having said that, the loose \"update that can create or delete\" may\n> actually be used by applications that do not care about overwriting\n> competing operation, so I am not sure if we can easily deprecate\n> that mode of operation.  Saying \"update refs/heads/next to point at\n> this object\" and have it created if it does not exist may be handy\n> for some loosely written applications.\n\nI wouldn't say \"loosely written here\". I certainly know that we do use\nthese implicit modes in Gitaly, and we have conciously chosen them\nbecause they have been supported by Git all along. It simply makes our\nlifes easier when we don't have to special-case creations and deletions\nin any way.\n\nSo I'd really not want those to go away or become deprecated.\n\nPatrick\n\n> So perhaps we can say \"update with a concrete <old-oid> will ensure\n> that the <ref> poitns at <old-oid> before proceeding, but update\n> with 0{40} as <old-oid> to ensure creation is deprecated.  update\n> with 0{40} as <new-oid> as deletion is also deprecated.  Use create\n> and delete for these two deprecated operation modes\".\n> \n> This assumes that create and delete currently ensures that what is\n> asked to be created does not exist, and what is asked to be deleted\n> does exist, before the operation.  If we are loosely doing these two\n> operations, then we cannot easily deprecate the checking-update,\n> without breaking existing users.\n> \n> As {create,update,delete,verify}-symref do not exist yet, we can\n> start with the right semantics from day one.  \"update-symref\" will\n> accept a <old-ref> only to ensure that the symref is pointing to\n> that ref and there is no \"zero\" value based creation/deletion\n> validation offered via \"update-symref\".  \"create-symref\" will error\n> out if the ref asked to be created already exists, \"delete-symref\"\n> will error out if the ref asked to be deleted does not exist.\n> \n"},{"id":"492053","messageId":"Zgv4JbwM3HNX_Ftp@tanuki","threadId":"61235","inReplyTo":"20240330224623.579457-9-knayak@gitlab.com","subject":"Re: [PATCH 8/8] refs: support symrefs in 'reference-transaction' hook","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-02T12:20:53Z","receivedAt":"2024-04-02T12:20:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sat, Mar 30, 2024 at 11:46:23PM +0100, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The 'reference-transaction' hook runs whenever a reference update is\n> made to the system. In the previous commit, we added support for the\n> `update-symref` command in `git-update-ref`. While it allowed us to now\n> create symbolic refs via `git-update-ref`, it didn't activate the\n> 'reference-transaction' hook.\n> \n> Let's activate the hook for symbolic reference updates too. This takes\n> the form `<symref-target> SP <ref-name> LF`, which deviates from the\n> form for regular updates since this only contains two fields.\n> \n> While this seems to be backward incompatible, it is okay, since the only\n> way the `reference-transaction` hook outputs this syntax is when\n> `git-update-ref` is used with `update-symref` command. The command was\n> only introduced in the previous commit and hence only users of this\n> command will face this incompatibility.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  Documentation/githooks.txt       | 13 +++++++++++--\n>  refs.c                           | 17 +++++++++--------\n>  t/t1416-ref-transaction-hooks.sh | 27 +++++++++++++++++++++++++++\n>  3 files changed, 47 insertions(+), 10 deletions(-)\n> \n> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\n> index 37f91d5b50..ae9f02974d 100644\n> --- a/Documentation/githooks.txt\n> +++ b/Documentation/githooks.txt\n> @@ -485,8 +485,7 @@ reference-transaction\n>  \n>  This hook is invoked by any Git command that performs reference\n>  updates. It executes whenever a reference transaction is prepared,\n> -committed or aborted and may thus get called multiple times. The hook\n> -does not cover symbolic references (but that may change in the future).\n> +committed or aborted and may thus get called multiple times.\n>  \n>  The hook takes exactly one argument, which is the current state the\n>  given reference transaction is in:\n> @@ -513,6 +512,16 @@ to be created anew, `<old-value>` is the all-zeroes object name. To\n>  distinguish these cases, you can inspect the current value of\n>  `<ref-name>` via `git rev-parse`.\n>  \n> +For each symbolic reference update that was added to the transaction,\n> +the hook receives on standard input a line of the format:\n> +\n> +  <symref-target> SP <ref-name> LF\n\nI was wondering whether we want the format to be a bit more explicit.\nThe proposed format works alright because refnames must not contain any\nspaces, and thus it can be disambiguated from normal ref updates. But\nit's rather easy to miss for authors of this hook.\n\nI don't really know of a better format though. We could of course prefix\nthings with \"symref:\" or something like this, but that might not be a\ngood idea either. In hindsight it would've been clever to have a\nspecific prefix for every single ref update in the reference-transaction\nhook. But well, here we are.\n\nPatrick\n\n> +where `<symref-target>` is the target of the symbolic reference update\n> +passed into the reference transaction, `<ref-name>` is the full name of\n> +the ref being updated. To distinguish from the regular updates, we can\n> +note that there are only two fields.\n> +\n>  The exit status of the hook is ignored for any state except for the\n>  \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n>  cause the transaction to be aborted. The hook will not be called with\n> diff --git a/refs.c b/refs.c\n> index 706dcd6deb..d0929c5684 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -2342,16 +2342,17 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n>  \n>  \tfor (i = 0; i < transaction->nr; i++) {\n>  \t\tstruct ref_update *update = transaction->updates[i];\n> +\t\tstrbuf_reset(&buf);\n>  \n> -\t\t// Reference transaction does not support symbolic updates.\n>  \t\tif (update->flags & REF_UPDATE_SYMREF)\n> -\t\t\tcontinue;\n> -\n> -\t\tstrbuf_reset(&buf);\n> -\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n> -\t\t\t    oid_to_hex(&update->old_oid),\n> -\t\t\t    oid_to_hex(&update->new_oid),\n> -\t\t\t    update->refname);\n> +\t\t\tstrbuf_addf(&buf, \"%s %s\\n\",\n> +\t\t\t\t    update->symref_target,\n> +\t\t\t\t    update->refname);\n> +\t\telse\n> +\t\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n> +\t\t\t\t    oid_to_hex(&update->old_oid),\n> +\t\t\t\t    oid_to_hex(&update->new_oid),\n> +\t\t\t\t    update->refname);\n>  \n>  \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n>  \t\t\tif (errno != EPIPE) {\n> diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\n> index 2092488090..fac5d5fc6d 100755\n> --- a/t/t1416-ref-transaction-hooks.sh\n> +++ b/t/t1416-ref-transaction-hooks.sh\n> @@ -83,6 +83,33 @@ test_expect_success 'hook gets all queued updates in committed state' '\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success 'hook gets all queued symref updates' '\n> +\ttest_when_finished \"rm actual\" &&\n> +\ttest_hook reference-transaction <<-\\EOF &&\n> +\t\techo \"$*\" >>actual\n> +\t\twhile read -r line\n> +\t\tdo\n> +\t\t\tprintf \"%s\\n\" \"$line\"\n> +\t\tdone >>actual\n> +\tEOF\n> +\tcat >expect <<-EOF &&\n> +\t\tprepared\n> +\t\trefs/heads/test TESTSYMREF\n> +\t\trefs/heads/test refs/heads/symref\n> +\t\tcommitted\n> +\t\trefs/heads/test TESTSYMREF\n> +\t\trefs/heads/test refs/heads/symref\n> +\tEOF\n> +\tgit update-ref --no-deref --stdin <<-EOF &&\n> +\t\tstart\n> +\t\tupdate-symref TESTSYMREF refs/heads/test\n> +\t\tupdate-symref refs/heads/symref refs/heads/test\n> +\t\tprepare\n> +\t\tcommit\n> +\tEOF\n> +\ttest_cmp expect actual\n> +'\n> +\n>  test_expect_success 'hook gets all queued updates in aborted state' '\n>  \ttest_when_finished \"rm actual\" &&\n>  \tgit reset --hard PRE &&\n> -- \n> 2.43.GIT\n> \n"},{"id":"492065","messageId":"xmqqil0z7m5y.fsf@gitster.g","threadId":"61235","inReplyTo":"Zgv4H66NmLZ_o1IC@tanuki","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-02T16:40:41Z","receivedAt":"2024-04-02T16:40:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> because they have been supported by Git all along. It simply makes our\n> lifes easier when we don't have to special-case creations and deletions\n> in any way.\n>\n> So I'd really not want those to go away or become deprecated.\n\nThat is a good input.\n\nDo you have anything to add as a counter-proposal?  The \"I do not\ncare what was there before\" update mode does make it necessary to\nhave a \"zero\" value for symrefs that can be distinguishable from\nnot having a value at all.\n\nThanks.\n"},{"id":"492162","messageId":"CAOLa=ZS5czBhvE88sK-mQTeYBsRTn9MWnFQeAD5Nxu-uDpe3qQ@mail.gmail.com","threadId":"61235","inReplyTo":"Zgv3_K_gCkPyZvRQ@tanuki","subject":"Re: [PATCH 1/8] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-03T14:52:04Z","receivedAt":"2024-04-03T14:52:06Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n>> +static int create_and_commit_symref(struct files_ref_store *refs,\n>> +\t\t\t\t    struct ref_lock *lock, const char *refname,\n>> +\t\t\t\t    const char *target, const char *logmsg)\n>> +{\n>> +\tint ret;\n>> +\n>>  \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n>>  \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>>  \t\treturn 0;\n>>  \t}\n>>\n>> -\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n>> -\t\treturn error(\"unable to fdopen %s: %s\",\n>> -\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> +\tret = create_symref_lock(refs, lock, refname, target);\n>> +\tif (!ret) {\n>> +\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>\n> I feel like the resulting code here is a bit hard to read because the\n> successful path is now nested into the condition. This does not really\n> conform to our typical coding style. Exiting early in case the function\n> returns an error would be easier to read.\n\nAgreed, will modify this to exit early.\n\n>> -\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>> +\t\tif (commit_ref(lock) < 0)\n>> +\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n>> +\t\t\t\t     strerror(errno));\n>> +\t}\n>>\n>> -\t/* no error check; commit_ref will check ferror */\n>> -\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n>> -\tif (commit_ref(lock) < 0)\n>> -\t\treturn error(\"unable to write symref for %s: %s\", refname,\n>> -\t\t\t     strerror(errno));\n>>  \treturn 0;\n>\n> Also, is it correct to `return 0` here in case `create_symref_lock()`\n> returns an error? If so it certainly requires an in-code comment to\n> explain what is going on. If this is a bug I feel like we have\n> identified a test gap that we might want to plug.\n>\n\nIt's wrong, we should definitely be catching and returning that error.\nRegarding fixing the test gap, it is kinda hard to do so, since this\nis capturing a filesystem error. It would be easier to do so in the\nupcoming commits where I could possibly do:\n\n1. Start transaction\n2. Add symref creation to transaction\n3. Before preparing the transaction, manually create the lock file.\n4. This should hit this error.\n\nI'll add something in corresponding commit.\n"},{"id":"492630","messageId":"ZhUsrolJ0_HOH1eU@tanuki","threadId":"61235","inReplyTo":"xmqqil0z7m5y.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-09T11:55:26Z","receivedAt":"2024-04-09T11:55:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 02, 2024 at 09:40:41AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > because they have been supported by Git all along. It simply makes our\n> > lifes easier when we don't have to special-case creations and deletions\n> > in any way.\n> >\n> > So I'd really not want those to go away or become deprecated.\n> \n> That is a good input.\n> \n> Do you have anything to add as a counter-proposal?  The \"I do not\n> care what was there before\" update mode does make it necessary to\n> have a \"zero\" value for symrefs that can be distinguishable from\n> not having a value at all.\n> \n> Thanks.\n\nSorry for taking this long to answer your question.\n\nI might have missed it while scanning through this thread, but why\nexactly is the zero OID not a good enough placeholder here to say that\nthe ref must not exist? A symref cannot point to a ref named after the\nzero OID anyway.\n\nIn my opinion, \"update-symref\" with an old-value must be able to accept\nboth object IDs and symrefs as old value. Like this it would be possible\nto update a proper ref to a symref in a race-free way. So you can say:\n\n    git update-ref SYMREF refs/heads/main 19981daefd7c147444462739375462b49412ce33\n\nTo update \"SYRMEF\" to \"refs/heads/main\", but only in case it currently\nis a proper ref that points to 19981daefd7c147444462739375462b49412ce33.\nSimilarly...\n\n    git update-ref SYMREF refs/heads/main refs/heads/master\n\nwould update \"SYMREF\" to \"refs/heads/main\", but only if it currently\npoints to the symref \"refs/heads/master\". And by extension I think that\nthe zero OID should retain its established meaning of \"This ref must not\nexist\":\n\n    git update-ref SYMREF refs/heads/main 0000000000000000000000000000000000000000\n\nThis would only update \"SYMREF\" to \"refs/heads/main\" if it does not yet\nexist.\n\nPatrick\n"},{"id":"492641","messageId":"CAOLa=ZSmDZcM473c2dNB0FTv79Vrh92YRTnVR74jOfzX1naNRA@mail.gmail.com","threadId":"61235","inReplyTo":"ZhUsrolJ0_HOH1eU@tanuki","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-09T16:15:59Z","receivedAt":"2024-04-09T16:16:00Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Tue, Apr 02, 2024 at 09:40:41AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > because they have been supported by Git all along. It simply makes our\n>> > lifes easier when we don't have to special-case creations and deletions\n>> > in any way.\n>> >\n>> > So I'd really not want those to go away or become deprecated.\n>>\n>> That is a good input.\n>>\n>> Do you have anything to add as a counter-proposal?  The \"I do not\n>> care what was there before\" update mode does make it necessary to\n>> have a \"zero\" value for symrefs that can be distinguishable from\n>> not having a value at all.\n>>\n>> Thanks.\n>\n> Sorry for taking this long to answer your question.\n>\n> I might have missed it while scanning through this thread, but why\n> exactly is the zero OID not a good enough placeholder here to say that\n> the ref must not exist? A symref cannot point to a ref named after the\n> zero OID anyway.\n>\n> In my opinion, \"update-symref\" with an old-value must be able to accept\n> both object IDs and symrefs as old value. Like this it would be possible\n> to update a proper ref to a symref in a race-free way. So you can say:\n>\n>     git update-ref SYMREF refs/heads/main 19981daefd7c147444462739375462b49412ce33\n>\n> To update \"SYRMEF\" to \"refs/heads/main\", but only in case it currently\n> is a proper ref that points to 19981daefd7c147444462739375462b49412ce33.\n> Similarly...\n>\n>     git update-ref SYMREF refs/heads/main refs/heads/master\n>\n> would update \"SYMREF\" to \"refs/heads/main\", but only if it currently\n> points to the symref \"refs/heads/master\". And by extension I think that\n> the zero OID should retain its established meaning of \"This ref must not\n> exist\":\n>\n>     git update-ref SYMREF refs/heads/main 0000000000000000000000000000000000000000\n>\n> This would only update \"SYMREF\" to \"refs/heads/main\" if it does not yet\n> exist.\n>\n\nThis is definitely nicer experience for the user. From looking at the\nother commands, {verify, create, delete} I can only see this applying to\n`symref-update`. Making the syntax for update something like:\n\n    symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n\nI think this makes sense to me, will incorporate this and send the next\nversion in the next few days.\n\nOn a side-note: This would also mean that we should somehow support\nmoving from symrefs to a regular ref via a transaction, so that means we\nshould allow\n\n    update SP <ref> SP <new-oid> [SP (<old-oid> | <old-rev>)] LF\n\ntoo, but I'm not going to tackle that in my patches.\n"},{"id":"492678","messageId":"ZhYTe2zpJyHJ8iIT@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZSmDZcM473c2dNB0FTv79Vrh92YRTnVR74jOfzX1naNRA@mail.gmail.com","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-10T04:20:11Z","receivedAt":"2024-04-10T04:20:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Apr 09, 2024 at 09:15:59AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Tue, Apr 02, 2024 at 09:40:41AM -0700, Junio C Hamano wrote:\n> >> Patrick Steinhardt <ps@pks.im> writes:\n> >>\n> >> > because they have been supported by Git all along. It simply makes our\n> >> > lifes easier when we don't have to special-case creations and deletions\n> >> > in any way.\n> >> >\n> >> > So I'd really not want those to go away or become deprecated.\n> >>\n> >> That is a good input.\n> >>\n> >> Do you have anything to add as a counter-proposal?  The \"I do not\n> >> care what was there before\" update mode does make it necessary to\n> >> have a \"zero\" value for symrefs that can be distinguishable from\n> >> not having a value at all.\n> >>\n> >> Thanks.\n> >\n> > Sorry for taking this long to answer your question.\n> >\n> > I might have missed it while scanning through this thread, but why\n> > exactly is the zero OID not a good enough placeholder here to say that\n> > the ref must not exist? A symref cannot point to a ref named after the\n> > zero OID anyway.\n> >\n> > In my opinion, \"update-symref\" with an old-value must be able to accept\n> > both object IDs and symrefs as old value. Like this it would be possible\n> > to update a proper ref to a symref in a race-free way. So you can say:\n> >\n> >     git update-ref SYMREF refs/heads/main 19981daefd7c147444462739375462b49412ce33\n> >\n> > To update \"SYRMEF\" to \"refs/heads/main\", but only in case it currently\n> > is a proper ref that points to 19981daefd7c147444462739375462b49412ce33.\n> > Similarly...\n> >\n> >     git update-ref SYMREF refs/heads/main refs/heads/master\n> >\n> > would update \"SYMREF\" to \"refs/heads/main\", but only if it currently\n> > points to the symref \"refs/heads/master\". And by extension I think that\n> > the zero OID should retain its established meaning of \"This ref must not\n> > exist\":\n> >\n> >     git update-ref SYMREF refs/heads/main 0000000000000000000000000000000000000000\n> >\n> > This would only update \"SYMREF\" to \"refs/heads/main\" if it does not yet\n> > exist.\n> >\n> \n> This is definitely nicer experience for the user. From looking at the\n> other commands, {verify, create, delete} I can only see this applying to\n> `symref-update`. Making the syntax for update something like:\n> \n>     symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n> \n> I think this makes sense to me, will incorporate this and send the next\n> version in the next few days.\n> \n> On a side-note: This would also mean that we should somehow support\n> moving from symrefs to a regular ref via a transaction, so that means we\n> should allow\n> \n>     update SP <ref> SP <new-oid> [SP (<old-oid> | <old-rev>)] LF\n> \n> too, but I'm not going to tackle that in my patches.\n\nYes, I think that would be a sensible idea, even though we have to be\ncareful with backwards compatibility here. In any case, I think it makes\nsense to not extend the scope of your patch series and leave this for\nthe future.\n\nPatrick\n"},{"id":"492720","messageId":"xmqqzfu1w6ay.fsf@gitster.g","threadId":"61235","inReplyTo":"ZhYTe2zpJyHJ8iIT@tanuki","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-10T16:06:45Z","receivedAt":"2024-04-10T16:06:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> > I might have missed it while scanning through this thread, but why\n>> > exactly is the zero OID not a good enough placeholder here to say that\n>> > the ref must not exist? A symref cannot point to a ref named after the\n>> > zero OID anyway.\n>\n>> > In my opinion, \"update-symref\" with an old-value must be able to accept\n>> > both object IDs and symrefs as old value. Like this it would be possible\n>> > to update a proper ref to a symref in a race-free way. So you can say:\n>> >\n>> >     git update-ref SYMREF refs/heads/main 19981daefd7c147444462739375462b49412ce33\n>\n>> > To update \"SYRMEF\" to \"refs/heads/main\", but only in case it currently\n>> > is a proper ref that points to 19981daefd7c147444462739375462b49412ce33.\n>> > Similarly...\n>> >\n>> >     git update-ref SYMREF refs/heads/main refs/heads/master\n>\n>> > would update \"SYMREF\" to \"refs/heads/main\", but only if it currently\n>> > points to the symref \"refs/heads/master\".\n\nI think that would work well.  We need to explicitly forbid a file\n$GIT_DIR/[0-9a-f]{40} to be used as a pseudoref, which I think that\nis an improvement.  I do not know how the transition to move to a\nworld with a stricter rule would look like, though.\n\n>> > And by extension I think that\n>> > the zero OID should retain its established meaning of \"This ref must not\n>> > exist\":\n>> >\n>> >     git update-ref SYMREF refs/heads/main 0000000000000000000000000000000000000000\n>> >\n>> > This would only update \"SYMREF\" to \"refs/heads/main\" if it does not yet\n>> > exist.\n\nWe express \"SYMREF must currently be a symref\" by naming an old-ref,\nand we say \"SYMREF can currently be either a ref or symref, but it\nmust point at this object\" by naming an old-oid.  The looseness of\nthe latter (i.e. we cannot express \"Before making HEAD point at ref\nX, ensure that it is detached at OID\") looks a bit disturbing, but\notherwise looks good.  I offhand do not know how expressive we would\nwant the \"old\" requirement to be.\n\n>> On a side-note: This would also mean that we should somehow support\n>> moving from symrefs to a regular ref via a transaction, so that means we\n>> should allow\n>> \n>>     update SP <ref> SP <new-oid> [SP (<old-oid> | <old-rev>)] LF\n>> \n>> too, but I'm not going to tackle that in my patches.\n>\n> Yes, I think that would be a sensible idea, even though we have to be\n> careful with backwards compatibility here. In any case, I think it makes\n> sense to not extend the scope of your patch series and leave this for\n> the future.\n\nLikewise, we may want to be able to express \"Before making HEAD\ndetached at commit X, ensure that HEAD points at ref Y that points\nat commit Z\".  IOW, the \"old\" part might have to be not\n\n\t[SP (<old-oid> | <old-ref>)]\n\nbut\n\n\t[SP <old-oid> SP <old-ref>]\n\nto specify both, perhaps?\n"},{"id":"492728","messageId":"ZhbND7flvT7INnrz@ncase","threadId":"61235","inReplyTo":"xmqqzfu1w6ay.fsf@gitster.g","subject":"Re: [PATCH 7/8] refs: add 'update-symref' command to 'update-ref'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-10T17:31:59Z","receivedAt":"2024-04-10T17:32:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Apr 10, 2024 at 09:06:45AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> >> > I might have missed it while scanning through this thread, but why\n> >> > exactly is the zero OID not a good enough placeholder here to say that\n> >> > the ref must not exist? A symref cannot point to a ref named after the\n> >> > zero OID anyway.\n> >\n> >> > In my opinion, \"update-symref\" with an old-value must be able to accept\n> >> > both object IDs and symrefs as old value. Like this it would be possible\n> >> > to update a proper ref to a symref in a race-free way. So you can say:\n> >> >\n> >> >     git update-ref SYMREF refs/heads/main 19981daefd7c147444462739375462b49412ce33\n> >\n> >> > To update \"SYRMEF\" to \"refs/heads/main\", but only in case it currently\n> >> > is a proper ref that points to 19981daefd7c147444462739375462b49412ce33.\n> >> > Similarly...\n> >> >\n> >> >     git update-ref SYMREF refs/heads/main refs/heads/master\n> >\n> >> > would update \"SYMREF\" to \"refs/heads/main\", but only if it currently\n> >> > points to the symref \"refs/heads/master\".\n> \n> I think that would work well.  We need to explicitly forbid a file\n> $GIT_DIR/[0-9a-f]{40} to be used as a pseudoref, which I think that\n> is an improvement.  I do not know how the transition to move to a\n> world with a stricter rule would look like, though.\n\nI thought that Git already refuses such refnames anyway. Otherwise it\nwould be impossible to distinguish a ref called [0-9a-f]{40} from the\nactual object hash in much of our tooling. I certainly know that GitLab\ndoes refuse such refnames, and thought that GitHub does, too.\n\nBut turns out that at least git-update-ref(1) is happy to write such\nrefs:\n\n```\n$ git update-ref 1111111111111111111111111111111111111111 HEAD\n$ cat .git/1111111111111111111111111111111111111111\ncf6ba211cd2fce88f5d22d9f036029d502565509\n```\n\nWhat does it resolve to?\n\n```\n$ git rev-parse --verify 1111111111111111111111111111111111111111\nwarning: refname '1111111111111111111111111111111111111111' is ambiguous.\nGit normally never creates a ref that ends with 40 hex characters\nbecause it will be ignored when you just specify 40-hex. These refs\nmay be created by mistake. For example,\n\n  git switch -c $br $(git rev-parse ...)\n\nwhere \"$br\" is somehow empty and a 40-hex ref is created. Please\nexamine these refs and maybe delete them. Turn this message off by\nrunning \"git config advice.objectNameWarning false\"\n1111111111111111111111111111111111111111\n```\n\nIt resolves to 1111111111111111111111111111111111111111 because it's a\nvalid object ID already. And what if we try to peel it?\n\n```\n$ git rev-parse --verify 1111111111111111111111111111111111111111^{commit}\nwarning: refname '1111111111111111111111111111111111111111' is ambiguous.\nGit normally never creates a ref that ends with 40 hex characters\nbecause it will be ignored when you just specify 40-hex. These refs\nmay be created by mistake. For example,\n\n  git switch -c $br $(git rev-parse ...)\n\nwhere \"$br\" is somehow empty and a 40-hex ref is created. Please\nexamine these refs and maybe delete them. Turn this message off by\nrunning \"git config advice.objectNameWarning false\"\nfatal: Needed a single revision\n```\n\nDoesn't work.\n\nSo these refs essentially cannot be accessed anyway. Restricting\ngit-update-ref(1) such that it cannot write them in the first place thus\nshouldn't be breaking much, I'd claim, and feels like a strict\nimprovement overall.\n\nPatrick\n"},{"id":"492859","messageId":"20240412095908.1134387-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240330224623.579457-1-knayak@gitlab.com","subject":"[PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:01Z","receivedAt":"2024-04-12T09:59:20Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'git-update-ref(1)' command allows transactional reference updates.\nBut currently only supports regular reference updates. Meaning, if one\nwants to update HEAD (symbolic ref) in a transaction, there is no tool\nto do so.\n\nOne option to obtain transactional updates for the HEAD ref is to\nmanually create the HEAD.lock file and commit. This is intrusive, where\nthe user needs to mimic internal git behavior. Also, this only works\nwhen using the files backend.\n\nAt GitLab, we've been using the manual process till date, to allow users\nto set and change their default branch. But with the introduction of\nreftables as a reference backend, this becomes a necessity to be solved\nwithin git.\n\nThis patch series goes about introducing a set of commands\nsymref-{create,verify,delete,update} to work with symrefs complimenting\nthe existing commands for the regular refs within 'git-update-ref(1)'.\n\nThe 'symref-verify' command can be used to verify if a symref exists and\nits existing value.\n\nThe 'symref-create' command can be used to create a new symref.\n\nThe 'symref-delete' command can be used to delete an existing symref while\noptionally checking its existing value.\n\nThe 'symref-update' command can be used to update a symref, create a symref,\ndelete a symref or even convert an existing regular ref to a symref. Wherein\nlike the regular 'update' command, the zero OID can be used to create/delete\na symref.\n\nV1 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\n\nI'm not adding a range diff here, cause I redid the whole series, things which\nhave changed:\n1. The earlier series simply propagated a 'symref_target' and only supported the\n'symref-update' command, without checks for existing values and such. Now we\nsupport the entire fleet of commands with support for checking old_values.\n2. The flow is now changedc to send an old_ref, new_ref pair in supplement to\nthe existing old_oid, new_oid pair to the reference backends. This allows the\nbackends to simply do a combination of changes based on what values are set.\nThis allows us to do symref-update's where we change a regular ref to a symref\nwhile also validating its old OID.\n3. I added a lot more tests to cover reflog checks and also '-z' input.\n4. The entered <old-ref> and <new-ref> values are checked within update-ref, to\nensure we don't create dangling refs. This could be extended in the future with\na flag, maybe \"REF_ALLOW_DANGLING_SYMREF\" and a corresponding option within\nupdate-ref.\n5. Removed some commits where reftable backend code was reused for symref creation.\nThis actually caused issues since the reused code also created a reflog along with\nthe symref but we should defer reflog creations only after all ref creations have\ntaken place. There is a bit of DRY here, but I think overall its still much cleaner.\n\nThanks all for the review and discussion in the previous version. Patrick, I did\nincorporate the changes you suggested, but I just noticed that I didn't reply to\nyour emails.\n\nKarthik Nayak (7):\n  refs: accept symref values in `ref_transaction[_add]_update`\n  update-ref: add support for symref-verify\n  update-ref: add support for symref-delete\n  files-backend: extract out `create_symref_lock`\n  update-ref: add support for symref-create\n  update-ref: add support for symref-update\n  refs: support symrefs in 'reference-transaction' hook\n\n Documentation/git-update-ref.txt |  25 +++\n Documentation/githooks.txt       |  13 +-\n branch.c                         |   2 +-\n builtin/clone.c                  |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   4 +-\n builtin/receive-pack.c           |   4 +-\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             | 202 +++++++++++++++++--\n refs.c                           |  74 +++++--\n refs.h                           |  15 +-\n refs/files-backend.c             | 143 +++++++++++---\n refs/refs-internal.h             |  21 ++\n refs/reftable-backend.c          |  51 ++++-\n sequencer.c                      |   9 +-\n t/t0600-reffiles-backend.sh      |  32 ++++\n t/t1400-update-ref.sh            | 320 ++++++++++++++++++++++++++++++-\n t/t1416-ref-transaction-hooks.sh |  41 ++++\n walker.c                         |   2 +-\n 20 files changed, 886 insertions(+), 82 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"492860","messageId":"20240412095908.1134387-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:02Z","receivedAt":"2024-04-12T09:59:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `ref_transaction[_add]_update` functions obtain ref information and\nflags to create a `ref_update` and add it to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref values and process it. In this commit, let's add the\nrequired paramaters to the function and modify all call sites.\n\nThe two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\nused to denote what the reference should point to when the transaction\nis applied. Some functions allow this parameter to be NULL, meaning that\nthe reference is not changed, or `\"\"`, meaning that the reference should\nbe deleted.\n\nThe `old_ref` denotes the value of that the reference must have before\nthe update. Some functions allow this parameter to be NULL, meaning that\nthe old value of the reference is not checked, or `\"\"`, meaning that the\nreference must not exist before the update. A copy of this value is made\nin the transaction.\n\nThe handling logic of these parameters will be added in consequent\ncommits as we implement symref-{create, update, delete, verify}.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 15 ++++++++++-----\n refs.h                  |  9 ++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 55 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 621019fcf4..3ebcfdca65 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex 782bda007c..6a0b39de32 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex 56d8a77ed7..ebea1747cb 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..967c81167e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_ref, const char *old_ref,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1253,6 +1254,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_ref, const char *old_ref,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1282,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1297,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1311,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1325,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1340,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..645fe9fdb8 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -696,13 +696,19 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  */\n #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n \n+/*\n+ * The reference update is considered to be done on a symbolic reference. This\n+ * ensures that we verify, delete, create and update the ref correspondingly.\n+ */\n+#define REF_SYMREF_UPDATE (1 << 12)\n+\n /*\n  * Bitmask of all of the flags that are allowed to be passed in to\n  * ref_transaction_update() and friends:\n  */\n #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n-\t REF_SKIP_REFNAME_VERIFICATION)\n+\t REF_SKIP_REFNAME_VERIFICATION | REF_SYMREF_UPDATE )\n \n /*\n  * Add a reference update to transaction. `new_oid` is the value that\n@@ -722,6 +728,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_ref, const char *old_ref,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..4c5fe02687 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,19 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n+\t * value (or delete it, if `new_ref` is an empty string).\n+\t */\n+\tconst char *new_ref;\n+\n+\t/*\n+\t * If (type & REF_SYMREF_UPDATE), check that the reference\n+\t * previously had this value (or didn't previously exist,\n+\t * if `old_ref` is an empty string).\n+\t */\n+\tconst char *old_ref;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_ref, const char *old_ref,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 2c19846385..af1b25692b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -616,7 +616,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1248,7 +1248,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3764,8 +3764,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"492861","messageId":"20240412095908.1134387-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 2/7] update-ref: add support for symref-verify","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:03Z","receivedAt":"2024-04-12T09:59:23Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commit, we added the required base for adding symref\nsupport in transactions provided by the 'git-update-ref(1)'. This commit\nintroduces the 'symref-verify' command which is similar to the existing\n'verify' command for regular refs.\n\nThe 'symref-verify' command allows users to verify if a provided <ref>\ncontains the provided <old-ref> without changing the <ref>. If <old-ref>\nis not provided, the command will verify that the <ref> doesn't exist.\nSince we're checking for symbolic refs, this command will only work with\nthe 'no-deref' mode. This is because any dereferenced symbolic ref will\npoint to an object and not a ref and the regular 'verify' command can be\nused in such situations.\n\nThis commit adds all required helper functions required to also\nintroduce the other symref commands, namely create, delete, and update.\nWe also add tests to test the command in both the regular stdin mode and\nalso with the '-z' flag.\n\nWhen the user doesn't provide a <old-ref> we need to check that the\nprovided <ref> doesn't exist. And to do this, we take over the existing\nunderstanding that <old-oid> when set to its zero value, it refers to\nthe ref not existing. While this seems like a mix of contexts between\nusing <*-ref> and <*-oid>, this actually works really well, especially\nconsidering the fact that we want to eventually also introduce\n\n    symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n\nand here, we'd allow the user to update a regular <ref> to a symref and\nuse <old-oid> to check the <ref>'s oid. This can be extrapolated to the\nuser using this to create a symref when provided a zero <old-oid>. Which\nwill work given how we're setting it up.\n\nWe also disable the reference-transaction hook for symref-updates which\nwill be tackled in its own commit.\n\nAdd required tests for 'symref-verify' while also adding reflog checks for\nthe pre-existing 'verify' tests.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  7 +++\n builtin/update-ref.c             | 82 ++++++++++++++++++++++++++++----\n refs.c                           | 37 +++++++++++---\n refs.h                           |  1 +\n refs/files-backend.c             | 46 +++++++++++++++++-\n refs/refs-internal.h             |  7 +++\n refs/reftable-backend.c          | 23 ++++++++-\n t/t1400-update-ref.sh            | 80 ++++++++++++++++++++++++++++++-\n 8 files changed, 262 insertions(+), 21 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 374a2ebd2b..749aaa7892 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-verify SP <ref> [SP <old-ref>] LF\n \toption SP <opt> LF\n \tstart LF\n \tprepare LF\n@@ -86,6 +87,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-verify SP <ref> [NUL <old-ref>] NUL\n \toption SP <opt> NUL\n \tstart NUL\n \tprepare NUL\n@@ -117,6 +119,11 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-verify::\n+\tVerify symbolic <ref> against <old-ref> but do not change it.\n+\tIf <old-ref> is missing, the ref must not exist.  Can only be\n+\tused in `no-deref` mode.\n+\n option::\n \tModify the behavior of the next command naming a <ref>.\n \tThe only valid option is `no-deref` to avoid dereferencing\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 21fdbf6ac8..4ae6bdcb12 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -76,6 +76,30 @@ static char *parse_refname(const char **next)\n \treturn strbuf_detach(&ref, NULL);\n }\n \n+\n+\n+/*\n+ * Wrapper around parse_refname which skips the next delimiter.\n+ */\n+static char *parse_next_refname(const char **next)\n+{\n+        if (line_termination) {\n+                /* Without -z, consume SP and use next argument */\n+                if (!**next || **next == line_termination)\n+                        return NULL;\n+                if (**next != ' ')\n+                        die(\"expected SP but got: %s\", *next);\n+        } else {\n+                /* With -z, read the next NUL-terminated line */\n+                if (**next)\n+                        return NULL;\n+        }\n+        /* Skip the delimiter */\n+        (*next)++;\n+\n+        return parse_refname(next);\n+}\n+\n /*\n  * The value being parsed is <old-oid> (as opposed to <new-oid>; the\n  * difference affects which error messages are generated):\n@@ -297,11 +321,48 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n \t\tdie(\"verify %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_verify(transaction, refname, &old_oid,\n-\t\t\t\t   update_flags, &err))\n+\t\t\t\t   NULL, update_flags, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tstrbuf_release(&err);\n+}\n+\n+static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n+                                    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct object_id old_oid;\n+\tchar *refname, *old_ref;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+\t\tdie(\"symref-verify: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-verify: missing <ref>\");\n+\n+\t/*\n+\t * old_ref is optional, but we want to differentiate between\n+\t * a NULL and zero value.\n+\t */\n+\told_ref = parse_next_refname(&next);\n+\tif (!old_ref)\n+\t\told_oid = *null_oid();\n+\telse if (read_ref(old_ref, NULL))\n+\t\tdie(\"symref-verify %s: invalid <old-ref>\", refname);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_verify(transaction, refname, old_ref ? NULL : &old_oid,\n+\t\t\t\t   old_ref, update_flags | REF_SYMREF_UPDATE, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n+\tfree(old_ref);\n \tstrbuf_release(&err);\n }\n \n@@ -380,15 +441,16 @@ static const struct parse_cmd {\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n-\t{ \"update\",  parse_cmd_update,  3, UPDATE_REFS_OPEN },\n-\t{ \"create\",  parse_cmd_create,  2, UPDATE_REFS_OPEN },\n-\t{ \"delete\",  parse_cmd_delete,  2, UPDATE_REFS_OPEN },\n-\t{ \"verify\",  parse_cmd_verify,  2, UPDATE_REFS_OPEN },\n-\t{ \"option\",  parse_cmd_option,  1, UPDATE_REFS_OPEN },\n-\t{ \"start\",   parse_cmd_start,   0, UPDATE_REFS_STARTED },\n-\t{ \"prepare\", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },\n-\t{ \"abort\",   parse_cmd_abort,   0, UPDATE_REFS_CLOSED },\n-\t{ \"commit\",  parse_cmd_commit,  0, UPDATE_REFS_CLOSED },\n+\t{ \"update\",        parse_cmd_update,        3, UPDATE_REFS_OPEN },\n+\t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n+\t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n+\t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n+\t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n+\t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\n+\t{ \"prepare\",       parse_cmd_prepare,       0, UPDATE_REFS_PREPARED },\n+\t{ \"abort\",         parse_cmd_abort,         0, UPDATE_REFS_CLOSED },\n+\t{ \"commit\",        parse_cmd_commit,        0, UPDATE_REFS_CLOSED },\n };\n \n static void update_refs_stdin(void)\ndiff --git a/refs.c b/refs.c\nindex 967c81167e..124b294c9f 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -19,6 +19,7 @@\n #include \"object-store-ll.h\"\n #include \"object.h\"\n #include \"path.h\"\n+#include \"string.h\"\n #include \"tag.h\"\n #include \"submodule.h\"\n #include \"worktree.h\"\n@@ -29,6 +30,7 @@\n #include \"date.h\"\n #include \"commit.h\"\n #include \"wildmatch.h\"\n+#include \"wrapper.h\"\n \n /*\n  * List of all available backends\n@@ -1217,6 +1219,7 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((void *)transaction->updates[i]->old_ref);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1242,10 +1245,19 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n-\t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n-\t\toidcpy(&update->old_oid, old_oid);\n+\t/*\n+\t * The ref values are to be considered over the oid values when we're\n+\t * doing symref operations.\n+\t */\n+\tif (update->flags & REF_SYMREF_UPDATE) {\n+\t\tif (old_ref)\n+\t\t\tupdate->old_ref = xstrdup(old_ref);\n+\t} else {\n+\t\tif (flags & REF_HAVE_NEW)\n+\t\t\toidcpy(&update->new_oid, new_oid);\n+\t\tif (flags & REF_HAVE_OLD)\n+\t\t\toidcpy(&update->old_oid, old_oid);\n+\t}\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -1280,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_ref ? REF_HAVE_NEW : 0) | (old_ref ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n@@ -1318,14 +1331,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_ref,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!old_oid)\n+\tif (flags & REF_SYMREF_UPDATE && !old_ref && !old_oid)\n+\t\tBUG(\"verify called with old_ref set to NULL\");\n+\tif (!(flags & REF_SYMREF_UPDATE) && !old_oid)\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n-\t\t\t\t      NULL, NULL,\n+\t\t\t\t      NULL, old_ref,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -2342,6 +2358,9 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \n+\t\tif (update->flags & REF_SYMREF_UPDATE)\n+\t\t\tcontinue;\n+\n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n \t\t\t    oid_to_hex(&update->old_oid),\n@@ -2795,3 +2814,9 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+int null_new_value(struct ref_update *update) {\n+\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref)\n+\t\treturn 0;\n+\treturn is_null_oid(&update->new_oid);\n+}\ndiff --git a/refs.h b/refs.h\nindex 645fe9fdb8..a988e672ff 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -772,6 +772,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_ref,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..8421530bde 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2411,6 +2411,37 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+/*\n+ * Check whether the REF_HAVE_OLD and old_ref values stored in update\n+ * are consistent with ref, which is the symbolic reference's current\n+ * value. If everything is OK, return 0; otherwise, write an error\n+ * message to err and return -1.\n+ */\n+static int check_old_ref(struct ref_update *update, char *ref,\n+\t\t\t struct strbuf *err)\n+{\n+\tif (!(update->flags & REF_HAVE_OLD) ||\n+\t    !strcmp(update->old_ref, ref))\n+\t\treturn 0;\n+\n+\tif (!strcmp(update->old_ref, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference already exists\",\n+\t\t\t    original_update_refname(update));\n+\telse if (!strcmp(ref, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    update->old_ref);\n+\telse\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    ref, update->old_ref);\n+\n+\treturn -1;\n+}\n+\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2464,8 +2495,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t       struct strbuf *err)\n {\n \tstruct strbuf referent = STRBUF_INIT;\n-\tint mustexist = (update->flags & REF_HAVE_OLD) &&\n-\t\t!is_null_oid(&update->old_oid);\n+\tint mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid);\n \tint ret = 0;\n \tstruct ref_lock *lock;\n \n@@ -2514,6 +2544,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * For symref verification, we need to check the referent value\n+\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n+\t\t\t * verifying a dereferenced symref, we then check the oid.\n+\t\t\t */\n+\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->old_ref) {\n+\t\t\t\tif (check_old_ref(update, referent.buf, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 4c5fe02687..21c6b940d8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int null_new_value(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..7a03922c7b 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -938,7 +938,28 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif ((u->flags & REF_HAVE_OLD) &&\n+\t\t    (u->flags & REF_SYMREF_UPDATE) &&\n+\t\t    u->old_ref) {\n+\t\t\tif   (strcmp(referent.buf, u->old_ref)) {\n+\t\t\t\tif (!strcmp(u->old_ref, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t\t\t\t    \"reference already exists\",\n+\t\t\t\t\t\t    original_update_refname(u));\n+\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    u->old_ref);\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t\t\t\t    \"is at %s but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    referent.buf, u->old_ref);\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t    \"reference already exists\"),\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex ec3443cc87..d8ffda4096 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '\n '\n \n test_expect_success 'stdin verify succeeds for correct value' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \tgit rev-parse $m >expect &&\n \techo \"verify $m $m\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n \tgit rev-parse $m >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify succeeds for missing reference' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \techo \"verify refs/heads/missing $Z\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n-\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify treats no value as missing' '\n@@ -1641,4 +1647,74 @@ test_expect_success PIPE 'transaction flushes status updates' '\n \ttest_cmp expected actual\n '\n \n+create_stdin_buf ()\n+{\n+\tif test \"$1\" = \"-z\"\n+\tthen\n+\t\tshift\n+\t\tprintf \"$F\" \"$@\" >stdin\n+\telse\n+\t\techo \"$@\" >stdin\n+\tfi\n+}\n+\n+for type in \"\" \"-z\"\n+do\n+\n+test_expect_success \"stdin ${type} symref-verify fails without --no-deref\" '\n+\tgit symbolic-ref refs/heads/symref $a &&\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\tgrep \"fatal: symref-verify: cannot operate with deref mode\" err\n+'\n+\n+test_expect_success \"stdin ${type} symref-verify fails with too many arguments\" '\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" \"$a\" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n+\tif test \"$type\" = \"-z\"\n+\tthen\n+\t\tgrep \"fatal: unknown command: $a\" err\n+\telse\n+\t\tgrep \"fatal: symref-verify refs/heads/symref: extra input:  $a\" err\n+\tfi\n+'\n+\n+test_expect_success \"stdin ${type} symref-verify succeeds for correct value\" '\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\ttest_cmp before after\n+'\n+\n+test_expect_success \"stdin ${type} symref-verify succeeds for missing reference\" '\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/missing\" &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\ttest_cmp before after\n+'\n+\n+test_expect_success \"stdin ${type} symref-verify fails for wrong value\" '\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$b\" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\" '\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+done\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"492862","messageId":"20240412095908.1134387-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 3/7] update-ref: add support for symref-delete","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:04Z","receivedAt":"2024-04-12T09:59:23Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nSimilar to the previous commit, add 'symref-delete' to allow deletions\nof symbolic refs in a transaction via the 'git-update-ref' command. The\n'symref-delete' command can when given with an <old-ref>, deletes the\nprovided <ref> only when it points to <old-ref>.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  6 +++++\n builtin/fetch.c                  |  2 +-\n builtin/receive-pack.c           |  3 ++-\n builtin/update-ref.c             | 34 +++++++++++++++++++++++++++-\n refs.c                           | 16 +++++++++----\n refs.h                           |  4 +++-\n refs/files-backend.c             |  2 +-\n refs/reftable-backend.c          |  2 +-\n t/t1400-update-ref.sh            | 39 ++++++++++++++++++++++++++++++++\n 9 files changed, 97 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 749aaa7892..ef22a1a2f4 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-delete SP <ref> [SP <old-ref>] LF\n \tsymref-verify SP <ref> [SP <old-ref>] LF\n \toption SP <opt> LF\n \tstart LF\n@@ -87,6 +88,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-delete SP <ref> [NUL <old-ref>] NUL\n \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n \toption SP <opt> NUL\n \tstart NUL\n@@ -119,6 +121,10 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-delete::\n+\tDelete <ref> after verifying it exists with <old-ref>, if\n+\tgiven.\n+\n symref-verify::\n \tVerify symbolic <ref> against <old-ref> but do not change it.\n \tIf <old-ref> is missing, the ref must not exist.  Can only be\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 66840b7c5b..d02592efca 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,\n \t\tif (transaction) {\n \t\t\tfor (ref = stale_refs; ref; ref = ref->next) {\n \t\t\t\tresult = ref_transaction_delete(transaction, ref->name, NULL, 0,\n-\t\t\t\t\t\t\t\t\"fetch: prune\", &err);\n+\t\t\t\t\t\t\t\tNULL, \"fetch: prune\", &err);\n \t\t\t\tif (result)\n \t\t\t\t\tgoto cleanup;\n \t\t\t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex ebea1747cb..6b728baaac 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_delete(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   old_oid,\n-\t\t\t\t\t   0, \"push\", &err)) {\n+\t\t\t\t\t   0, NULL,\n+\t\t\t\t\t   \"push\", &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tret = \"failed to delete\";\n \t\t} else {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 4ae6bdcb12..3be9ae0d00 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -294,7 +294,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \n \tif (ref_transaction_delete(transaction, refname,\n \t\t\t\t   have_old ? &old_oid : NULL,\n-\t\t\t\t   update_flags, msg, &err))\n+\t\t\t\t   update_flags, NULL, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n@@ -302,6 +302,37 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *old_ref;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+                die(\"symref-delete: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-delete: missing <ref>\");\n+\n+        old_ref = parse_next_refname(&next);\n+\tif (old_ref && read_ref(old_ref, NULL))\n+\t\tdie(\"symref-delete %s: invalid <old-ref>\", refname);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-delete %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_delete(transaction, refname, NULL,\n+\t\t\t\t   update_flags | REF_SYMREF_UPDATE,\n+\t\t\t\t   old_ref, msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(old_ref);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_verify(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -445,6 +476,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n \t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\ndiff --git a/refs.c b/refs.c\nindex 124b294c9f..6d98d9652d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -981,7 +981,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,\n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname, old_oid,\n-\t\t\t\t   flags, msg, &err) ||\n+\t\t\t\t   flags, NULL, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n@@ -1220,6 +1220,7 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n \t\tfree((void *)transaction->updates[i]->old_ref);\n+\t\tfree((void *)transaction->updates[i]->new_ref);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1252,6 +1253,8 @@ struct ref_update *ref_transaction_add_update(\n \tif (update->flags & REF_SYMREF_UPDATE) {\n \t\tif (old_ref)\n \t\t\tupdate->old_ref = xstrdup(old_ref);\n+\t\tif (new_ref)\n+\t\t\tupdate->new_ref = xstrdup(new_ref);\n \t} else {\n \t\tif (flags & REF_HAVE_NEW)\n \t\t\toidcpy(&update->new_oid, new_oid);\n@@ -1317,14 +1320,17 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_ref,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err)\n {\n-\tif (old_oid && is_null_oid(old_oid))\n+\tif (!(flags & REF_SYMREF_UPDATE) && old_oid &&\n+\t    is_null_oid(old_oid))\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      NULL, old_ref, flags,\n \t\t\t\t      msg, err);\n }\n \n@@ -2748,7 +2754,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n \n \tfor_each_string_list_item(item, refnames) {\n \t\tret = ref_transaction_delete(transaction, item->string,\n-\t\t\t\t\t     NULL, flags, msg, &err);\n+\t\t\t\t\t     NULL, flags, 0, msg, &err);\n \t\tif (ret) {\n \t\t\twarning(_(\"could not delete reference %s: %s\"),\n \t\t\t\titem->string, err.buf);\ndiff --git a/refs.h b/refs.h\nindex a988e672ff..60e6a21a31 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -758,7 +758,9 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_ref,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 8421530bde..f74ea308b5 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2501,7 +2501,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 7a03922c7b..935bf407df 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1122,7 +1122,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex d8ffda4096..cf01c5d867 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1715,6 +1715,45 @@ test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\"\n \ttest_cmp expect actual\n '\n \n+test_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n+\tgit symbolic-ref refs/heads/symref $a &&\n+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-delete with no ref\" '\n+\tcreate_stdin_buf ${type} \"symref-delete \" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-delete: missing <ref>\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-delete with too many arguments\" '\n+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tif test \"$type\" = \"-z\"\n+\tthen\n+\t\tgrep \"fatal: unknown command: $a\" err\n+\telse\n+\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n+\tfi\n+'\n+\n+test_expect_success \"stdin ${type} symref-delete ref fails with wrong old value\" '\n+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: cannot lock ref '\"'\"'refs/heads/symref'\"'\"'\" err &&\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\techo $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-delete ref works with right old value\" '\n+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\ttest_must_fail git rev-parse --verify -q $b\n+'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"492863","messageId":"20240412095908.1134387-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 4/7] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:05Z","receivedAt":"2024-04-12T09:59:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nSplit this into two individual functions `create_and_commit_symref` and\n`create_symref_locked`. This way we can create the symref lock and\ncommit it at different times. This will be used to provide symref\nsupport in `git-update-ref(1)`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f74ea308b5..7c894ebe65 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n+\t\treturn error(\"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\n+\t/* no error check; commit_ref will check ferror */\n+\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n \treturn 0;\n }\n \n@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"492864","messageId":"20240412095908.1134387-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:06Z","receivedAt":"2024-04-12T09:59:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd 'symref-create' to allow creation of symbolic refs in a transaction\nvia the 'git-update-ref' command. The 'symref-create' command takes in a\n<new-ref>, which the created <ref> will point to.\n\nWe also support the 'core.prefersymlinkrefs', wherein if the flag is set\nand the filesystem supports symlinks, we create the symbolic ref as a\nsymlink.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  6 ++++\n builtin/clone.c                  |  2 +-\n builtin/update-ref.c             | 36 +++++++++++++++++++-\n refs.c                           |  9 +++--\n refs.h                           |  1 +\n refs/files-backend.c             | 38 +++++++++++++++++++++\n refs/reftable-backend.c          | 19 +++++++++--\n t/t0600-reffiles-backend.sh      | 32 ++++++++++++++++++\n t/t1400-update-ref.sh            | 58 ++++++++++++++++++++++++++++++++\n 9 files changed, 194 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex ef22a1a2f4..a5b1f42728 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-create SP <ref> SP <new-ref> LF\n \tsymref-delete SP <ref> [SP <old-ref>] LF\n \tsymref-verify SP <ref> [SP <old-ref>] LF\n \toption SP <opt> LF\n@@ -88,6 +89,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-create SP <ref> NUL <new-ref> NUL\n \tsymref-delete SP <ref> [NUL <old-ref>] NUL\n \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n \toption SP <opt> NUL\n@@ -121,6 +123,10 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-create::\n+\tCreate symbolic ref <ref> with <new-ref> after verifying\n+\tit does not exist.  Can only be used in `no-deref` mode.\n+\n symref-delete::\n \tDelete <ref> after verifying it exists with <old-ref>, if\n \tgiven.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 74ec14542e..c0eed8e795 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -547,7 +547,7 @@ static void write_remote_refs(const struct ref *local_refs)\n \t\tif (!r->peer_ref)\n \t\t\tcontinue;\n \t\tif (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,\n-\t\t\t\t\t   0, NULL, &err))\n+\t\t\t\t\t   NULL, 0, NULL, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t}\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 3be9ae0d00..24556a28a8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -258,7 +258,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_create(transaction, refname, &new_oid,\n+\tif (ref_transaction_create(transaction, refname, &new_oid, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\n@@ -268,6 +268,39 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+static void parse_cmd_symref_create(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *new_ref;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+                die(\"symref-create: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-create: missing <ref>\");\n+\n+\tnew_ref = parse_next_refname(&next);\n+\tif (!new_ref)\n+\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n+\tif (read_ref(new_ref, NULL))\n+\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_create(transaction, refname, NULL, new_ref,\n+\t\t\t\t   update_flags | create_reflog_flag |\n+\t\t\t\t   REF_SYMREF_UPDATE, msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(new_ref);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_delete(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -476,6 +509,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\ndiff --git a/refs.c b/refs.c\nindex 6d98d9652d..e62c0f4aca 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1305,15 +1305,20 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_ref,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!new_oid || is_null_oid(new_oid)) {\n+\tif ((flags & REF_SYMREF_UPDATE) && !new_ref) {\n+\t\tstrbuf_addf(err, \"'%s' has a no new ref\", refname);\n+\t\treturn 1;\n+\t}\n+\tif (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {\n \t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      null_oid(), new_ref, NULL, flags,\n \t\t\t\t      msg, err);\n }\n \ndiff --git a/refs.h b/refs.h\nindex 60e6a21a31..c01a517e40 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -744,6 +744,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_ref,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 7c894ebe65..59d438878a 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2609,6 +2609,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n+\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t}\n+\n+\n \tif ((update->flags & REF_HAVE_NEW) &&\n \t    !(update->flags & REF_DELETING) &&\n \t    !(update->flags & REF_LOG_ONLY)) {\n@@ -2904,6 +2925,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n+\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n+\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n+\t\t\t\t\tupdate->new_oid = *null_oid();\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\tif (files_log_ref_write(refs,\n \t\t\t\t\t\tlock->ref_name,\n \t\t\t\t\t\t&lock->old_oid,\n@@ -2921,6 +2950,15 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next updated. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->flags & REF_SYMREF_UPDATE && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_ref))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 935bf407df..6d42838e15 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if (u->flags & REF_HAVE_NEW && !null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -1064,7 +1064,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1122,7 +1122,20 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n+\t\tif (u->flags & REF_SYMREF_UPDATE &&\n+\t\t    u->flags & REF_HAVE_NEW &&\n+\t\t    !null_new_value(u)) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_ref,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 64214340e7..c5061c26cf 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -472,4 +472,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '\n \tesac\n '\n \n+test_expect_success SYMLINKS 'symref transaction supports symlinks' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs true &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tsymref-create TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_symlink .git/TESTSYMREFONE &&\n+\ttest \"$(test_readlink .git/TESTSYMREFONE)\" = refs/heads/new\n+'\n+\n+test_expect_success 'symref transaction supports false symlink config' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs false &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tsymref-create TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_file .git/TESTSYMREFONE &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\techo refs/heads/new >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex cf01c5d867..f4e63fae6e 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1754,6 +1754,64 @@ test_expect_success \"stdin ${type} symref-delete ref works with right old value\"\n \ttest_must_fail git rev-parse --verify -q $b\n '\n \n+test_expect_success \"stdin ${type} symref-create fails without --no-deref\" '\n+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" &&\n+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\tgrep \"fatal: symref-create: cannot operate with deref mode\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-create with no ref\" '\n+\tcreate_stdin_buf ${type} \"symref-create \" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-create: missing <ref>\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-create with no new value\" '\n+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-create refs/heads/symref: missing <new-ref>\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-create with too many arguments\" '\n+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" \"$a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tif test \"$type\" = \"-z\"\n+\tthen\n+\t\tgrep \"fatal: unknown command: $a\" err\n+\telse\n+\t\tgrep \"fatal: symref-create refs/heads/symref: extra input:  $a\" err\n+\tfi\n+'\n+\n+test_expect_success \"stdin ${type} symref-create ref works\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\techo $a >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-create does not create reflogs by default\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-create refs/symref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/symref >expect &&\n+\techo $a >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git reflog exists refs/symref\n+'\n+\n+test_expect_success \"stdin ${type} symref-create reflogs with --create-reflog\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n+\tgit update-ref --create-reflog --stdin ${type} --no-deref <stdin &&\n+\tgit symbolic-ref refs/heads/symref >expect &&\n+\techo $a >actual &&\n+\ttest_cmp expect actual &&\n+\tgit reflog exists refs/heads/symref\n+'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"492865","messageId":"20240412095908.1134387-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 7/7] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:08Z","receivedAt":"2024-04-12T09:59:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In the previous commits, we added support for\nvarious symref commands in `git-update-ref`. While it allowed us to now\nmanipulate symbolic refs via `git-update-ref`, it didn't activate the\n'reference-transaction' hook.\n\nLet's activate the hook for symbolic reference updates too. There is no\nnew format described for this and we stick to the existing format of:\n    <old-value> SP <new-value> SP <ref-name> LF\nbut now, <old-value> and <new-value> could also denote references\ninstead of objects.\n\nWhile this seems to be backward incompatible, it is okay, since the only\nway the `reference-transaction` hook has refs in its output is when\n`git-update-ref` is used with `update-symref` command. Also the\ndocumentation for reference-transaction hook always stated that support\nfor symbolic references may be added in the future.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n\nWe did initially discuss the possibility of having {symref-<command>} as\nthe prefix [1]. But this information is not propagated through the system\nand we don't know which command is actually registering at the hook. We\ncould use flags for propagating this, but this simply complicates things.\n\nI then thought of adding 'symref' prefix to the symref updates. But I\nrealized that that is no different from not having the symref prefix, because\neven 'symref' prefix entries received by the hook as input could contain\neither object IDs or refnames, like updating a regular ref to a symref. So\nthe hook needs to dynamically read inputs anyways. So might as well keep the\ncurrent syntax.\n\n[1]: https://lore.kernel.org/git/CAOLa=ZTLv39b4Q=AAUA39tXKgOSuu54xk3-r9OUenzxR-6qcag@mail.gmail.com/\n\n Documentation/githooks.txt       | 13 ++++++----\n refs.c                           | 17 ++++++++-----\n t/t1416-ref-transaction-hooks.sh | 41 ++++++++++++++++++++++++++++++++\n 3 files changed, 60 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..1db5ab02d6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also cover symbolic references.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,19 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 31c09c3317..010f426def 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2362,15 +2362,20 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tconst char *new_value, *old_value;\n \n-\t\tif (update->flags & REF_SYMREF_UPDATE)\n-\t\t\tcontinue;\n+\t\tnew_value = oid_to_hex(&update->new_oid);\n+\t\told_value = oid_to_hex(&update->old_oid);\n+\n+\t\tif (update->flags & REF_SYMREF_UPDATE) {\n+\t\t\tif (update->flags & REF_HAVE_NEW && !null_new_value(update))\n+\t\t\t\tnew_value = update->new_ref;\n+\t\t\tif (update->flags & REF_HAVE_OLD && update->old_ref)\n+\t\t\t\told_value = update->old_ref;\n+\t\t}\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\t\tstrbuf_addf(&buf, \"%s %s %s\\n\", old_value, new_value, update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..104e2c5da4 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -108,6 +108,10 @@ test_expect_success 'hook gets all queued updates in aborted state' '\n \ttest_cmp expect actual\n '\n \n+# This test doesn't add a check for 'symref-delete' since there is a\n+# variation between the ref backends WRT 'delete'. In the files backend,\n+# 'delete' also triggers an additional transaction update on the\n+# packed-refs backend, which constitutes additional reflog entries.\n test_expect_success 'interleaving hook calls succeed' '\n \ttest_when_finished \"rm -r target-repo.git\" &&\n \n@@ -134,4 +138,41 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+test_expect_success 'hook gets all queued symref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\tgit update-ref refs/heads/branch $POST_OID &&\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\tgit symbolic-ref refs/heads/symrefu refs/heads/main &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t\tprepared\n+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n+\t\tcommitted\n+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n+\tEOF\n+\n+\tgit update-ref --no-deref --stdin <<-EOF &&\n+\t\tstart\n+\t\tsymref-verify refs/heads/symref refs/heads/main\n+\t\tsymref-create refs/heads/symrefc refs/heads/main\n+\t\tsymref-update refs/heads/symrefu refs/heads/branch refs/heads/main\n+\t\tprepare\n+\t\tcommit\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"492866","messageId":"20240412095908.1134387-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v2 6/7] update-ref: add support for symref-update","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T09:59:07Z","receivedAt":"2024-04-12T09:59:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd 'symref-update' to allow updates of symbolic refs in a transaction\nvia the 'git-update-ref' command. The 'symref-update' command takes in a\n<new-ref>, which the <ref> will be updated to. If the <ref> doesn't\nexist it will be created.\n\nIt also optionally takes either an <old-ref> or <old-oid>. If the\n<old-ref> is provided, it checks to see if the <ref> ponints to the\n<old-ref> before the update. If <old-oid> is provided it checks <ref> to\nensure that it is a regular ref and <old-oid> is the OID before the\nupdate. This by extension also means that this when a zero <old-oid> is\nprovided, it ensures that the ref didn't exist before.\n\nThis command will also support deref mode, to ensure that we can update\ndereferenced regular refs to symrefs.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |   6 ++\n builtin/update-ref.c             |  49 +++++++++++\n refs.c                           |  24 ++----\n refs/files-backend.c             |  15 ++--\n refs/reftable-backend.c          |   7 +-\n t/t1400-update-ref.sh            | 143 +++++++++++++++++++++++++++++++\n 6 files changed, 220 insertions(+), 24 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex a5b1f42728..9710c9bc78 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-update SP <ref> SP <new-ref> [SP (<old-ref> | <old-oid>)] LF\n \tsymref-create SP <ref> SP <new-ref> LF\n \tsymref-delete SP <ref> [SP <old-ref>] LF\n \tsymref-verify SP <ref> [SP <old-ref>] LF\n@@ -89,6 +90,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-update SP <ref> NUL <new-ref> [NUL (<old-ref> | <old-oid>)] NUL\n \tsymref-create SP <ref> NUL <new-ref> NUL\n \tsymref-delete SP <ref> [NUL <old-ref>] NUL\n \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n@@ -123,6 +125,10 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-update::\n+\tSet <ref> to <new-ref> after verifying <old-ref> or <old-oid>,\n+\tif given. Can be used to delete or create symrefs too.\n+\n symref-create::\n \tCreate symbolic ref <ref> with <new-ref> after verifying\n \tit does not exist.  Can only be used in `no-deref` mode.\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 24556a28a8..809c1c7a76 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -238,6 +238,54 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+static void parse_cmd_symref_update(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *new_ref, *old_ref;\n+\tstruct object_id old_oid;\n+\tint have_old = 0;\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-update: missing <ref>\");\n+\n+\tnew_ref = parse_next_refname(&next);\n+\tif (!new_ref)\n+\t\tdie(\"symref-update %s: missing <new-ref>\", refname);\n+\tif (read_ref(new_ref, NULL))\n+\t\tdie(\"symref-update %s: invalid <new-ref>\", refname);\n+\n+\told_ref = parse_next_refname(&next);\n+\t/*\n+\t * Since the user can also send in an old-oid, we try to parse\n+\t * it as such too.\n+\t */\n+\tif (old_ref && read_ref(old_ref, NULL)) {\n+\t\tif (!repo_get_oid(the_repository, old_ref, &old_oid)) {\n+\t\t\told_ref = NULL;\n+\t\t\thave_old = 1;\n+\t\t} else\n+\t\t\tdie(\"symref-update %s: invalid <old-ref> or <old-oid>\", refname);\n+\t}\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n+\n+\tupdate_flags |= create_reflog_flag | REF_SYMREF_UPDATE;\n+\tif (ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t   have_old ? &old_oid : NULL,\n+\t\t\t\t   new_ref, old_ref, update_flags,\n+\t\t\t\t   msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(old_ref);\n+\tfree(new_ref);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_create(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -509,6 +557,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-update\", parse_cmd_symref_update, 3, UPDATE_REFS_OPEN },\n \t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\ndiff --git a/refs.c b/refs.c\nindex e62c0f4aca..31c09c3317 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1246,21 +1246,15 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\t/*\n-\t * The ref values are to be considered over the oid values when we're\n-\t * doing symref operations.\n-\t */\n-\tif (update->flags & REF_SYMREF_UPDATE) {\n-\t\tif (old_ref)\n-\t\t\tupdate->old_ref = xstrdup(old_ref);\n-\t\tif (new_ref)\n-\t\t\tupdate->new_ref = xstrdup(new_ref);\n-\t} else {\n-\t\tif (flags & REF_HAVE_NEW)\n-\t\t\toidcpy(&update->new_oid, new_oid);\n-\t\tif (flags & REF_HAVE_OLD)\n-\t\t\toidcpy(&update->old_oid, old_oid);\n-\t}\n+\tif (old_ref)\n+\t\tupdate->old_ref = xstrdup(old_ref);\n+\tif (new_ref)\n+\t\tupdate->new_ref = xstrdup(new_ref);\n+\tif (new_oid && flags & REF_HAVE_NEW)\n+\t\toidcpy(&update->new_oid, new_oid);\n+\tif (old_oid && flags & REF_HAVE_OLD)\n+\t\toidcpy(&update->old_oid, old_oid);\n+\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 59d438878a..fb9886484c 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2386,7 +2386,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_ref, update->old_ref, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2609,7 +2609,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n+\tif (update->flags & REF_SYMREF_UPDATE &&\n+\t    !(update->flags & REF_LOG_ONLY) &&\n+\t    update->new_ref) {\n \t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n@@ -2627,12 +2629,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t * phase of the transaction only needs to commit the lock.\n \t\t */\n \t\tupdate->flags |= REF_NEEDS_COMMIT;\n-\t}\n-\n-\n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6d42838e15..4bc6a369c7 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_ref, u->old_ref, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -1106,6 +1106,11 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n \n+\t\t\tif (u->flags & REF_SYMREF_UPDATE && u->new_ref)\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_ref,\n+\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n+\t\t\t\t\tgoto done;\n+\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlog = &logs[logs_nr++];\n \t\t\tmemset(log, 0, sizeof(*log));\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex f4e63fae6e..eeb3ee7952 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1360,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '\n '\n \n test_expect_success 'fails with duplicate ref update via symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref2\" &&\n \tgit branch target2 $A &&\n \tgit symbolic-ref refs/heads/symref2 refs/heads/target2 &&\n \tcat >stdin <<-EOF &&\n@@ -1812,6 +1813,148 @@ test_expect_success \"stdin ${type} symref-create reflogs with --create-reflog\" '\n \tgit reflog exists refs/heads/symref\n '\n \n+test_expect_success \"stdin ${type} fails symref-update with no ref\" '\n+\tcreate_stdin_buf ${type} \"symref-update \" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-update: missing <ref>\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-update with no new value\" '\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-update refs/heads/symref: missing <new-ref>\" err\n+'\n+\n+test_expect_success \"stdin ${type} fails symref-update with too many arguments\" '\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"$a\" \"$a\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tif test \"$type\" = \"-z\"\n+\tthen\n+\t\tgrep \"fatal: unknown command: $a\" err\n+\telse\n+\t\tgrep \"fatal: symref-update refs/heads/symref: extra input:  $a\" err\n+\tfi\n+'\n+\n+test_expect_success \"stdin ${type} symref-update ref creates with zero old value\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"$Z\" >stdin &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update ref creates with empty old value\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update ref fails with wrong old value\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tgit symbolic-ref refs/heads/symref $a &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" \"$b\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\tgrep \"fatal: symref-update refs/heads/symref: invalid <old-ref> or <old-oid>\" err &&\n+\ttest_must_fail git rev-parse --verify -q $c\n+'\n+\n+test_expect_success \"stdin ${type} symref-update ref works with right old value\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tgit symbolic-ref refs/heads/symref $a &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\techo $m >expect &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update creates symref (with deref)\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$Z $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update updates symref (with deref)\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\ttest_when_finished \"git update-ref -d --no-deref refs/heads/symref2\" &&\n+\tgit update-ref refs/heads/symref2 $a &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref2 >actual &&\n+\ttest_cmp expect actual &&\n+\techo refs/heads/symref2 >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update regular ref\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" >stdin &&\n+\tgit update-ref --stdin ${type} <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update regular ref with correct old-oid\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" \"$(git rev-parse $a)\" >stdin &&\n+\tgit update-ref --stdin ${type} <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update regular ref fails with wrong old-oid\" '\n+\ttest_when_finished \"git update-ref -d refs/heads/regularref\" &&\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" \"$(git rev-parse refs/heads/target2)\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\techo $(git rev-parse $a) >expect &&\n+\tgit rev-parse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update with zero old-oid\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/symref\" &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"$Z\" >stdin &&\n+\tgit update-ref --stdin ${type} <stdin 2>err &&\n+\techo $a >expect &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success \"stdin ${type} symref-update ref with zero old-oid\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/symref\" &&\n+\tgit symbolic-ref refs/heads/symref refs/heads/target2 &&\n+\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"$Z\" >stdin &&\n+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\tgrep \"fatal: cannot lock ref '\"'\"'refs/heads/symref'\"'\"': reference already exists\" err &&\n+\techo refs/heads/target2 >expect &&\n+\tgit symbolic-ref refs/heads/symref >actual &&\n+\ttest_cmp expect actual\n+'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"492891","messageId":"xmqqwmp2lasu.fsf@gitster.g","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-12T18:01:53Z","receivedAt":"2024-04-12T18:01:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"make refs.sp\" should have told you that you should not write NULL\nas just 0.  I'll queue a SQUASH??? fix-up on top before merging it\ninto 'seen' for today's integration.\n\ndiff --git c/refs.c w/refs.c\nindex 010f426def..d578a2823b 100644\n--- c/refs.c\n+++ w/refs.c\n@@ -2758,7 +2758,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n \n \tfor_each_string_list_item(item, refnames) {\n \t\tret = ref_transaction_delete(transaction, item->string,\n-\t\t\t\t\t     NULL, flags, 0, msg, &err);\n+\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n \t\tif (ret) {\n \t\t\twarning(_(\"could not delete reference %s: %s\"),\n \t\t\t\titem->string, err.buf);\n\n\n\n\nAlso there are quite many whitespace breakages.\n\n$ git am -s ./+kn7-v2-update-ref-symref\nApplying: refs: accept symref values in `ref_transaction[_add]_update`\nApplying: update-ref: add support for symref-verify\n.git/rebase-apply/patch:59: indent with spaces.\n        if (line_termination) {\n.git/rebase-apply/patch:60: indent with spaces.\n                /* Without -z, consume SP and use next argument */\n.git/rebase-apply/patch:61: indent with spaces.\n                if (!**next || **next == line_termination)\n.git/rebase-apply/patch:62: indent with spaces.\n                        return NULL;\n.git/rebase-apply/patch:63: indent with spaces.\n                if (**next != ' ')\nwarning: squelched 10 whitespace errors\nwarning: 15 lines applied after fixing whitespace errors.\nApplying: update-ref: add support for symref-delete\n.git/rebase-apply/patch:95: indent with spaces.\n                die(\"symref-delete: cannot operate with deref mode\");\n.git/rebase-apply/patch:101: indent with spaces.\n        old_ref = parse_next_refname(&next);\nwarning: 2 lines applied after fixing whitespace errors.\nApplying: files-backend: extract out `create_symref_lock`\nApplying: update-ref: add support for symref-create\n.git/rebase-apply/patch:81: indent with spaces.\n                die(\"symref-create: cannot operate with deref mode\");\nwarning: 1 line applied after fixing whitespace errors.\nApplying: update-ref: add support for symref-update\nApplying: refs: support symrefs in 'reference-transaction' hook\n"},{"id":"492894","messageId":"CAOLa=ZQsQr9MK=Tn3az9NZ+xQsSfw+8CTXYiO2r=WbtM=MhBwQ@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqwmp2lasu.fsf@gitster.g","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-12T18:49:37Z","receivedAt":"2024-04-12T18:49:39Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> \"make refs.sp\" should have told you that you should not write NULL\n> as just 0.  I'll queue a SQUASH??? fix-up on top before merging it\n> into 'seen' for today's integration.\n>\n> diff --git c/refs.c w/refs.c\n> index 010f426def..d578a2823b 100644\n> --- c/refs.c\n> +++ w/refs.c\n> @@ -2758,7 +2758,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n>\n>  \tfor_each_string_list_item(item, refnames) {\n>  \t\tret = ref_transaction_delete(transaction, item->string,\n> -\t\t\t\t\t     NULL, flags, 0, msg, &err);\n> +\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n>  \t\tif (ret) {\n>  \t\t\twarning(_(\"could not delete reference %s: %s\"),\n>  \t\t\t\titem->string, err.buf);\n>\n>\n\nThanks Junio, I'll apply it locally too.\n\n>\n>\n> Also there are quite many whitespace breakages.\n>\n> $ git am -s ./+kn7-v2-update-ref-symref\n> Applying: refs: accept symref values in `ref_transaction[_add]_update`\n> Applying: update-ref: add support for symref-verify\n> .git/rebase-apply/patch:59: indent with spaces.\n>         if (line_termination) {\n> .git/rebase-apply/patch:60: indent with spaces.\n>                 /* Without -z, consume SP and use next argument */\n> .git/rebase-apply/patch:61: indent with spaces.\n>                 if (!**next || **next == line_termination)\n> .git/rebase-apply/patch:62: indent with spaces.\n>                         return NULL;\n> .git/rebase-apply/patch:63: indent with spaces.\n>                 if (**next != ' ')\n> warning: squelched 10 whitespace errors\n> warning: 15 lines applied after fixing whitespace errors.\n> Applying: update-ref: add support for symref-delete\n> .git/rebase-apply/patch:95: indent with spaces.\n>                 die(\"symref-delete: cannot operate with deref mode\");\n> .git/rebase-apply/patch:101: indent with spaces.\n>         old_ref = parse_next_refname(&next);\n> warning: 2 lines applied after fixing whitespace errors.\n> Applying: files-backend: extract out `create_symref_lock`\n> Applying: update-ref: add support for symref-create\n> .git/rebase-apply/patch:81: indent with spaces.\n>                 die(\"symref-create: cannot operate with deref mode\");\n> warning: 1 line applied after fixing whitespace errors.\n> Applying: update-ref: add support for symref-update\n> Applying: refs: support symrefs in 'reference-transaction' hook\n\nSigh. Even with editorconfig, this seems to be problematic for me on\nEmacs. I'll just start applying locally from now before sending it.\n\nAs such I've applied this locally and will refrain from sending a new\nversion until a review happens.\n\n1:  3269d0e91e = 1:  3269d0e91e refs: accept symref values in\n`ref_transaction[_add]_update`\n2:  a8cb0e0a1d ! 2:  2293b514a8 update-ref: add support for symref-verify\n    @@ builtin/update-ref.c: static char *parse_refname(const char **next)\n      \treturn strbuf_detach(&ref, NULL);\n      }\n\n    -+\n    -+\n     +/*\n     + * Wrapper around parse_refname which skips the next delimiter.\n     + */\n     +static char *parse_next_refname(const char **next)\n     +{\n    -+        if (line_termination) {\n    -+                /* Without -z, consume SP and use next argument */\n    -+                if (!**next || **next == line_termination)\n    -+                        return NULL;\n    -+                if (**next != ' ')\n    -+                        die(\"expected SP but got: %s\", *next);\n    -+        } else {\n    -+                /* With -z, read the next NUL-terminated line */\n    -+                if (**next)\n    -+                        return NULL;\n    -+        }\n    -+        /* Skip the delimiter */\n    -+        (*next)++;\n    ++\tif (line_termination) {\n    ++\t\t/* Without -z, consume SP and use next argument */\n    ++\t\tif (!**next || **next == line_termination)\n    ++\t\t\treturn NULL;\n    ++\t\tif (**next != ' ')\n    ++\t\t\tdie(\"expected SP but got: %s\", *next);\n    ++\t} else {\n    ++\t\t/* With -z, read the next NUL-terminated line */\n    ++\t\tif (**next)\n    ++\t\t\treturn NULL;\n    ++\t}\n    ++\t/* Skip the delimiter */\n    ++\t(*next)++;\n     +\n    -+        return parse_refname(next);\n    ++\treturn parse_refname(next);\n     +}\n     +\n      /*\n    @@ builtin/update-ref.c: static void parse_cmd_verify(struct\nref_transaction *trans\n     +}\n     +\n     +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n    -+                                    const char *next, const char *end)\n    ++\t\t\t\t    const char *next, const char *end)\n     +{\n     +\tstruct strbuf err = STRBUF_INIT;\n     +\tstruct object_id old_oid;\n3:  37c3e006da ! 3:  62d05e7cb7 update-ref: add support for symref-delete\n    @@ builtin/update-ref.c: static void parse_cmd_delete(struct\nref_transaction *trans\n     +\tchar *refname, *old_ref;\n     +\n     +\tif (!(update_flags & REF_NO_DEREF))\n    -+                die(\"symref-delete: cannot operate with deref mode\");\n    ++\t\tdie(\"symref-delete: cannot operate with deref mode\");\n     +\n     +\trefname = parse_refname(&next);\n     +\tif (!refname)\n     +\t\tdie(\"symref-delete: missing <ref>\");\n     +\n    -+        old_ref = parse_next_refname(&next);\n    ++\told_ref = parse_next_refname(&next);\n     +\tif (old_ref && read_ref(old_ref, NULL))\n     +\t\tdie(\"symref-delete %s: invalid <old-ref>\", refname);\n     +\n    @@ refs.c: int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n      \tfor_each_string_list_item(item, refnames) {\n      \t\tret = ref_transaction_delete(transaction, item->string,\n     -\t\t\t\t\t     NULL, flags, msg, &err);\n    -+\t\t\t\t\t     NULL, flags, 0, msg, &err);\n    ++\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n      \t\tif (ret) {\n      \t\t\twarning(_(\"could not delete reference %s: %s\"),\n      \t\t\t\titem->string, err.buf);\n4:  53fdb408ef = 4:  1bd39a38dc files-backend: extract out `create_symref_lock`\n5:  8fa0151f94 ! 5:  732d31b43d update-ref: add support for symref-create\n    @@ builtin/update-ref.c: static void parse_cmd_create(struct\nref_transaction *trans\n     +\tchar *refname, *new_ref;\n     +\n     +\tif (!(update_flags & REF_NO_DEREF))\n    -+                die(\"symref-create: cannot operate with deref mode\");\n    ++\t\tdie(\"symref-create: cannot operate with deref mode\");\n     +\n     +\trefname = parse_refname(&next);\n     +\tif (!refname)\n6:  714492ede3 = 6:  eb3d239b4b update-ref: add support for symref-update\n7:  c483104562 = 7:  3cdfb9e528 refs: support symrefs in\n'reference-transaction' hook\n"},{"id":"493139","messageId":"CAP8UFD2s5GZBbZ_oKk6hFCQ+jtjRjx1QgRvLz+1hrBfkWKVCbw@mail.gmail.com","threadId":"61235","inReplyTo":"20240412095908.1134387-2-knayak@gitlab.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-18T14:25:33Z","receivedAt":"2024-04-18T14:25:47Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> The `ref_transaction[_add]_update` functions obtain ref information and\n> flags to create a `ref_update` and add it to the transaction at hand.\n>\n> To extend symref support in transactions, we need to also accept the\n> old and new ref values and process it. In this commit, let's add the\n> required paramaters to the function and modify all call sites.\n\ns/paramaters/parameters/\n\n> The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n\ns/paramaters/parameters/\n\n> used to denote what the reference should point to when the transaction\n> is applied. Some functions allow this parameter to be NULL, meaning that\n> the reference is not changed, or `\"\"`, meaning that the reference should\n> be deleted.\n\n> The `old_ref` denotes the value of that the reference must have before\n\ns/the value of that the reference/the value the reference/\n\n> the update. Some functions allow this parameter to be NULL, meaning that\n> the old value of the reference is not checked, or `\"\"`, meaning that the\n> reference must not exist before the update. A copy of this value is made\n> in the transaction.\n>\n> The handling logic of these parameters will be added in consequent\n> commits as we implement symref-{create, update, delete, verify}.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n\n> diff --git a/refs.h b/refs.h\n> index d278775e08..645fe9fdb8 100644\n> --- a/refs.h\n> +++ b/refs.h\n\nThere is the following big comment in this file:\n\n/*\n * Reference transaction updates\n *\n * The following four functions add a reference check or update to a\n * ref_transaction.  They have some common similar parameters:\n *\n *     transaction -- a pointer to an open ref_transaction, obtained\n *         from ref_transaction_begin().\n *\n *     refname -- the name of the reference to be affected.\n *\n *     new_oid -- the object ID that should be set to be the new value\n *         of the reference. Some functions allow this parameter to be\n *         NULL, meaning that the reference is not changed, or\n *         null_oid, meaning that the reference should be deleted. A\n *         copy of this value is made in the transaction.\n *\n *     old_oid -- the object ID that the reference must have before\n *         the update. Some functions allow this parameter to be NULL,\n *         meaning that the old value of the reference is not checked,\n *         or null_oid, meaning that the reference must not exist\n *         before the update. A copy of this value is made in the\n *         transaction.\n *\n *     flags -- flags affecting the update, passed to\n *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n *         REF_FORCE_CREATE_REFLOG. See those constants for more\n *         information.\n *\n *     msg -- a message describing the change (for the reflog).\n *\n *     err -- a strbuf for receiving a description of any error that\n *         might have occurred.\n *\n * The functions make internal copies of refname and msg, so the\n * caller retains ownership of these parameters.\n *\n * The functions return 0 on success and non-zero on failure. A\n * failure means that the transaction as a whole has failed and needs\n * to be rolled back.\n */\n\nI would expect the patch to update this comment.\n\n> @@ -722,6 +728,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>                            const char *refname,\n>                            const struct object_id *new_oid,\n>                            const struct object_id *old_oid,\n> +                          const char *new_ref, const char *old_ref,\n>                            unsigned int flags, const char *msg,\n>                            struct strbuf *err);\n\nThe patch might also want to update the comment just above the\nref_transaction_update() declaration as it is changing what the\nfunction can (or will be able to) do.\n\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 56641aa57a..4c5fe02687 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -124,6 +124,19 @@ struct ref_update {\n>          */\n>         struct object_id old_oid;\n>\n> +       /*\n> +        * If (flags & REF_SYMREF_UPDATE), set the reference to this\n> +        * value (or delete it, if `new_ref` is an empty string).\n\nWhat if this value is NULL?\n\n> +        */\n> +       const char *new_ref;\n> +\n> +       /*\n> +        * If (type & REF_SYMREF_UPDATE), check that the reference\n> +        * previously had this value (or didn't previously exist,\n> +        * if `old_ref` is an empty string).\n\nWhat if this value is NULL?\n\n\n> +        */\n> +       const char *old_ref;\n> +\n>         /*\n>          * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n>          * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n"},{"id":"493140","messageId":"CAP8UFD2bqdMujHgS9oPn4Xqzu=EhGMPms7ftOB7uf-AHke79AQ@mail.gmail.com","threadId":"61235","inReplyTo":"20240412095908.1134387-3-knayak@gitlab.com","subject":"Re: [PATCH v2 2/7] update-ref: add support for symref-verify","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-18T14:26:49Z","receivedAt":"2024-04-18T14:27:02Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> In the previous commit, we added the required base for adding symref\n> support in transactions provided by the 'git-update-ref(1)'. This commit\n\ns/by the 'git-update-ref(1)'/by 'git-update-ref(1)'/\n\n> introduces the 'symref-verify' command which is similar to the existing\n> 'verify' command for regular refs.\n>\n> The 'symref-verify' command allows users to verify if a provided <ref>\n> contains the provided <old-ref> without changing the <ref>.\n\nWhat if <old-ref> looks like an oid? Will it work if <ref> is a\nregular ref that actually contains this oid?\n\n> If <old-ref>\n> is not provided, the command will verify that the <ref> doesn't exist.\n> Since we're checking for symbolic refs,\n\nSo if <ref> isn't a symbolic ref, it will fail? I guess the answer is\nyes, but I think it would be better to be clear about this.\n\n> this command will only work with\n> the 'no-deref' mode. This is because any dereferenced symbolic ref will\n> point to an object and not a ref and the regular 'verify' command can be\n> used in such situations.\n>\n> This commit adds all required helper functions required to also\n> introduce the other symref commands, namely create, delete, and update.\n\nAre these helper functions actually used in this commit? If yes, it\nwould be nice to say it explicitly. If not, why is it a good idea to\nadd them now?\n\nAlso I think we prefer imperative wordings like \"Add all required...\"\nover wordings like \"This commit adds all required...\"\n\n> We also add tests to test the command in both the regular stdin mode and\n> also with the '-z' flag.\n\n> When the user doesn't provide a <old-ref> we need to check that the\n> provided <ref> doesn't exist.\n\nThat the provided <ref> doesn't exist or that it isn't a symref?\n\n> And to do this, we take over the existing\n> understanding that <old-oid> when set to its zero value, it refers to\n> the ref not existing. While this seems like a mix of contexts between\n> using <*-ref> and <*-oid>, this actually works really well, especially\n> considering the fact that we want to eventually also introduce\n>\n>     symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n>\n> and here, we'd allow the user to update a regular <ref> to a symref and\n> use <old-oid> to check the <ref>'s oid.\n\nThis means that the ref actually exists but isn't a symref.\n\nSo if I understand correctly, for now it will work only if the ref\ndoesn't exist, but in the future we can make it work also if the ref\nexists but isn't a symref.\n\n> This can be extrapolated to the\n> user using this to create a symref when provided a zero <old-oid>. Which\n> will work given how we're setting it up.\n>\n> We also disable the reference-transaction hook for symref-updates which\n> will be tackled in its own commit.\n\nWhy do we need to disable it?\n\n> Add required tests for 'symref-verify' while also adding reflog checks for\n> the pre-existing 'verify' tests.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n\n> +symref-verify::\n> +       Verify symbolic <ref> against <old-ref> but do not change it.\n> +       If <old-ref> is missing, the ref must not exist.\n\n\"must not exist\" means that we will need to change this when we make\nit work if the ref exists but isn't a symref. Ok.\n\n>  Can only be\n> +       used in `no-deref` mode.\n> +\n\n\n> +       /*\n> +        * old_ref is optional, but we want to differentiate between\n> +        * a NULL and zero value.\n> +        */\n> +       old_ref = parse_next_refname(&next);\n> +       if (!old_ref)\n> +               old_oid = *null_oid();\n\nSo for now we always overwrite old_oid when old_ref is not provided.\nSo the ref should not exist and the command will fail if it exists as\na regular ref. Ok.\n\n> +       else if (read_ref(old_ref, NULL))\n> +               die(\"symref-verify %s: invalid <old-ref>\", refname);\n\nSo I guess we die() if old_ref is the empty string.\n\nIt's kind of strange as in the previous patch there was:\n\n> +        * If (type & REF_SYMREF_UPDATE), check that the reference\n> +        * previously had this value (or didn't previously exist,\n> +        * if `old_ref` is an empty string).\n\nSo it said the empty string meant the old_ref didn't previously exist,\nbut now it's actually NULL that means the old_ref didn't previously\nexist.\n\n> +       if (*next != line_termination)\n> +               die(\"symref-verify %s: extra input: %s\", refname, next);\n> +\n> +       if (ref_transaction_verify(transaction, refname, old_ref ? NULL : &old_oid,\n> +                                  old_ref, update_flags | REF_SYMREF_UPDATE, &err))\n>                 die(\"%s\", err.buf);\n>\n>         update_flags = default_flags;\n>         free(refname);\n> +       free(old_ref);\n>         strbuf_release(&err);\n>  }\n"},{"id":"493141","messageId":"CAP8UFD1Fwq4exKKz3kyJiwcgtOe4TjQ938C=sAbdWOTsw6241A@mail.gmail.com","threadId":"61235","inReplyTo":"20240412095908.1134387-4-knayak@gitlab.com","subject":"Re: [PATCH v2 3/7] update-ref: add support for symref-delete","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-18T14:52:39Z","receivedAt":"2024-04-18T14:52:52Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> Similar to the previous commit, add 'symref-delete' to allow deletions\n> of symbolic refs in a transaction via the 'git-update-ref' command. The\n> 'symref-delete' command can when given with an <old-ref>, deletes the\n> provided <ref> only when it points to <old-ref>.\n\nI have a similar question as with the previous patch about what\nhappens if <old-ref> looks like an oid and <ref> is a regular ref\npointing to it.\n\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n\n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 749aaa7892..ef22a1a2f4 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n>         create SP <ref> SP <new-oid> LF\n>         delete SP <ref> [SP <old-oid>] LF\n>         verify SP <ref> [SP <old-oid>] LF\n> +       symref-delete SP <ref> [SP <old-ref>] LF\n>         symref-verify SP <ref> [SP <old-ref>] LF\n>         option SP <opt> LF\n>         start LF\n> @@ -87,6 +88,7 @@ quoting:\n>         create SP <ref> NUL <new-oid> NUL\n>         delete SP <ref> NUL [<old-oid>] NUL\n>         verify SP <ref> NUL [<old-oid>] NUL\n> +       symref-delete SP <ref> [NUL <old-ref>] NUL\n>         symref-verify SP <ref> [NUL <old-ref>] NUL\n>         option SP <opt> NUL\n>         start NUL\n\nAlso I wonder if there is a test where <old-ref> is an empty string, so where:\n\n    symref-delete SP <ref> SP LF\n\nor:\n\n    symref-delete SP <ref> NUL NUL\n\nare used?\n"},{"id":"493142","messageId":"CAP8UFD19J+KY51hVwQbnF8CaK_ZqqjdqW49qjGtJ8A6UtCJeUg@mail.gmail.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2024-04-18T15:05:15Z","receivedAt":"2024-04-18T15:05:29Z","isPatch":true,"sender":{"key":"christian.couder@gmail.com","avatar":"https://avatars.githubusercontent.com/u/208954?v=4"},"body":"On Fri, Apr 12, 2024 at 12:11 PM Karthik Nayak <karthik.188@gmail.com> wrote:\n\n> The 'symref-update' command can be used to update a symref, create a symref,\n> delete a symref or even convert an existing regular ref to a symref. Wherein\n> like the regular 'update' command, the zero OID can be used to create/delete\n> a symref.\n\nIs it possible to also convert a symref to a regular ref, like when\nHEAD becomes detached?\n\n> 2. The flow is now changedc to send an old_ref, new_ref pair in supplement to\n\ns/changedc/changed/\n\n> the existing old_oid, new_oid pair to the reference backends. This allows the\n> backends to simply do a combination of changes based on what values are set.\n\nI had a number of comments and questions when reading the first few\npatches in this series. I also took a very quick look at the rest of\nthe series, but I think answering my questions about the first few\npatches would make reading the rest of the series easier, so I will\ntake a deeper look later.\n\nThanks.\n"},{"id":"493143","messageId":"c182c68c-77f6-422a-81ed-f6199c6b8a6b@gmail.com","threadId":"61235","inReplyTo":"20240412095908.1134387-2-knayak@gitlab.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-18T15:08:00Z","receivedAt":"2024-04-18T15:08:03Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nI agree with Christian's comments on this patch, I've got a couple of \nadditional comments below\n\nOn 12/04/2024 10:59, Karthik Nayak wrote:\n> diff --git a/refs.c b/refs.c\n> index 55d2e0b2cb..967c81167e 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n>   \t\tconst char *refname, unsigned int flags,\n>   \t\tconst struct object_id *new_oid,\n>   \t\tconst struct object_id *old_oid,\n> +\t\tconst char *new_ref, const char *old_ref,\n>   \t\tconst char *msg)\n>   {\n>   \tstruct ref_update *update;\n> @@ -1253,6 +1254,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>   \t\t\t   const char *refname,\n>   \t\t\t   const struct object_id *new_oid,\n>   \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *new_ref, const char *old_ref,\n>   \t\t\t   unsigned int flags, const char *msg,\n>   \t\t\t   struct strbuf *err)\n>   {\n\nAdding these two new parameters is quite disruptive as all the existing \ncallers have to be updated. It makes it easy for callers to misuse this \nfunction for example by providing old_oid and old_ref (I'm assuming that \nis an error but it is hard to know for sure without any documentation). \nIt also makes the calling code harder to read because there are so many \nparameters it is hard to keep track of exactly what is being passed. An \nalternative strategy would be to add a new function that takes a struct \ninstead of lots of individual parameters. That would make the calling \ncode more readable as it would be clear which struct members are being \nset (see reset.h for an example of this). The approach of adding a \nstruct is still prone to setting the wrong combination of options so \neither way it would be helpful to add some assertions to detect mistakes\n\n\tif (old_oid && old_ref)\n\t\tBUG(\"Only one of old_oid and old_ref should be non NULL\");\n\tif (new_oid && new_ref)\n\t\tBUG(\"Only one of new_oid and new_ref should be non NULL\");\n\n\n> diff --git a/refs.h b/refs.h\n> index d278775e08..645fe9fdb8 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -696,13 +696,19 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>    */\n>   #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n>   \n> +/*\n> + * The reference update is considered to be done on a symbolic reference. This\n> + * ensures that we verify, delete, create and update the ref correspondingly.\n> + */\n> +#define REF_SYMREF_UPDATE (1 << 12)\n\nI'm confused as to why we need this as I assumed that we could use the \npresence of old_ref/new_ref to determine that the caller wants to update \nsymbolic ref. Having this flag means that there are more possibilities \nto misuse the new API setting this flag but providing NULL for old_ref \nand new_ref.\n\nBest Wishes\n\nPhillip\n"},{"id":"493198","messageId":"ZiI8AfkJL4O3K7Xk@tanuki","threadId":"61235","inReplyTo":"20240412095908.1134387-2-knayak@gitlab.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:17Z","receivedAt":"2024-04-19T09:40:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:02AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The `ref_transaction[_add]_update` functions obtain ref information and\n> flags to create a `ref_update` and add it to the transaction at hand.\n> \n> To extend symref support in transactions, we need to also accept the\n> old and new ref values and process it. In this commit, let's add the\n> required paramaters to the function and modify all call sites.\n> \n> The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n\nWould `new_target` and `old_target` be easier to understand? `new_ref`\nand `old_ref` to me sound as if they might also apply to the ref itself,\nfor example when doing a rename.\n\n[snip]\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 56641aa57a..4c5fe02687 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -124,6 +124,19 @@ struct ref_update {\n>  \t */\n>  \tstruct object_id old_oid;\n>  \n> +\t/*\n> +\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n> +\t * value (or delete it, if `new_ref` is an empty string).\n> +\t */\n> +\tconst char *new_ref;\n> +\n> +\t/*\n> +\t * If (type & REF_SYMREF_UPDATE), check that the reference\n> +\t * previously had this value (or didn't previously exist,\n> +\t * if `old_ref` is an empty string).\n> +\t */\n> +\tconst char *old_ref;\n\nI think one important bit of information here would be how to handle the\nupdate from a plain ref to a symref or vice versa. Would I set both\n`REF_SYMREF_UPDATE` and `REF_HAVE_NEW`/`REF_HAVE_OLD`?\n\nPatrick\n"},{"id":"493199","messageId":"ZiI8B4VOL3agHBgK@tanuki","threadId":"61235","inReplyTo":"c182c68c-77f6-422a-81ed-f6199c6b8a6b@gmail.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:23Z","receivedAt":"2024-04-19T09:40:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Apr 18, 2024 at 04:08:00PM +0100, Phillip Wood wrote:\n[snip]\n> > diff --git a/refs.h b/refs.h\n> > index d278775e08..645fe9fdb8 100644\n> > --- a/refs.h\n> > +++ b/refs.h\n> > @@ -696,13 +696,19 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n> >    */\n> >   #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n> > +/*\n> > + * The reference update is considered to be done on a symbolic reference. This\n> > + * ensures that we verify, delete, create and update the ref correspondingly.\n> > + */\n> > +#define REF_SYMREF_UPDATE (1 << 12)\n> \n> I'm confused as to why we need this as I assumed that we could use the\n> presence of old_ref/new_ref to determine that the caller wants to update\n> symbolic ref. Having this flag means that there are more possibilities to\n> misuse the new API setting this flag but providing NULL for old_ref and\n> new_ref.\n\nIn my opinion the same comment applies to `REF_HAVE_NEW` and\n`REF_HAVE_OLD`, which I found to be redundant, as well. Those may make\nsense in the internals when the object IDs are stored as non-pointers,\nbut queueing ref updates only accepts pointers anyway.\n\nPatrick\n"},{"id":"493200","messageId":"ZiI8DmqrbvdCDWt0@tanuki","threadId":"61235","inReplyTo":"20240412095908.1134387-3-knayak@gitlab.com","subject":"Re: [PATCH v2 2/7] update-ref: add support for symref-verify","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:30Z","receivedAt":"2024-04-19T09:40:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:03AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> In the previous commit, we added the required base for adding symref\n> support in transactions provided by the 'git-update-ref(1)'. This commit\n> introduces the 'symref-verify' command which is similar to the existing\n> 'verify' command for regular refs.\n> \n> The 'symref-verify' command allows users to verify if a provided <ref>\n> contains the provided <old-ref> without changing the <ref>. If <old-ref>\n> is not provided, the command will verify that the <ref> doesn't exist.\n> Since we're checking for symbolic refs, this command will only work with\n> the 'no-deref' mode. This is because any dereferenced symbolic ref will\n> point to an object and not a ref and the regular 'verify' command can be\n> used in such situations.\n> \n> This commit adds all required helper functions required to also\n> introduce the other symref commands, namely create, delete, and update.\n> We also add tests to test the command in both the regular stdin mode and\n> also with the '-z' flag.\n> \n> When the user doesn't provide a <old-ref> we need to check that the\n> provided <ref> doesn't exist. And to do this, we take over the existing\n> understanding that <old-oid> when set to its zero value, it refers to\n> the ref not existing. While this seems like a mix of contexts between\n> using <*-ref> and <*-oid>, this actually works really well, especially\n> considering the fact that we want to eventually also introduce\n> \n>     symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n> \n> and here, we'd allow the user to update a regular <ref> to a symref and\n> use <old-oid> to check the <ref>'s oid. This can be extrapolated to the\n> user using this to create a symref when provided a zero <old-oid>. Which\n> will work given how we're setting it up.\n> \n> We also disable the reference-transaction hook for symref-updates which\n> will be tackled in its own commit.\n> \n> Add required tests for 'symref-verify' while also adding reflog checks for\n> the pre-existing 'verify' tests.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  Documentation/git-update-ref.txt |  7 +++\n>  builtin/update-ref.c             | 82 ++++++++++++++++++++++++++++----\n>  refs.c                           | 37 +++++++++++---\n>  refs.h                           |  1 +\n>  refs/files-backend.c             | 46 +++++++++++++++++-\n>  refs/refs-internal.h             |  7 +++\n>  refs/reftable-backend.c          | 23 ++++++++-\n>  t/t1400-update-ref.sh            | 80 ++++++++++++++++++++++++++++++-\n>  8 files changed, 262 insertions(+), 21 deletions(-)\n> \n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index 374a2ebd2b..749aaa7892 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n>  \tcreate SP <ref> SP <new-oid> LF\n>  \tdelete SP <ref> [SP <old-oid>] LF\n>  \tverify SP <ref> [SP <old-oid>] LF\n> +\tsymref-verify SP <ref> [SP <old-ref>] LF\n>  \toption SP <opt> LF\n>  \tstart LF\n>  \tprepare LF\n> @@ -86,6 +87,7 @@ quoting:\n>  \tcreate SP <ref> NUL <new-oid> NUL\n>  \tdelete SP <ref> NUL [<old-oid>] NUL\n>  \tverify SP <ref> NUL [<old-oid>] NUL\n> +\tsymref-verify SP <ref> [NUL <old-ref>] NUL\n>  \toption SP <opt> NUL\n>  \tstart NUL\n>  \tprepare NUL\n> @@ -117,6 +119,11 @@ verify::\n>  \tVerify <ref> against <old-oid> but do not change it.  If\n>  \t<old-oid> is zero or missing, the ref must not exist.\n>  \n> +symref-verify::\n> +\tVerify symbolic <ref> against <old-ref> but do not change it.\n> +\tIf <old-ref> is missing, the ref must not exist.  Can only be\n> +\tused in `no-deref` mode.\n\nShould this say \"is zero or missing\", like the comment for \"verify\"\ndoes?\n\n[snip]\n> @@ -297,11 +321,48 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n>  \t\tdie(\"verify %s: extra input: %s\", refname, next);\n>  \n>  \tif (ref_transaction_verify(transaction, refname, &old_oid,\n> -\t\t\t\t   update_flags, &err))\n> +\t\t\t\t   NULL, update_flags, &err))\n> +\t\tdie(\"%s\", err.buf);\n> +\n> +\tupdate_flags = default_flags;\n> +\tfree(refname);\n> +\tstrbuf_release(&err);\n> +}\n> +\n> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n> +                                    const char *next, const char *end)\n> +{\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tstruct object_id old_oid;\n> +\tchar *refname, *old_ref;\n> +\n> +\tif (!(update_flags & REF_NO_DEREF))\n> +\t\tdie(\"symref-verify: cannot operate with deref mode\");\n\nThis feels quite restrictive to me. Wouldn't it be preferable to simply\nignore `REF_NO_DEREF` here? It basically means that this command can't\never be used in a normal `git update-ref --stdin` session.\n\n> +\trefname = parse_refname(&next);\n> +\tif (!refname)\n> +\t\tdie(\"symref-verify: missing <ref>\");\n> +\n> +\t/*\n> +\t * old_ref is optional, but we want to differentiate between\n> +\t * a NULL and zero value.\n> +\t */\n> +\told_ref = parse_next_refname(&next);\n> +\tif (!old_ref)\n> +\t\told_oid = *null_oid();\n> +\telse if (read_ref(old_ref, NULL))\n> +\t\tdie(\"symref-verify %s: invalid <old-ref>\", refname);\n> +\n> +\tif (*next != line_termination)\n> +\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n> +\n> +\tif (ref_transaction_verify(transaction, refname, old_ref ? NULL : &old_oid,\n> +\t\t\t\t   old_ref, update_flags | REF_SYMREF_UPDATE, &err))\n>  \t\tdie(\"%s\", err.buf);\n>  \n>  \tupdate_flags = default_flags;\n>  \tfree(refname);\n> +\tfree(old_ref);\n>  \tstrbuf_release(&err);\n>  }\n>  \n> @@ -380,15 +441,16 @@ static const struct parse_cmd {\n>  \tunsigned args;\n>  \tenum update_refs_state state;\n>  } command[] = {\n> -\t{ \"update\",  parse_cmd_update,  3, UPDATE_REFS_OPEN },\n> -\t{ \"create\",  parse_cmd_create,  2, UPDATE_REFS_OPEN },\n> -\t{ \"delete\",  parse_cmd_delete,  2, UPDATE_REFS_OPEN },\n> -\t{ \"verify\",  parse_cmd_verify,  2, UPDATE_REFS_OPEN },\n> -\t{ \"option\",  parse_cmd_option,  1, UPDATE_REFS_OPEN },\n> -\t{ \"start\",   parse_cmd_start,   0, UPDATE_REFS_STARTED },\n> -\t{ \"prepare\", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },\n> -\t{ \"abort\",   parse_cmd_abort,   0, UPDATE_REFS_CLOSED },\n> -\t{ \"commit\",  parse_cmd_commit,  0, UPDATE_REFS_CLOSED },\n> +\t{ \"update\",        parse_cmd_update,        3, UPDATE_REFS_OPEN },\n> +\t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n> +\t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n> +\t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n> +\t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n> +\t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n> +\t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\n> +\t{ \"prepare\",       parse_cmd_prepare,       0, UPDATE_REFS_PREPARED },\n> +\t{ \"abort\",         parse_cmd_abort,         0, UPDATE_REFS_CLOSED },\n> +\t{ \"commit\",        parse_cmd_commit,        0, UPDATE_REFS_CLOSED },\n>  };\n>  \n>  static void update_refs_stdin(void)\n> diff --git a/refs.c b/refs.c\n> index 967c81167e..124b294c9f 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -19,6 +19,7 @@\n>  #include \"object-store-ll.h\"\n>  #include \"object.h\"\n>  #include \"path.h\"\n> +#include \"string.h\"\n>  #include \"tag.h\"\n>  #include \"submodule.h\"\n>  #include \"worktree.h\"\n> @@ -29,6 +30,7 @@\n>  #include \"date.h\"\n>  #include \"commit.h\"\n>  #include \"wildmatch.h\"\n> +#include \"wrapper.h\"\n>  \n>  /*\n>   * List of all available backends\n> @@ -1217,6 +1219,7 @@ void ref_transaction_free(struct ref_transaction *transaction)\n>  \n>  \tfor (i = 0; i < transaction->nr; i++) {\n>  \t\tfree(transaction->updates[i]->msg);\n> +\t\tfree((void *)transaction->updates[i]->old_ref);\n>  \t\tfree(transaction->updates[i]);\n>  \t}\n>  \tfree(transaction->updates);\n> @@ -1242,10 +1245,19 @@ struct ref_update *ref_transaction_add_update(\n>  \n>  \tupdate->flags = flags;\n>  \n> -\tif (flags & REF_HAVE_NEW)\n> -\t\toidcpy(&update->new_oid, new_oid);\n> -\tif (flags & REF_HAVE_OLD)\n> -\t\toidcpy(&update->old_oid, old_oid);\n> +\t/*\n> +\t * The ref values are to be considered over the oid values when we're\n> +\t * doing symref operations.\n> +\t */\n\nI feel like this is a statement that should be backed up by a deeper\nexplanation of why that is. I'm still wondering here why we cannot\nassert that the old value is an object ID when I want to update it to a\nsymref, or alternatively why it would even be possible to have both\n`REF_SYMREF_UPDATE` and a set of other, incompatible fields set. It\nfeels like this should be a `BUG()` instead if this is supposedly an\nunsupported configuration rather than silently ignoring it.\n\nIn any case, I feel like it would be easier to reason about if this was\nintroduced together with the actual user. As far as I can see this code\nshouldn't ever be hit for \"verify-symref\", right? Currently, the reader\nis forced to figure out what is and isn't related to the new command.\n\n> +\tif (update->flags & REF_SYMREF_UPDATE) {\n> +\t\tif (old_ref)\n> +\t\t\tupdate->old_ref = xstrdup(old_ref);\n> +\t} else {\n> +\t\tif (flags & REF_HAVE_NEW)\n> +\t\t\toidcpy(&update->new_oid, new_oid);\n> +\t\tif (flags & REF_HAVE_OLD)\n> +\t\t\toidcpy(&update->old_oid, old_oid);\n> +\t}\n>  \tupdate->msg = normalize_reflog_message(msg);\n>  \treturn update;\n>  }\n> @@ -1280,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>  \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n>  \n>  \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n> +\tflags |= (new_ref ? REF_HAVE_NEW : 0) | (old_ref ? REF_HAVE_OLD : 0);\n>  \n>  \tref_transaction_add_update(transaction, refname, flags,\n>  \t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n> @@ -1318,14 +1331,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n>  int ref_transaction_verify(struct ref_transaction *transaction,\n>  \t\t\t   const char *refname,\n>  \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *old_ref,\n>  \t\t\t   unsigned int flags,\n>  \t\t\t   struct strbuf *err)\n>  {\n> -\tif (!old_oid)\n> +\tif (flags & REF_SYMREF_UPDATE && !old_ref && !old_oid)\n> +\t\tBUG(\"verify called with old_ref set to NULL\");\n> +\tif (!(flags & REF_SYMREF_UPDATE) && !old_oid)\n>  \t\tBUG(\"verify called with old_oid set to NULL\");\n>  \treturn ref_transaction_update(transaction, refname,\n>  \t\t\t\t      NULL, old_oid,\n> -\t\t\t\t      NULL, NULL,\n> +\t\t\t\t      NULL, old_ref,\n>  \t\t\t\t      flags, NULL, err);\n>  }\n>  \n> @@ -2342,6 +2358,9 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n>  \tfor (i = 0; i < transaction->nr; i++) {\n>  \t\tstruct ref_update *update = transaction->updates[i];\n>  \n> +\t\tif (update->flags & REF_SYMREF_UPDATE)\n> +\t\t\tcontinue;\n> +\n>  \t\tstrbuf_reset(&buf);\n>  \t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n>  \t\t\t    oid_to_hex(&update->old_oid),\n> @@ -2795,3 +2814,9 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n>  {\n>  \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n>  }\n> +\n> +int null_new_value(struct ref_update *update) {\n> +\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref)\n> +\t\treturn 0;\n> +\treturn is_null_oid(&update->new_oid);\n> +}\n> diff --git a/refs.h b/refs.h\n> index 645fe9fdb8..a988e672ff 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -772,6 +772,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n>  int ref_transaction_verify(struct ref_transaction *transaction,\n>  \t\t\t   const char *refname,\n>  \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *old_ref,\n>  \t\t\t   unsigned int flags,\n>  \t\t\t   struct strbuf *err);\n>  \n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index e4d0aa3d41..8421530bde 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2411,6 +2411,37 @@ static const char *original_update_refname(struct ref_update *update)\n>  \treturn update->refname;\n>  }\n>  \n> +/*\n> + * Check whether the REF_HAVE_OLD and old_ref values stored in update\n> + * are consistent with ref, which is the symbolic reference's current\n> + * value. If everything is OK, return 0; otherwise, write an error\n> + * message to err and return -1.\n> + */\n> +static int check_old_ref(struct ref_update *update, char *ref,\n> +\t\t\t struct strbuf *err)\n> +{\n> +\tif (!(update->flags & REF_HAVE_OLD) ||\n> +\t    !strcmp(update->old_ref, ref))\n> +\t\treturn 0;\n> +\n> +\tif (!strcmp(update->old_ref, \"\"))\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"reference already exists\",\n> +\t\t\t    original_update_refname(update));\n> +\telse if (!strcmp(ref, \"\"))\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    update->old_ref);\n> +\telse\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    ref, update->old_ref);\n> +\n> +\treturn -1;\n> +}\n> +\n>  /*\n>   * Check whether the REF_HAVE_OLD and old_oid values stored in update\n>   * are consistent with oid, which is the reference's current value. If\n> @@ -2464,8 +2495,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t       struct strbuf *err)\n>  {\n>  \tstruct strbuf referent = STRBUF_INIT;\n> -\tint mustexist = (update->flags & REF_HAVE_OLD) &&\n> -\t\t!is_null_oid(&update->old_oid);\n> +\tint mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid);\n\nThis change is a no-op, right? If so, let's rather drop it.\n\n>  \tint ret = 0;\n>  \tstruct ref_lock *lock;\n>  \n> @@ -2514,6 +2544,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\t\tgoto out;\n>  \t\t\t\t}\n> +\t\t\t}\n> +\n> +\t\t\t/*\n> +\t\t\t * For symref verification, we need to check the referent value\n> +\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n> +\t\t\t * verifying a dereferenced symref, we then check the oid.\n> +\t\t\t */\n> +\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->old_ref) {\n> +\t\t\t\tif (check_old_ref(update, referent.buf, err)) {\n> +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n>  \t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n>  \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\tgoto out;\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 4c5fe02687..21c6b940d8 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n>   */\n>  struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n>  \n> +/*\n> + * Helper function to check if the new value is null, this\n> + * takes into consideration that the update could be a regular\n> + * ref or a symbolic ref.\n> + */\n> +int null_new_value(struct ref_update *update);\n\nWhen adding it to the header we should probably prefix this to avoid\nname collisions. `ref_update_is_null_new_value()` might be a mouth full,\nbut feels preferable to me.\n\n>  #endif /* REFS_REFS_INTERNAL_H */\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 6104471199..7a03922c7b 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -938,7 +938,28 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t * individual refs. But the error messages match what the files\n>  \t\t * backend returns, which keeps our tests happy.\n>  \t\t */\n> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n> +\t\tif ((u->flags & REF_HAVE_OLD) &&\n> +\t\t    (u->flags & REF_SYMREF_UPDATE) &&\n> +\t\t    u->old_ref) {\n> +\t\t\tif   (strcmp(referent.buf, u->old_ref)) {\n\ns/   / /\n\n> +\t\t\t\tif (!strcmp(u->old_ref, \"\"))\n> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t\t\t\t    \"reference already exists\",\n> +\t\t\t\t\t\t    original_update_refname(u));\n> +\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    u->old_ref);\n> +\t\t\t\telse\n> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    referent.buf, u->old_ref);\n\nI'd use better-matching error messages here. I know that we talk about\n\"cannot lock ref\" in the next branch, as well. But the only reason we\ndid this is to have the same error messages as the \"files\" backend.\nSemantically, those errors don't make much sense as the \"reftable\"\nbackend never locks specific refs, but only the complete stack.\n\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto done;\n> +\t\t\t}\n> +\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>  \t\t\tif (is_null_oid(&u->old_oid))\n>  \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>  \t\t\t\t\t    \"reference already exists\"),\n> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\n> index ec3443cc87..d8ffda4096 100755\n> --- a/t/t1400-update-ref.sh\n> +++ b/t/t1400-update-ref.sh\n> @@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '\n>  '\n>  \n>  test_expect_success 'stdin verify succeeds for correct value' '\n> +\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n>  \tgit rev-parse $m >expect &&\n>  \techo \"verify $m $m\" >stdin &&\n>  \tgit update-ref --stdin <stdin &&\n>  \tgit rev-parse $m >actual &&\n> -\ttest_cmp expect actual\n> +\ttest_cmp expect actual &&\n> +\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n> +\ttest_cmp before after\n>  '\n>  \n>  test_expect_success 'stdin verify succeeds for missing reference' '\n> +\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n>  \techo \"verify refs/heads/missing $Z\" >stdin &&\n>  \tgit update-ref --stdin <stdin &&\n> -\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n> +\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n> +\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n> +\ttest_cmp before after\n>  '\n\nThe updated tests merely assert that the refs didn't change, right?\n\n>  test_expect_success 'stdin verify treats no value as missing' '\n> @@ -1641,4 +1647,74 @@ test_expect_success PIPE 'transaction flushes status updates' '\n>  \ttest_cmp expected actual\n>  '\n>  \n> +create_stdin_buf ()\n> +{\n\nThe curly brace should go on the same line as the function name.\n\n> +\tif test \"$1\" = \"-z\"\n> +\tthen\n> +\t\tshift\n> +\t\tprintf \"$F\" \"$@\" >stdin\n> +\telse\n> +\t\techo \"$@\" >stdin\n> +\tfi\n> +}\n> +\n> +for type in \"\" \"-z\"\n> +do\n\nWe should probably indent all of the tests to make it easier to see that\nthey run in a loop.\n\nPatrick\n\n> +test_expect_success \"stdin ${type} symref-verify fails without --no-deref\" '\n> +\tgit symbolic-ref refs/heads/symref $a &&\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n> +\tgrep \"fatal: symref-verify: cannot operate with deref mode\" err\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-verify fails with too many arguments\" '\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" \"$a\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n> +\tif test \"$type\" = \"-z\"\n> +\tthen\n> +\t\tgrep \"fatal: unknown command: $a\" err\n> +\telse\n> +\t\tgrep \"fatal: symref-verify refs/heads/symref: extra input:  $a\" err\n> +\tfi\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-verify succeeds for correct value\" '\n> +\tgit symbolic-ref refs/heads/symref >expect &&\n> +\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n> +\tgit update-ref --stdin ${type} --no-deref <stdin &&\n> +\tgit symbolic-ref refs/heads/symref >actual &&\n> +\ttest_cmp expect actual &&\n> +\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n> +\ttest_cmp before after\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-verify succeeds for missing reference\" '\n> +\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/missing\" &&\n> +\tgit update-ref --stdin ${type} --no-deref <stdin &&\n> +\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n> +\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n> +\ttest_cmp before after\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-verify fails for wrong value\" '\n> +\tgit symbolic-ref refs/heads/symref >expect &&\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$b\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n> +\tgit symbolic-ref refs/heads/symref >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\" '\n> +\tgit symbolic-ref refs/heads/symref >expect &&\n> +\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n> +\tgit symbolic-ref refs/heads/symref >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +done\n> +\n>  test_done\n> -- \n> 2.43.GIT\n> \n"},{"id":"493201","messageId":"ZiI8E2ZJT51akoAk@tanuki","threadId":"61235","inReplyTo":"20240412095908.1134387-4-knayak@gitlab.com","subject":"Re: [PATCH v2 3/7] update-ref: add support for symref-delete","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:35Z","receivedAt":"2024-04-19T09:40:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:04AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n[snip]\n> @@ -302,6 +302,37 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n>  \tstrbuf_release(&err);\n>  }\n>  \n> +static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n> +\t\t\t\t    const char *next, const char *end)\n> +{\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tchar *refname, *old_ref;\n> +\n> +\tif (!(update_flags & REF_NO_DEREF))\n> +                die(\"symref-delete: cannot operate with deref mode\");\n\nAgain, I'm a bit on the fence regarding this restriction. I feel like it\nought to be possible to delete both plain and symbolic refs in a single\ngit-update-ref(1) command.\n\n> +\trefname = parse_refname(&next);\n> +\tif (!refname)\n> +\t\tdie(\"symref-delete: missing <ref>\");\n> +\n> +        old_ref = parse_next_refname(&next);\n\nThis line is indented with spaces and not tabs.\n\n[snip]\n> --- a/t/t1400-update-ref.sh\n> +++ b/t/t1400-update-ref.sh\n> @@ -1715,6 +1715,45 @@ test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\"\n>  \ttest_cmp expect actual\n>  '\n>  \n> +test_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n> +\tgit symbolic-ref refs/heads/symref $a &&\n> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n> +\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n> +'\n> +\n> +test_expect_success \"stdin ${type} fails symref-delete with no ref\" '\n> +\tcreate_stdin_buf ${type} \"symref-delete \" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n> +\tgrep \"fatal: symref-delete: missing <ref>\" err\n> +'\n> +\n> +test_expect_success \"stdin ${type} fails symref-delete with too many arguments\" '\n> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n> +\tif test \"$type\" = \"-z\"\n> +\tthen\n> +\t\tgrep \"fatal: unknown command: $a\" err\n> +\telse\n> +\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n> +\tfi\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-delete ref fails with wrong old value\" '\n> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n> +\tgrep \"fatal: cannot lock ref '\"'\"'refs/heads/symref'\"'\"'\" err &&\n\nYou can use \"${SQ}\" to insert single quotes.\n\nPatrick\n\n> +\tgit symbolic-ref refs/heads/symref >expect &&\n> +\techo $a >actual &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success \"stdin ${type} symref-delete ref works with right old value\" '\n> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n> +\tgit update-ref --stdin ${type} --no-deref <stdin &&\n> +\ttest_must_fail git rev-parse --verify -q $b\n> +'\n> +\n>  done\n>  \n>  test_done\n> -- \n> 2.43.GIT\n> \n"},{"id":"493202","messageId":"ZiI8GaGupNzbLqnE@tanuki","threadId":"61235","inReplyTo":"20240412095908.1134387-6-knayak@gitlab.com","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:41Z","receivedAt":"2024-04-19T09:40:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:06AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n[snip]\n> @@ -268,6 +268,39 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n>  \tstrbuf_release(&err);\n>  }\n>  \n> +static void parse_cmd_symref_create(struct ref_transaction *transaction,\n> +\t\t\t\t    const char *next, const char *end)\n> +{\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tchar *refname, *new_ref;\n> +\n> +\tif (!(update_flags & REF_NO_DEREF))\n> +                die(\"symref-create: cannot operate with deref mode\");\n> +\n> +\trefname = parse_refname(&next);\n> +\tif (!refname)\n> +\t\tdie(\"symref-create: missing <ref>\");\n> +\n> +\tnew_ref = parse_next_refname(&next);\n> +\tif (!new_ref)\n> +\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n> +\tif (read_ref(new_ref, NULL))\n> +\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n\nThis restricts the creation of dangling symrefs, right? I think we might\nwant to lift this restriction, in which case we can eventually get rid\nof the `create_symref` callback in ref backends completely.\n\n> +\tif (*next != line_termination)\n> +\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n> +\n> +\tif (ref_transaction_create(transaction, refname, NULL, new_ref,\n> +\t\t\t\t   update_flags | create_reflog_flag |\n> +\t\t\t\t   REF_SYMREF_UPDATE, msg, &err))\n> +\t\tdie(\"%s\", err.buf);\n> +\n> +\tupdate_flags = default_flags;\n> +\tfree(refname);\n> +\tfree(new_ref);\n> +\tstrbuf_release(&err);\n> +}\n> +\n>  static void parse_cmd_delete(struct ref_transaction *transaction,\n>  \t\t\t     const char *next, const char *end)\n>  {\n> @@ -476,6 +509,7 @@ static const struct parse_cmd {\n>  \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n>  \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n>  \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n> +\t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n>  \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n>  \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n>  \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n> diff --git a/refs.c b/refs.c\n> index 6d98d9652d..e62c0f4aca 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1305,15 +1305,20 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>  int ref_transaction_create(struct ref_transaction *transaction,\n>  \t\t\t   const char *refname,\n>  \t\t\t   const struct object_id *new_oid,\n> +\t\t\t   const char *new_ref,\n>  \t\t\t   unsigned int flags, const char *msg,\n>  \t\t\t   struct strbuf *err)\n>  {\n> -\tif (!new_oid || is_null_oid(new_oid)) {\n> +\tif ((flags & REF_SYMREF_UPDATE) && !new_ref) {\n> +\t\tstrbuf_addf(err, \"'%s' has a no new ref\", refname);\n> +\t\treturn 1;\n> +\t}\n> +\tif (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {\n>  \t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n>  \t\treturn 1;\n>  \t}\n>  \treturn ref_transaction_update(transaction, refname, new_oid,\n> -\t\t\t\t      null_oid(), NULL, NULL, flags,\n> +\t\t\t\t      null_oid(), new_ref, NULL, flags,\n>  \t\t\t\t      msg, err);\n>  }\n>  \n> diff --git a/refs.h b/refs.h\n> index 60e6a21a31..c01a517e40 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -744,6 +744,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>  int ref_transaction_create(struct ref_transaction *transaction,\n>  \t\t\t   const char *refname,\n>  \t\t\t   const struct object_id *new_oid,\n> +\t\t\t   const char *new_ref,\n>  \t\t\t   unsigned int flags, const char *msg,\n>  \t\t\t   struct strbuf *err);\n>  \n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 7c894ebe65..59d438878a 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2609,6 +2609,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t}\n>  \t}\n>  \n> +\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n\nLet's add braces around `(updaet->flags & REF_SYMREF_UPDATE)` to make\nthis easier to read.\n\n> +\t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\tif (close_ref_gently(lock)) {\n> +\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n> +\t\t\t\t    update->refname);\n> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\tgoto out;\n> +\t\t}\n> +\n> +\t\t/*\n> +\t\t * Once we have created the symref lock, the commit\n> +\t\t * phase of the transaction only needs to commit the lock.\n> +\t\t */\n> +\t\tupdate->flags |= REF_NEEDS_COMMIT;\n> +\t}\n> +\n> +\n>  \tif ((update->flags & REF_HAVE_NEW) &&\n>  \t    !(update->flags & REF_DELETING) &&\n>  \t    !(update->flags & REF_LOG_ONLY)) {\n> @@ -2904,6 +2925,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>  \n>  \t\tif (update->flags & REF_NEEDS_COMMIT ||\n>  \t\t    update->flags & REF_LOG_ONLY) {\n> +\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n\nHere, as well.\n\n> +\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n> +\t\t\t\t\tupdate->new_oid = *null_oid();\n> +\t\t\t\t}\n\nCan this actually happenn right now? I thought that the `read_ref()`\nfurther up forbids this case.\n\nPatrick\n"},{"id":"493203","messageId":"ZiI8IFdfBHEqr02C@tanuki","threadId":"61235","inReplyTo":"20240412095908.1134387-7-knayak@gitlab.com","subject":"Re: [PATCH v2 6/7] update-ref: add support for symref-update","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-19T09:40:48Z","receivedAt":"2024-04-19T09:40:54Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:07AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> Add 'symref-update' to allow updates of symbolic refs in a transaction\n> via the 'git-update-ref' command. The 'symref-update' command takes in a\n> <new-ref>, which the <ref> will be updated to. If the <ref> doesn't\n> exist it will be created.\n> \n> It also optionally takes either an <old-ref> or <old-oid>. If the\n> <old-ref> is provided, it checks to see if the <ref> ponints to the\n> <old-ref> before the update. If <old-oid> is provided it checks <ref> to\n> ensure that it is a regular ref and <old-oid> is the OID before the\n> update. This by extension also means that this when a zero <old-oid> is\n> provided, it ensures that the ref didn't exist before.\n> \n> This command will also support deref mode, to ensure that we can update\n> dereferenced regular refs to symrefs.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>  Documentation/git-update-ref.txt |   6 ++\n>  builtin/update-ref.c             |  49 +++++++++++\n>  refs.c                           |  24 ++----\n>  refs/files-backend.c             |  15 ++--\n>  refs/reftable-backend.c          |   7 +-\n>  t/t1400-update-ref.sh            | 143 +++++++++++++++++++++++++++++++\n>  6 files changed, 220 insertions(+), 24 deletions(-)\n> \n> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n> index a5b1f42728..9710c9bc78 100644\n> --- a/Documentation/git-update-ref.txt\n> +++ b/Documentation/git-update-ref.txt\n> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n>  \tcreate SP <ref> SP <new-oid> LF\n>  \tdelete SP <ref> [SP <old-oid>] LF\n>  \tverify SP <ref> [SP <old-oid>] LF\n> +\tsymref-update SP <ref> SP <new-ref> [SP (<old-ref> | <old-oid>)] LF\n>  \tsymref-create SP <ref> SP <new-ref> LF\n>  \tsymref-delete SP <ref> [SP <old-ref>] LF\n>  \tsymref-verify SP <ref> [SP <old-ref>] LF\n> @@ -89,6 +90,7 @@ quoting:\n>  \tcreate SP <ref> NUL <new-oid> NUL\n>  \tdelete SP <ref> NUL [<old-oid>] NUL\n>  \tverify SP <ref> NUL [<old-oid>] NUL\n> +\tsymref-update SP <ref> NUL <new-ref> [NUL (<old-ref> | <old-oid>)] NUL\n>  \tsymref-create SP <ref> NUL <new-ref> NUL\n>  \tsymref-delete SP <ref> [NUL <old-ref>] NUL\n>  \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n> @@ -123,6 +125,10 @@ verify::\n>  \tVerify <ref> against <old-oid> but do not change it.  If\n>  \t<old-oid> is zero or missing, the ref must not exist.\n>  \n> +symref-update::\n> +\tSet <ref> to <new-ref> after verifying <old-ref> or <old-oid>,\n> +\tif given. Can be used to delete or create symrefs too.\n> +\n>  symref-create::\n>  \tCreate symbolic ref <ref> with <new-ref> after verifying\n>  \tit does not exist.  Can only be used in `no-deref` mode.\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 24556a28a8..809c1c7a76 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -238,6 +238,54 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>  \tstrbuf_release(&err);\n>  }\n>  \n> +static void parse_cmd_symref_update(struct ref_transaction *transaction,\n> +\t\t\t\t    const char *next, const char *end)\n> +{\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tchar *refname, *new_ref, *old_ref;\n> +\tstruct object_id old_oid;\n> +\tint have_old = 0;\n> +\n> +\trefname = parse_refname(&next);\n> +\tif (!refname)\n> +\t\tdie(\"symref-update: missing <ref>\");\n> +\n> +\tnew_ref = parse_next_refname(&next);\n> +\tif (!new_ref)\n> +\t\tdie(\"symref-update %s: missing <new-ref>\", refname);\n> +\tif (read_ref(new_ref, NULL))\n> +\t\tdie(\"symref-update %s: invalid <new-ref>\", refname);\n> +\n> +\told_ref = parse_next_refname(&next);\n> +\t/*\n> +\t * Since the user can also send in an old-oid, we try to parse\n> +\t * it as such too.\n> +\t */\n> +\tif (old_ref && read_ref(old_ref, NULL)) {\n> +\t\tif (!repo_get_oid(the_repository, old_ref, &old_oid)) {\n> +\t\t\told_ref = NULL;\n> +\t\t\thave_old = 1;\n> +\t\t} else\n> +\t\t\tdie(\"symref-update %s: invalid <old-ref> or <old-oid>\", refname);\n> +\t}\n\nSo we first try to parse it as a ref, and then as an object ID? Wouldn't\nit preferable to try it the other way round and first check whether it\nis a valid object ID? That would likely be cheaper, even though it may\nbe premature optimization.\n\nPatrick\n"},{"id":"493216","messageId":"CAOLa=ZQfk+uMNjiSSB9o2SiFT+Cjm10W2bbCBy2nCQ-zwc5nHQ@mail.gmail.com","threadId":"61235","inReplyTo":"CAP8UFD2s5GZBbZ_oKk6hFCQ+jtjRjx1QgRvLz+1hrBfkWKVCbw@mail.gmail.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-19T10:28:51Z","receivedAt":"2024-04-19T10:28:53Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The `ref_transaction[_add]_update` functions obtain ref information and\n>> flags to create a `ref_update` and add it to the transaction at hand.\n>>\n>> To extend symref support in transactions, we need to also accept the\n>> old and new ref values and process it. In this commit, let's add the\n>> required paramaters to the function and modify all call sites.\n>\n> s/paramaters/parameters/\n>\n>> The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n>\n> s/paramaters/parameters/\n>\n\nThanks, will fix both.\n\n>> used to denote what the reference should point to when the transaction\n>> is applied. Some functions allow this parameter to be NULL, meaning that\n>> the reference is not changed, or `\"\"`, meaning that the reference should\n>> be deleted.\n>\n>> The `old_ref` denotes the value of that the reference must have before\n>\n> s/the value of that the reference/the value the reference/\n>\n\nWill change.\n\n>> the update. Some functions allow this parameter to be NULL, meaning that\n>> the old value of the reference is not checked, or `\"\"`, meaning that the\n>> reference must not exist before the update. A copy of this value is made\n>> in the transaction.\n>>\n>> The handling logic of these parameters will be added in consequent\n>> commits as we implement symref-{create, update, delete, verify}.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>\n>> diff --git a/refs.h b/refs.h\n>> index d278775e08..645fe9fdb8 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>\n> There is the following big comment in this file:\n>\n> /*\n>  * Reference transaction updates\n>  *\n>  * The following four functions add a reference check or update to a\n>  * ref_transaction.  They have some common similar parameters:\n>  *\n>  *     transaction -- a pointer to an open ref_transaction, obtained\n>  *         from ref_transaction_begin().\n>  *\n>  *     refname -- the name of the reference to be affected.\n>  *\n>  *     new_oid -- the object ID that should be set to be the new value\n>  *         of the reference. Some functions allow this parameter to be\n>  *         NULL, meaning that the reference is not changed, or\n>  *         null_oid, meaning that the reference should be deleted. A\n>  *         copy of this value is made in the transaction.\n>  *\n>  *     old_oid -- the object ID that the reference must have before\n>  *         the update. Some functions allow this parameter to be NULL,\n>  *         meaning that the old value of the reference is not checked,\n>  *         or null_oid, meaning that the reference must not exist\n>  *         before the update. A copy of this value is made in the\n>  *         transaction.\n>  *\n>  *     flags -- flags affecting the update, passed to\n>  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n>  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n>  *         information.\n>  *\n>  *     msg -- a message describing the change (for the reflog).\n>  *\n>  *     err -- a strbuf for receiving a description of any error that\n>  *         might have occurred.\n>  *\n>  * The functions make internal copies of refname and msg, so the\n>  * caller retains ownership of these parameters.\n>  *\n>  * The functions return 0 on success and non-zero on failure. A\n>  * failure means that the transaction as a whole has failed and needs\n>  * to be rolled back.\n>  */\n>\n> I would expect the patch to update this comment.\n>\n\nSince this patch doesn't use this value, I think its best to modify\nthis in each patch as we start using it, I'll do that.\n\n>> @@ -722,6 +728,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>>                            const char *refname,\n>>                            const struct object_id *new_oid,\n>>                            const struct object_id *old_oid,\n>> +                          const char *new_ref, const char *old_ref,\n>>                            unsigned int flags, const char *msg,\n>>                            struct strbuf *err);\n>\n> The patch might also want to update the comment just above the\n> ref_transaction_update() declaration as it is changing what the\n> function can (or will be able to) do.\n>\n\nAgreed, same as above, will modify in each patch. Also will add a\ncomment in the commit.\n\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 56641aa57a..4c5fe02687 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -124,6 +124,19 @@ struct ref_update {\n>>          */\n>>         struct object_id old_oid;\n>>\n>> +       /*\n>> +        * If (flags & REF_SYMREF_UPDATE), set the reference to this\n>> +        * value (or delete it, if `new_ref` is an empty string).\n>\n> What if this value is NULL?\n>\n>> +        */\n>> +       const char *new_ref;\n>> +\n>> +       /*\n>> +        * If (type & REF_SYMREF_UPDATE), check that the reference\n>> +        * previously had this value (or didn't previously exist,\n>> +        * if `old_ref` is an empty string).\n>\n> What if this value is NULL?\n>\n\nWell we ignore NULL values, but I see that the documentation is lacking,\nwill update.\n"},{"id":"493220","messageId":"CAOLa=ZSwtOQXwbgregzKMtVA30wUCH8R=8D7u1+KGnsGEDD1oA@mail.gmail.com","threadId":"61235","inReplyTo":"c182c68c-77f6-422a-81ed-f6199c6b8a6b@gmail.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-19T15:47:48Z","receivedAt":"2024-04-19T15:47:50Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Hey Phillip,\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> I agree with Christian's comments on this patch, I've got a couple of\n> additional comments below\n>\n> On 12/04/2024 10:59, Karthik Nayak wrote:\n>> diff --git a/refs.c b/refs.c\n>> index 55d2e0b2cb..967c81167e 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n>>   \t\tconst char *refname, unsigned int flags,\n>>   \t\tconst struct object_id *new_oid,\n>>   \t\tconst struct object_id *old_oid,\n>> +\t\tconst char *new_ref, const char *old_ref,\n>>   \t\tconst char *msg)\n>>   {\n>>   \tstruct ref_update *update;\n>> @@ -1253,6 +1254,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>>   \t\t\t   const char *refname,\n>>   \t\t\t   const struct object_id *new_oid,\n>>   \t\t\t   const struct object_id *old_oid,\n>> +\t\t\t   const char *new_ref, const char *old_ref,\n>>   \t\t\t   unsigned int flags, const char *msg,\n>>   \t\t\t   struct strbuf *err)\n>>   {\n>\n> Adding these two new parameters is quite disruptive as all the existing\n> callers have to be updated. It makes it easy for callers to misuse this\n> function for example by providing old_oid and old_ref (I'm assuming that\n> is an error but it is hard to know for sure without any documentation).\n\n\n\n> It also makes the calling code harder to read because there are so many\n> parameters it is hard to keep track of exactly what is being passed. An\n> alternative strategy would be to add a new function that takes a struct\n> instead of lots of individual parameters. That would make the calling\n> code more readable as it would be clear which struct members are being\n> set (see reset.h for an example of this). The approach of adding a\n> struct is still prone to setting the wrong combination of options so\n\nWe do already have refs-internal.h:ref_update and this struct would be\nthe best candidate for what you're saying. I even thought of exposing\nthis struct and using it. I think I realized that it's a lot more work\nto do this, because there are checks and cleanups which are built around\nthis struct. So exposing and using it would mean we refactor a bunch of\nthat code. Which while necessary I believe should be out of this series.\nI'd actually be happy to do it right after we can agree that this is a\ngood direction.\n\n> either way it would be helpful to add some assertions to detect mistakes\n>\n> \tif (old_oid && old_ref)\n> \t\tBUG(\"Only one of old_oid and old_ref should be non NULL\");\n> \tif (new_oid && new_ref)\n> \t\tBUG(\"Only one of new_oid and new_ref should be non NULL\");\n>\n\nI have slightly modified it to:\n\n \tif (old_oid && !is_null_oid(old_oid) && old_ref)\n \t\tBUG(\"Only one of old_oid and old_ref should be non NULL\");\n \tif (new_oid && !is_null_oid(new_oid) && new_ref)\n \t\tBUG(\"Only one of new_oid and new_ref should be non NULL\");\n\nBut I agree, this is needed and have added it.\n\n>> diff --git a/refs.h b/refs.h\n>> index d278775e08..645fe9fdb8 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -696,13 +696,19 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>>    */\n>>   #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n>>\n>> +/*\n>> + * The reference update is considered to be done on a symbolic reference. This\n>> + * ensures that we verify, delete, create and update the ref correspondingly.\n>> + */\n>> +#define REF_SYMREF_UPDATE (1 << 12)\n>\n> I'm confused as to why we need this as I assumed that we could use the\n> presence of old_ref/new_ref to determine that the caller wants to update\n> symbolic ref. Having this flag means that there are more possibilities\n> to misuse the new API setting this flag but providing NULL for old_ref\n> and new_ref.\n>\n\nI think I started with this flag but as the direction of the series\nchanged and I moved using zero_oid values for deletion or for using the\nverify command, this is not really needed anymore. I just tried removing\nall the code around the flags and fixing up things and all the tests\nstill pass. Thanks for brining this up.\n\nPatrick Steinhardt <ps@pks.im> writes:\n>> I'm confused as to why we need this as I assumed that we could use the\n>> presence of old_ref/new_ref to determine that the caller wants to update\n>> symbolic ref. Having this flag means that there are more possibilities to\n>> misuse the new API setting this flag but providing NULL for old_ref and\n>> new_ref.\n>\n> In my opinion the same comment applies to `REF_HAVE_NEW` and\n> `REF_HAVE_OLD`, which I found to be redundant, as well. Those may make\n> sense in the internals when the object IDs are stored as non-pointers,\n> but queueing ref updates only accepts pointers anyway.\n>\n\nYeah like you mentioned, since we're dealing with pointers, checking the\nif its set is enough indication, which doesn't work with the static OID\nvalues.\n"},{"id":"493221","messageId":"xmqqil0dmjyv.fsf@gitster.g","threadId":"61235","inReplyTo":"ZiI8GaGupNzbLqnE@tanuki","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-19T15:48:56Z","receivedAt":"2024-04-19T15:49:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n>> +\tif (!new_ref)\n>> +\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n>> +\tif (read_ref(new_ref, NULL))\n>> +\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n>\n> This restricts the creation of dangling symrefs, right? I think we might\n> want to lift this restriction, in which case we can eventually get rid\n> of the `create_symref` callback in ref backends completely.\n\nTrue.\n\n>> @@ -2609,6 +2609,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>  \t\t}\n>>  \t}\n>>  \n>> +\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n>\n> Let's add braces around `(updaet->flags & REF_SYMREF_UPDATE)` to make\n> this easier to read.\n\nYup, the language may not require the extra pair of parentheses, but\nhuman eyes are helped by them.\n\nThanks, again and as always, for carefully reading.\n"},{"id":"493230","messageId":"CAOLa=ZT8=1qDbsHhk2gnVfgcGOLv11+WgvyB_DtuB7=hPL+jyw@mail.gmail.com","threadId":"61235","inReplyTo":"ZiI8AfkJL4O3K7Xk@tanuki","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-19T18:09:39Z","receivedAt":"2024-04-19T18:09:41Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Apr 12, 2024 at 11:59:02AM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The `ref_transaction[_add]_update` functions obtain ref information and\n>> flags to create a `ref_update` and add it to the transaction at hand.\n>>\n>> To extend symref support in transactions, we need to also accept the\n>> old and new ref values and process it. In this commit, let's add the\n>> required paramaters to the function and modify all call sites.\n>>\n>> The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n>\n> Would `new_target` and `old_target` be easier to understand? `new_ref`\n> and `old_ref` to me sound as if they might also apply to the ref itself,\n> for example when doing a rename.\n>\n\nI guess that makes sense, I'll add it.\n\n> [snip]\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 56641aa57a..4c5fe02687 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -124,6 +124,19 @@ struct ref_update {\n>>  \t */\n>>  \tstruct object_id old_oid;\n>>\n>> +\t/*\n>> +\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n>> +\t * value (or delete it, if `new_ref` is an empty string).\n>> +\t */\n>> +\tconst char *new_ref;\n>> +\n>> +\t/*\n>> +\t * If (type & REF_SYMREF_UPDATE), check that the reference\n>> +\t * previously had this value (or didn't previously exist,\n>> +\t * if `old_ref` is an empty string).\n>> +\t */\n>> +\tconst char *old_ref;\n>\n> I think one important bit of information here would be how to handle the\n> update from a plain ref to a symref or vice versa. Would I set both\n> `REF_SYMREF_UPDATE` and `REF_HAVE_NEW`/`REF_HAVE_OLD`?\n\nI'll now remove `REF_SYMREF_UPDATE` and add documentation around the\nusage on `new_target` and `old_target`, where if either of them are set,\nthey take precedence over `old_oid` and `new_oid`. The `new_target` will\nensure that the ref is now a symbolic ref which points to the\n`new_target` value. While the `old_target` will treat the ref as a\nsymbolic ref and check its old value.\n\n`REF_HAVE_NEW`/`REF_HAVE_OLD` should however never be set by users of\nref.c, this is set internally. See REF_TRANSACTION_UPDATE_ALLOWED_FLAGS.\n"},{"id":"493236","messageId":"CAOLa=ZSFXH=xGYF0on3B2YeiudJBGMpjctHazLcn-fyO=UR4yA@mail.gmail.com","threadId":"61235","inReplyTo":"CAP8UFD2bqdMujHgS9oPn4Xqzu=EhGMPms7ftOB7uf-AHke79AQ@mail.gmail.com","subject":"Re: [PATCH v2 2/7] update-ref: add support for symref-verify","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-19T21:21:26Z","receivedAt":"2024-04-19T21:21:28Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> In the previous commit, we added the required base for adding symref\n>> support in transactions provided by the 'git-update-ref(1)'. This commit\n>\n> s/by the 'git-update-ref(1)'/by 'git-update-ref(1)'/\n>\n\nwill change.\n\n>> introduces the 'symref-verify' command which is similar to the existing\n>> 'verify' command for regular refs.\n>>\n>> The 'symref-verify' command allows users to verify if a provided <ref>\n>> contains the provided <old-ref> without changing the <ref>.\n>\n> What if <old-ref> looks like an oid? Will it work if <ref> is a\n> regular ref that actually contains this oid?\n>\n\nThis would fail, since we parse and check the ref entered.\n\n>> If <old-ref>\n>> is not provided, the command will verify that the <ref> doesn't exist.\n>> Since we're checking for symbolic refs,\n>\n> So if <ref> isn't a symbolic ref, it will fail? I guess the answer is\n> yes, but I think it would be better to be clear about this.\n\nWill try and clarify more in the commit message.\n\n>> this command will only work with\n>> the 'no-deref' mode. This is because any dereferenced symbolic ref will\n>> point to an object and not a ref and the regular 'verify' command can be\n>> used in such situations.\n>>\n>> This commit adds all required helper functions required to also\n>> introduce the other symref commands, namely create, delete, and update.\n>\n> Are these helper functions actually used in this commit? If yes, it\n> would be nice to say it explicitly. If not, why is it a good idea to\n> add them now?\n>\n> Also I think we prefer imperative wordings like \"Add all required...\"\n> over wordings like \"This commit adds all required...\"\n>\n\nWill clarify and make it imperative.\n\n>> We also add tests to test the command in both the regular stdin mode and\n>> also with the '-z' flag.\n>\n>> When the user doesn't provide a <old-ref> we need to check that the\n>> provided <ref> doesn't exist.\n>\n> That the provided <ref> doesn't exist or that it isn't a symref?\n\nThat it doesn't exist. I don't know if it makes sense to make it 'that\nit isn't a symref'.\n\n>> And to do this, we take over the existing\n>> understanding that <old-oid> when set to its zero value, it refers to\n>> the ref not existing. While this seems like a mix of contexts between\n>> using <*-ref> and <*-oid>, this actually works really well, especially\n>> considering the fact that we want to eventually also introduce\n>>\n>>     symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n>>\n>> and here, we'd allow the user to update a regular <ref> to a symref and\n>> use <old-oid> to check the <ref>'s oid.\n>\n> This means that the ref actually exists but isn't a symref.\n>\n> So if I understand correctly, for now it will work only if the ref\n> doesn't exist, but in the future we can make it work also if the ref\n> exists but isn't a symref.\n\nThis comment specifically talks about the `symref-update` command which\nis in an upcoming commit, in that commit, we do allow users to update a\nregular ref to a symref, while also checking the old_oid value of that\nref.\n\n>> This can be extrapolated to the\n>> user using this to create a symref when provided a zero <old-oid>. Which\n>> will work given how we're setting it up.\n>>\n>> We also disable the reference-transaction hook for symref-updates which\n>> will be tackled in its own commit.\n>\n> Why do we need to disable it?\n>\n\nHistorically, reference transaction didn't support symrefs, so we're not\nactually changing functionality here. While we can fix that in this\ncommit and add tests, it makes more sense to tackle it in its own\ncommit, which we do at the end.\n\n>> Add required tests for 'symref-verify' while also adding reflog checks for\n>> the pre-existing 'verify' tests.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>\n>> +symref-verify::\n>> +       Verify symbolic <ref> against <old-ref> but do not change it.\n>> +       If <old-ref> is missing, the ref must not exist.\n>\n> \"must not exist\" means that we will need to change this when we make\n> it work if the ref exists but isn't a symref. Ok.\n>\n>>  Can only be\n>> +       used in `no-deref` mode.\n>> +\n>\n>\n>> +       /*\n>> +        * old_ref is optional, but we want to differentiate between\n>> +        * a NULL and zero value.\n>> +        */\n>> +       old_ref = parse_next_refname(&next);\n>> +       if (!old_ref)\n>> +               old_oid = *null_oid();\n>\n> So for now we always overwrite old_oid when old_ref is not provided.\n> So the ref should not exist and the command will fail if it exists as\n> a regular ref. Ok.\n>\n>> +       else if (read_ref(old_ref, NULL))\n>> +               die(\"symref-verify %s: invalid <old-ref>\", refname);\n>\n> So I guess we die() if old_ref is the empty string.\n>\n> It's kind of strange as in the previous patch there was:\n>\n>> +        * If (type & REF_SYMREF_UPDATE), check that the reference\n>> +        * previously had this value (or didn't previously exist,\n>> +        * if `old_ref` is an empty string).\n>\n> So it said the empty string meant the old_ref didn't previously exist,\n> but now it's actually NULL that means the old_ref didn't previously\n> exist.\n>\n\nGood catch, I initially did start with the empty string idea, but\nswitched to using zero_oid, since it made a lot more sense, especially\nbecause now it sets interoperability. I will fix that comment.\n"},{"id":"493240","messageId":"CAOLa=ZQJmxrQ+n060P0X8BW4ay2XMvOHHyJ3vxrW7C+j+5kWSA@mail.gmail.com","threadId":"61235","inReplyTo":"ZiI8DmqrbvdCDWt0@tanuki","subject":"Re: [PATCH v2 2/7] update-ref: add support for symref-verify","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-19T21:53:01Z","receivedAt":"2024-04-19T21:53:04Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n>> +symref-verify::\n>> +\tVerify symbolic <ref> against <old-ref> but do not change it.\n>> +\tIf <old-ref> is missing, the ref must not exist.  Can only be\n>> +\tused in `no-deref` mode.\n>\n> Should this say \"is zero or missing\", like the comment for \"verify\"\n> does?\n>\n\nWe don't allow users to enter OID here, we do convert it to zero OID\ninternally. But the user input is expected to be old_ref or nothing.\n\n> [snip]\n>> @@ -297,11 +321,48 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n>>  \t\tdie(\"verify %s: extra input: %s\", refname, next);\n>>\n>>  \tif (ref_transaction_verify(transaction, refname, &old_oid,\n>> -\t\t\t\t   update_flags, &err))\n>> +\t\t\t\t   NULL, update_flags, &err))\n>> +\t\tdie(\"%s\", err.buf);\n>> +\n>> +\tupdate_flags = default_flags;\n>> +\tfree(refname);\n>> +\tstrbuf_release(&err);\n>> +}\n>> +\n>> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n>> +                                    const char *next, const char *end)\n>> +{\n>> +\tstruct strbuf err = STRBUF_INIT;\n>> +\tstruct object_id old_oid;\n>> +\tchar *refname, *old_ref;\n>> +\n>> +\tif (!(update_flags & REF_NO_DEREF))\n>> +\t\tdie(\"symref-verify: cannot operate with deref mode\");\n>\n> This feels quite restrictive to me. Wouldn't it be preferable to simply\n> ignore `REF_NO_DEREF` here? It basically means that this command can't\n> ever be used in a normal `git update-ref --stdin` session.\n>\n\nWe do support 'option' with the '--stdin' flag. So technically a user\nshould be able to do.\n\n   $ git update-ref --stdin\n   no-deref\n   symref-verify refs/heads/symref refs/heads/master\n   update-ref refs/heads/branch 0b3b55ad0e593ead604f80fe3f621239b34cce7e\n\nI guess we could make it implicit, but I thought it's better to keep it\nexplicit so the user knows that there is no dereferencing taking place\nhere, eventhough the default option is to dereference.\n\n[snip]\n>>\n>>  \tupdate->flags = flags;\n>>\n>> -\tif (flags & REF_HAVE_NEW)\n>> -\t\toidcpy(&update->new_oid, new_oid);\n>> -\tif (flags & REF_HAVE_OLD)\n>> -\t\toidcpy(&update->old_oid, old_oid);\n>> +\t/*\n>> +\t * The ref values are to be considered over the oid values when we're\n>> +\t * doing symref operations.\n>> +\t */\n>\n> I feel like this is a statement that should be backed up by a deeper\n> explanation of why that is. I'm still wondering here why we cannot\n> assert that the old value is an object ID when I want to update it to a\n> symref, or alternatively why it would even be possible to have both\n> `REF_SYMREF_UPDATE` and a set of other, incompatible fields set. It\n> feels like this should be a `BUG()` instead if this is supposedly an\n> unsupported configuration rather than silently ignoring it.\n>\n> In any case, I feel like it would be easier to reason about if this was\n> introduced together with the actual user. As far as I can see this code\n> shouldn't ever be hit for \"verify-symref\", right? Currently, the reader\n> is forced to figure out what is and isn't related to the new command.\n>\n\nI've changed this now to no longer have this condition and also added\n'BUG' for cases where both old_{ref,target} and new_{ref,target} exist.\n\n[snip]\n>> @@ -2464,8 +2495,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>  \t\t\t       struct strbuf *err)\n>>  {\n>>  \tstruct strbuf referent = STRBUF_INIT;\n>> -\tint mustexist = (update->flags & REF_HAVE_OLD) &&\n>> -\t\t!is_null_oid(&update->old_oid);\n>> +\tint mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid);\n>\n> This change is a no-op, right? If so, let's rather drop it.\n>\n\nYeah, will do.\n\n>>  \tint ret = 0;\n>>  \tstruct ref_lock *lock;\n>>\n>> @@ -2514,6 +2544,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>>  \t\t\t\t\tgoto out;\n>>  \t\t\t\t}\n>> +\t\t\t}\n>> +\n>> +\t\t\t/*\n>> +\t\t\t * For symref verification, we need to check the referent value\n>> +\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n>> +\t\t\t * verifying a dereferenced symref, we then check the oid.\n>> +\t\t\t */\n>> +\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->old_ref) {\n>> +\t\t\t\tif (check_old_ref(update, referent.buf, err)) {\n>> +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>> +\t\t\t\t\tgoto out;\n>> +\t\t\t\t}\n>>  \t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n>>  \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>>  \t\t\t\tgoto out;\n>> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> index 4c5fe02687..21c6b940d8 100644\n>> --- a/refs/refs-internal.h\n>> +++ b/refs/refs-internal.h\n>> @@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n>>   */\n>>  struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n>>\n>> +/*\n>> + * Helper function to check if the new value is null, this\n>> + * takes into consideration that the update could be a regular\n>> + * ref or a symbolic ref.\n>> + */\n>> +int null_new_value(struct ref_update *update);\n>\n> When adding it to the header we should probably prefix this to avoid\n> name collisions. `ref_update_is_null_new_value()` might be a mouth full,\n> but feels preferable to me.\n>\n\nMakes sense.\n\n>>  #endif /* REFS_REFS_INTERNAL_H */\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 6104471199..7a03922c7b 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -938,7 +938,28 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>  \t\t * individual refs. But the error messages match what the files\n>>  \t\t * backend returns, which keeps our tests happy.\n>>  \t\t */\n>> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>> +\t\tif ((u->flags & REF_HAVE_OLD) &&\n>> +\t\t    (u->flags & REF_SYMREF_UPDATE) &&\n>> +\t\t    u->old_ref) {\n>> +\t\t\tif   (strcmp(referent.buf, u->old_ref)) {\n>\n> s/   / /\n>\n>> +\t\t\t\tif (!strcmp(u->old_ref, \"\"))\n>> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t\t\t\t    \"reference already exists\",\n>> +\t\t\t\t\t\t    original_update_refname(u));\n>> +\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n>> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    u->old_ref);\n>> +\t\t\t\telse\n>> +\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    referent.buf, u->old_ref);\n>\n> I'd use better-matching error messages here. I know that we talk about\n> \"cannot lock ref\" in the next branch, as well. But the only reason we\n> did this is to have the same error messages as the \"files\" backend.\n> Semantically, those errors don't make much sense as the \"reftable\"\n> backend never locks specific refs, but only the complete stack.\n>\n\nFair enough, will change.\n\n>> +\t\t\t\tret = -1;\n>> +\t\t\t\tgoto done;\n>> +\t\t\t}\n>> +\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>>  \t\t\tif (is_null_oid(&u->old_oid))\n>>  \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>  \t\t\t\t\t    \"reference already exists\"),\n>> diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\n>> index ec3443cc87..d8ffda4096 100755\n>> --- a/t/t1400-update-ref.sh\n>> +++ b/t/t1400-update-ref.sh\n>> @@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '\n>>  '\n>>\n>>  test_expect_success 'stdin verify succeeds for correct value' '\n>> +\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n>>  \tgit rev-parse $m >expect &&\n>>  \techo \"verify $m $m\" >stdin &&\n>>  \tgit update-ref --stdin <stdin &&\n>>  \tgit rev-parse $m >actual &&\n>> -\ttest_cmp expect actual\n>> +\ttest_cmp expect actual &&\n>> +\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n>> +\ttest_cmp before after\n>>  '\n>>\n>>  test_expect_success 'stdin verify succeeds for missing reference' '\n>> +\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n>>  \techo \"verify refs/heads/missing $Z\" >stdin &&\n>>  \tgit update-ref --stdin <stdin &&\n>> -\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n>> +\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n>> +\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n>> +\ttest_cmp before after\n>>  '\n>\n> The updated tests merely assert that the refs didn't change, right?\n>\n\nYes, also that we didn't add anything unexpected to the reflog.\n\n\n>>  test_expect_success 'stdin verify treats no value as missing' '\n>> @@ -1641,4 +1647,74 @@ test_expect_success PIPE 'transaction flushes status updates' '\n>>  \ttest_cmp expected actual\n>>  '\n>>\n>> +create_stdin_buf ()\n>> +{\n>\n> The curly brace should go on the same line as the function name.\n>\n\nRight, will change.\n\n>> +\tif test \"$1\" = \"-z\"\n>> +\tthen\n>> +\t\tshift\n>> +\t\tprintf \"$F\" \"$@\" >stdin\n>> +\telse\n>> +\t\techo \"$@\" >stdin\n>> +\tfi\n>> +}\n>> +\n>> +for type in \"\" \"-z\"\n>> +do\n>\n> We should probably indent all of the tests to make it easier to see that\n> they run in a loop.\n>\n> Patrick\n>\n\nI was a bit confused about this, I saw smaller tests with loops\nindented, while some larger ones not indented. I think its better to do\nso too, let me do that.\n"},{"id":"493246","messageId":"ZiNdvEhuhtH87Mw6@framework","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-20T06:16:28Z","receivedAt":"2024-04-20T06:16:35Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 12, 2024 at 11:59:01AM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The 'git-update-ref(1)' command allows transactional reference updates.\n> But currently only supports regular reference updates. Meaning, if one\n> wants to update HEAD (symbolic ref) in a transaction, there is no tool\n> to do so.\n> \n> One option to obtain transactional updates for the HEAD ref is to\n> manually create the HEAD.lock file and commit. This is intrusive, where\n> the user needs to mimic internal git behavior. Also, this only works\n> when using the files backend.\n> \n> At GitLab, we've been using the manual process till date, to allow users\n> to set and change their default branch. But with the introduction of\n> reftables as a reference backend, this becomes a necessity to be solved\n> within git.\n> \n> This patch series goes about introducing a set of commands\n> symref-{create,verify,delete,update} to work with symrefs complimenting\n> the existing commands for the regular refs within 'git-update-ref(1)'.\n\nOne more thought crossed my mind this night: is it even necessary to\nintroduce new commands for this? As far as I can see, it should be\nfeasible to introduce symref support for all existing commands without\nbreaking backwards compatibility. This can be achieved by using a prefix\nthat cannot ever be an object ID, like for example \"ref:\".\n\nThus, all of the following should work (with 1234 being an OID and 0000\nbeing the null OID):\n\n    update HEAD ref:refs/heads/main ref:refs/heads/master\n    update HEAD ref:refs/heads/main 1234\n    update HEAD ref:refs/heads/main 0000\n    update HEAD 1234 ref:refs/heads/main\n    update HEAD 0000 ref:refs/heads/main\n    create HEAD ref:refs/heads/main\n    delete HEAD ref:refs/heads/main\n    verify HEAD ref:refs/heads/mains\n\nParsing is unambiguous because we can use `starts_with(\"ref:\")` for an\notherwise invalid object ID. Furthermore, because refs cannot have\nspaces, we also don't have an issue with the SP separator.\n\nI have a hunch that this variant might lead to less code duplication,\nlead to less confusing behaviour and also makes for an easier user\ninterface.\n\nPatrick\n"},{"id":"493262","messageId":"CAOLa=ZRATdwogJfWH4y0htOzEK1SzKfK+CWXHjBny7waoodkNg@mail.gmail.com","threadId":"61235","inReplyTo":"CAP8UFD1Fwq4exKKz3kyJiwcgtOe4TjQ938C=sAbdWOTsw6241A@mail.gmail.com","subject":"Re: [PATCH v2 3/7] update-ref: add support for symref-delete","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T10:43:17Z","receivedAt":"2024-04-21T10:43:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 12, 2024 at 11:59 AM Karthik Nayak <karthik.188@gmail.com> wrote:\n>>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> Similar to the previous commit, add 'symref-delete' to allow deletions\n>> of symbolic refs in a transaction via the 'git-update-ref' command. The\n>> 'symref-delete' command can when given with an <old-ref>, deletes the\n>> provided <ref> only when it points to <old-ref>.\n>\n> I have a similar question as with the previous patch about what\n> happens if <old-ref> looks like an oid and <ref> is a regular ref\n> pointing to it.\n>\n\nWe parse refs passed as <old-ref> and this would fail.\n\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>\n>> diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\n>> index 749aaa7892..ef22a1a2f4 100644\n>> --- a/Documentation/git-update-ref.txt\n>> +++ b/Documentation/git-update-ref.txt\n>> @@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n>>         create SP <ref> SP <new-oid> LF\n>>         delete SP <ref> [SP <old-oid>] LF\n>>         verify SP <ref> [SP <old-oid>] LF\n>> +       symref-delete SP <ref> [SP <old-ref>] LF\n>>         symref-verify SP <ref> [SP <old-ref>] LF\n>>         option SP <opt> LF\n>>         start LF\n>> @@ -87,6 +88,7 @@ quoting:\n>>         create SP <ref> NUL <new-oid> NUL\n>>         delete SP <ref> NUL [<old-oid>] NUL\n>>         verify SP <ref> NUL [<old-oid>] NUL\n>> +       symref-delete SP <ref> [NUL <old-ref>] NUL\n>>         symref-verify SP <ref> [NUL <old-ref>] NUL\n>>         option SP <opt> NUL\n>>         start NUL\n>\n> Also I wonder if there is a test where <old-ref> is an empty string, so where:\n>\n>     symref-delete SP <ref> SP LF\n>\n> or:\n>\n>     symref-delete SP <ref> NUL NUL\n>\n> are used?\n\nI didn't add such tests, will add.\n"},{"id":"493263","messageId":"CAOLa=ZS7+sqpf2TajahUM95DMVfYyxUOL3BBhT=-EhfnGBi0+w@mail.gmail.com","threadId":"61235","inReplyTo":"ZiI8E2ZJT51akoAk@tanuki","subject":"Re: [PATCH v2 3/7] update-ref: add support for symref-delete","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T10:45:46Z","receivedAt":"2024-04-21T10:45:48Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Apr 12, 2024 at 11:59:04AM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n> [snip]\n>> @@ -302,6 +302,37 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n>>  \tstrbuf_release(&err);\n>>  }\n>>\n>> +static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n>> +\t\t\t\t    const char *next, const char *end)\n>> +{\n>> +\tstruct strbuf err = STRBUF_INIT;\n>> +\tchar *refname, *old_ref;\n>> +\n>> +\tif (!(update_flags & REF_NO_DEREF))\n>> +                die(\"symref-delete: cannot operate with deref mode\");\n>\n> Again, I'm a bit on the fence regarding this restriction. I feel like it\n> ought to be possible to delete both plain and symbolic refs in a single\n> git-update-ref(1) command.\n>\n\nYup this is still possible since we have the 'no-deref' option.\n\n>> +\trefname = parse_refname(&next);\n>> +\tif (!refname)\n>> +\t\tdie(\"symref-delete: missing <ref>\");\n>> +\n>> +        old_ref = parse_next_refname(&next);\n>\n> This line is indented with spaces and not tabs.\n>\n\nThere was a bunch of this, I'll have them all fixed.\n\n> [snip]\n>> --- a/t/t1400-update-ref.sh\n>> +++ b/t/t1400-update-ref.sh\n>> @@ -1715,6 +1715,45 @@ test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\"\n>>  \ttest_cmp expect actual\n>>  '\n>>\n>> +test_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n>> +\tgit symbolic-ref refs/heads/symref $a &&\n>> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n>> +\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n>> +\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n>> +'\n>> +\n>> +test_expect_success \"stdin ${type} fails symref-delete with no ref\" '\n>> +\tcreate_stdin_buf ${type} \"symref-delete \" &&\n>> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n>> +\tgrep \"fatal: symref-delete: missing <ref>\" err\n>> +'\n>> +\n>> +test_expect_success \"stdin ${type} fails symref-delete with too many arguments\" '\n>> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n>> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n>> +\tif test \"$type\" = \"-z\"\n>> +\tthen\n>> +\t\tgrep \"fatal: unknown command: $a\" err\n>> +\telse\n>> +\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n>> +\tfi\n>> +'\n>> +\n>> +test_expect_success \"stdin ${type} symref-delete ref fails with wrong old value\" '\n>> +\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n>> +\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n>> +\tgrep \"fatal: cannot lock ref '\"'\"'refs/heads/symref'\"'\"'\" err &&\n>\n> You can use \"${SQ}\" to insert single quotes.\n>\n> Patrick\n>\n\nNeat, this is much better, thanks!\n"},{"id":"493265","messageId":"CAOLa=ZSvx4bOmJY+P5j3fJTqfjY37teMHQhOYDjTM4oJtnv_qw@mail.gmail.com","threadId":"61235","inReplyTo":"ZiI8GaGupNzbLqnE@tanuki","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T12:50:07Z","receivedAt":"2024-04-21T12:50:09Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Apr 12, 2024 at 11:59:06AM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n> [snip]\n>> @@ -268,6 +268,39 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n>>  \tstrbuf_release(&err);\n>>  }\n>>\n>> +static void parse_cmd_symref_create(struct ref_transaction *transaction,\n>> +\t\t\t\t    const char *next, const char *end)\n>> +{\n>> +\tstruct strbuf err = STRBUF_INIT;\n>> +\tchar *refname, *new_ref;\n>> +\n>> +\tif (!(update_flags & REF_NO_DEREF))\n>> +                die(\"symref-create: cannot operate with deref mode\");\n>> +\n>> +\trefname = parse_refname(&next);\n>> +\tif (!refname)\n>> +\t\tdie(\"symref-create: missing <ref>\");\n>> +\n>> +\tnew_ref = parse_next_refname(&next);\n>> +\tif (!new_ref)\n>> +\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n>> +\tif (read_ref(new_ref, NULL))\n>> +\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n>\n> This restricts the creation of dangling symrefs, right? I think we might\n> want to lift this restriction, in which case we can eventually get rid\n> of the `create_symref` callback in ref backends completely.\n>\n\nYes it does. I thought it'd be more consistent with what update-ref does\nwith regular ref updates. We verify that the object exists. Also this\ncould be an added option 'allow-dangling'.\n\nI'm not sure I understand what you mean 'the `create_symref` callback in\nref backends completely.'.\n\n>> +\tif (*next != line_termination)\n>> +\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n>> +\n>> +\tif (ref_transaction_create(transaction, refname, NULL, new_ref,\n>> +\t\t\t\t   update_flags | create_reflog_flag |\n>> +\t\t\t\t   REF_SYMREF_UPDATE, msg, &err))\n>> +\t\tdie(\"%s\", err.buf);\n>> +\n>> +\tupdate_flags = default_flags;\n>> +\tfree(refname);\n>> +\tfree(new_ref);\n>> +\tstrbuf_release(&err);\n>> +}\n>> +\n>>  static void parse_cmd_delete(struct ref_transaction *transaction,\n>>  \t\t\t     const char *next, const char *end)\n>>  {\n>> @@ -476,6 +509,7 @@ static const struct parse_cmd {\n>>  \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n>>  \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n>>  \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n>> +\t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n>>  \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n>>  \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n>>  \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n>> diff --git a/refs.c b/refs.c\n>> index 6d98d9652d..e62c0f4aca 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -1305,15 +1305,20 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>>  int ref_transaction_create(struct ref_transaction *transaction,\n>>  \t\t\t   const char *refname,\n>>  \t\t\t   const struct object_id *new_oid,\n>> +\t\t\t   const char *new_ref,\n>>  \t\t\t   unsigned int flags, const char *msg,\n>>  \t\t\t   struct strbuf *err)\n>>  {\n>> -\tif (!new_oid || is_null_oid(new_oid)) {\n>> +\tif ((flags & REF_SYMREF_UPDATE) && !new_ref) {\n>> +\t\tstrbuf_addf(err, \"'%s' has a no new ref\", refname);\n>> +\t\treturn 1;\n>> +\t}\n>> +\tif (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {\n>>  \t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n>>  \t\treturn 1;\n>>  \t}\n>>  \treturn ref_transaction_update(transaction, refname, new_oid,\n>> -\t\t\t\t      null_oid(), NULL, NULL, flags,\n>> +\t\t\t\t      null_oid(), new_ref, NULL, flags,\n>>  \t\t\t\t      msg, err);\n>>  }\n>>\n>> diff --git a/refs.h b/refs.h\n>> index 60e6a21a31..c01a517e40 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -744,6 +744,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>>  int ref_transaction_create(struct ref_transaction *transaction,\n>>  \t\t\t   const char *refname,\n>>  \t\t\t   const struct object_id *new_oid,\n>> +\t\t\t   const char *new_ref,\n>>  \t\t\t   unsigned int flags, const char *msg,\n>>  \t\t\t   struct strbuf *err);\n>>\n>> diff --git a/refs/files-backend.c b/refs/files-backend.c\n>> index 7c894ebe65..59d438878a 100644\n>> --- a/refs/files-backend.c\n>> +++ b/refs/files-backend.c\n>> @@ -2609,6 +2609,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>  \t\t}\n>>  \t}\n>>\n>> +\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n>\n> Let's add braces around `(updaet->flags & REF_SYMREF_UPDATE)` to make\n> this easier to read.\n>\n\nThis should now be cleaned up as we removed the flag entirely.\n\n>> +\t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n>> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>> +\t\t\tgoto out;\n>> +\t\t}\n>> +\n>> +\t\tif (close_ref_gently(lock)) {\n>> +\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n>> +\t\t\t\t    update->refname);\n>> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>> +\t\t\tgoto out;\n>> +\t\t}\n>> +\n>> +\t\t/*\n>> +\t\t * Once we have created the symref lock, the commit\n>> +\t\t * phase of the transaction only needs to commit the lock.\n>> +\t\t */\n>> +\t\tupdate->flags |= REF_NEEDS_COMMIT;\n>> +\t}\n>> +\n>> +\n>>  \tif ((update->flags & REF_HAVE_NEW) &&\n>>  \t    !(update->flags & REF_DELETING) &&\n>>  \t    !(update->flags & REF_LOG_ONLY)) {\n>> @@ -2904,6 +2925,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>>\n>>  \t\tif (update->flags & REF_NEEDS_COMMIT ||\n>>  \t\t    update->flags & REF_LOG_ONLY) {\n>> +\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n>\n> Here, as well.\n>\n\nSame here.\n\n>> +\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n>> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n>> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n>> +\t\t\t\t\tupdate->new_oid = *null_oid();\n>> +\t\t\t\t}\n>\n> Can this actually happenn right now? I thought that the `read_ref()`\n> further up forbids this case.\n>\n> Patrick\n\nWith update-ref, it won't happen anymore, because as you mentioned, we\nuse `read_ref()`. I thought it was still worthwhile to have. But I guess\nits cleaner to remove this.\n"},{"id":"493272","messageId":"CAOLa=ZRcXteRGQa82aw+WxfxsufrU2R-6hr7XYu9-SqjtbaS6Q@mail.gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZSvx4bOmJY+P5j3fJTqfjY37teMHQhOYDjTM4oJtnv_qw@mail.gmail.com","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T15:57:54Z","receivedAt":"2024-04-21T15:57:56Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Karthik Nayak <karthik.188@gmail.\n\n[snip]\n>>> +\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n>>> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n>>> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n>>> +\t\t\t\t\tupdate->new_oid = *null_oid();\n>>> +\t\t\t\t}\n>>\n>> Can this actually happenn right now? I thought that the `read_ref()`\n>> further up forbids this case.\n>>\n>> Patrick\n>\n> With update-ref, it won't happen anymore, because as you mentioned, we\n> use `read_ref()`. I thought it was still worthwhile to have. But I guess\n> its cleaner to remove this.\n\nWell, just realized that this needs to stay because we also want the\nresolved OID for the reflogs. Will modify the comment though.\n"},{"id":"493284","messageId":"CAOLa=ZTA94HModfa40So7Uk7WAG99G+XabvKfOY93RCL6Xac5Q@mail.gmail.com","threadId":"61235","inReplyTo":"ZiI8IFdfBHEqr02C@tanuki","subject":"Re: [PATCH v2 6/7] update-ref: add support for symref-update","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T19:00:11Z","receivedAt":"2024-04-21T19:00:13Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n[snip]\n>> +\t/*\n>> +\t * Since the user can also send in an old-oid, we try to parse\n>> +\t * it as such too.\n>> +\t */\n>> +\tif (old_ref && read_ref(old_ref, NULL)) {\n>> +\t\tif (!repo_get_oid(the_repository, old_ref, &old_oid)) {\n>> +\t\t\told_ref = NULL;\n>> +\t\t\thave_old = 1;\n>> +\t\t} else\n>> +\t\t\tdie(\"symref-update %s: invalid <old-ref> or <old-oid>\", refname);\n>> +\t}\n>\n> So we first try to parse it as a ref, and then as an object ID? Wouldn't\n> it preferable to try it the other way round and first check whether it\n> is a valid object ID? That would likely be cheaper, even though it may\n> be premature optimization.\n>\n> Patrick\n\nI think the issue is `repo_get_oid` would also parse a refname to an\nOID. Whereas we want to first check and keep refnames and only if it\nisn't a refname, we'd want to parse it as an OID.\n\nAlso, why do you say it is cheaper?\n"},{"id":"493285","messageId":"CAOLa=ZRrb=MnHZVQZ5WxT5D0HF9Qk5eEnCOhPc=ZS1_32bQgsQ@mail.gmail.com","threadId":"61235","inReplyTo":"CAP8UFD19J+KY51hVwQbnF8CaK_ZqqjdqW49qjGtJ8A6UtCJeUg@mail.gmail.com","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T19:06:30Z","receivedAt":"2024-04-21T19:06:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Fri, Apr 12, 2024 at 12:11 PM Karthik Nayak <karthik.188@gmail.com> wrote:\n>\n>> The 'symref-update' command can be used to update a symref, create a symref,\n>> delete a symref or even convert an existing regular ref to a symref. Wherein\n>> like the regular 'update' command, the zero OID can be used to create/delete\n>> a symref.\n>\n> Is it possible to also convert a symref to a regular ref, like when\n> HEAD becomes detached?\n>\n\nCurrently not, that could be a follow up, since that would involve\nupdating the regular commands. A quick way to think about it is that\nthis series adds commands whose end product is a symref. Since what\nyou're talking about is the inverse, it makes sense to add it to the\nexisting commands.\n\n>> 2. The flow is now changedc to send an old_ref, new_ref pair in supplement to\n>\n> s/changedc/changed/\n>\n>> the existing old_oid, new_oid pair to the reference backends. This allows the\n>> backends to simply do a combination of changes based on what values are set.\n>\n> I had a number of comments and questions when reading the first few\n> patches in this series. I also took a very quick look at the rest of\n> the series, but I think answering my questions about the first few\n> patches would make reading the rest of the series easier, so I will\n> take a deeper look later.\n>\n> Thanks.\n\nThanks for the review. Really appreciate it.\n"},{"id":"493287","messageId":"CAOLa=ZRsNHgWriZhiCxBfbqS2D6gSytnG9Hy3y2BMK2vV+x0Lw@mail.gmail.com","threadId":"61235","inReplyTo":"ZiNdvEhuhtH87Mw6@framework","subject":"Re: [PATCH v2 0/7] update-ref: add symref oriented commands","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-21T19:11:10Z","receivedAt":"2024-04-21T19:11:11Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Apr 12, 2024 at 11:59:01AM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The 'git-update-ref(1)' command allows transactional reference updates.\n>> But currently only supports regular reference updates. Meaning, if one\n>> wants to update HEAD (symbolic ref) in a transaction, there is no tool\n>> to do so.\n>>\n>> One option to obtain transactional updates for the HEAD ref is to\n>> manually create the HEAD.lock file and commit. This is intrusive, where\n>> the user needs to mimic internal git behavior. Also, this only works\n>> when using the files backend.\n>>\n>> At GitLab, we've been using the manual process till date, to allow users\n>> to set and change their default branch. But with the introduction of\n>> reftables as a reference backend, this becomes a necessity to be solved\n>> within git.\n>>\n>> This patch series goes about introducing a set of commands\n>> symref-{create,verify,delete,update} to work with symrefs complimenting\n>> the existing commands for the regular refs within 'git-update-ref(1)'.\n>\n> One more thought crossed my mind this night: is it even necessary to\n> introduce new commands for this? As far as I can see, it should be\n> feasible to introduce symref support for all existing commands without\n> breaking backwards compatibility. This can be achieved by using a prefix\n> that cannot ever be an object ID, like for example \"ref:\".\n>\n\nI couldn't think of any reason that we can't do this. I think it would\nwork. I'll try to patch something with my existing tests and see if\nbreaks something.\n\n> Thus, all of the following should work (with 1234 being an OID and 0000\n> being the null OID):\n>\n>     update HEAD ref:refs/heads/main ref:refs/heads/master\n>     update HEAD ref:refs/heads/main 1234\n>     update HEAD ref:refs/heads/main 0000\n>     update HEAD 1234 ref:refs/heads/main\n>     update HEAD 0000 ref:refs/heads/main\n>     create HEAD ref:refs/heads/main\n>     delete HEAD ref:refs/heads/main\n>     verify HEAD ref:refs/heads/mains\n>\n> Parsing is unambiguous because we can use `starts_with(\"ref:\")` for an\n> otherwise invalid object ID. Furthermore, because refs cannot have\n> spaces, we also don't have an issue with the SP separator.\n>\n> I have a hunch that this variant might lead to less code duplication,\n> lead to less confusing behaviour and also makes for an easier user\n> interface.\n\nI'm certain that the duplication in update-ref.c will go away with this.\nRegarding other code changes, I think they'll stay the same.\n\nBut I do agree, this seems much nicer in terms of UX and also allows us\nto introduce `symref => regular ref` conversion in this series itself.\n"},{"id":"493352","messageId":"ZidVxMcTOd9ga9TL@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZT8=1qDbsHhk2gnVfgcGOLv11+WgvyB_DtuB7=hPL+jyw@mail.gmail.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-23T06:31:32Z","receivedAt":"2024-04-23T06:31:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 19, 2024 at 01:09:39PM -0500, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Fri, Apr 12, 2024 at 11:59:02AM +0200, Karthik Nayak wrote:\n> >> From: Karthik Nayak <karthik.188@gmail.com>\n> > [snip]\n> >> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> >> index 56641aa57a..4c5fe02687 100644\n> >> --- a/refs/refs-internal.h\n> >> +++ b/refs/refs-internal.h\n> >> @@ -124,6 +124,19 @@ struct ref_update {\n> >>  \t */\n> >>  \tstruct object_id old_oid;\n> >>\n> >> +\t/*\n> >> +\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n> >> +\t * value (or delete it, if `new_ref` is an empty string).\n> >> +\t */\n> >> +\tconst char *new_ref;\n> >> +\n> >> +\t/*\n> >> +\t * If (type & REF_SYMREF_UPDATE), check that the reference\n> >> +\t * previously had this value (or didn't previously exist,\n> >> +\t * if `old_ref` is an empty string).\n> >> +\t */\n> >> +\tconst char *old_ref;\n> >\n> > I think one important bit of information here would be how to handle the\n> > update from a plain ref to a symref or vice versa. Would I set both\n> > `REF_SYMREF_UPDATE` and `REF_HAVE_NEW`/`REF_HAVE_OLD`?\n> \n> I'll now remove `REF_SYMREF_UPDATE` and add documentation around the\n> usage on `new_target` and `old_target`, where if either of them are set,\n> they take precedence over `old_oid` and `new_oid`. The `new_target` will\n> ensure that the ref is now a symbolic ref which points to the\n> `new_target` value. While the `old_target` will treat the ref as a\n> symbolic ref and check its old value.\n> \n> `REF_HAVE_NEW`/`REF_HAVE_OLD` should however never be set by users of\n> ref.c, this is set internally. See REF_TRANSACTION_UPDATE_ALLOWED_FLAGS.\n\nShould they really take precedence, or should it be forbidden to pass\nboth old target and old object ID or new target and new object ID,\nrespectively? I'd rather claim the latter, and that should be detected\nsuch that there is no bad surprises here.\n\nPatrick\n"},{"id":"493353","messageId":"ZidXrdi7hXdAnDhy@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZSvx4bOmJY+P5j3fJTqfjY37teMHQhOYDjTM4oJtnv_qw@mail.gmail.com","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-23T06:39:41Z","receivedAt":"2024-04-23T06:39:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Apr 21, 2024 at 08:50:07AM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > On Fri, Apr 12, 2024 at 11:59:06AM +0200, Karthik Nayak wrote:\n> >> From: Karthik Nayak <karthik.188@gmail.com>\n> > [snip]\n> >> @@ -268,6 +268,39 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n> >>  \tstrbuf_release(&err);\n> >>  }\n> >>\n> >> +static void parse_cmd_symref_create(struct ref_transaction *transaction,\n> >> +\t\t\t\t    const char *next, const char *end)\n> >> +{\n> >> +\tstruct strbuf err = STRBUF_INIT;\n> >> +\tchar *refname, *new_ref;\n> >> +\n> >> +\tif (!(update_flags & REF_NO_DEREF))\n> >> +                die(\"symref-create: cannot operate with deref mode\");\n> >> +\n> >> +\trefname = parse_refname(&next);\n> >> +\tif (!refname)\n> >> +\t\tdie(\"symref-create: missing <ref>\");\n> >> +\n> >> +\tnew_ref = parse_next_refname(&next);\n> >> +\tif (!new_ref)\n> >> +\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n> >> +\tif (read_ref(new_ref, NULL))\n> >> +\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n> >\n> > This restricts the creation of dangling symrefs, right? I think we might\n> > want to lift this restriction, in which case we can eventually get rid\n> > of the `create_symref` callback in ref backends completely.\n> >\n> \n> Yes it does. I thought it'd be more consistent with what update-ref does\n> with regular ref updates. We verify that the object exists. Also this\n> could be an added option 'allow-dangling'.\n> \n> I'm not sure I understand what you mean 'the `create_symref` callback in\n> ref backends completely.'.\n\nWell, once normal reference transactions learn to update symrefs we can\ndo the following:\n\n    diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n    index 56641aa57a..2302311282 100644\n    --- a/refs/refs-internal.h\n    +++ b/refs/refs-internal.h\n    @@ -676,7 +676,6 @@ struct ref_storage_be {\n        ref_transaction_commit_fn *initial_transaction_commit;\n     \n        pack_refs_fn *pack_refs;\n    -\tcreate_symref_fn *create_symref;\n        rename_ref_fn *rename_ref;\n        copy_ref_fn *copy_ref;\n\nThere would be no need to specifically have this as a separate callback\nanymore as we can now provide a generic wrapper `refs_create_symref()`\n(in pseudo-code):\n\n```\nint refs_create_symref(struct ref_store *refs, const char *refname,\n                       const char *target)\n{\n    tx = ref_store_transaction_begin(refs);\n    ref_transaction_create_symref(tx, refname, target);\n    ref_transaction_commit(tx);\n}\n```\n\nPatrick\n\n> >> +\tif (*next != line_termination)\n> >> +\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n> >> +\n> >> +\tif (ref_transaction_create(transaction, refname, NULL, new_ref,\n> >> +\t\t\t\t   update_flags | create_reflog_flag |\n> >> +\t\t\t\t   REF_SYMREF_UPDATE, msg, &err))\n> >> +\t\tdie(\"%s\", err.buf);\n> >> +\n> >> +\tupdate_flags = default_flags;\n> >> +\tfree(refname);\n> >> +\tfree(new_ref);\n> >> +\tstrbuf_release(&err);\n> >> +}\n> >> +\n> >>  static void parse_cmd_delete(struct ref_transaction *transaction,\n> >>  \t\t\t     const char *next, const char *end)\n> >>  {\n> >> @@ -476,6 +509,7 @@ static const struct parse_cmd {\n> >>  \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n> >>  \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n> >>  \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n> >> +\t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n> >>  \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n> >>  \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n> >>  \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n> >> diff --git a/refs.c b/refs.c\n> >> index 6d98d9652d..e62c0f4aca 100644\n> >> --- a/refs.c\n> >> +++ b/refs.c\n> >> @@ -1305,15 +1305,20 @@ int ref_transaction_update(struct ref_transaction *transaction,\n> >>  int ref_transaction_create(struct ref_transaction *transaction,\n> >>  \t\t\t   const char *refname,\n> >>  \t\t\t   const struct object_id *new_oid,\n> >> +\t\t\t   const char *new_ref,\n> >>  \t\t\t   unsigned int flags, const char *msg,\n> >>  \t\t\t   struct strbuf *err)\n> >>  {\n> >> -\tif (!new_oid || is_null_oid(new_oid)) {\n> >> +\tif ((flags & REF_SYMREF_UPDATE) && !new_ref) {\n> >> +\t\tstrbuf_addf(err, \"'%s' has a no new ref\", refname);\n> >> +\t\treturn 1;\n> >> +\t}\n> >> +\tif (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {\n> >>  \t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n> >>  \t\treturn 1;\n> >>  \t}\n> >>  \treturn ref_transaction_update(transaction, refname, new_oid,\n> >> -\t\t\t\t      null_oid(), NULL, NULL, flags,\n> >> +\t\t\t\t      null_oid(), new_ref, NULL, flags,\n> >>  \t\t\t\t      msg, err);\n> >>  }\n> >>\n> >> diff --git a/refs.h b/refs.h\n> >> index 60e6a21a31..c01a517e40 100644\n> >> --- a/refs.h\n> >> +++ b/refs.h\n> >> @@ -744,6 +744,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n> >>  int ref_transaction_create(struct ref_transaction *transaction,\n> >>  \t\t\t   const char *refname,\n> >>  \t\t\t   const struct object_id *new_oid,\n> >> +\t\t\t   const char *new_ref,\n> >>  \t\t\t   unsigned int flags, const char *msg,\n> >>  \t\t\t   struct strbuf *err);\n> >>\n> >> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> >> index 7c894ebe65..59d438878a 100644\n> >> --- a/refs/files-backend.c\n> >> +++ b/refs/files-backend.c\n> >> @@ -2609,6 +2609,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n> >>  \t\t}\n> >>  \t}\n> >>\n> >> +\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n> >\n> > Let's add braces around `(updaet->flags & REF_SYMREF_UPDATE)` to make\n> > this easier to read.\n> >\n> \n> This should now be cleaned up as we removed the flag entirely.\n> \n> >> +\t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n> >> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> >> +\t\t\tgoto out;\n> >> +\t\t}\n> >> +\n> >> +\t\tif (close_ref_gently(lock)) {\n> >> +\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n> >> +\t\t\t\t    update->refname);\n> >> +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> >> +\t\t\tgoto out;\n> >> +\t\t}\n> >> +\n> >> +\t\t/*\n> >> +\t\t * Once we have created the symref lock, the commit\n> >> +\t\t * phase of the transaction only needs to commit the lock.\n> >> +\t\t */\n> >> +\t\tupdate->flags |= REF_NEEDS_COMMIT;\n> >> +\t}\n> >> +\n> >> +\n> >>  \tif ((update->flags & REF_HAVE_NEW) &&\n> >>  \t    !(update->flags & REF_DELETING) &&\n> >>  \t    !(update->flags & REF_LOG_ONLY)) {\n> >> @@ -2904,6 +2925,14 @@ static int files_transaction_finish(struct ref_store *ref_store,\n> >>\n> >>  \t\tif (update->flags & REF_NEEDS_COMMIT ||\n> >>  \t\t    update->flags & REF_LOG_ONLY) {\n> >> +\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n> >\n> > Here, as well.\n> >\n> \n> Same here.\n> \n> >> +\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n> >> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n> >> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n> >> +\t\t\t\t\tupdate->new_oid = *null_oid();\n> >> +\t\t\t\t}\n> >\n> > Can this actually happenn right now? I thought that the `read_ref()`\n> > further up forbids this case.\n> >\n> > Patrick\n> \n> With update-ref, it won't happen anymore, because as you mentioned, we\n> use `read_ref()`. I thought it was still worthwhile to have. But I guess\n> its cleaner to remove this.\n\n\n"},{"id":"493354","messageId":"ZidZ8N6WsvgxMcrv@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZTA94HModfa40So7Uk7WAG99G+XabvKfOY93RCL6Xac5Q@mail.gmail.com","subject":"Re: [PATCH v2 6/7] update-ref: add support for symref-update","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-23T06:49:20Z","receivedAt":"2024-04-23T06:49:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Sun, Apr 21, 2024 at 03:00:11PM -0400, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> [snip]\n> >> +\t/*\n> >> +\t * Since the user can also send in an old-oid, we try to parse\n> >> +\t * it as such too.\n> >> +\t */\n> >> +\tif (old_ref && read_ref(old_ref, NULL)) {\n> >> +\t\tif (!repo_get_oid(the_repository, old_ref, &old_oid)) {\n> >> +\t\t\told_ref = NULL;\n> >> +\t\t\thave_old = 1;\n> >> +\t\t} else\n> >> +\t\t\tdie(\"symref-update %s: invalid <old-ref> or <old-oid>\", refname);\n> >> +\t}\n> >\n> > So we first try to parse it as a ref, and then as an object ID? Wouldn't\n> > it preferable to try it the other way round and first check whether it\n> > is a valid object ID? That would likely be cheaper, even though it may\n> > be premature optimization.\n> >\n> > Patrick\n> \n> I think the issue is `repo_get_oid` would also parse a refname to an\n> OID. Whereas we want to first check and keep refnames and only if it\n> isn't a refname, we'd want to parse it as an OID.\n\nOkay. The question is whether this matches precedence rules that we have\nin other places. Namely, whether a reference name that looks like an\nobject ID overrides an object with the same name. Testing it:\n\n```\n$ rm -rf repo/\n$ git init --ref-format=files repo\nInitialized empty Git repository in /tmp/repo/.git/\n$ cd repo/\n$ git commit --allow-empty --message first\n[main (root-commit) 09293d8] first\n$ git commit --allow-empty --message second\n[main 1588e76] second\n\n$ git update-ref $(git rev-parse HEAD~) HEAD\n$ cat .git/09293d82c434cdc1f7f286cf7b90cf35a6e57c43\n1588e76ce7ef1ab25ee6f846a7b5d7032f83a69e\n\n$ git rev-parse 09293d82c434cdc1f7f286cf7b90cf35a6e57c43\nwarning: refname '09293d82c434cdc1f7f286cf7b90cf35a6e57c43' is ambiguous.\nGit normally never creates a ref that ends with 40 hex characters\nbecause it will be ignored when you just specify 40-hex. These refs\nmay be created by mistake. For example,\n\n  git switch -c $br $(git rev-parse ...)\n\nwhere \"$br\" is somehow empty and a 40-hex ref is created. Please\nexamine these refs and maybe delete them. Turn this message off by\nrunning \"git config advice.objectNameWarning false\"\n09293d82c434cdc1f7f286cf7b90cf35a6e57c43\n```\n\nSo the object ID has precedence over the reference with the same name.\nUnless I'm mistaken, your proposed order would reverse that, wouldn't\nit?\n\n> Also, why do you say it is cheaper?\n\nChecking whether a string can be parsed as an object ID should be faster\nthan having to ask the ref backend whether it knows any reference with\nthat name. So it should be fast for `repo_get_oid()` to bail out in case\nthe provided string doesn't look like an object ID.\n\nPatrick\n"},{"id":"493357","messageId":"CAOLa=ZQsfxsNCQ6GF4jv+KN7U6uHxA5w6x9jNjrn+zDTmRox6g@mail.gmail.com","threadId":"61235","inReplyTo":"ZidVxMcTOd9ga9TL@tanuki","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T10:48:59Z","receivedAt":"2024-04-23T10:49:00Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Apr 19, 2024 at 01:09:39PM -0500, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > On Fri, Apr 12, 2024 at 11:59:02AM +0200, Karthik Nayak wrote:\n>> >> From: Karthik Nayak <karthik.188@gmail.com>\n>> > [snip]\n>> >> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>> >> index 56641aa57a..4c5fe02687 100644\n>> >> --- a/refs/refs-internal.h\n>> >> +++ b/refs/refs-internal.h\n>> >> @@ -124,6 +124,19 @@ struct ref_update {\n>> >>  \t */\n>> >>  \tstruct object_id old_oid;\n>> >>\n>> >> +\t/*\n>> >> +\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n>> >> +\t * value (or delete it, if `new_ref` is an empty string).\n>> >> +\t */\n>> >> +\tconst char *new_ref;\n>> >> +\n>> >> +\t/*\n>> >> +\t * If (type & REF_SYMREF_UPDATE), check that the reference\n>> >> +\t * previously had this value (or didn't previously exist,\n>> >> +\t * if `old_ref` is an empty string).\n>> >> +\t */\n>> >> +\tconst char *old_ref;\n>> >\n>> > I think one important bit of information here would be how to handle the\n>> > update from a plain ref to a symref or vice versa. Would I set both\n>> > `REF_SYMREF_UPDATE` and `REF_HAVE_NEW`/`REF_HAVE_OLD`?\n>>\n>> I'll now remove `REF_SYMREF_UPDATE` and add documentation around the\n>> usage on `new_target` and `old_target`, where if either of them are set,\n>> they take precedence over `old_oid` and `new_oid`. The `new_target` will\n>> ensure that the ref is now a symbolic ref which points to the\n>> `new_target` value. While the `old_target` will treat the ref as a\n>> symbolic ref and check its old value.\n>>\n>> `REF_HAVE_NEW`/`REF_HAVE_OLD` should however never be set by users of\n>> ref.c, this is set internally. See REF_TRANSACTION_UPDATE_ALLOWED_FLAGS.\n>\n> Should they really take precedence, or should it be forbidden to pass\n> both old target and old object ID or new target and new object ID,\n> respectively? I'd rather claim the latter, and that should be detected\n> such that there is no bad surprises here.\n>\n> Patrick\n\nI think after that email, I agreed to Phillip's suggestion [1] and now\nI've added an explicit check for this.\n\n[1]: https://lore.kernel.org/git/CAOLa=ZSwtOQXwbgregzKMtVA30wUCH8R=8D7u1+KGnsGEDD1oA@mail.gmail.com/\n"},{"id":"493358","messageId":"CAOLa=ZQsH5=2NqfWUh1HUtGuOkeRwoGdPe_OUqJfvPGda3bHjw@mail.gmail.com","threadId":"61235","inReplyTo":"ZidXrdi7hXdAnDhy@tanuki","subject":"Re: [PATCH v2 5/7] update-ref: add support for symref-create","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T10:52:13Z","receivedAt":"2024-04-23T10:52:15Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Sun, Apr 21, 2024 at 08:50:07AM -0400, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>> > On Fri, Apr 12, 2024 at 11:59:06AM +0200, Karthik Nayak wrote:\n>> >> From: Karthik Nayak <karthik.188@gmail.com>\n>> > [snip]\n>> >> @@ -268,6 +268,39 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n>> >>  \tstrbuf_release(&err);\n>> >>  }\n>> >>\n>> >> +static void parse_cmd_symref_create(struct ref_transaction *transaction,\n>> >> +\t\t\t\t    const char *next, const char *end)\n>> >> +{\n>> >> +\tstruct strbuf err = STRBUF_INIT;\n>> >> +\tchar *refname, *new_ref;\n>> >> +\n>> >> +\tif (!(update_flags & REF_NO_DEREF))\n>> >> +                die(\"symref-create: cannot operate with deref mode\");\n>> >> +\n>> >> +\trefname = parse_refname(&next);\n>> >> +\tif (!refname)\n>> >> +\t\tdie(\"symref-create: missing <ref>\");\n>> >> +\n>> >> +\tnew_ref = parse_next_refname(&next);\n>> >> +\tif (!new_ref)\n>> >> +\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n>> >> +\tif (read_ref(new_ref, NULL))\n>> >> +\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n>> >\n>> > This restricts the creation of dangling symrefs, right? I think we might\n>> > want to lift this restriction, in which case we can eventually get rid\n>> > of the `create_symref` callback in ref backends completely.\n>> >\n>>\n>> Yes it does. I thought it'd be more consistent with what update-ref does\n>> with regular ref updates. We verify that the object exists. Also this\n>> could be an added option 'allow-dangling'.\n>>\n>> I'm not sure I understand what you mean 'the `create_symref` callback in\n>> ref backends completely.'.\n>\n> Well, once normal reference transactions learn to update symrefs we can\n> do the following:\n>\n>     diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n>     index 56641aa57a..2302311282 100644\n>     --- a/refs/refs-internal.h\n>     +++ b/refs/refs-internal.h\n>     @@ -676,7 +676,6 @@ struct ref_storage_be {\n>         ref_transaction_commit_fn *initial_transaction_commit;\n>\n>         pack_refs_fn *pack_refs;\n>     -\tcreate_symref_fn *create_symref;\n>         rename_ref_fn *rename_ref;\n>         copy_ref_fn *copy_ref;\n>\n> There would be no need to specifically have this as a separate callback\n> anymore as we can now provide a generic wrapper `refs_create_symref()`\n> (in pseudo-code):\n>\n> ```\n> int refs_create_symref(struct ref_store *refs, const char *refname,\n>                        const char *target)\n> {\n>     tx = ref_store_transaction_begin(refs);\n>     ref_transaction_create_symref(tx, refname, target);\n>     ref_transaction_commit(tx);\n> }\n> ```\n>\n> Patrick\n>\n\nIndeed, this is pretty nice side-effect. Thanks for explaining, I'll\nremove the explicit check and allow dangling refs.\n"},{"id":"493359","messageId":"CAOLa=ZSMjhEzSMhb3BnRdOmd_NV9ZaBy6aOE4Sm9_1Y8OCBT2w@mail.gmail.com","threadId":"61235","inReplyTo":"ZidZ8N6WsvgxMcrv@tanuki","subject":"Re: [PATCH v2 6/7] update-ref: add support for symref-update","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T11:30:36Z","receivedAt":"2024-04-23T11:30:40Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Sun, Apr 21, 2024 at 03:00:11PM -0400, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> [snip]\n>> >> +\t/*\n>> >> +\t * Since the user can also send in an old-oid, we try to parse\n>> >> +\t * it as such too.\n>> >> +\t */\n>> >> +\tif (old_ref && read_ref(old_ref, NULL)) {\n>> >> +\t\tif (!repo_get_oid(the_repository, old_ref, &old_oid)) {\n>> >> +\t\t\told_ref = NULL;\n>> >> +\t\t\thave_old = 1;\n>> >> +\t\t} else\n>> >> +\t\t\tdie(\"symref-update %s: invalid <old-ref> or <old-oid>\", refname);\n>> >> +\t}\n>> >\n>> > So we first try to parse it as a ref, and then as an object ID? Wouldn't\n>> > it preferable to try it the other way round and first check whether it\n>> > is a valid object ID? That would likely be cheaper, even though it may\n>> > be premature optimization.\n>> >\n>> > Patrick\n>>\n>> I think the issue is `repo_get_oid` would also parse a refname to an\n>> OID. Whereas we want to first check and keep refnames and only if it\n>> isn't a refname, we'd want to parse it as an OID.\n>\n> Okay. The question is whether this matches precedence rules that we have\n> in other places. Namely, whether a reference name that looks like an\n> object ID overrides an object with the same name. Testing it:\n>\n> ```\n> $ rm -rf repo/\n> $ git init --ref-format=files repo\n> Initialized empty Git repository in /tmp/repo/.git/\n> $ cd repo/\n> $ git commit --allow-empty --message first\n> [main (root-commit) 09293d8] first\n> $ git commit --allow-empty --message second\n> [main 1588e76] second\n>\n> $ git update-ref $(git rev-parse HEAD~) HEAD\n> $ cat .git/09293d82c434cdc1f7f286cf7b90cf35a6e57c43\n> 1588e76ce7ef1ab25ee6f846a7b5d7032f83a69e\n>\n> $ git rev-parse 09293d82c434cdc1f7f286cf7b90cf35a6e57c43\n> warning: refname '09293d82c434cdc1f7f286cf7b90cf35a6e57c43' is ambiguous.\n> Git normally never creates a ref that ends with 40 hex characters\n> because it will be ignored when you just specify 40-hex. These refs\n> may be created by mistake. For example,\n>\n>   git switch -c $br $(git rev-parse ...)\n>\n> where \"$br\" is somehow empty and a 40-hex ref is created. Please\n> examine these refs and maybe delete them. Turn this message off by\n> running \"git config advice.objectNameWarning false\"\n> 09293d82c434cdc1f7f286cf7b90cf35a6e57c43\n> ```\n>\n> So the object ID has precedence over the reference with the same name.\n> Unless I'm mistaken, your proposed order would reverse that, wouldn't\n> it?\n>\n\nI wasn't talking about a reference being mistaken for a object ID,\nrather I was talking about how a reference will be `rev-parse`'d into an\nobject ID.\n\nSo instead if we did something like:\n\n```\nif (old_target) {\n\tif (!repo_get_oid(the_repository, old_target, &old_oid)) {\n\t\told_target = NULL;\n\t\thave_old = 1;\n\t} else if (read_ref(old_target, NULL)) {\n\t} else {\n\t\tdie(\"symref-update %s: invalid <old-target> or <old-oid>\", refname);\n\t}\n}\n```\n\nThe problem is that now:\n\n$ git init repo && cd repo\n$ git commit --allow-empty -m\"c1\"\n[master (root-commit) af416de] c1\n$ git commit --allow-empty -m\"c2\"\n[master 52e95b2] c2\n$ git symbolic-ref refs/heads/symref refs/heads/master\n$ git branch b1 master~1\n$ git branch b2 master\n$ cat .git/refs/heads/symref\nref: refs/heads/master\n$ git update-ref --no-deref --stdin\nsymref-update refs/heads/symref refs/heads/b1 refs/heads/b2\n$ cat .git/refs/heads/symref\nref: refs/heads/b1\n\nThis shouldn't have worked because symref was pointing to master, but\nthis did work, because `refs/heads/b2` was rev-parse'd to '52e95b2'\nwhich is also what master is at.\n\nThe issue is that we call 'repo_get_oid', which takes in a commitish and\nthis could even be a reference. But ideally in 'symref' commands we want\nto first treat refnames as refnames and not have them parsed as an OID.\n\nSince, I'm not moving to 'refs:<target>' with the existing commands.\nThis is no longer an issue.\n\n>> Also, why do you say it is cheaper?\n>\n> Checking whether a string can be parsed as an object ID should be faster\n> than having to ask the ref backend whether it knows any reference with\n> that name. So it should be fast for `repo_get_oid()` to bail out in case\n> the provided string doesn't look like an object ID.\n>\n> Patrick\n\nYes. But for a valid refname, the `repo_get_oid()` is querying the\nreference backend similar to `read_ref()`. Also `read_ref` also does\ninitial checks with `check_refname_format` which I think doesn't check\nthe ref backend.\n\nThanks\n"},{"id":"493365","messageId":"20240423212818.574123-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240412095908.1134387-1-knayak@gitlab.com","subject":"[PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:10Z","receivedAt":"2024-04-23T21:28:23Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'git-update-ref(1)' command allows transactional reference updates.\nBut currently only supports regular reference updates. Meaning, if one\nwants to update HEAD (symbolic ref) in a transaction, there is no tool\nto do so.\n\nOne option to obtain transactional updates for the HEAD ref is to\nmanually create the HEAD.lock file and commit. This is intrusive, where\nthe user needs to mimic internal git behavior. Also, this only works\nwhen using the files backend.\n\nAt GitLab, we've been using the manual process till date, to allow users\nto set and change their default branch. But with the introduction of\nreftables as a reference backend, this becomes a necessity to be solved\nwithin git.\n\nThe patch series adds symref support to the existing commands {verify,\ncreate, delete, update} within 'git-update-ref'. This is done by parsing\ninputs with the 'ref:' prefix as symref targets. This updates our current\ncommands to:\n\nupdate SP <ref> SP (<new-oid> | ref:<new-target>) [SP (<old-oid> | ref:<old-target>)] LF\ncreate SP <ref> SP (<new-oid> | ref:<new-target>) LF\ndelete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\nverify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n\nWherein, when ref:<new-target> is provided, the update ensures that\nthe <ref> is a symbolic ref which targets <new-target>. When\nref:<old-target> is provided, we ensure that <ref> is a symbolic ref\nwhich targets <old-target> before the update.\n\nWith this it is possible to:\n1. Create, verify, delete, and update symrefs\n2. Create dangling symrefs\n3. Update regular refs to become symrefs\n4. Update symrefs to become regular refs\n\nV1 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\nV2 of the patch series can be found here:\nhttps://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\n\nChanges from v2:\n1. We no longer have separate commands for symrefs, instead the regular\ncommands learn to parse 'ref:<target>' as symref targets. This reduces\nthe code in this series. Thanks Patrick for the suggestion.\n2. Apart from supporting regular refs => symrefs. We also support the\ninverse now.\n3. Also allow creation of dangling refs.\n4. Bunch of cleanups\n   - whitespace issues in the previous patch series\n   - uneeded header includes\n   - uneeded tests\n5. Added more documentation and better error messages, especially in reftables.\n6. Better assertions around the input data.\n\nThanks all for the reviews on the previous iteration. Appreciate the support!\n\nRange diff against v2:\n\n1:  3269d0e91e ! 1:  4e49d54dcc refs: accept symref values in `ref_transaction[_add]_update`\n    @@ Commit message\n         flags to create a `ref_update` and add it to the transaction at hand.\n     \n         To extend symref support in transactions, we need to also accept the\n    -    old and new ref values and process it. In this commit, let's add the\n    -    required paramaters to the function and modify all call sites.\n    +    old and new ref targets and process it. In this commit, let's add the\n    +    required parameters to the function and modify all call sites.\n     \n    -    The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n    -    used to denote what the reference should point to when the transaction\n    -    is applied. Some functions allow this parameter to be NULL, meaning that\n    -    the reference is not changed, or `\"\"`, meaning that the reference should\n    -    be deleted.\n    +    The two parameters added are `new_target` and `old_target`. The\n    +    `new_target` is used to denote what the reference should point to when\n    +    the transaction is applied. Some functions allow this parameter to be\n    +    NULL, meaning that the reference is not changed.\n     \n    -    The `old_ref` denotes the value of that the reference must have before\n    -    the update. Some functions allow this parameter to be NULL, meaning that\n    -    the old value of the reference is not checked, or `\"\"`, meaning that the\n    -    reference must not exist before the update. A copy of this value is made\n    +    The `old_target` denotes the value the reference must have before the\n    +    update. Some functions allow this parameter to be NULL, meaning that the\n    +    old value of the reference is not checked. A copy of this value is made\n         in the transaction.\n     \n         The handling logic of these parameters will be added in consequent\n    -    commits as we implement symref-{create, update, delete, verify}.\n    +    commits as we add symref support to the existing 'git-update-ref'\n    +    commands.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \t\tconst char *refname, unsigned int flags,\n      \t\tconst struct object_id *new_oid,\n      \t\tconst struct object_id *old_oid,\n    -+\t\tconst char *new_ref, const char *old_ref,\n    ++\t\tconst char *new_target, const char *old_target,\n      \t\tconst char *msg)\n      {\n      \tstruct ref_update *update;\n    +@@ refs.c: struct ref_update *ref_transaction_add_update(\n    + \tif (transaction->state != REF_TRANSACTION_OPEN)\n    + \t\tBUG(\"update called for transaction that is not open\");\n    + \n    ++\tif (old_oid && !is_null_oid(old_oid) && old_target)\n    ++\t\tBUG(\"Only one of old_oid and old_target should be non NULL\");\n    ++\tif (new_oid && !is_null_oid(new_oid) && new_target)\n    ++\t\tBUG(\"Only one of new_oid and new_target should be non NULL\");\n    ++\n    + \tFLEX_ALLOC_STR(update, refname, refname);\n    + \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n    + \ttransaction->updates[transaction->nr++] = update;\n     @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *new_ref, const char *old_ref,\n    ++\t\t\t   const char *new_target,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err)\n      {\n    @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \n      \tref_transaction_add_update(transaction, refname, flags,\n     -\t\t\t\t   new_oid, old_oid, msg);\n    -+\t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n    ++\t\t\t\t   new_oid, old_oid, new_target,\n    ++\t\t\t\t   old_target, msg);\n      \treturn 0;\n      }\n      \n    @@ refs.c: int refs_update_ref(struct ref_store *refs, const char *msg,\n     \n      ## refs.h ##\n     @@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n    -  */\n    - #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n    - \n    -+/*\n    -+ * The reference update is considered to be done on a symbolic reference. This\n    -+ * ensures that we verify, delete, create and update the ref correspondingly.\n    -+ */\n    -+#define REF_SYMREF_UPDATE (1 << 12)\n    -+\n    - /*\n    -  * Bitmask of all of the flags that are allowed to be passed in to\n    -  * ref_transaction_update() and friends:\n    -  */\n    - #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n    - \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n    --\t REF_SKIP_REFNAME_VERIFICATION)\n    -+\t REF_SKIP_REFNAME_VERIFICATION | REF_SYMREF_UPDATE )\n    - \n    - /*\n    -  * Add a reference update to transaction. `new_oid` is the value that\n    +  *         before the update. A copy of this value is made in the\n    +  *         transaction.\n    +  *\n    ++ *     new_target -- the target reference that the reference will be\n    ++ *         update to point to. This takes precedence over new_oid when\n    ++ *         set. If the reference is a regular reference, it will be\n    ++ *         converted to a symbolic reference.\n    ++ *\n    ++ *     old_target -- the reference that the reference must be pointing to.\n    ++ *         Will only be taken into account when the reference is a symbolic\n    ++ *         reference.\n    ++ *\n    +  *     flags -- flags affecting the update, passed to\n    +  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n    +  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n    +@@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n    +  * beforehand. The old value is checked after the lock is taken to\n    +  * prevent races. If the old value doesn't agree with old_oid, the\n    +  * whole transaction fails. If old_oid is NULL, then the previous\n    +- * value is not checked.\n    ++ * value is not checked. If `old_target` is not NULL, treat the reference\n    ++ * as a symbolic ref and validate that its target before the update is\n    ++ * `old_target`. If the `new_target` is not NULL, then the reference\n    ++ * will be updated to a symbolic ref which targets `new_target`.\n    ++ * Together, these allow us to update between regular refs and symrefs.\n    +  *\n    +  * See the above comment \"Reference transaction updates\" for more\n    +  * information.\n     @@ refs.h: int ref_transaction_update(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *new_ref, const char *old_ref,\n    ++\t\t\t   const char *new_target,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err);\n      \n    @@ refs/refs-internal.h: struct ref_update {\n      \tstruct object_id old_oid;\n      \n     +\t/*\n    -+\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n    -+\t * value (or delete it, if `new_ref` is an empty string).\n    ++\t * If set, point the reference to this value. This can also be\n    ++\t * used to convert regular references to become symbolic refs.\n     +\t */\n    -+\tconst char *new_ref;\n    ++\tconst char *new_target;\n     +\n     +\t/*\n    -+\t * If (type & REF_SYMREF_UPDATE), check that the reference\n    -+\t * previously had this value (or didn't previously exist,\n    -+\t * if `old_ref` is an empty string).\n    ++\t * If set and the reference is a symbolic ref, check that the\n    ++\t * reference previously pointed to this value.\n     +\t */\n    -+\tconst char *old_ref;\n    ++\tconst char *old_target;\n     +\n      \t/*\n      \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n    @@ refs/refs-internal.h: struct ref_update *ref_transaction_add_update(\n      \t\tconst char *refname, unsigned int flags,\n      \t\tconst struct object_id *new_oid,\n      \t\tconst struct object_id *old_oid,\n    -+\t\tconst char *new_ref, const char *old_ref,\n    ++\t\tconst char *new_target, const char *old_target,\n      \t\tconst char *msg);\n      \n      /*\n2:  a8cb0e0a1d < -:  ---------- update-ref: add support for symref-verify\n3:  37c3e006da < -:  ---------- update-ref: add support for symref-delete\n-:  ---------- > 2:  37b7aadca4 update-ref: support parsing ref targets in `parse_next_oid`\n4:  53fdb408ef = 3:  9cb7817f94 files-backend: extract out `create_symref_lock`\n5:  8fa0151f94 < -:  ---------- update-ref: add support for symref-create\n6:  714492ede3 < -:  ---------- update-ref: add support for symref-update\n-:  ---------- > 4:  c7f43f6058 update-ref: support symrefs in the verify command\n-:  ---------- > 5:  4016d6ca98 update-ref: support symrefs in the delete command\n-:  ---------- > 6:  9f19e82f00 update-ref: support symrefs in the create command\n-:  ---------- > 7:  132dbfcc5f update-ref: support symrefs in the update command\n7:  c483104562 ! 8:  1b709f995b refs: support symrefs in 'reference-transaction' hook\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    refs: support symrefs in 'reference-transaction' hook\n    +    ref: support symrefs in 'reference-transaction' hook\n     \n         The 'reference-transaction' hook runs whenever a reference update is\n    -    made to the system. In the previous commits, we added support for\n    -    various symref commands in `git-update-ref`. While it allowed us to now\n    +    made to the system. In the previous commits, we added symref support for\n    +    various commands in `git-update-ref`. While it allowed us to now\n         manipulate symbolic refs via `git-update-ref`, it didn't activate the\n         'reference-transaction' hook.\n     \n    @@ Commit message\n         new format described for this and we stick to the existing format of:\n             <old-value> SP <new-value> SP <ref-name> LF\n         but now, <old-value> and <new-value> could also denote references\n    -    instead of objects.\n    +    instead of objects, where the format is similar to that in\n    +    'git-update-ref', i.e. 'ref:<ref-target>'.\n     \n         While this seems to be backward incompatible, it is okay, since the only\n         way the `reference-transaction` hook has refs in its output is when\n    -    `git-update-ref` is used with `update-symref` command. Also the\n    -    documentation for reference-transaction hook always stated that support\n    -    for symbolic references may be added in the future.\n    +    `git-update-ref` is used to manipulate symrefs. Also the documentation\n    +    for reference-transaction hook always stated that support for symbolic\n    +    references may be added in the future.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ Documentation/githooks.txt: given reference transaction is in:\n      `<ref-name>` via `git rev-parse`.\n      \n     +For symbolic reference updates the `<old_value>` and `<new-value>`\n    -+fields could denote references instead of objects.\n    ++fields could denote references instead of objects, denoted via the\n    ++`ref:<ref-target>` format.\n     +\n      The exit status of the hook is ignored for any state except for the\n      \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n    @@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,\n      \n      \tfor (i = 0; i < transaction->nr; i++) {\n      \t\tstruct ref_update *update = transaction->updates[i];\n    -+\t\tconst char *new_value, *old_value;\n    ++\t\tstrbuf_reset(&buf);\n      \n    --\t\tif (update->flags & REF_SYMREF_UPDATE)\n    +-\t\t/*\n    +-\t\t * Skip reference transaction for symbolic refs.\n    +-\t\t */\n    +-\t\tif (update->new_target || update->old_target)\n     -\t\t\tcontinue;\n    -+\t\tnew_value = oid_to_hex(&update->new_oid);\n    -+\t\told_value = oid_to_hex(&update->old_oid);\n    -+\n    -+\t\tif (update->flags & REF_SYMREF_UPDATE) {\n    -+\t\t\tif (update->flags & REF_HAVE_NEW && !null_new_value(update))\n    -+\t\t\t\tnew_value = update->new_ref;\n    -+\t\t\tif (update->flags & REF_HAVE_OLD && update->old_ref)\n    -+\t\t\t\told_value = update->old_ref;\n    -+\t\t}\n    ++\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n    ++\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n    ++\t\telse\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n      \n    - \t\tstrbuf_reset(&buf);\n    +-\t\tstrbuf_reset(&buf);\n     -\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n     -\t\t\t    oid_to_hex(&update->old_oid),\n     -\t\t\t    oid_to_hex(&update->new_oid),\n     -\t\t\t    update->refname);\n    -+\t\tstrbuf_addf(&buf, \"%s %s %s\\n\", old_value, new_value, update->refname);\n    ++\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n    ++\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n    ++\t\telse\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n    ++\n    ++\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n      \n      \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n      \t\t\tif (errno != EPIPE) {\n     \n      ## t/t1416-ref-transaction-hooks.sh ##\n    -@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'hook gets all queued updates in aborted state' '\n    - \ttest_cmp expect actual\n    +@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '\n    + \ttest_cmp expect target-repo.git/actual\n      '\n      \n    -+# This test doesn't add a check for 'symref-delete' since there is a\n    ++# This test doesn't add a check for symref 'delete' since there is a\n     +# variation between the ref backends WRT 'delete'. In the files backend,\n     +# 'delete' also triggers an additional transaction update on the\n     +# packed-refs backend, which constitutes additional reflog entries.\n    - test_expect_success 'interleaving hook calls succeed' '\n    - \ttest_when_finished \"rm -r target-repo.git\" &&\n    - \n    -@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '\n    - \ttest_cmp expect target-repo.git/actual\n    - '\n    - \n     +test_expect_success 'hook gets all queued symref updates' '\n     +\ttest_when_finished \"rm actual\" &&\n     +\n    @@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls s\n     +\n     +\tcat >expect <<-EOF &&\n     +\t\tprepared\n    -+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n    -+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n    -+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n    ++\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n    ++\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n    ++\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n     +\t\tcommitted\n    -+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n    -+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n    -+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n    ++\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n    ++\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n    ++\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n     +\tEOF\n     +\n     +\tgit update-ref --no-deref --stdin <<-EOF &&\n     +\t\tstart\n    -+\t\tsymref-verify refs/heads/symref refs/heads/main\n    -+\t\tsymref-create refs/heads/symrefc refs/heads/main\n    -+\t\tsymref-update refs/heads/symrefu refs/heads/branch refs/heads/main\n    ++\t\tverify refs/heads/symref ref:refs/heads/main\n    ++\t\tcreate refs/heads/symrefc ref:refs/heads/main\n    ++\t\tupdate refs/heads/symrefu ref:refs/heads/branch ref:refs/heads/main\n     +\t\tprepare\n     +\t\tcommit\n     +\tEOF\n\n\nKarthik Nayak (8):\n  refs: accept symref values in `ref_transaction[_add]_update`\n  update-ref: support parsing ref targets in `parse_next_oid`\n  files-backend: extract out `create_symref_lock`\n  update-ref: support symrefs in the verify command\n  update-ref: support symrefs in the delete command\n  update-ref: support symrefs in the create command\n  update-ref: support symrefs in the update command\n  ref: support symrefs in 'reference-transaction' hook\n\n Documentation/git-update-ref.txt |  41 ++--\n Documentation/githooks.txt       |  14 +-\n branch.c                         |   2 +-\n builtin/clone.c                  |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   4 +-\n builtin/receive-pack.c           |   4 +-\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             | 106 ++++++++--\n refs.c                           |  78 +++++--\n refs.h                           |  23 +-\n refs/files-backend.c             | 141 +++++++++++--\n refs/refs-internal.h             |  20 ++\n refs/reftable-backend.c          |  49 ++++-\n sequencer.c                      |   9 +-\n t/t0600-reffiles-backend.sh      |  32 +++\n t/t1400-update-ref.sh            | 346 ++++++++++++++++++++++++++++++-\n t/t1416-ref-transaction-hooks.sh |  41 ++++\n walker.c                         |   2 +-\n 20 files changed, 817 insertions(+), 105 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"493366","messageId":"20240423212818.574123-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 1/8] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:11Z","receivedAt":"2024-04-23T21:28:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `ref_transaction[_add]_update` functions obtain ref information and\nflags to create a `ref_update` and add it to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. In this commit, let's add the\nrequired parameters to the function and modify all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied. Some functions allow this parameter to be\nNULL, meaning that the reference is not changed.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked. A copy of this value is made\nin the transaction.\n\nThe handling logic of these parameters will be added in consequent\ncommits as we add symref support to the existing 'git-update-ref'\ncommands.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 17 ++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 13 +++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 69 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..060a31616d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n+\t\tBUG(\"Only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n+\t\tBUG(\"Only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..c792e13a64 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         update to point to. This takes precedence over new_oid when\n+ *         set. If the reference is a regular reference, it will be\n+ *         converted to a symbolic reference.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Will only be taken into account when the reference is a symbolic\n+ *         reference.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +735,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..3040d4797c 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,18 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set and the reference is a symbolic ref, check that the\n+\t * reference previously pointed to this value.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +185,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 2c19846385..af1b25692b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -616,7 +616,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1248,7 +1248,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3764,8 +3764,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"493367","messageId":"20240423212818.574123-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 2/8] update-ref: support parsing ref targets in `parse_next_oid`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:12Z","receivedAt":"2024-04-23T21:28:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `parse_next_oid` is used for parsing the next oid present in the\ninput buffer. Extend this function to also parse refnames in the form\n`ref:<ref_target>`. This will be used in the upcoming commits to add\nsymref support to the existing update-ref commands.\n\nSince `parse_next_oid` now also parses refs apart from oids, we rename\nthe function to a more apt name `parse_next_arg`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/update-ref.c | 58 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 42 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 21fdbf6ac8..98ec356394 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -88,6 +88,11 @@ static char *parse_refname(const char **next)\n  */\n #define PARSE_SHA1_ALLOW_EMPTY 0x02\n \n+/*\n+ * Parse refname targets using the ref:<ref_target> format.\n+ */\n+#define PARSE_REFNAME_TARGETS 0x04\n+\n /*\n  * Parse an argument separator followed by the next argument, if any.\n  * If there is an argument, convert it to a SHA-1, write it to sha1,\n@@ -95,10 +100,13 @@ static char *parse_refname(const char **next)\n  * return 0.  If there is no argument at all (not even the empty\n  * string), return 1 and leave *next unchanged.  If the value is\n  * provided but cannot be converted to a SHA-1, die.  flags can\n- * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.\n+ * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY and/or\n+ * PARSE_REFNAME_TARGETS. When PARSE_REFNAME_TARGETS is set, parse\n+ * the argument as `ref:<refname>` and store the refname into\n+ * the target strbuf.\n  */\n-static int parse_next_oid(const char **next, const char *end,\n-\t\t\t  struct object_id *oid,\n+static int parse_next_arg(const char **next, const char *end,\n+\t\t\t  struct object_id *oid, struct strbuf *target,\n \t\t\t  const char *command, const char *refname,\n \t\t\t  int flags)\n {\n@@ -118,8 +126,17 @@ static int parse_next_oid(const char **next, const char *end,\n \t\t(*next)++;\n \t\t*next = parse_arg(*next, &arg);\n \t\tif (arg.len) {\n-\t\t\tif (repo_get_oid(the_repository, arg.buf, oid))\n-\t\t\t\tgoto invalid;\n+\t\t\tif (repo_get_oid(the_repository, arg.buf, oid)) {\n+\t\t\t\tconst char *value;\n+\t\t\t\tif (flags & PARSE_REFNAME_TARGETS &&\n+\t\t\t\t    skip_prefix(arg.buf, \"ref:\", &value)) {\n+\t\t\t\t\tif (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))\n+\t\t\t\t\t\tdie(\"invalid ref format: %s\", value);\n+\t\t\t\t\tstrbuf_addstr(target, value);\n+\t\t\t\t} else {\n+\t\t\t\t\tgoto invalid;\n+\t\t\t\t}\n+\t\t\t}\n \t\t} else {\n \t\t\t/* Without -z, an empty value means all zeros: */\n \t\t\toidclr(oid);\n@@ -136,8 +153,17 @@ static int parse_next_oid(const char **next, const char *end,\n \t\t*next += arg.len;\n \n \t\tif (arg.len) {\n-\t\t\tif (repo_get_oid(the_repository, arg.buf, oid))\n-\t\t\t\tgoto invalid;\n+\t\t\tif (repo_get_oid(the_repository, arg.buf, oid)) {\n+\t\t\t\tconst char *value;\n+\t\t\t\tif (flags & PARSE_REFNAME_TARGETS &&\n+\t\t\t\t    skip_prefix(arg.buf, \"ref:\", &value)) {\n+\t\t\t\t\tif (check_refname_format(value, REFNAME_ALLOW_ONELEVEL))\n+\t\t\t\t\t\tdie(\"invalid ref format: %s\", value);\n+\t\t\t\t\tstrbuf_addstr(target, value);\n+\t\t\t\t} else {\n+\t\t\t\t\tgoto invalid;\n+\t\t\t\t}\n+\t\t\t}\n \t\t} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {\n \t\t\t/* With -z, treat an empty value as all zeros: */\n \t\t\twarning(\"%s %s: missing <new-oid>, treating as zero\",\n@@ -192,12 +218,12 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"update: missing <ref>\");\n \n-\tif (parse_next_oid(&next, end, &new_oid, \"update\", refname,\n-\t\t\t   PARSE_SHA1_ALLOW_EMPTY))\n+\tif (parse_next_arg(&next, end, &new_oid, NULL,\n+\t\t\t   \"update\", refname, PARSE_SHA1_ALLOW_EMPTY))\n \t\tdie(\"update %s: missing <new-oid>\", refname);\n \n-\thave_old = !parse_next_oid(&next, end, &old_oid, \"update\", refname,\n-\t\t\t\t   PARSE_SHA1_OLD);\n+\thave_old = !parse_next_arg(&next, end, &old_oid, NULL,\n+\t\t\t\t   \"update\", refname, PARSE_SHA1_OLD);\n \n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n@@ -225,7 +251,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"create: missing <ref>\");\n \n-\tif (parse_next_oid(&next, end, &new_oid, \"create\", refname, 0))\n+\tif (parse_next_arg(&next, end, &new_oid, NULL, \"create\", refname, 0))\n \t\tdie(\"create %s: missing <new-oid>\", refname);\n \n \tif (is_null_oid(&new_oid))\n@@ -256,8 +282,8 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"delete: missing <ref>\");\n \n-\tif (parse_next_oid(&next, end, &old_oid, \"delete\", refname,\n-\t\t\t   PARSE_SHA1_OLD)) {\n+\tif (parse_next_arg(&next, end, &old_oid, NULL,\n+\t\t\t   \"delete\", refname, PARSE_SHA1_OLD)) {\n \t\thave_old = 0;\n \t} else {\n \t\tif (is_null_oid(&old_oid))\n@@ -289,8 +315,8 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"verify: missing <ref>\");\n \n-\tif (parse_next_oid(&next, end, &old_oid, \"verify\", refname,\n-\t\t\t   PARSE_SHA1_OLD))\n+\tif (parse_next_arg(&next, end, &old_oid, NULL,\n+\t\t\t   \"verify\", refname, PARSE_SHA1_OLD))\n \t\toidclr(&old_oid);\n \n \tif (*next != line_termination)\n-- \n2.43.GIT\n\n"},{"id":"493368","messageId":"20240423212818.574123-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 3/8] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:13Z","receivedAt":"2024-04-23T21:28:26Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nSplit this into two individual functions `create_and_commit_symref` and\n`create_symref_locked`. This way we can create the symref lock and\ncommit it at different times. This will be used to provide symref\nsupport in `git-update-ref(1)`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..2420dac2aa 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n+\t\treturn error(\"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\n+\t/* no error check; commit_ref will check ferror */\n+\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n \treturn 0;\n }\n \n@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"493369","messageId":"20240423212818.574123-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 4/8] update-ref: support symrefs in the verify command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:14Z","receivedAt":"2024-04-23T21:28:27Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we added the required base for adding symref\nsupport to the transaction commands provided by 'git-update-ref(1)'.\nUsing them, extend the 'verify' command to support symrefs.\n\nThe 'verify' command allows users to verify if a provided symbolic\nreference `<ref>` contains the provided `<old-oid>` without changing the\n`<ref>`. Now we alternatively allow users to provide a\n`ref:<old-target>` instead to verify if a symref targets the provided\ntarget. Since we're checking for symbolic refs, this will only work with\nthe 'no-deref' mode. This is because any dereferenced symbolic ref will\npoint to an object and not a ref.\n\nAdd and use `null_new_value`, a helper function which is used to check\nif there is a new_value in a reference update. The new value could\neither be a symref target `new_target` or a OID `new_oid`. We also add\ntests to test the command in both the regular stdin mode and also with\nthe '-z' flag.\n\nWe also disable the reference-transaction hook for symref-updates which\nwill be tackled in its own commit.\n\nAdd required tests for symref support in 'verify' while also adding\nreflog checks for the pre-existing 'verify' tests.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt | 13 ++++--\n builtin/update-ref.c             | 14 ++++--\n refs.c                           | 30 ++++++++++--\n refs.h                           |  1 +\n refs/files-backend.c             | 43 +++++++++++++++++\n refs/refs-internal.h             |  7 +++\n refs/reftable-backend.c          | 21 ++++++++-\n t/t1400-update-ref.sh            | 79 +++++++++++++++++++++++++++++++-\n 8 files changed, 194 insertions(+), 14 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 374a2ebd2b..9f8c059944 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -64,7 +64,7 @@ performs all modifications together.  Specify commands of the form:\n \tupdate SP <ref> SP <new-oid> [SP <old-oid>] LF\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n-\tverify SP <ref> [SP <old-oid>] LF\n+\tverify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \toption SP <opt> LF\n \tstart LF\n \tprepare LF\n@@ -85,7 +85,7 @@ quoting:\n \tupdate SP <ref> NUL <new-oid> NUL [<old-oid>] NUL\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n-\tverify SP <ref> NUL [<old-oid>] NUL\n+\tverify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \toption SP <opt> NUL\n \tstart NUL\n \tprepare NUL\n@@ -95,6 +95,12 @@ quoting:\n In this format, use 40 \"0\" to specify a zero value, and use the empty\n string to specify a missing value.\n \n+For commands which support it, substituting the <old-oid> value with\n+ref:<old-target> will ensure that the <ref> targets the specified\n+old-target before the update.  Similarly, substituting the <new-oid>\n+with ref:<new-target> will ensure that the <ref> is a symbolic ref\n+targeting the new-target after the update.\n+\n In either format, values can be specified in any form that Git\n recognizes as an object name.  Commands in any other format or a\n repeated <ref> produce an error.  Command meanings are:\n@@ -115,7 +121,8 @@ delete::\n \n verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n-\t<old-oid> is zero or missing, the ref must not exist.\n+\t<old-oid> is zero or missing, the ref must not exist. For\n+\tverifying symbolic refs, provide ref:<old-target>.\n \n option::\n \tModify the behavior of the next command naming a <ref>.\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 98ec356394..246167e835 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -308,6 +308,7 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct strbuf old_target = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id old_oid;\n \n@@ -315,20 +316,27 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"verify: missing <ref>\");\n \n-\tif (parse_next_arg(&next, end, &old_oid, NULL,\n-\t\t\t   \"verify\", refname, PARSE_SHA1_OLD))\n+\tif (parse_next_arg(&next, end, &old_oid, &old_target,\n+\t\t\t   \"verify\", refname,\n+\t\t\t   PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS))\n \t\toidclr(&old_oid);\n \n+\tif (old_target.len && !(update_flags & REF_NO_DEREF))\n+\t\tdie(\"verify %s: cannot operate on symrefs in deref mode\", refname);\n+\n \tif (*next != line_termination)\n \t\tdie(\"verify %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_verify(transaction, refname, &old_oid,\n+\tif (ref_transaction_verify(transaction, refname,\n+\t\t\t\t   old_target.len ? NULL : &old_oid,\n+\t\t\t\t   old_target.len ? old_target.buf : NULL,\n \t\t\t\t   update_flags, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n+\tstrbuf_release(&old_target);\n }\n \n static void report_ok(const char *command)\ndiff --git a/refs.c b/refs.c\nindex 060a31616d..0e1013b5ab 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((void *)transaction->updates[i]->old_target);\n+\t\tfree((void *)transaction->updates[i]->new_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tif (new_target)\n+\t\tupdate->new_target = xstrdup(new_target);\n+\tif (old_target)\n+\t\tupdate->old_target = xstrdup(old_target);\n+\tif (new_oid && flags & REF_HAVE_NEW)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif (old_oid && flags & REF_HAVE_OLD)\n \t\toidcpy(&update->old_oid, old_oid);\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n@@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!old_oid)\n-\t\tBUG(\"verify called with old_oid set to NULL\");\n+\tif (!old_target && !old_oid)\n+\t\tBUG(\"verify called with old_oid and old_target set to NULL\");\n+\tif (old_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"verify cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n-\t\t\t\t      NULL, NULL,\n+\t\t\t\t      NULL, old_target,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \n+\t\t/*\n+\t\t * Skip reference transaction for symbolic refs.\n+\t\t */\n+\t\tif (update->new_target || update->old_target)\n+\t\t\tcontinue;\n+\n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n \t\t\t    oid_to_hex(&update->old_oid),\n@@ -2802,3 +2818,7 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+int ref_update_is_null_new_value(struct ref_update *update) {\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\ndiff --git a/refs.h b/refs.h\nindex c792e13a64..27b9aeaf54 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -780,6 +780,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 2420dac2aa..53197fa3af 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+/*\n+ * Check whether the REF_HAVE_OLD and old_target values stored in\n+ * update are consistent with ref, which is the symbolic reference's\n+ * current value. If everything is OK, return 0; otherwise, write an\n+ * error message to err and return -1.\n+ */\n+static int check_old_target(struct ref_update *update, char *ref,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (!(update->flags & REF_HAVE_OLD) ||\n+\t    !strcmp(update->old_target, ref))\n+\t\treturn 0;\n+\n+\tif (!strcmp(update->old_target, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference already exists\",\n+\t\t\t    original_update_refname(update));\n+\telse if (!strcmp(ref, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    ref, update->old_target);\n+\n+\treturn -1;\n+}\n+\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * For symref verification, we need to check the reference value\n+\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n+\t\t\t * verifying a dereferenced symref, we then check the oid.\n+\t\t\t */\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 3040d4797c..23e65f65e8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -748,4 +748,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_is_null_new_value(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..a2474245aa 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif ((u->flags & REF_HAVE_OLD) && u->old_target) {\n+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n+\t\t\t\tif (!strcmp(u->old_target, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"provided target is empty\",\n+\t\t\t\t\t\t    original_update_refname(u));\n+\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    u->old_target);\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"is at %s but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    referent.buf, u->old_target);\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t    \"reference already exists\"),\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex ec3443cc87..1f2b63755a 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '\n '\n \n test_expect_success 'stdin verify succeeds for correct value' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \tgit rev-parse $m >expect &&\n \techo \"verify $m $m\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n \tgit rev-parse $m >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify succeeds for missing reference' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \techo \"verify refs/heads/missing $Z\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n-\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify treats no value as missing' '\n@@ -1641,4 +1647,73 @@ test_expect_success PIPE 'transaction flushes status updates' '\n \ttest_cmp expected actual\n '\n \n+create_stdin_buf () {\n+\tif test \"$1\" = \"-z\"\n+\tthen\n+\t\tshift\n+\t\tprintf \"$F\" \"$@\" >stdin\n+\telse\n+\t\techo \"$@\" >stdin\n+\tfi\n+}\n+\n+for type in \"\" \"-z\"\n+do\n+\n+\ttest_expect_success \"stdin ${type} verify symref fails without --no-deref\" '\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/symref\" \"ref:$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: verify refs/heads/symref: cannot operate on symrefs in deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} verify symref fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/symref\" \"ref:$a\" \"ref:$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: ref:$a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: verify refs/heads/symref: extra input:  ref:$a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} verify symref succeeds for correct value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/symref\" \"ref:$a\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\t\ttest_cmp before after\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} verify symref succeeds for missing reference\" '\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/missing\" \"$Z\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\t\ttest_cmp before after\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} verify symref fails for wrong value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/symref\" \"$b\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} verify symref fails for mistaken null value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\tcreate_stdin_buf ${type} \"verify refs/heads/symref\" \"$Z\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+done\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493370","messageId":"20240423212818.574123-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 5/8] update-ref: support symrefs in the delete command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:15Z","receivedAt":"2024-04-23T21:28:28Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'delete' command in 'git-update-ref' allows users to delete `<ref>`\nafter verifying it exists with `<old-oid>`, if given. Extend this command\nto alternatively take in `ref:<old-target>` which is used to verify if\nthe symbolic ref targets the provided `<old-target>` before deletion.\nThis will only work when used with the 'no-deref' mode as it doesn't\nmake sense to deref a symref during deletion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt | 10 +++++---\n builtin/fetch.c                  |  2 +-\n builtin/receive-pack.c           |  3 ++-\n builtin/update-ref.c             | 18 +++++++++----\n refs.c                           | 12 ++++++---\n refs.h                           |  4 ++-\n refs/files-backend.c             |  2 +-\n refs/reftable-backend.c          |  2 +-\n t/t1400-update-ref.sh            | 44 ++++++++++++++++++++++++++++++++\n 9 files changed, 79 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 9f8c059944..f28b026cd7 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -63,7 +63,7 @@ performs all modifications together.  Specify commands of the form:\n \n \tupdate SP <ref> SP <new-oid> [SP <old-oid>] LF\n \tcreate SP <ref> SP <new-oid> LF\n-\tdelete SP <ref> [SP <old-oid>] LF\n+\tdelete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \tverify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \toption SP <opt> LF\n \tstart LF\n@@ -84,7 +84,7 @@ quoting:\n \n \tupdate SP <ref> NUL <new-oid> NUL [<old-oid>] NUL\n \tcreate SP <ref> NUL <new-oid> NUL\n-\tdelete SP <ref> NUL [<old-oid>] NUL\n+\tdelete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \tverify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \toption SP <opt> NUL\n \tstart NUL\n@@ -116,8 +116,10 @@ create::\n \texist.  The given <new-oid> may not be zero.\n \n delete::\n-\tDelete <ref> after verifying it exists with <old-oid>, if\n-\tgiven.  If given, <old-oid> may not be zero.\n+\tDelete <ref> after verifying it exists with <old-oid>, if given.\n+\tIf given, <old-oid> may not be zero.  If instead, ref:<old-target>\n+\tis provided, verify that the symbolic ref <ref> targets\n+\t<old-target> before deleting it.\n \n verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 66840b7c5b..d02592efca 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,\n \t\tif (transaction) {\n \t\t\tfor (ref = stale_refs; ref; ref = ref->next) {\n \t\t\t\tresult = ref_transaction_delete(transaction, ref->name, NULL, 0,\n-\t\t\t\t\t\t\t\t\"fetch: prune\", &err);\n+\t\t\t\t\t\t\t\tNULL, \"fetch: prune\", &err);\n \t\t\t\tif (result)\n \t\t\t\t\tgoto cleanup;\n \t\t\t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex b150ef39a8..9a4667d57d 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_delete(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   old_oid,\n-\t\t\t\t\t   0, \"push\", &err)) {\n+\t\t\t\t\t   0, NULL,\n+\t\t\t\t\t   \"push\", &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tret = \"failed to delete\";\n \t\t} else {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 246167e835..cee7a5ebc0 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -274,6 +274,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct strbuf old_target = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id old_oid;\n \tint have_old;\n@@ -282,26 +283,33 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"delete: missing <ref>\");\n \n-\tif (parse_next_arg(&next, end, &old_oid, NULL,\n-\t\t\t   \"delete\", refname, PARSE_SHA1_OLD)) {\n+\tif (parse_next_arg(&next, end, &old_oid, &old_target,\n+\t\t\t   \"delete\", refname, PARSE_SHA1_OLD |\n+\t\t\t   PARSE_REFNAME_TARGETS)) {\n \t\thave_old = 0;\n \t} else {\n-\t\tif (is_null_oid(&old_oid))\n+\t\tif (!old_target.len && is_null_oid(&old_oid))\n \t\t\tdie(\"delete %s: zero <old-oid>\", refname);\n-\t\thave_old = 1;\n+\t\thave_old = 1 && !old_target.len;\n \t}\n \n+\tif (old_target.len && !(update_flags & REF_NO_DEREF))\n+\t\tdie(\"delete %s: cannot operate on symrefs in deref mode\", refname);\n+\n \tif (*next != line_termination)\n \t\tdie(\"delete %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_delete(transaction, refname,\n \t\t\t\t   have_old ? &old_oid : NULL,\n-\t\t\t\t   update_flags, msg, &err))\n+\t\t\t\t   update_flags,\n+\t\t\t\t   old_target.len ? old_target.buf : NULL,\n+\t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n+\tstrbuf_release(&old_target);\n }\n \n static void parse_cmd_verify(struct ref_transaction *transaction,\ndiff --git a/refs.c b/refs.c\nindex 0e1013b5ab..6b7c46bfd8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,\n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname, old_oid,\n-\t\t\t\t   flags, msg, &err) ||\n+\t\t\t\t   flags, NULL, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n@@ -1318,14 +1318,18 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_target,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tif (old_oid && is_null_oid(old_oid))\n \t\tBUG(\"delete called with old_oid set to zeros\");\n+\tif (old_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"delete cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      NULL, old_target, flags,\n \t\t\t\t      msg, err);\n }\n \n@@ -2752,7 +2756,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n \n \tfor_each_string_list_item(item, refnames) {\n \t\tret = ref_transaction_delete(transaction, item->string,\n-\t\t\t\t\t     NULL, flags, msg, &err);\n+\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n \t\tif (ret) {\n \t\t\twarning(_(\"could not delete reference %s: %s\"),\n \t\t\t\titem->string, err.buf);\ndiff --git a/refs.h b/refs.h\nindex 27b9aeaf54..4be4930f04 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -766,7 +766,9 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_target,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 53197fa3af..fc5037fe5a 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2516,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex a2474245aa..2b2cbca8c0 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1120,7 +1120,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 1f2b63755a..cd1ad0d2ec 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1714,6 +1714,50 @@ do\n \t\ttest_cmp expect actual\n \t'\n \n+\ttest_expect_success \"stdin ${type} delete symref fails without --no-deref\" '\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"delete refs/heads/symref\" \"ref:$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: delete refs/heads/symref: cannot operate on symrefs in deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} delete symref fails with no ref\" '\n+\t\tcreate_stdin_buf ${type} \"delete \" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tgrep \"fatal: delete: missing <ref>\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} delete symref fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"delete refs/heads/symref\" \"ref:$a\" \"ref:$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: ref:$a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: delete refs/heads/symref: extra input:  ref:$a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} delete symref fails with wrong old value\" '\n+\t\tcreate_stdin_buf ${type} \"delete refs/heads/symref\" \"ref:$m\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test_have_prereq REFTABLE\n+\t\tthen\n+\t\t\tgrep \"fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main\" err\n+\t\telse\n+\t\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}\" err\n+\t\tfi &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} delete symref works with right old value\" '\n+\t\tcreate_stdin_buf ${type} \"delete refs/heads/symref\" \"ref:$a\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git rev-parse --verify -q $b\n+\t'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"493371","messageId":"20240423212818.574123-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 6/8] update-ref: support symrefs in the create command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:16Z","receivedAt":"2024-04-23T21:28:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'create' commands in 'git-update-ref' allows users to create `<ref>`\nwith `<new-oid>` after verifying it does not exist. Extend this command\nto alternatively take in `ref:<new-target>` which is used to create a\nsymref with `<new-target>` as its target.\n\nAlso, support the 'core.prefersymlinkrefs' config, wherein if the config\nis set and the filesystem supports symlinks, we create the symbolic ref\nas a symlink. We fallback to creating a regular symref if creating the\nsymlink is unsuccessful.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  8 +++--\n builtin/clone.c                  |  2 +-\n builtin/update-ref.c             | 14 ++++++--\n refs.c                           |  9 ++++--\n refs.h                           |  1 +\n refs/files-backend.c             | 42 ++++++++++++++++++++++++\n refs/reftable-backend.c          | 23 +++++++++++--\n t/t0600-reffiles-backend.sh      | 32 +++++++++++++++++++\n t/t1400-update-ref.sh            | 55 ++++++++++++++++++++++++++++++++\n 9 files changed, 173 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex f28b026cd7..1202769178 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -62,7 +62,7 @@ With `--stdin`, update-ref reads instructions from standard input and\n performs all modifications together.  Specify commands of the form:\n \n \tupdate SP <ref> SP <new-oid> [SP <old-oid>] LF\n-\tcreate SP <ref> SP <new-oid> LF\n+\tcreate SP <ref> SP (<new-oid> | ref:<new-target>) LF\n \tdelete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \tverify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \toption SP <opt> LF\n@@ -83,7 +83,7 @@ Alternatively, use `-z` to specify in NUL-terminated format, without\n quoting:\n \n \tupdate SP <ref> NUL <new-oid> NUL [<old-oid>] NUL\n-\tcreate SP <ref> NUL <new-oid> NUL\n+\tcreate SP <ref> NUL (<new-oid> | ref:<new-target>) NUL\n \tdelete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \tverify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \toption SP <opt> NUL\n@@ -113,7 +113,9 @@ update::\n \n create::\n \tCreate <ref> with <new-oid> after verifying it does not\n-\texist.  The given <new-oid> may not be zero.\n+\texist.  The given <new-oid> may not be zero.  If instead\n+\tref:<new-target> is provided, a symbolic ref is created\n+\twhich targets <new-target>.\n \n delete::\n \tDelete <ref> after verifying it exists with <old-oid>, if given.\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 74ec14542e..c0eed8e795 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -547,7 +547,7 @@ static void write_remote_refs(const struct ref *local_refs)\n \t\tif (!r->peer_ref)\n \t\t\tcontinue;\n \t\tif (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,\n-\t\t\t\t\t   0, NULL, &err))\n+\t\t\t\t\t   NULL, 0, NULL, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t}\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex cee7a5ebc0..afab706cd7 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -244,6 +244,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct strbuf new_target = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid;\n \n@@ -251,16 +252,22 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"create: missing <ref>\");\n \n-\tif (parse_next_arg(&next, end, &new_oid, NULL, \"create\", refname, 0))\n+\tif (parse_next_arg(&next, end, &new_oid, &new_target,\n+\t\t\t   \"create\", refname, PARSE_REFNAME_TARGETS))\n \t\tdie(\"create %s: missing <new-oid>\", refname);\n \n-\tif (is_null_oid(&new_oid))\n+\tif (!new_target.len && is_null_oid(&new_oid))\n \t\tdie(\"create %s: zero <new-oid>\", refname);\n \n+\tif (new_target.len && !(update_flags & REF_NO_DEREF))\n+\t\tdie(\"create %s: cannot create symrefs in deref mode\", refname);\n+\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_create(transaction, refname, &new_oid,\n+\tif (ref_transaction_create(transaction, refname,\n+\t\t\t\t   new_target.len ? NULL : &new_oid ,\n+\t\t\t\t   new_target.len ? new_target.buf : NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\n@@ -268,6 +275,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n+\tstrbuf_release(&new_target);\n }\n \n static void parse_cmd_delete(struct ref_transaction *transaction,\ndiff --git a/refs.c b/refs.c\nindex 6b7c46bfd8..42cb4126a7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1303,15 +1303,18 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!new_oid || is_null_oid(new_oid)) {\n-\t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n+\tif ((!new_oid || is_null_oid(new_oid)) && !new_target) {\n+\t\tstrbuf_addf(err, \"'%s' has a null OID or no new target\", refname);\n \t\treturn 1;\n \t}\n+\tif (new_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"create cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      null_oid(), new_target, NULL, flags,\n \t\t\t\t      msg, err);\n }\n \ndiff --git a/refs.h b/refs.h\nindex 4be4930f04..bde8606213 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -752,6 +752,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex fc5037fe5a..f5e271a442 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2610,6 +2610,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n+\tif (update->new_target) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t}\n+\n+\n \tif ((update->flags & REF_HAVE_NEW) &&\n \t    !(update->flags & REF_DELETING) &&\n \t    !(update->flags & REF_LOG_ONLY)) {\n@@ -2905,6 +2926,18 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n+\t\t\tif (update->new_target) {\n+\t\t\t\t/*\n+\t\t\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t\t\t * that the correct value is added to the reflog.\n+\t\t\t\t */\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n+\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n+\t\t\t\t\tupdate->new_oid = *null_oid();\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\tif (files_log_ref_write(refs,\n \t\t\t\t\t\tlock->ref_name,\n \t\t\t\t\t\t&lock->old_oid,\n@@ -2922,6 +2955,15 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next updated. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 2b2cbca8c0..e203c697f2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -1062,7 +1062,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1104,6 +1104,12 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n \n+\t\t\tif (u->new_target)\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n+\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n+\t\t\t\t\tu->new_oid = *null_oid();\n+\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlog = &logs[logs_nr++];\n \t\t\tmemset(log, 0, sizeof(*log));\n@@ -1120,7 +1126,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n+\t\tif (u->flags & REF_HAVE_NEW && u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 64214340e7..1291242940 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -472,4 +472,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '\n \tesac\n '\n \n+test_expect_success SYMLINKS 'symref transaction supports symlinks' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs true &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tcreate TESTSYMREFONE ref:refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_symlink .git/TESTSYMREFONE &&\n+\ttest \"$(test_readlink .git/TESTSYMREFONE)\" = refs/heads/new\n+'\n+\n+test_expect_success 'symref transaction supports false symlink config' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs false &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tcreate TESTSYMREFONE ref:refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_file .git/TESTSYMREFONE &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\techo refs/heads/new >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex cd1ad0d2ec..e85d08ce5c 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1758,6 +1758,61 @@ do\n \t\ttest_must_fail git rev-parse --verify -q $b\n \t'\n \n+\ttest_expect_success \"stdin ${type} create symref fails without --no-deref\" '\n+\t\tcreate_stdin_buf ${type} \"create refs/heads/symref\" \"ref:$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: create refs/heads/symref: cannot create symrefs in deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create symref fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"create refs/heads/symref\" \"ref:$a\" \"ref:$a\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: ref:$a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: create refs/heads/symref: extra input:  ref:$a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create symref ref works\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"create refs/heads/symref\" \"ref:$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create dangling symref ref works\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"create refs/heads/symref\" \"ref:refs/heads/unkown\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo refs/heads/unkown >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create symref does not create reflogs by default\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"create refs/symref\" \"ref:$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest_must_fail git reflog exists refs/symref\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create symref reflogs with --create-reflog\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"create refs/heads/symref\" \"ref:$a\" >stdin &&\n+\t\tgit update-ref --create-reflog --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\tgit reflog exists refs/heads/symref\n+\t'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"493372","messageId":"20240423212818.574123-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 7/8] update-ref: support symrefs in the update command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:17Z","receivedAt":"2024-04-23T21:28:30Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'update' command in 'git-update-ref' allows users to set `<ref>` to\n`<new-oid>` after verifying `<old-oid>`, if given. Extend this command\nto alternatively take in `ref:<new-target>` which is used to update to/a\nsymref with `<new-target>` as its target. And take in `ref:<old-target>`\nwhich is used to verify that the symref points to `<old-target>` before\nthe update.\n\nWith this the 'update' command can also now be used to:\n- create symrefs\n- change a regular ref to a symref\n- change a symref to a regular ref\n\nThis command will also support deref mode, to ensure that we can update\ndereferenced regular refs to symrefs.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  10 +-\n builtin/update-ref.c             |  21 ++--\n refs/files-backend.c             |  14 ++-\n refs/reftable-backend.c          |   3 +-\n t/t1400-update-ref.sh            | 168 +++++++++++++++++++++++++++++++\n 5 files changed, 198 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 1202769178..79e29fead6 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -61,7 +61,7 @@ still contains <old-oid>.\n With `--stdin`, update-ref reads instructions from standard input and\n performs all modifications together.  Specify commands of the form:\n \n-\tupdate SP <ref> SP <new-oid> [SP <old-oid>] LF\n+\tupdate SP <ref> SP (<new-oid> | ref:<new-target>) [SP (<old-oid> | ref:<old-target>)] LF\n \tcreate SP <ref> SP (<new-oid> | ref:<new-target>) LF\n \tdelete SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n \tverify SP <ref> [SP (<old-oid> | ref:<old-target>)] LF\n@@ -82,7 +82,7 @@ specify a missing value, omit the value and its preceding SP entirely.\n Alternatively, use `-z` to specify in NUL-terminated format, without\n quoting:\n \n-\tupdate SP <ref> NUL <new-oid> NUL [<old-oid>] NUL\n+\tupdate SP <ref> NUL (<new-oid> | ref:<new-target>) NUL [(<old-oid> | ref:<old-target>)] NUL\n \tcreate SP <ref> NUL (<new-oid> | ref:<new-target>) NUL\n \tdelete SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n \tverify SP <ref> NUL [(<old-oid> | ref:<old-target>)] NUL\n@@ -109,7 +109,11 @@ update::\n \tSet <ref> to <new-oid> after verifying <old-oid>, if given.\n \tSpecify a zero <new-oid> to ensure the ref does not exist\n \tafter the update and/or a zero <old-oid> to make sure the\n-\tref does not exist before the update.\n+\tref does not exist before the update.  If ref:<old-target>\n+\tis provided, we verify that the <ref> is an existing symbolic\n+\tref which targets <old-target>.  If ref:<new-target> is given,\n+\tthe update ensures <ref> is a symbolic ref which targets\n+\t<new-target>.\n \n create::\n \tCreate <ref> with <new-oid> after verifying it does not\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex afab706cd7..175579148f 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -210,6 +210,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n \tstruct strbuf err = STRBUF_INIT;\n+\tstruct strbuf new_target = STRBUF_INIT;\n+\tstruct strbuf old_target = STRBUF_INIT;\n \tchar *refname;\n \tstruct object_id new_oid, old_oid;\n \tint have_old;\n@@ -218,19 +220,24 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (!refname)\n \t\tdie(\"update: missing <ref>\");\n \n-\tif (parse_next_arg(&next, end, &new_oid, NULL,\n-\t\t\t   \"update\", refname, PARSE_SHA1_ALLOW_EMPTY))\n+\tif (parse_next_arg(&next, end, &new_oid,\n+\t\t\t   &new_target, \"update\", refname,\n+\t\t\t   PARSE_SHA1_ALLOW_EMPTY | PARSE_REFNAME_TARGETS))\n \t\tdie(\"update %s: missing <new-oid>\", refname);\n \n-\thave_old = !parse_next_arg(&next, end, &old_oid, NULL,\n-\t\t\t\t   \"update\", refname, PARSE_SHA1_OLD);\n+\thave_old = !parse_next_arg(&next, end, &old_oid,\n+\t\t\t\t   &old_target, \"update\", refname,\n+\t\t\t\t   PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);\n+\thave_old = have_old & !old_target.len;\n \n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n-\t\t\t\t   NULL, NULL,\n+\t\t\t\t   new_target.len ? NULL : &new_oid,\n+\t\t\t\t   have_old ? &old_oid : NULL,\n+\t\t\t\t   new_target.len ? new_target.buf : NULL,\n+\t\t\t\t   old_target.len ? old_target.buf : NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\n@@ -238,6 +245,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tupdate_flags = default_flags;\n \tfree(refname);\n \tstrbuf_release(&err);\n+\tstrbuf_release(&old_target);\n+\tstrbuf_release(&new_target);\n }\n \n static void parse_cmd_create(struct ref_transaction *transaction,\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f5e271a442..59d1ab3eeb 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2386,7 +2386,8 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_target, update->old_target,\n+\t\t\tupdate->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2610,7 +2611,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif (update->new_target) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n \t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n@@ -2628,12 +2629,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t * phase of the transaction only needs to commit the lock.\n \t\t */\n \t\tupdate->flags |= REF_NEEDS_COMMIT;\n-\t}\n-\n-\n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e203c697f2..a00f55802a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -908,7 +908,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\t\tu->old_target, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex e85d08ce5c..5b2d23da37 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1360,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '\n '\n \n test_expect_success 'fails with duplicate ref update via symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref2\" &&\n \tgit branch target2 $A &&\n \tgit symbolic-ref refs/heads/symref2 refs/heads/target2 &&\n \tcat >stdin <<-EOF &&\n@@ -1813,6 +1814,173 @@ do\n \t\tgit reflog exists refs/heads/symref\n \t'\n \n+\ttest_expect_success \"stdin ${type} update symref fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$a\" \"ref:$a\" \"ref:$a\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: ref:$a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: update refs/heads/symref: extra input:  ref:$a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update creates symref with zero old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$a\" \"$Z\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update creates symref with empty old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$a\" \"\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update symref fails with wrong old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$m\" \"ref:$b\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test_have_prereq REFTABLE\n+\t\tthen\n+\t\t\tgrep \"fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b\" err\n+\t\telse\n+\t\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: is at $a but expected $b\" err\n+\t\tfi &&\n+\t\ttest_must_fail git rev-parse --verify -q $c\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update symref works with right old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$m\" \"ref:$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $m >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update creates symref (with deref)\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$a\" \"\" >stdin &&\n+\t\tgit update-ref --stdin ${type} <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\t\tgrep \"$Z $(git rev-parse $a)\" actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update regular ref to symref with correct old-oid\" '\n+\t\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\t\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/regularref\" \"ref:$a\" \"$(git rev-parse $a)\" >stdin &&\n+\t\tgit update-ref --stdin ${type} <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\t\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update regular ref to symref fails with wrong old-oid\" '\n+\t\ttest_when_finished \"git update-ref -d refs/heads/regularref\" &&\n+\t\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/regularref\" \"ref:$a\" \"$(git rev-parse refs/heads/target2)\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\techo $(git rev-parse $a) >expect &&\n+\t\tgit rev-parse refs/heads/regularref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update existing symref with zero old-oid\" '\n+\t\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/target2 &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"ref:$a\" \"$Z\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists\" err &&\n+\t\techo refs/heads/target2 >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} update existing symref to regular ref\" '\n+\t\ttest_when_finished \"git update-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/target2 &&\n+\t\tcreate_stdin_buf ${type} \"update refs/heads/symref\" \"$(git rev-parse $a)\" \"ref:refs/heads/target2\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $(git rev-parse $a) >expect &&\n+\t\tgit rev-parse refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\t\tgrep \"$(git rev-parse refs/heads/target2) $(git rev-parse $a)\" actual\n+\t'\n+\n done\n \n+test_expect_success \"stdin update symref (with deref)\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\ttest_when_finished \"git update-ref -d --no-deref refs/heads/symref2\" &&\n+\tgit update-ref refs/heads/symref2 $a &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&\n+\techo \"update refs/heads/symref\" \"ref:$a\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref2 >actual &&\n+\ttest_cmp expect actual &&\n+\techo refs/heads/symref2 >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin -z update symref (with deref)\" '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\ttest_when_finished \"git update-ref -d --no-deref refs/heads/symref2\" &&\n+\tgit update-ref refs/heads/symref2 $a &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&\n+\tprintf \"$F\" \"update refs/heads/symref\" \"ref:$a\" \"\" >stdin &&\n+\tgit update-ref --stdin -z <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref2 >actual &&\n+\ttest_cmp expect actual &&\n+\techo refs/heads/symref2 >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin update regular ref to symref\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\techo \"update refs/heads/regularref\" \"ref:$a\" >stdin &&\n+\tgit update-ref --stdin <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n+test_expect_success \"stdin -z update regular ref to symref\" '\n+\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\tprintf \"$F\" \"update refs/heads/regularref\" \"ref:$a\" \"\" >stdin &&\n+\tgit update-ref --stdin -z <stdin &&\n+\techo $a >expect &&\n+\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493373","messageId":"20240423212818.574123-9-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v3 8/8] ref: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-23T21:28:18Z","receivedAt":"2024-04-23T21:28:31Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In the previous commits, we added symref support for\nvarious commands in `git-update-ref`. While it allowed us to now\nmanipulate symbolic refs via `git-update-ref`, it didn't activate the\n'reference-transaction' hook.\n\nLet's activate the hook for symbolic reference updates too. There is no\nnew format described for this and we stick to the existing format of:\n    <old-value> SP <new-value> SP <ref-name> LF\nbut now, <old-value> and <new-value> could also denote references\ninstead of objects, where the format is similar to that in\n'git-update-ref', i.e. 'ref:<ref-target>'.\n\nWhile this seems to be backward incompatible, it is okay, since the only\nway the `reference-transaction` hook has refs in its output is when\n`git-update-ref` is used to manipulate symrefs. Also the documentation\nfor reference-transaction hook always stated that support for symbolic\nreferences may be added in the future.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt       | 14 +++++++----\n refs.c                           | 21 ++++++++--------\n t/t1416-ref-transaction-hooks.sh | 41 ++++++++++++++++++++++++++++++++\n 3 files changed, 61 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..0bf8ca87a6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also cover symbolic references.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects, denoted via the\n+`ref:<ref-target>` format.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 42cb4126a7..9a510744a7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2365,18 +2365,19 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tstrbuf_reset(&buf);\n \n-\t\t/*\n-\t\t * Skip reference transaction for symbolic refs.\n-\t\t */\n-\t\tif (update->new_target || update->old_target)\n-\t\t\tcontinue;\n+\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n \n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..abd4777819 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,45 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+# This test doesn't add a check for symref 'delete' since there is a\n+# variation between the ref backends WRT 'delete'. In the files backend,\n+# 'delete' also triggers an additional transaction update on the\n+# packed-refs backend, which constitutes additional reflog entries.\n+test_expect_success 'hook gets all queued symref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\tgit update-ref refs/heads/branch $POST_OID &&\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\tgit symbolic-ref refs/heads/symrefu refs/heads/main &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t\tprepared\n+\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n+\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n+\t\tcommitted\n+\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n+\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n+\tEOF\n+\n+\tgit update-ref --no-deref --stdin <<-EOF &&\n+\t\tstart\n+\t\tverify refs/heads/symref ref:refs/heads/main\n+\t\tcreate refs/heads/symrefc ref:refs/heads/main\n+\t\tupdate refs/heads/symrefu ref:refs/heads/branch ref:refs/heads/main\n+\t\tprepare\n+\t\tcommit\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493376","messageId":"20240423220308.GC1172807@coredump.intra.peff.net","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-23T22:03:08Z","receivedAt":"2024-04-23T22:03:09Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:\n\n> Changes from v2:\n> 1. We no longer have separate commands for symrefs, instead the regular\n> commands learn to parse 'ref:<target>' as symref targets. This reduces\n> the code in this series. Thanks Patrick for the suggestion.\n\nHmm. I can see how this makes things a lot simpler, but it introduces an\nambiguity, since you can pass full ref expressions to \"update-ref\" (like\n\"ref:foo\" to find the \"foo\" entry in ref^{tree}). I see that you only\nkick in the symref \"ref:\" handling if the regular oid lookup failed, so\nthere's no backwards-compatibility issue (anything that used to work\nwill still take precedence, and the new code only runs when the old code\nwould have reported an error).\n\nBut I wonder if it would let somebody cause mischief in a repository\nthey can push to, but which may get updates from other sources. For\nexample, imagine a forge like GitLab runs the equivalent of:\n\n  echo \"create refs/whatever ref:refs/heads/main\" |\n  git update-ref --stdin\n\nas part of some system process. Now if I push up \"refs/heads/ref\" that\ncontains the path \"refs/heads/main\" in its tree, that will take\nprecedence, causing the system process to do something it did not\nexpect.\n\nI think you'd have to pile on a lot of assumptions to get any kind of\nsecurity problem. Something like:\n\n 1. The system has a hidden ref namespace like refs/gitlab that normal\n    remote push/fetch users are not allowed to read/write to.\n\n 2. The system tries to make a symlink within that namespace. Say,\n    \"refs/gitlab/metadata/HEAD\" to point to\n    \"refs/gitlab/metadata/branches/main\" or something.\n\n 3. The user pushes up \"refs/heads/ref\" with a tree that contains\n    \"refs/gitlab/metadata/branches/main\". Now when (2) happens, the\n    hidden ref points to user-controlled data.\n\nThat's pretty convoluted. But we can avoid it entirely if there's no\nambiguity in the protocol at all.\n\n-Peff\n"},{"id":"493385","messageId":"xmqqcyqf4l0r.fsf@gitster.g","threadId":"61235","inReplyTo":"20240423220308.GC1172807@coredump.intra.peff.net","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-24T01:17:08Z","receivedAt":"2024-04-24T01:17:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> That's pretty convoluted. But we can avoid it entirely if there's no\n> ambiguity in the protocol at all.\n\n;-).\n"},{"id":"493417","messageId":"CAOLa=ZRk8QR4qkbkRm6AirapdrAqz6bG-tXfY3zRQU_9XkJM1Q@mail.gmail.com","threadId":"61235","inReplyTo":"20240423220308.GC1172807@coredump.intra.peff.net","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-24T16:25:27Z","receivedAt":"2024-04-24T16:25:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Jeff King <peff@peff.net> writes:\n\n> On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:\n>\n>> Changes from v2:\n>> 1. We no longer have separate commands for symrefs, instead the regular\n>> commands learn to parse 'ref:<target>' as symref targets. This reduces\n>> the code in this series. Thanks Patrick for the suggestion.\n>\n> Hmm. I can see how this makes things a lot simpler, but it introduces an\n> ambiguity, since you can pass full ref expressions to \"update-ref\" (like\n> \"ref:foo\" to find the \"foo\" entry in ref^{tree}). I see that you only\n> kick in the symref \"ref:\" handling if the regular oid lookup failed, so\n> there's no backwards-compatibility issue (anything that used to work\n> will still take precedence, and the new code only runs when the old code\n> would have reported an error).\n>\n> But I wonder if it would let somebody cause mischief in a repository\n> they can push to, but which may get updates from other sources. For\n> example, imagine a forge like GitLab runs the equivalent of:\n>\n>   echo \"create refs/whatever ref:refs/heads/main\" |\n>   git update-ref --stdin\n>\n> as part of some system process. Now if I push up \"refs/heads/ref\" that\n> contains the path \"refs/heads/main\" in its tree, that will take\n> precedence, causing the system process to do something it did not\n> expect.\n>\n> I think you'd have to pile on a lot of assumptions to get any kind of\n> security problem. Something like:\n>\n>  1. The system has a hidden ref namespace like refs/gitlab that normal\n>     remote push/fetch users are not allowed to read/write to.\n>\n>  2. The system tries to make a symlink within that namespace. Say,\n>     \"refs/gitlab/metadata/HEAD\" to point to\n>     \"refs/gitlab/metadata/branches/main\" or something.\n>\n>  3. The user pushes up \"refs/heads/ref\" with a tree that contains\n>     \"refs/gitlab/metadata/branches/main\". Now when (2) happens, the\n>     hidden ref points to user-controlled data.\n>\n> That's pretty convoluted. But we can avoid it entirely if there's no\n> ambiguity in the protocol at all.\n>\n> -Peff\n\n\nThanks Peff, that is indeed something I totally missed thinking about.\n\nThis also brings light onto the previous versions we were considering:\n\n    symref-update SP <ref> SP <new-target> [SP (<old-target> | <old-oid>)] LF\n\nThere is also some ambiguity here which we missed, especially when we\nsupport dangling refs. If we're updating a dangling ref <ref>, and we\nprovide an old value. Then there is uncertainty around whether the\nprovided value is actually a <old-target> or if it's an <old-oid>.\n\nFor non dangling ref symref, we first parse it as an <old-target> and\nsince the <old-target> would exist, we can move on.\n\nSo I see two ways to go about this,\n\n1. In the symref-update function, we need to parse and see if <ref> is a\nregular ref or a symref, if it is symref, we simply set the provided old\nvalue as <old-target>, if not, we set it as <old-oid>. This seems clunky\nbecause we'll be parsing the ref and trying to understand its type in\n'update-ref.c', before the actual update.\n\n2. We change the syntax to something like\n\n    symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid\n<old-oid>)] LF\n\nthis would remove any ambiguity since the user specifies the data type\nthey're providing.\n\nOverall, I think it is best to discard this version and I will push v4\nwith the older schematics of introducing new commands.\n\nI'm currently considering going ahead with the [2], but will wait for a\nday or two to consider other opinions.\n\nAlso on a sidenote, it's worth considering that with the direction of\n[2], we could also extrapolate to introduce {verify, update, create,\ndelete} v2, which support both symrefs and regular refs. But require\nexplicit types from the user:\n\n    update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n[(oid <old-oid> | ref <old-target>)] NUL\n\tcreate-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n\tdelete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n\tverify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n\nThis is similar to the v3 patches I've currently sent out, in that it\nwould also allow cross operations between regular refs and symrefs.\n"},{"id":"493461","messageId":"Zin67FKk6XXOotQh@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZRk8QR4qkbkRm6AirapdrAqz6bG-tXfY3zRQU_9XkJM1Q@mail.gmail.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-25T06:40:44Z","receivedAt":"2024-04-25T06:40:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:\n> Jeff King <peff@peff.net> writes:\n> > On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:\n[snip]\n> Also on a sidenote, it's worth considering that with the direction of\n> [2], we could also extrapolate to introduce {verify, update, create,\n> delete} v2, which support both symrefs and regular refs. But require\n> explicit types from the user:\n> \n>     update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n> [(oid <old-oid> | ref <old-target>)] NUL\n> \tcreate-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n> \tdelete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n> \tverify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n> \n> This is similar to the v3 patches I've currently sent out, in that it\n> would also allow cross operations between regular refs and symrefs.\n\nOne could put this new syntax behind a feature flag to avoid the \"-v2\"\nsuffixes, e.g. `git update-ref --with-symrefs`. But I'm not sure whether\nthis would be beneficial.\n\nPatrick\n"},{"id":"493477","messageId":"xmqq8r11gyhy.fsf@gitster.g","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-25T17:09:45Z","receivedAt":"2024-04-25T17:09:48Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"You'd want something like this squashed into an appropriate step to\navoid breaking \"make sparse\".\n\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 175579148f..1cdafc33f3 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -228,7 +228,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \thave_old = !parse_next_arg(&next, end, &old_oid,\n \t\t\t\t   &old_target, \"update\", refname,\n \t\t\t\t   PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);\n-\thave_old = have_old & !old_target.len;\n+\thave_old = have_old && !old_target.len;\n \n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n"},{"id":"493478","messageId":"xmqq4jbpgw32.fsf@gitster.g","threadId":"61235","inReplyTo":"CAOLa=ZRk8QR4qkbkRm6AirapdrAqz6bG-tXfY3zRQU_9XkJM1Q@mail.gmail.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-25T18:01:53Z","receivedAt":"2024-04-25T18:01:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> 2. We change the syntax to something like\n>\n>     symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid\n> <old-oid>)] LF\n>\n> this would remove any ambiguity since the user specifies the data type\n> they're providing.\n\nYup.  Being explicit helps, especially if you are only dealing with\nprograms that do not complain \"that's too many keystrokes\" like\npesky humans ;-).\n\nWhen the topic's overall title is to add support for symbolic refs\nto the update-ref command, a natural expectation is that in a far\nenough future everything that can be done with \"git symbolic-ref\"\ncan be done with \"git update-ref\" and we can, if we wanted to,\ndepreate \"git symbolic-ref\".  Is that really what is going on here?\n\nIOW, we should add support for operation modes other than \"--stdin\"\nas well, shouldn't we?\n\nThanks.\n\n"},{"id":"493483","messageId":"CAOLa=ZTw8Y5iohGkha2RUtaj7Xsp5WwBs1C+_XbgwP+Lbc-ALA@mail.gmail.com","threadId":"61235","inReplyTo":"xmqq8r11gyhy.fsf@gitster.g","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-25T21:07:27Z","receivedAt":"2024-04-25T21:07:29Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> You'd want something like this squashed into an appropriate step to\n> avoid breaking \"make sparse\".\n>\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 175579148f..1cdafc33f3 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -228,7 +228,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>  \thave_old = !parse_next_arg(&next, end, &old_oid,\n>  \t\t\t\t   &old_target, \"update\", refname,\n>  \t\t\t\t   PARSE_SHA1_OLD | PARSE_REFNAME_TARGETS);\n> -\thave_old = have_old & !old_target.len;\n> +\thave_old = have_old && !old_target.len;\n>\n>  \tif (*next != line_termination)\n>  \t\tdie(\"update %s: extra input: %s\", refname, next);\n\nThanks, will check and squash this in.\n"},{"id":"493484","messageId":"CAOLa=ZStA8tyA=COVM+7Cvr6W5BmPaesgMk6NSHpr5w+Ve08BQ@mail.gmail.com","threadId":"61235","inReplyTo":"Zin67FKk6XXOotQh@tanuki","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-25T21:12:01Z","receivedAt":"2024-04-25T21:12:03Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:\n>> Jeff King <peff@peff.net> writes:\n>> > On Tue, Apr 23, 2024 at 11:28:10PM +0200, Karthik Nayak wrote:\n> [snip]\n>> Also on a sidenote, it's worth considering that with the direction of\n>> [2], we could also extrapolate to introduce {verify, update, create,\n>> delete} v2, which support both symrefs and regular refs. But require\n>> explicit types from the user:\n>>\n>>     update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n>> [(oid <old-oid> | ref <old-target>)] NUL\n>> \tcreate-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n>> \tdelete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n>> \tverify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n>>\n>> This is similar to the v3 patches I've currently sent out, in that it\n>> would also allow cross operations between regular refs and symrefs.\n>\n> One could put this new syntax behind a feature flag to avoid the \"-v2\"\n> suffixes, e.g. `git update-ref --with-symrefs`. But I'm not sure whether\n> this would be beneficial.\n>\n\nYeah, me neither. I mean it doesn't provide any new functionality. The\nonly usecase we're missing currently (with old + symref-* commands), is\nthat we have no way to convert a symref to a regular ref while checking\nthe old_value. This could however simply be solved by introducing a new\n`symref-convert` command.\n"},{"id":"493486","messageId":"CAOLa=ZTjwRnqmXRuWpN7o86DZdfOsgZwR7=Gk0A3EG0zSHiCBA@mail.gmail.com","threadId":"61235","inReplyTo":"xmqq4jbpgw32.fsf@gitster.g","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-25T21:14:34Z","receivedAt":"2024-04-25T21:14:35Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> 2. We change the syntax to something like\n>>\n>>     symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid\n>> <old-oid>)] LF\n>>\n>> this would remove any ambiguity since the user specifies the data type\n>> they're providing.\n>\n> Yup.  Being explicit helps, especially if you are only dealing with\n> programs that do not complain \"that's too many keystrokes\" like\n> pesky humans ;-).\n>\n> When the topic's overall title is to add support for symbolic refs\n> to the update-ref command, a natural expectation is that in a far\n> enough future everything that can be done with \"git symbolic-ref\"\n> can be done with \"git update-ref\" and we can, if we wanted to,\n> depreate \"git symbolic-ref\".  Is that really what is going on here?\n>\n> IOW, we should add support for operation modes other than \"--stdin\"\n> as well, shouldn't we?\n>\n> Thanks.\n\nThat's a good point, I was thinking of something like this too, but\ndidn't want to bloat this series. Would it make sense to add this\nfunctionality in a follow up series with the cleanup that Patrick\nmentioned [1] too?\n\n[1]: https://lore.kernel.org/git/ZidXrdi7hXdAnDhy@tanuki/\n"},{"id":"493490","messageId":"xmqqcyqdds4b.fsf@gitster.g","threadId":"61235","inReplyTo":"CAOLa=ZTjwRnqmXRuWpN7o86DZdfOsgZwR7=Gk0A3EG0zSHiCBA@mail.gmail.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-25T21:55:48Z","receivedAt":"2024-04-25T21:55:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n>> IOW, we should add support for operation modes other than \"--stdin\"\n>> as well, shouldn't we?\n>>\n>> Thanks.\n>\n> That's a good point, I was thinking of something like this too, but\n> didn't want to bloat this series. Would it make sense to add this\n> functionality in a follow up series with the cleanup that Patrick\n> mentioned [1] too?\n\nGoing multi-step is usually a preferred approach but we have to be\nsure that we do not have to retract the way earlier steps gave our\nusers when we move to later steps.  \"Earlier we said that your\ncommands should look like this, but now you have to give your\ncommands differently\" is absolutely what we want to avoid.\n\nSo, \"in this iteration, you can use this enhanced feature only via\nthe \"--stdin\" mode, but we promise we will make the same available\non the command line\" is perfectly fine.  You may need to retitle the\ntopic to clarify the scope of each iteration, though.\n\nThansk.\n\n"},{"id":"493512","messageId":"CAOLa=ZQ=4n5jPii3rZkKjj7rMmxu=3jhRMx9aEsLvr8atbSCQg@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqcyqdds4b.fsf@gitster.g","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T12:48:20Z","receivedAt":"2024-04-26T12:48:22Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>>> IOW, we should add support for operation modes other than \"--stdin\"\n>>> as well, shouldn't we?\n>>>\n>>> Thanks.\n>>\n>> That's a good point, I was thinking of something like this too, but\n>> didn't want to bloat this series. Would it make sense to add this\n>> functionality in a follow up series with the cleanup that Patrick\n>> mentioned [1] too?\n>\n> Going multi-step is usually a preferred approach but we have to be\n> sure that we do not have to retract the way earlier steps gave our\n> users when we move to later steps.  \"Earlier we said that your\n> commands should look like this, but now you have to give your\n> commands differently\" is absolutely what we want to avoid.\n>\n> So, \"in this iteration, you can use this enhanced feature only via\n> the \"--stdin\" mode, but we promise we will make the same available\n> on the command line\" is perfectly fine.  You may need to retitle the\n> topic to clarify the scope of each iteration, though.\n>\n> Thansk.\n\nYeah, will do that. Possibly also some of the commits too, where we\ncould mention that this is being added to the `--stdin` mode and can be\nextended further on.\n"},{"id":"493516","messageId":"20240426152449.228860-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240423212818.574123-1-knayak@gitlab.com","subject":"[PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:42Z","receivedAt":"2024-04-26T15:24:55Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'git-update-ref(1)' command allows transactional reference updates.\nBut currently only supports regular reference updates. Meaning, if one\nwants to update HEAD (symbolic ref) in a transaction, there is no tool\nto do so.\n\nOne option to obtain transactional updates for the HEAD ref is to\nmanually create the HEAD.lock file and commit. This is intrusive, where\nthe user needs to mimic internal git behavior. Also, this only works\nwhen using the files backend.\n\nAt GitLab, we've been using the manual process till date, to allow users\nto set and change their default branch. But with the introduction of\nreftables as a reference backend, this becomes a necessity to be solved\nwithin git.\n\nThis patch series goes about introducing a set of commands\nsymref-{create,verify,delete,update} to work with symrefs complimenting\nthe existing commands for the regular refs in the '--stdin' mode of\n'git-update-ref'.\n\nThe 'symref-verify' command can be used to verify if a symref exists and\nits existing value.\n\nThe 'symref-create' command can be used to create a new symref.\n\nThe 'symref-delete' command can be used to delete an existing symref while\noptionally checking its existing value.\n\nThe 'symref-update' command can be used to update a symref, create a symref,\ndelete a symref or even convert an existing regular ref to a symref. Wherein\nlike the regular 'update' command, the zero OID can be used to create/delete\na symref.\n\nWhile this series adds the commands and the required ground work, it only\nis accessile within the '--stdin' mode of 'git-update-ref'. However, it makes\nit easy to extend it further to the command line too, which will be present\nin a follow up series.\n\nPrevious versions:\nV1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\nV2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\nV3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/\n\nV3 took a different approach of incorporating changes into the existing commands, \nof 'git-update-ref --stdin' but we realized that there was some ambiguity in how\nthese commands are parsed [1]. In that sense it makes more sense to compare this\nversion with v2 instead.\n\nChanges over v2 are:\n\n- Rename (old|new)_ref to (old|new)_target, to avoid confusion.\n- Better assertions around the input data.\n- Removing the REF_SYMREF_UPDATE flag.\n- Filled in missing/new documentation.\n- For symref-update, realized that there was some ambiguity on how the\n  old_oid and old_target was parsed, and now the command requires the\n  user to explicitly input the data type.\n- Support dangling refs in all operations.\n- More test cases around empty values.\n- Removed unecessary header includes.\n- Fixed whitespace issues with the previous series.\n- Other review comments.\n\n\n[1]: https://lore.kernel.org/git/20240423220308.GC1172807@coredump.intra.peff.net/\n\nRange diff (against v2):\n\n1:  3269d0e91e ! 1:  4a56e3ede4 refs: accept symref values in `ref_transaction[_add]_update`\n    @@ Commit message\n         flags to create a `ref_update` and add it to the transaction at hand.\n     \n         To extend symref support in transactions, we need to also accept the\n    -    old and new ref values and process it. In this commit, let's add the\n    -    required paramaters to the function and modify all call sites.\n    +    old and new ref targets and process it. In this commit, let's add the\n    +    required parameters to the function and modify all call sites.\n     \n    -    The two paramaters added are `new_ref` and `old_ref`. The `new_ref` is\n    -    used to denote what the reference should point to when the transaction\n    -    is applied. Some functions allow this parameter to be NULL, meaning that\n    -    the reference is not changed, or `\"\"`, meaning that the reference should\n    -    be deleted.\n    +    The two parameters added are `new_target` and `old_target`. The\n    +    `new_target` is used to denote what the reference should point to when\n    +    the transaction is applied.\n     \n    -    The `old_ref` denotes the value of that the reference must have before\n    -    the update. Some functions allow this parameter to be NULL, meaning that\n    -    the old value of the reference is not checked, or `\"\"`, meaning that the\n    -    reference must not exist before the update. A copy of this value is made\n    -    in the transaction.\n    +    The `old_target` denotes the value the reference must have before the\n    +    update. Some functions allow this parameter to be NULL, meaning that the\n    +    old value of the reference is not checked.\n     \n         The handling logic of these parameters will be added in consequent\n    -    commits as we implement symref-{create, update, delete, verify}.\n    +    commits as we add symref commands to the '--stdin' mode of\n    +    'git-update-ref'.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \t\tconst char *refname, unsigned int flags,\n      \t\tconst struct object_id *new_oid,\n      \t\tconst struct object_id *old_oid,\n    -+\t\tconst char *new_ref, const char *old_ref,\n    ++\t\tconst char *new_target, const char *old_target,\n      \t\tconst char *msg)\n      {\n      \tstruct ref_update *update;\n    +@@ refs.c: struct ref_update *ref_transaction_add_update(\n    + \tif (transaction->state != REF_TRANSACTION_OPEN)\n    + \t\tBUG(\"update called for transaction that is not open\");\n    + \n    ++\tif (old_oid && !is_null_oid(old_oid) && old_target)\n    ++\t\tBUG(\"Only one of old_oid and old_target should be non NULL\");\n    ++\tif (new_oid && !is_null_oid(new_oid) && new_target)\n    ++\t\tBUG(\"Only one of new_oid and new_target should be non NULL\");\n    ++\n    + \tFLEX_ALLOC_STR(update, refname, refname);\n    + \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n    + \ttransaction->updates[transaction->nr++] = update;\n     @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *new_ref, const char *old_ref,\n    ++\t\t\t   const char *new_target,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err)\n      {\n    @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \n      \tref_transaction_add_update(transaction, refname, flags,\n     -\t\t\t\t   new_oid, old_oid, msg);\n    -+\t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n    ++\t\t\t\t   new_oid, old_oid, new_target,\n    ++\t\t\t\t   old_target, msg);\n      \treturn 0;\n      }\n      \n    @@ refs.c: int refs_update_ref(struct ref_store *refs, const char *msg,\n     \n      ## refs.h ##\n     @@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n    -  */\n    - #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n    - \n    -+/*\n    -+ * The reference update is considered to be done on a symbolic reference. This\n    -+ * ensures that we verify, delete, create and update the ref correspondingly.\n    -+ */\n    -+#define REF_SYMREF_UPDATE (1 << 12)\n    -+\n    - /*\n    -  * Bitmask of all of the flags that are allowed to be passed in to\n    -  * ref_transaction_update() and friends:\n    -  */\n    - #define REF_TRANSACTION_UPDATE_ALLOWED_FLAGS                                  \\\n    - \t(REF_NO_DEREF | REF_FORCE_CREATE_REFLOG | REF_SKIP_OID_VERIFICATION | \\\n    --\t REF_SKIP_REFNAME_VERIFICATION)\n    -+\t REF_SKIP_REFNAME_VERIFICATION | REF_SYMREF_UPDATE )\n    - \n    - /*\n    -  * Add a reference update to transaction. `new_oid` is the value that\n    +  *         before the update. A copy of this value is made in the\n    +  *         transaction.\n    +  *\n    ++ *     new_target -- the target reference that the reference will be\n    ++ *         update to point to. This takes precedence over new_oid when\n    ++ *         set. If the reference is a regular reference, it will be\n    ++ *         converted to a symbolic reference.\n    ++ *\n    ++ *     old_target -- the reference that the reference must be pointing to.\n    ++ *         Will only be taken into account when the reference is a symbolic\n    ++ *         reference.\n    ++ *\n    +  *     flags -- flags affecting the update, passed to\n    +  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n    +  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n    +@@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n    +  * beforehand. The old value is checked after the lock is taken to\n    +  * prevent races. If the old value doesn't agree with old_oid, the\n    +  * whole transaction fails. If old_oid is NULL, then the previous\n    +- * value is not checked.\n    ++ * value is not checked. If `old_target` is not NULL, treat the reference\n    ++ * as a symbolic ref and validate that its target before the update is\n    ++ * `old_target`. If the `new_target` is not NULL, then the reference\n    ++ * will be updated to a symbolic ref which targets `new_target`.\n    ++ * Together, these allow us to update between regular refs and symrefs.\n    +  *\n    +  * See the above comment \"Reference transaction updates\" for more\n    +  * information.\n     @@ refs.h: int ref_transaction_update(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *new_ref, const char *old_ref,\n    ++\t\t\t   const char *new_target,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err);\n      \n    @@ refs/refs-internal.h: struct ref_update {\n      \tstruct object_id old_oid;\n      \n     +\t/*\n    -+\t * If (flags & REF_SYMREF_UPDATE), set the reference to this\n    -+\t * value (or delete it, if `new_ref` is an empty string).\n    ++\t * If set, point the reference to this value. This can also be\n    ++\t * used to convert regular references to become symbolic refs.\n     +\t */\n    -+\tconst char *new_ref;\n    ++\tconst char *new_target;\n     +\n     +\t/*\n    -+\t * If (type & REF_SYMREF_UPDATE), check that the reference\n    -+\t * previously had this value (or didn't previously exist,\n    -+\t * if `old_ref` is an empty string).\n    ++\t * If set and the reference is a symbolic ref, check that the\n    ++\t * reference previously pointed to this value.\n     +\t */\n    -+\tconst char *old_ref;\n    ++\tconst char *old_target;\n     +\n      \t/*\n      \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n    @@ refs/refs-internal.h: struct ref_update *ref_transaction_add_update(\n      \t\tconst char *refname, unsigned int flags,\n      \t\tconst struct object_id *new_oid,\n      \t\tconst struct object_id *old_oid,\n    -+\t\tconst char *new_ref, const char *old_ref,\n    ++\t\tconst char *new_target, const char *old_target,\n      \t\tconst char *msg);\n      \n      /*\n4:  53fdb408ef = 2:  496bf14f28 files-backend: extract out `create_symref_lock`\n2:  a8cb0e0a1d ! 3:  6337859cbb update-ref: add support for symref-verify\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    update-ref: add support for symref-verify\n    +    update-ref: add support for 'symref-verify' command\n     \n    -    In the previous commit, we added the required base for adding symref\n    -    support in transactions provided by the 'git-update-ref(1)'. This commit\n    -    introduces the 'symref-verify' command which is similar to the existing\n    -    'verify' command for regular refs.\n    +    In the previous commits, we added the required base for adding symref\n    +    commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using\n    +    them, add a new 'symref-verify' command to verify symrefs.\n     \n         The 'symref-verify' command allows users to verify if a provided <ref>\n    -    contains the provided <old-ref> without changing the <ref>. If <old-ref>\n    -    is not provided, the command will verify that the <ref> doesn't exist.\n    -    Since we're checking for symbolic refs, this command will only work with\n    -    the 'no-deref' mode. This is because any dereferenced symbolic ref will\n    -    point to an object and not a ref and the regular 'verify' command can be\n    -    used in such situations.\n    +    contains the provided <old-target> without changing the <ref>. If\n    +    <old-target> is not provided, the command will verify that the <ref>\n    +    doesn't exist. Since we're checking for symbolic refs, this command will\n    +    only work with the 'no-deref' mode. This is because any dereferenced\n    +    symbolic ref will point to an object and not a ref and the regular\n    +    'verify' command can be used in such situations.\n     \n    -    This commit adds all required helper functions required to also\n    -    introduce the other symref commands, namely create, delete, and update.\n    +    Add and use `ref_update_is_null_new_value`, a helper function which is\n    +    used to check if there is a new_value in a reference update. The new\n    +    value could either be a symref target `new_target` or a OID `new_oid`.\n         We also add tests to test the command in both the regular stdin mode and\n         also with the '-z' flag.\n     \n    -    When the user doesn't provide a <old-ref> we need to check that the\n    -    provided <ref> doesn't exist. And to do this, we take over the existing\n    -    understanding that <old-oid> when set to its zero value, it refers to\n    -    the ref not existing. While this seems like a mix of contexts between\n    -    using <*-ref> and <*-oid>, this actually works really well, especially\n    -    considering the fact that we want to eventually also introduce\n    -\n    -        symref-update SP <ref> SP <new-ref> [SP (<old-oid> | <old-rev>)] LF\n    -\n    -    and here, we'd allow the user to update a regular <ref> to a symref and\n    -    use <old-oid> to check the <ref>'s oid. This can be extrapolated to the\n    -    user using this to create a symref when provided a zero <old-oid>. Which\n    -    will work given how we're setting it up.\n    -\n         We also disable the reference-transaction hook for symref-updates which\n         will be tackled in its own commit.\n     \n    -    Add required tests for 'symref-verify' while also adding reflog checks for\n    -    the pre-existing 'verify' tests.\n    +    Add required tests for symref support in 'verify' while also adding\n    +    reflog checks for the pre-existing 'verify' tests.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ Documentation/git-update-ref.txt: performs all modifications together.  Specify\n      \tcreate SP <ref> SP <new-oid> LF\n      \tdelete SP <ref> [SP <old-oid>] LF\n      \tverify SP <ref> [SP <old-oid>] LF\n    -+\tsymref-verify SP <ref> [SP <old-ref>] LF\n    ++\tsymref-verify SP <ref> [SP <old-target>] LF\n      \toption SP <opt> LF\n      \tstart LF\n      \tprepare LF\n    @@ Documentation/git-update-ref.txt: quoting:\n      \tcreate SP <ref> NUL <new-oid> NUL\n      \tdelete SP <ref> NUL [<old-oid>] NUL\n      \tverify SP <ref> NUL [<old-oid>] NUL\n    -+\tsymref-verify SP <ref> [NUL <old-ref>] NUL\n    ++\tsymref-verify SP <ref> [NUL <old-target>] NUL\n      \toption SP <opt> NUL\n      \tstart NUL\n      \tprepare NUL\n    @@ Documentation/git-update-ref.txt: verify::\n      \t<old-oid> is zero or missing, the ref must not exist.\n      \n     +symref-verify::\n    -+\tVerify symbolic <ref> against <old-ref> but do not change it.\n    -+\tIf <old-ref> is missing, the ref must not exist.  Can only be\n    ++\tVerify symbolic <ref> against <old-target> but do not change it.\n    ++\tIf <old-target> is missing, the ref must not exist.  Can only be\n     +\tused in `no-deref` mode.\n     +\n      option::\n    @@ builtin/update-ref.c: static char *parse_refname(const char **next)\n      \treturn strbuf_detach(&ref, NULL);\n      }\n      \n    -+\n    -+\n     +/*\n     + * Wrapper around parse_refname which skips the next delimiter.\n     + */\n     +static char *parse_next_refname(const char **next)\n     +{\n    -+        if (line_termination) {\n    -+                /* Without -z, consume SP and use next argument */\n    -+                if (!**next || **next == line_termination)\n    -+                        return NULL;\n    -+                if (**next != ' ')\n    -+                        die(\"expected SP but got: %s\", *next);\n    -+        } else {\n    -+                /* With -z, read the next NUL-terminated line */\n    -+                if (**next)\n    -+                        return NULL;\n    -+        }\n    -+        /* Skip the delimiter */\n    -+        (*next)++;\n    ++\tif (line_termination) {\n    ++\t\t/* Without -z, consume SP and use next argument */\n    ++\t\tif (!**next || **next == line_termination)\n    ++\t\t\treturn NULL;\n    ++\t\tif (**next != ' ')\n    ++\t\t\tdie(\"expected SP but got: %s\", *next);\n    ++\t} else {\n    ++\t\t/* With -z, read the next NUL-terminated line */\n    ++\t\tif (**next)\n    ++\t\t\treturn NULL;\n    ++\t}\n    ++\t/* Skip the delimiter */\n    ++\t(*next)++;\n     +\n    -+        return parse_refname(next);\n    ++\treturn parse_refname(next);\n     +}\n    ++\n     +\n      /*\n       * The value being parsed is <old-oid> (as opposed to <new-oid>; the\n    @@ builtin/update-ref.c: static void parse_cmd_verify(struct ref_transaction *trans\n     +}\n     +\n     +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n    -+                                    const char *next, const char *end)\n    ++\t\t\t\t    const char *next, const char *end)\n     +{\n     +\tstruct strbuf err = STRBUF_INIT;\n     +\tstruct object_id old_oid;\n    -+\tchar *refname, *old_ref;\n    ++\tchar *refname, *old_target;\n     +\n     +\tif (!(update_flags & REF_NO_DEREF))\n     +\t\tdie(\"symref-verify: cannot operate with deref mode\");\n    @@ builtin/update-ref.c: static void parse_cmd_verify(struct ref_transaction *trans\n     +\t * old_ref is optional, but we want to differentiate between\n     +\t * a NULL and zero value.\n     +\t */\n    -+\told_ref = parse_next_refname(&next);\n    -+\tif (!old_ref)\n    ++\told_target = parse_next_refname(&next);\n    ++\tif (!old_target)\n     +\t\told_oid = *null_oid();\n    -+\telse if (read_ref(old_ref, NULL))\n    -+\t\tdie(\"symref-verify %s: invalid <old-ref>\", refname);\n     +\n     +\tif (*next != line_termination)\n     +\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n     +\n    -+\tif (ref_transaction_verify(transaction, refname, old_ref ? NULL : &old_oid,\n    -+\t\t\t\t   old_ref, update_flags | REF_SYMREF_UPDATE, &err))\n    ++\tif (ref_transaction_verify(transaction, refname,\n    ++\t\t\t\t   old_target ? NULL : &old_oid,\n    ++\t\t\t\t   old_target, update_flags, &err))\n      \t\tdie(\"%s\", err.buf);\n      \n      \tupdate_flags = default_flags;\n      \tfree(refname);\n    -+\tfree(old_ref);\n    ++\tfree(old_target);\n      \tstrbuf_release(&err);\n      }\n      \n    @@ builtin/update-ref.c: static const struct parse_cmd {\n      static void update_refs_stdin(void)\n     \n      ## refs.c ##\n    -@@\n    - #include \"object-store-ll.h\"\n    - #include \"object.h\"\n    - #include \"path.h\"\n    -+#include \"string.h\"\n    - #include \"tag.h\"\n    - #include \"submodule.h\"\n    - #include \"worktree.h\"\n    -@@\n    - #include \"date.h\"\n    - #include \"commit.h\"\n    - #include \"wildmatch.h\"\n    -+#include \"wrapper.h\"\n    - \n    - /*\n    -  * List of all available backends\n     @@ refs.c: void ref_transaction_free(struct ref_transaction *transaction)\n      \n      \tfor (i = 0; i < transaction->nr; i++) {\n      \t\tfree(transaction->updates[i]->msg);\n    -+\t\tfree((void *)transaction->updates[i]->old_ref);\n    ++\t\tfree((void *)transaction->updates[i]->old_target);\n    ++\t\tfree((void *)transaction->updates[i]->new_target);\n      \t\tfree(transaction->updates[i]);\n      \t}\n      \tfree(transaction->updates);\n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \tupdate->flags = flags;\n      \n     -\tif (flags & REF_HAVE_NEW)\n    --\t\toidcpy(&update->new_oid, new_oid);\n    ++\tif (new_target)\n    ++\t\tupdate->new_target = xstrdup(new_target);\n    ++\tif (old_target)\n    ++\t\tupdate->old_target = xstrdup(old_target);\n    ++\tif (new_oid && flags & REF_HAVE_NEW)\n    + \t\toidcpy(&update->new_oid, new_oid);\n     -\tif (flags & REF_HAVE_OLD)\n    --\t\toidcpy(&update->old_oid, old_oid);\n    -+\t/*\n    -+\t * The ref values are to be considered over the oid values when we're\n    -+\t * doing symref operations.\n    -+\t */\n    -+\tif (update->flags & REF_SYMREF_UPDATE) {\n    -+\t\tif (old_ref)\n    -+\t\t\tupdate->old_ref = xstrdup(old_ref);\n    -+\t} else {\n    -+\t\tif (flags & REF_HAVE_NEW)\n    -+\t\t\toidcpy(&update->new_oid, new_oid);\n    -+\t\tif (flags & REF_HAVE_OLD)\n    -+\t\t\toidcpy(&update->old_oid, old_oid);\n    -+\t}\n    ++\tif (old_oid && flags & REF_HAVE_OLD)\n    + \t\toidcpy(&update->old_oid, old_oid);\n      \tupdate->msg = normalize_reflog_message(msg);\n      \treturn update;\n    - }\n     @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n      \n      \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n    -+\tflags |= (new_ref ? REF_HAVE_NEW : 0) | (old_ref ? REF_HAVE_OLD : 0);\n    ++\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n      \n      \tref_transaction_add_update(transaction, refname, flags,\n    - \t\t\t\t   new_oid, old_oid, new_ref, old_ref, msg);\n    + \t\t\t\t   new_oid, old_oid, new_target,\n     @@ refs.c: int ref_transaction_delete(struct ref_transaction *transaction,\n      int ref_transaction_verify(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *old_ref,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags,\n      \t\t\t   struct strbuf *err)\n      {\n     -\tif (!old_oid)\n    -+\tif (flags & REF_SYMREF_UPDATE && !old_ref && !old_oid)\n    -+\t\tBUG(\"verify called with old_ref set to NULL\");\n    -+\tif (!(flags & REF_SYMREF_UPDATE) && !old_oid)\n    - \t\tBUG(\"verify called with old_oid set to NULL\");\n    +-\t\tBUG(\"verify called with old_oid set to NULL\");\n    ++\tif (!old_target && !old_oid)\n    ++\t\tBUG(\"verify called with old_oid and old_target set to NULL\");\n    ++\tif (old_target && !(flags & REF_NO_DEREF))\n    ++\t\tBUG(\"verify cannot operate on symrefs with deref mode\");\n      \treturn ref_transaction_update(transaction, refname,\n      \t\t\t\t      NULL, old_oid,\n     -\t\t\t\t      NULL, NULL,\n    -+\t\t\t\t      NULL, old_ref,\n    ++\t\t\t\t      NULL, old_target,\n      \t\t\t\t      flags, NULL, err);\n      }\n      \n    @@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,\n      \tfor (i = 0; i < transaction->nr; i++) {\n      \t\tstruct ref_update *update = transaction->updates[i];\n      \n    -+\t\tif (update->flags & REF_SYMREF_UPDATE)\n    ++\t\t/*\n    ++\t\t * Skip reference transaction for symbolic refs.\n    ++\t\t */\n    ++\t\tif (update->new_target || update->old_target)\n     +\t\t\tcontinue;\n     +\n      \t\tstrbuf_reset(&buf);\n    @@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char\n      \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n      }\n     +\n    -+int null_new_value(struct ref_update *update) {\n    -+\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref)\n    -+\t\treturn 0;\n    -+\treturn is_null_oid(&update->new_oid);\n    ++int ref_update_is_null_new_value(struct ref_update *update) {\n    ++\treturn !update->new_target && is_null_oid(&update->new_oid);\n     +}\n     \n      ## refs.h ##\n    @@ refs.h: int ref_transaction_delete(struct ref_transaction *transaction,\n      int ref_transaction_verify(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *old_oid,\n    -+\t\t\t   const char *old_ref,\n    ++\t\t\t   const char *old_target,\n      \t\t\t   unsigned int flags,\n      \t\t\t   struct strbuf *err);\n      \n    @@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda\n      }\n      \n     +/*\n    -+ * Check whether the REF_HAVE_OLD and old_ref values stored in update\n    -+ * are consistent with ref, which is the symbolic reference's current\n    -+ * value. If everything is OK, return 0; otherwise, write an error\n    -+ * message to err and return -1.\n    ++ * Check whether the REF_HAVE_OLD and old_target values stored in\n    ++ * update are consistent with ref, which is the symbolic reference's\n    ++ * current value. If everything is OK, return 0; otherwise, write an\n    ++ * error message to err and return -1.\n     + */\n    -+static int check_old_ref(struct ref_update *update, char *ref,\n    -+\t\t\t struct strbuf *err)\n    ++static int check_old_target(struct ref_update *update, char *ref,\n    ++\t\t\t    struct strbuf *err)\n     +{\n     +\tif (!(update->flags & REF_HAVE_OLD) ||\n    -+\t    !strcmp(update->old_ref, ref))\n    ++\t    !strcmp(update->old_target, ref))\n     +\t\treturn 0;\n     +\n    -+\tif (!strcmp(update->old_ref, \"\"))\n    ++\tif (!strcmp(update->old_target, \"\"))\n     +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n     +\t\t\t    \"reference already exists\",\n     +\t\t\t    original_update_refname(update));\n    @@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda\n     +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n     +\t\t\t    \"reference is missing but expected %s\",\n     +\t\t\t    original_update_refname(update),\n    -+\t\t\t    update->old_ref);\n    ++\t\t\t    update->old_target);\n     +\telse\n     +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n     +\t\t\t    \"is at %s but expected %s\",\n     +\t\t\t    original_update_refname(update),\n    -+\t\t\t    ref, update->old_ref);\n    ++\t\t\t    ref, update->old_target);\n     +\n     +\treturn -1;\n     +}\n    @@ refs/files-backend.c: static const char *original_update_refname(struct ref_upda\n      /*\n       * Check whether the REF_HAVE_OLD and old_oid values stored in update\n       * are consistent with oid, which is the reference's current value. If\n    -@@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,\n    - \t\t\t       struct strbuf *err)\n    - {\n    - \tstruct strbuf referent = STRBUF_INIT;\n    --\tint mustexist = (update->flags & REF_HAVE_OLD) &&\n    --\t\t!is_null_oid(&update->old_oid);\n    -+\tint mustexist = (update->flags & REF_HAVE_OLD) && !is_null_oid(&update->old_oid);\n    - \tint ret = 0;\n    - \tstruct ref_lock *lock;\n    - \n     @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,\n      \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n      \t\t\t\t\tgoto out;\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     +\t\t\t}\n     +\n     +\t\t\t/*\n    -+\t\t\t * For symref verification, we need to check the referent value\n    ++\t\t\t * For symref verification, we need to check the reference value\n     +\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n     +\t\t\t * verifying a dereferenced symref, we then check the oid.\n     +\t\t\t */\n    -+\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->old_ref) {\n    -+\t\t\t\tif (check_old_ref(update, referent.buf, err)) {\n    ++\t\t\tif (update->old_target) {\n    ++\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n     +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\t\t\tgoto out;\n     +\t\t\t\t}\n    @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re\n     + * takes into consideration that the update could be a regular\n     + * ref or a symbolic ref.\n     + */\n    -+int null_new_value(struct ref_update *update);\n    ++int ref_update_is_null_new_value(struct ref_update *update);\n     +\n      #endif /* REFS_REFS_INTERNAL_H */\n     \n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n      \t\t * backend returns, which keeps our tests happy.\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n    -+\t\tif ((u->flags & REF_HAVE_OLD) &&\n    -+\t\t    (u->flags & REF_SYMREF_UPDATE) &&\n    -+\t\t    u->old_ref) {\n    -+\t\t\tif   (strcmp(referent.buf, u->old_ref)) {\n    -+\t\t\t\tif (!strcmp(u->old_ref, \"\"))\n    -+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    -+\t\t\t\t\t\t    \"reference already exists\",\n    ++\t\tif ((u->flags & REF_HAVE_OLD) && u->old_target) {\n    ++\t\t\tif (strcmp(referent.buf, u->old_target)) {\n    ++\t\t\t\tif (!strcmp(u->old_target, \"\"))\n    ++\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n    ++\t\t\t\t\t\t    \"provided target is empty\",\n     +\t\t\t\t\t\t    original_update_refname(u));\n     +\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n    -+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n     +\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    u->old_ref);\n    ++\t\t\t\t\t\t    u->old_target);\n     +\t\t\t\telse\n    -+\t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t\t\t\t    \"is at %s but expected %s\",\n     +\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    referent.buf, u->old_ref);\n    ++\t\t\t\t\t\t    referent.buf, u->old_target);\n     +\t\t\t\tret = -1;\n     +\t\t\t\tgoto done;\n     +\t\t\t}\n    @@ t/t1400-update-ref.sh: test_expect_success PIPE 'transaction flushes status upda\n      \ttest_cmp expected actual\n      '\n      \n    -+create_stdin_buf ()\n    -+{\n    ++create_stdin_buf () {\n     +\tif test \"$1\" = \"-z\"\n     +\tthen\n     +\t\tshift\n    @@ t/t1400-update-ref.sh: test_expect_success PIPE 'transaction flushes status upda\n     +for type in \"\" \"-z\"\n     +do\n     +\n    -+test_expect_success \"stdin ${type} symref-verify fails without --no-deref\" '\n    -+\tgit symbolic-ref refs/heads/symref $a &&\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    -+\tgrep \"fatal: symref-verify: cannot operate with deref mode\" err\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify fails without --no-deref\" '\n    ++\t\tgit symbolic-ref refs/heads/symref $a &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    ++\t\tgrep \"fatal: symref-verify: cannot operate with deref mode\" err\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-verify fails with too many arguments\" '\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" \"$a\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n    -+\tif test \"$type\" = \"-z\"\n    -+\tthen\n    -+\t\tgrep \"fatal: unknown command: $a\" err\n    -+\telse\n    -+\t\tgrep \"fatal: symref-verify refs/heads/symref: extra input:  $a\" err\n    -+\tfi\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify fails with too many arguments\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" \"$a\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n    ++\t\tif test \"$type\" = \"-z\"\n    ++\t\tthen\n    ++\t\t\tgrep \"fatal: unknown command: $a\" err\n    ++\t\telse\n    ++\t\t\tgrep \"fatal: symref-verify refs/heads/symref: extra input:  $a\" err\n    ++\t\tfi\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-verify succeeds for correct value\" '\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n    -+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/heads/symref >actual &&\n    -+\ttest_cmp expect actual &&\n    -+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n    -+\ttest_cmp before after\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify succeeds for correct value\" '\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >actual &&\n    ++\t\ttest_cmp expect actual &&\n    ++\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n    ++\t\ttest_cmp before after\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-verify succeeds for missing reference\" '\n    -+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/missing\" &&\n    -+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    -+\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n    -+\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n    -+\ttest_cmp before after\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify no value is treated as zero value\" '\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n    ++\t'\n    ++\n    ++\ttest_expect_success \"stdin ${type} symref-verify succeeds for dangling reference\" '\n    ++\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref2\" &&\n    ++\t\ttest_must_fail git symbolic-ref refs/heads/nonexistent &&\n    ++\t\tgit symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref2\" \"refs/heads/nonexistent\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin\n    ++\t'\n    ++\n    ++\ttest_expect_success \"stdin ${type} symref-verify succeeds for missing reference\" '\n    ++\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/missing\" \"$Z\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n    ++\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n    ++\t\ttest_cmp before after\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-verify fails for wrong value\" '\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$b\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/heads/symref >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify fails for wrong value\" '\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$b\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >actual &&\n    ++\t\ttest_cmp expect actual\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\" '\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/heads/symref >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-verify fails for mistaken null value\" '\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$Z\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >actual &&\n    ++\t\ttest_cmp expect actual\n    ++\t'\n     +\n     +done\n     +\n3:  37c3e006da ! 4:  e611cb5a8c update-ref: add support for symref-delete\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    update-ref: add support for symref-delete\n    +    update-ref: add support for 'symref-delete' command\n     \n    -    Similar to the previous commit, add 'symref-delete' to allow deletions\n    -    of symbolic refs in a transaction via the 'git-update-ref' command. The\n    -    'symref-delete' command can when given with an <old-ref>, deletes the\n    -    provided <ref> only when it points to <old-ref>.\n    +    Add a new command 'symref-delete' to allow deletions of symbolic refs in\n    +    a transaction via the '--stdin' mode of the 'git-update-ref' command.\n    +    The 'symref-delete' command can, when given an <old-target>, delete the\n    +    provided <ref> only when it points to <old-target>. This will only work\n    +    when used with the 'no-deref' mode as it doesn't make sense to deref a\n    +    symref during deletion.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ Documentation/git-update-ref.txt: performs all modifications together.  Specify\n      \tcreate SP <ref> SP <new-oid> LF\n      \tdelete SP <ref> [SP <old-oid>] LF\n      \tverify SP <ref> [SP <old-oid>] LF\n    -+\tsymref-delete SP <ref> [SP <old-ref>] LF\n    - \tsymref-verify SP <ref> [SP <old-ref>] LF\n    ++\tsymref-delete SP <ref> [SP <old-target>] LF\n    + \tsymref-verify SP <ref> [SP <old-target>] LF\n      \toption SP <opt> LF\n      \tstart LF\n     @@ Documentation/git-update-ref.txt: quoting:\n      \tcreate SP <ref> NUL <new-oid> NUL\n      \tdelete SP <ref> NUL [<old-oid>] NUL\n      \tverify SP <ref> NUL [<old-oid>] NUL\n    -+\tsymref-delete SP <ref> [NUL <old-ref>] NUL\n    - \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n    ++\tsymref-delete SP <ref> [NUL <old-target>] NUL\n    + \tsymref-verify SP <ref> [NUL <old-target>] NUL\n      \toption SP <opt> NUL\n      \tstart NUL\n    -@@ Documentation/git-update-ref.txt: verify::\n    +@@ Documentation/git-update-ref.txt: create::\n    + \texist.  The given <new-oid> may not be zero.\n    + \n    + delete::\n    +-\tDelete <ref> after verifying it exists with <old-oid>, if\n    +-\tgiven.  If given, <old-oid> may not be zero.\n    ++\tDelete <ref> after verifying it exists with <old-oid>, if given.\n    ++\tIf given, <old-oid> may not be zero.  If instead, ref:<old-target>\n    ++\tis provided, verify that the symbolic ref <ref> targets\n    ++\t<old-target> before deleting it.\n    + \n    + verify::\n      \tVerify <ref> against <old-oid> but do not change it.  If\n      \t<old-oid> is zero or missing, the ref must not exist.\n      \n     +symref-delete::\n    -+\tDelete <ref> after verifying it exists with <old-ref>, if\n    -+\tgiven.\n    ++\tDelete <ref> after verifying it exists with <old-target>, if given.\n     +\n      symref-verify::\n    - \tVerify symbolic <ref> against <old-ref> but do not change it.\n    - \tIf <old-ref> is missing, the ref must not exist.  Can only be\n    + \tVerify symbolic <ref> against <old-target> but do not change it.\n    + \tIf <old-target> is missing, the ref must not exist.  Can only be\n     \n      ## builtin/fetch.c ##\n     @@ builtin/fetch.c: static int prune_refs(struct display_state *display_state,\n    @@ builtin/update-ref.c: static void parse_cmd_delete(struct ref_transaction *trans\n      \tstrbuf_release(&err);\n      }\n      \n    ++\n     +static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n     +\t\t\t\t    const char *next, const char *end)\n     +{\n     +\tstruct strbuf err = STRBUF_INIT;\n    -+\tchar *refname, *old_ref;\n    ++\tchar *refname, *old_target;\n     +\n     +\tif (!(update_flags & REF_NO_DEREF))\n    -+                die(\"symref-delete: cannot operate with deref mode\");\n    ++\t\tdie(\"symref-delete: cannot operate with deref mode\");\n     +\n     +\trefname = parse_refname(&next);\n     +\tif (!refname)\n     +\t\tdie(\"symref-delete: missing <ref>\");\n     +\n    -+        old_ref = parse_next_refname(&next);\n    -+\tif (old_ref && read_ref(old_ref, NULL))\n    -+\t\tdie(\"symref-delete %s: invalid <old-ref>\", refname);\n    ++\told_target = parse_next_refname(&next);\n     +\n     +\tif (*next != line_termination)\n     +\t\tdie(\"symref-delete %s: extra input: %s\", refname, next);\n     +\n     +\tif (ref_transaction_delete(transaction, refname, NULL,\n    -+\t\t\t\t   update_flags | REF_SYMREF_UPDATE,\n    -+\t\t\t\t   old_ref, msg, &err))\n    ++\t\t\t\t   update_flags, old_target, msg, &err))\n     +\t\tdie(\"%s\", err.buf);\n     +\n     +\tupdate_flags = default_flags;\n     +\tfree(refname);\n    -+\tfree(old_ref);\n    ++\tfree(old_target);\n     +\tstrbuf_release(&err);\n     +}\n    ++\n     +\n      static void parse_cmd_verify(struct ref_transaction *transaction,\n      \t\t\t     const char *next, const char *end)\n    @@ refs.c: int refs_delete_ref(struct ref_store *refs, const char *msg,\n      \t    ref_transaction_commit(transaction, &err)) {\n      \t\terror(\"%s\", err.buf);\n      \t\tref_transaction_free(transaction);\n    -@@ refs.c: void ref_transaction_free(struct ref_transaction *transaction)\n    - \tfor (i = 0; i < transaction->nr; i++) {\n    - \t\tfree(transaction->updates[i]->msg);\n    - \t\tfree((void *)transaction->updates[i]->old_ref);\n    -+\t\tfree((void *)transaction->updates[i]->new_ref);\n    - \t\tfree(transaction->updates[i]);\n    - \t}\n    - \tfree(transaction->updates);\n    -@@ refs.c: struct ref_update *ref_transaction_add_update(\n    - \tif (update->flags & REF_SYMREF_UPDATE) {\n    - \t\tif (old_ref)\n    - \t\t\tupdate->old_ref = xstrdup(old_ref);\n    -+\t\tif (new_ref)\n    -+\t\t\tupdate->new_ref = xstrdup(new_ref);\n    - \t} else {\n    - \t\tif (flags & REF_HAVE_NEW)\n    - \t\t\toidcpy(&update->new_oid, new_oid);\n     @@ refs.c: int ref_transaction_create(struct ref_transaction *transaction,\n      int ref_transaction_delete(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *old_oid,\n     -\t\t\t   unsigned int flags, const char *msg,\n     +\t\t\t   unsigned int flags,\n    -+\t\t\t   const char *old_ref,\n    ++\t\t\t   const char *old_target,\n     +\t\t\t   const char *msg,\n      \t\t\t   struct strbuf *err)\n      {\n    --\tif (old_oid && is_null_oid(old_oid))\n    -+\tif (!(flags & REF_SYMREF_UPDATE) && old_oid &&\n    -+\t    is_null_oid(old_oid))\n    + \tif (old_oid && is_null_oid(old_oid))\n      \t\tBUG(\"delete called with old_oid set to zeros\");\n    ++\tif (old_target && !(flags & REF_NO_DEREF))\n    ++\t\tBUG(\"delete cannot operate on symrefs with deref mode\");\n      \treturn ref_transaction_update(transaction, refname,\n      \t\t\t\t      null_oid(), old_oid,\n     -\t\t\t\t      NULL, NULL, flags,\n    -+\t\t\t\t      NULL, old_ref, flags,\n    ++\t\t\t\t      NULL, old_target, flags,\n      \t\t\t\t      msg, err);\n      }\n      \n    @@ refs.c: int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n      \tfor_each_string_list_item(item, refnames) {\n      \t\tret = ref_transaction_delete(transaction, item->string,\n     -\t\t\t\t\t     NULL, flags, msg, &err);\n    -+\t\t\t\t\t     NULL, flags, 0, msg, &err);\n    ++\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n      \t\tif (ret) {\n      \t\t\twarning(_(\"could not delete reference %s: %s\"),\n      \t\t\t\titem->string, err.buf);\n    @@ refs.h: int ref_transaction_create(struct ref_transaction *transaction,\n      \t\t\t   const struct object_id *old_oid,\n     -\t\t\t   unsigned int flags, const char *msg,\n     +\t\t\t   unsigned int flags,\n    -+\t\t\t   const char *old_ref,\n    ++\t\t\t   const char *old_target,\n     +\t\t\t   const char *msg,\n      \t\t\t   struct strbuf *err);\n      \n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n      \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n      \n     -\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n    -+\tif ((update->flags & REF_HAVE_NEW) && null_new_value(update))\n    ++\tif ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))\n      \t\tupdate->flags |= REF_DELETING;\n      \n      \tif (head_ref) {\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n      \t\t\tcontinue;\n      \n     -\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n    -+\t\tif (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n    ++\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n      \t\t\tstruct reftable_ref_record ref = {\n      \t\t\t\t.refname = (char *)u->refname,\n      \t\t\t\t.update_index = ts,\n     \n      ## t/t1400-update-ref.sh ##\n    -@@ t/t1400-update-ref.sh: test_expect_success \"stdin ${type} symref-verify fails for mistaken null value\"\n    - \ttest_cmp expect actual\n    - '\n    +@@ t/t1400-update-ref.sh: do\n    + \t\ttest_cmp before after\n    + \t'\n      \n    -+test_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n    -+\tgit symbolic-ref refs/heads/symref $a &&\n    -+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    -+\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n    -+'\n    +-\ttest_expect_success \"stdin ${type} symref-verify no value is treated as zero value\" '\n    ++\ttest_expect_success \"stdin ${type} symref-verify fails with no value\" '\n    + \t\tgit symbolic-ref refs/heads/symref >expect &&\n    + \t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"\" &&\n    + \t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n    +@@ t/t1400-update-ref.sh: do\n    + \t\ttest_cmp expect actual\n    + \t'\n    + \n    ++\ttest_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n    ++\t\tgit symbolic-ref refs/heads/symref $a &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    ++\t\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n    ++\t'\n    ++\n    ++\ttest_expect_success \"stdin ${type} symref-delete fails with no ref\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete \" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    ++\t\tgrep \"fatal: symref-delete: missing <ref>\" err\n    ++\t'\n    ++\n    ++\ttest_expect_success \"stdin ${type} symref-delete fails with too many arguments\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    ++\t\tif test \"$type\" = \"-z\"\n    ++\t\tthen\n    ++\t\t\tgrep \"fatal: unknown command: $a\" err\n    ++\t\telse\n    ++\t\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n    ++\t\tfi\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} fails symref-delete with no ref\" '\n    -+\tcreate_stdin_buf ${type} \"symref-delete \" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tgrep \"fatal: symref-delete: missing <ref>\" err\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-delete fails with wrong old value\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    ++\t\tif test_have_prereq REFTABLE\n    ++\t\tthen\n    ++\t\t\tgrep \"fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main\" err\n    ++\t\telse\n    ++\t\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}\" err\n    ++\t\tfi &&\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\techo $a >actual &&\n    ++\t\ttest_cmp expect actual\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} fails symref-delete with too many arguments\" '\n    -+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tif test \"$type\" = \"-z\"\n    -+\tthen\n    -+\t\tgrep \"fatal: unknown command: $a\" err\n    -+\telse\n    -+\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n    -+\tfi\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-delete works with right old value\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\ttest_must_fail git rev-parse --verify -q refs/heads/symref\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-delete ref fails with wrong old value\" '\n    -+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tgrep \"fatal: cannot lock ref '\"'\"'refs/heads/symref'\"'\"'\" err &&\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\techo $a >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-delete works with empty old value\" '\n    ++\t\tgit symbolic-ref refs/heads/symref $a &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\ttest_must_fail git rev-parse --verify -q $b\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-delete ref works with right old value\" '\n    -+\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n    -+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    -+\ttest_must_fail git rev-parse --verify -q $b\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-delete succeeds for dangling reference\" '\n    ++\t\ttest_must_fail git symbolic-ref refs/heads/nonexistent &&\n    ++\t\tgit symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref2\" \"refs/heads/nonexistent\" &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\ttest_must_fail git symbolic-ref -d refs/heads/symref2\n    ++\t'\n     +\n      done\n      \n5:  8fa0151f94 ! 5:  37f8be2f3f update-ref: add support for symref-create\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    update-ref: add support for symref-create\n    +    update-ref: add support for 'symref-create' command\n     \n    -    Add 'symref-create' to allow creation of symbolic refs in a transaction\n    -    via the 'git-update-ref' command. The 'symref-create' command takes in a\n    -    <new-ref>, which the created <ref> will point to.\n    +    Add 'symref-create' command to the '--stdin' mode 'git-update-ref' to\n    +    allow creation of symbolic refs in a transaction. The 'symref-create'\n    +    command takes in a <new-target>, which the created <ref> will point to.\n     \n    -    We also support the 'core.prefersymlinkrefs', wherein if the flag is set\n    -    and the filesystem supports symlinks, we create the symbolic ref as a\n    -    symlink.\n    +    Also, support the 'core.prefersymlinkrefs' config, wherein if the config\n    +    is set and the filesystem supports symlinks, we create the symbolic ref\n    +    as a symlink. We fallback to creating a regular symref if creating the\n    +    symlink is unsuccessful.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ Documentation/git-update-ref.txt: performs all modifications together.  Specify\n      \tcreate SP <ref> SP <new-oid> LF\n      \tdelete SP <ref> [SP <old-oid>] LF\n      \tverify SP <ref> [SP <old-oid>] LF\n    -+\tsymref-create SP <ref> SP <new-ref> LF\n    - \tsymref-delete SP <ref> [SP <old-ref>] LF\n    - \tsymref-verify SP <ref> [SP <old-ref>] LF\n    ++\tsymref-create SP <ref> SP <new-target> LF\n    + \tsymref-delete SP <ref> [SP <old-target>] LF\n    + \tsymref-verify SP <ref> [SP <old-target>] LF\n      \toption SP <opt> LF\n     @@ Documentation/git-update-ref.txt: quoting:\n      \tcreate SP <ref> NUL <new-oid> NUL\n      \tdelete SP <ref> NUL [<old-oid>] NUL\n      \tverify SP <ref> NUL [<old-oid>] NUL\n    -+\tsymref-create SP <ref> NUL <new-ref> NUL\n    - \tsymref-delete SP <ref> [NUL <old-ref>] NUL\n    - \tsymref-verify SP <ref> [NUL <old-ref>] NUL\n    ++\tsymref-create SP <ref> NUL <new-target> NUL\n    + \tsymref-delete SP <ref> [NUL <old-target>] NUL\n    + \tsymref-verify SP <ref> [NUL <old-target>] NUL\n      \toption SP <opt> NUL\n    +@@ Documentation/git-update-ref.txt: update::\n    + \n    + create::\n    + \tCreate <ref> with <new-oid> after verifying it does not\n    +-\texist.  The given <new-oid> may not be zero.\n    ++\texist.  The given <new-oid> may not be zero.  If instead\n    ++\tref:<new-target> is provided, a symbolic ref is created\n    ++\twhich targets <new-target>.\n    + \n    + delete::\n    + \tDelete <ref> after verifying it exists with <old-oid>, if given.\n     @@ Documentation/git-update-ref.txt: verify::\n      \tVerify <ref> against <old-oid> but do not change it.  If\n      \t<old-oid> is zero or missing, the ref must not exist.\n      \n     +symref-create::\n    -+\tCreate symbolic ref <ref> with <new-ref> after verifying\n    ++\tCreate symbolic ref <ref> with <new-target> after verifying\n     +\tit does not exist.  Can only be used in `no-deref` mode.\n     +\n      symref-delete::\n    - \tDelete <ref> after verifying it exists with <old-ref>, if\n    - \tgiven.\n    + \tDelete <ref> after verifying it exists with <old-target>, if given.\n    + \n     \n      ## builtin/clone.c ##\n     @@ builtin/clone.c: static void write_remote_refs(const struct ref *local_refs)\n    @@ builtin/update-ref.c: static void parse_cmd_create(struct ref_transaction *trans\n      \tstrbuf_release(&err);\n      }\n      \n    ++\n     +static void parse_cmd_symref_create(struct ref_transaction *transaction,\n     +\t\t\t\t    const char *next, const char *end)\n     +{\n     +\tstruct strbuf err = STRBUF_INIT;\n    -+\tchar *refname, *new_ref;\n    ++\tchar *refname, *new_target;\n     +\n     +\tif (!(update_flags & REF_NO_DEREF))\n    -+                die(\"symref-create: cannot operate with deref mode\");\n    ++\t\tdie(\"symref-create: cannot operate with deref mode\");\n     +\n     +\trefname = parse_refname(&next);\n     +\tif (!refname)\n     +\t\tdie(\"symref-create: missing <ref>\");\n     +\n    -+\tnew_ref = parse_next_refname(&next);\n    -+\tif (!new_ref)\n    -+\t\tdie(\"symref-create %s: missing <new-ref>\", refname);\n    -+\tif (read_ref(new_ref, NULL))\n    -+\t\tdie(\"symref-create %s: invalid <new-ref>\", refname);\n    ++\tnew_target = parse_next_refname(&next);\n    ++\tif (!new_target)\n    ++\t\tdie(\"symref-create %s: missing <new-target>\", refname);\n     +\n     +\tif (*next != line_termination)\n     +\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n     +\n    -+\tif (ref_transaction_create(transaction, refname, NULL, new_ref,\n    -+\t\t\t\t   update_flags | create_reflog_flag |\n    -+\t\t\t\t   REF_SYMREF_UPDATE, msg, &err))\n    ++\tif (ref_transaction_create(transaction, refname, NULL, new_target,\n    ++\t\t\t\t   update_flags | create_reflog_flag,\n    ++\t\t\t\t   msg, &err))\n     +\t\tdie(\"%s\", err.buf);\n     +\n     +\tupdate_flags = default_flags;\n     +\tfree(refname);\n    -+\tfree(new_ref);\n    ++\tfree(new_target);\n     +\tstrbuf_release(&err);\n     +}\n     +\n    @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      int ref_transaction_create(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n    -+\t\t\t   const char *new_ref,\n    ++\t\t\t   const char *new_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err)\n      {\n     -\tif (!new_oid || is_null_oid(new_oid)) {\n    -+\tif ((flags & REF_SYMREF_UPDATE) && !new_ref) {\n    -+\t\tstrbuf_addf(err, \"'%s' has a no new ref\", refname);\n    -+\t\treturn 1;\n    -+\t}\n    -+\tif (!(flags & REF_SYMREF_UPDATE) && (!new_oid || is_null_oid(new_oid))) {\n    - \t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n    +-\t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n    ++\tif ((!new_oid || is_null_oid(new_oid)) && !new_target) {\n    ++\t\tstrbuf_addf(err, \"'%s' has a null OID or no new target\", refname);\n      \t\treturn 1;\n      \t}\n    ++\tif (new_target && !(flags & REF_NO_DEREF))\n    ++\t\tBUG(\"create cannot operate on symrefs with deref mode\");\n      \treturn ref_transaction_update(transaction, refname, new_oid,\n     -\t\t\t\t      null_oid(), NULL, NULL, flags,\n    -+\t\t\t\t      null_oid(), new_ref, NULL, flags,\n    ++\t\t\t\t      null_oid(), new_target, NULL, flags,\n      \t\t\t\t      msg, err);\n      }\n      \n    @@ refs.h: int ref_transaction_update(struct ref_transaction *transaction,\n      int ref_transaction_create(struct ref_transaction *transaction,\n      \t\t\t   const char *refname,\n      \t\t\t   const struct object_id *new_oid,\n    -+\t\t\t   const char *new_ref,\n    ++\t\t\t   const char *new_target,\n      \t\t\t   unsigned int flags, const char *msg,\n      \t\t\t   struct strbuf *err);\n      \n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n      \t\t}\n      \t}\n      \n    -+\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n    -+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_ref)) {\n    ++\tif (update->new_target) {\n    ++\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n     +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\tgoto out;\n     +\t\t}\n    @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n      \n      \t\tif (update->flags & REF_NEEDS_COMMIT ||\n      \t\t    update->flags & REF_LOG_ONLY) {\n    -+\t\t\tif (update->flags & REF_SYMREF_UPDATE && update->new_ref) {\n    -+\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n    -+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_ref,\n    ++\t\t\tif (update->new_target) {\n    ++\t\t\t\t/*\n    ++\t\t\t\t * We want to get the resolved OID for the target, to ensure\n    ++\t\t\t\t * that the correct value is added to the reflog.\n    ++\t\t\t\t */\n    ++\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n     +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n    ++\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n     +\t\t\t\t\tupdate->new_oid = *null_oid();\n     +\t\t\t\t}\n     +\t\t\t}\n    @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n     +\t\t * We try creating a symlink, if that succeeds we continue to the\n     +\t\t * next updated. If not, we try and create a regular symref.\n     +\t\t */\n    -+\t\tif (update->flags & REF_SYMREF_UPDATE && prefer_symlink_refs)\n    -+\t\t\tif (!create_ref_symlink(lock, update->new_ref))\n    ++\t\tif (update->new_target && prefer_symlink_refs)\n    ++\t\t\tif (!create_ref_symlink(lock, update->new_target))\n     +\t\t\t\tcontinue;\n     +\n      \t\tif (update->flags & REF_NEEDS_COMMIT) {\n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n      \t\t\t * when the reference in question doesn't exist.\n      \t\t\t */\n     -\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n    -+\t\t\t if (u->flags & REF_HAVE_NEW && !null_new_value(u)) {\n    ++\t\t\t if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {\n      \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n      \t\t\t\t\t\t\t\t&current_oid, err);\n      \t\t\t\t if (ret)\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n      \t\t *   the given ref.\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n    -+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && null_new_value(u)) {\n    ++\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {\n      \t\t\tstruct reftable_log_record log = {0};\n      \t\t\tstruct reftable_iterator it = {0};\n      \n    +@@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n    + \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n    + \t\t\tstruct reftable_log_record *log;\n    + \n    ++\t\t\tif (u->new_target)\n    ++\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n    ++\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n    ++\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n    ++\t\t\t\t\tu->new_oid = *null_oid();\n    ++\n    + \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n    + \t\t\tlog = &logs[logs_nr++];\n    + \t\t\tmemset(log, 0, sizeof(*log));\n     @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n      \t\tif (u->flags & REF_LOG_ONLY)\n      \t\t\tcontinue;\n      \n    --\t\tif (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n    -+\t\tif (u->flags & REF_SYMREF_UPDATE &&\n    -+\t\t    u->flags & REF_HAVE_NEW &&\n    -+\t\t    !null_new_value(u)) {\n    +-\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n    ++\t\tif (u->flags & REF_HAVE_NEW && u->new_target) {\n     +\t\t\tstruct reftable_ref_record ref = {\n     +\t\t\t\t.refname = (char *)u->refname,\n     +\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n    -+\t\t\t\t.value.symref = (char *)u->new_ref,\n    ++\t\t\t\t.value.symref = (char *)u->new_target,\n     +\t\t\t\t.update_index = ts,\n     +\t\t\t};\n     +\n     +\t\t\tret = reftable_writer_add_ref(writer, &ref);\n     +\t\t\tif (ret < 0)\n     +\t\t\t\tgoto done;\n    -+\t\t} else if (u->flags & REF_HAVE_NEW && null_new_value(u)) {\n    ++\t\t} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n      \t\t\tstruct reftable_ref_record ref = {\n      \t\t\t\t.refname = (char *)u->refname,\n      \t\t\t\t.update_index = ts,\n    @@ t/t0600-reffiles-backend.sh: test_expect_success POSIXPERM 'git reflog expire ho\n      test_done\n     \n      ## t/t1400-update-ref.sh ##\n    -@@ t/t1400-update-ref.sh: test_expect_success \"stdin ${type} symref-delete ref works with right old value\"\n    - \ttest_must_fail git rev-parse --verify -q $b\n    - '\n    +@@ t/t1400-update-ref.sh: do\n    + \t\ttest_must_fail git symbolic-ref -d refs/heads/symref2\n    + \t'\n      \n    -+test_expect_success \"stdin ${type} symref-create fails without --no-deref\" '\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" &&\n    -+\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    -+\tgrep \"fatal: symref-create: cannot operate with deref mode\" err\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create fails without --no-deref\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n    ++\t\tgrep \"fatal: symref-create: cannot operate with deref mode\" err\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} fails symref-create with no ref\" '\n    -+\tcreate_stdin_buf ${type} \"symref-create \" >stdin &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tgrep \"fatal: symref-create: missing <ref>\" err\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create fails with too many arguments\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" \"$a\" >stdin &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    ++\t\tif test \"$type\" = \"-z\"\n    ++\t\tthen\n    ++\t\t\tgrep \"fatal: unknown command: $a\" err\n    ++\t\telse\n    ++\t\t\tgrep \"fatal: symref-create refs/heads/symref: extra input:  $a\" err\n    ++\t\tfi\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} fails symref-create with no new value\" '\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" >stdin &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tgrep \"fatal: symref-create refs/heads/symref: missing <new-ref>\" err\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create fails with no target\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" >stdin &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} fails symref-create with too many arguments\" '\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" \"$a\" >stdin &&\n    -+\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n    -+\tif test \"$type\" = \"-z\"\n    -+\tthen\n    -+\t\tgrep \"fatal: unknown command: $a\" err\n    -+\telse\n    -+\t\tgrep \"fatal: symref-create refs/heads/symref: extra input:  $a\" err\n    -+\tfi\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create fails with empty target\" '\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"\" >stdin &&\n    ++\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-create ref works\" '\n    -+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n    -+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\techo $a >actual &&\n    -+\ttest_cmp expect actual\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create works\" '\n    ++\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\techo $a >actual &&\n    ++\t\ttest_cmp expect actual\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-create does not create reflogs by default\" '\n    -+\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/symref\" \"$a\" >stdin &&\n    -+\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/symref >expect &&\n    -+\techo $a >actual &&\n    -+\ttest_cmp expect actual &&\n    -+\ttest_must_fail git reflog exists refs/symref\n    -+'\n    ++\ttest_expect_success \"stdin ${type} create dangling symref ref works\" '\n    ++\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"refs/heads/unkown\" >stdin &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\techo refs/heads/unkown >actual &&\n    ++\t\ttest_cmp expect actual\n    ++\t'\n     +\n    -+test_expect_success \"stdin ${type} symref-create reflogs with --create-reflog\" '\n    -+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n    -+\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n    -+\tgit update-ref --create-reflog --stdin ${type} --no-deref <stdin &&\n    -+\tgit symbolic-ref refs/heads/symref >expect &&\n    -+\techo $a >actual &&\n    -+\ttest_cmp expect actual &&\n    -+\tgit reflog exists refs/heads/symref\n    -+'\n    ++\ttest_expect_success \"stdin ${type} symref-create does not create reflogs by default\" '\n    ++\t\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/symref\" \"$a\" >stdin &&\n    ++\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/symref >expect &&\n    ++\t\techo $a >actual &&\n    ++\t\ttest_cmp expect actual &&\n    ++\t\ttest_must_fail git reflog exists refs/symref\n    ++\t'\n    ++\n    ++\ttest_expect_success \"stdin ${type} symref-create reflogs with --create-reflog\" '\n    ++\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n    ++\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n    ++\t\tgit update-ref --create-reflog --stdin ${type} --no-deref <stdin &&\n    ++\t\tgit symbolic-ref refs/heads/symref >expect &&\n    ++\t\techo $a >actual &&\n    ++\t\ttest_cmp expect actual &&\n    ++\t\tgit reflog exists refs/heads/symref\n    ++\t'\n     +\n      done\n      \n6:  714492ede3 < -:  ---------- update-ref: add support for symref-update\n-:  ---------- > 6:  b385f4d0d7 update-ref: add support for 'symref-update' command\n7:  c483104562 ! 7:  ef335e47d1 refs: support symrefs in 'reference-transaction' hook\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    refs: support symrefs in 'reference-transaction' hook\n    +    ref: support symrefs in 'reference-transaction' hook\n     \n         The 'reference-transaction' hook runs whenever a reference update is\n    -    made to the system. In the previous commits, we added support for\n    -    various symref commands in `git-update-ref`. While it allowed us to now\n    +    made to the system. In the previous commits, we added symref support for\n    +    various commands in `git-update-ref`. While it allowed us to now\n         manipulate symbolic refs via `git-update-ref`, it didn't activate the\n         'reference-transaction' hook.\n     \n    @@ Commit message\n         new format described for this and we stick to the existing format of:\n             <old-value> SP <new-value> SP <ref-name> LF\n         but now, <old-value> and <new-value> could also denote references\n    -    instead of objects.\n    +    instead of objects, where the format is similar to that in\n    +    'git-update-ref', i.e. 'ref:<ref-target>'.\n     \n         While this seems to be backward incompatible, it is okay, since the only\n         way the `reference-transaction` hook has refs in its output is when\n    -    `git-update-ref` is used with `update-symref` command. Also the\n    -    documentation for reference-transaction hook always stated that support\n    -    for symbolic references may be added in the future.\n    +    `git-update-ref` is used to manipulate symrefs. Also the documentation\n    +    for reference-transaction hook always stated that support for symbolic\n    +    references may be added in the future.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ Documentation/githooks.txt: given reference transaction is in:\n      `<ref-name>` via `git rev-parse`.\n      \n     +For symbolic reference updates the `<old_value>` and `<new-value>`\n    -+fields could denote references instead of objects.\n    ++fields could denote references instead of objects, denoted via the\n    ++`ref:<ref-target>` format.\n     +\n      The exit status of the hook is ignored for any state except for the\n      \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n    @@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,\n      \n      \tfor (i = 0; i < transaction->nr; i++) {\n      \t\tstruct ref_update *update = transaction->updates[i];\n    -+\t\tconst char *new_value, *old_value;\n    ++\t\tstrbuf_reset(&buf);\n      \n    --\t\tif (update->flags & REF_SYMREF_UPDATE)\n    +-\t\t/*\n    +-\t\t * Skip reference transaction for symbolic refs.\n    +-\t\t */\n    +-\t\tif (update->new_target || update->old_target)\n     -\t\t\tcontinue;\n    -+\t\tnew_value = oid_to_hex(&update->new_oid);\n    -+\t\told_value = oid_to_hex(&update->old_oid);\n    -+\n    -+\t\tif (update->flags & REF_SYMREF_UPDATE) {\n    -+\t\t\tif (update->flags & REF_HAVE_NEW && !null_new_value(update))\n    -+\t\t\t\tnew_value = update->new_ref;\n    -+\t\t\tif (update->flags & REF_HAVE_OLD && update->old_ref)\n    -+\t\t\t\told_value = update->old_ref;\n    -+\t\t}\n    ++\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n    ++\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n    ++\t\telse\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n      \n    - \t\tstrbuf_reset(&buf);\n    +-\t\tstrbuf_reset(&buf);\n     -\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n     -\t\t\t    oid_to_hex(&update->old_oid),\n     -\t\t\t    oid_to_hex(&update->new_oid),\n     -\t\t\t    update->refname);\n    -+\t\tstrbuf_addf(&buf, \"%s %s %s\\n\", old_value, new_value, update->refname);\n    ++\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n    ++\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n    ++\t\telse\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n    ++\n    ++\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n      \n      \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n      \t\t\tif (errno != EPIPE) {\n     \n      ## t/t1416-ref-transaction-hooks.sh ##\n    -@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'hook gets all queued updates in aborted state' '\n    - \ttest_cmp expect actual\n    +@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '\n    + \ttest_cmp expect target-repo.git/actual\n      '\n      \n    -+# This test doesn't add a check for 'symref-delete' since there is a\n    ++# This test doesn't add a check for symref 'delete' since there is a\n     +# variation between the ref backends WRT 'delete'. In the files backend,\n     +# 'delete' also triggers an additional transaction update on the\n     +# packed-refs backend, which constitutes additional reflog entries.\n    - test_expect_success 'interleaving hook calls succeed' '\n    - \ttest_when_finished \"rm -r target-repo.git\" &&\n    - \n    -@@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '\n    - \ttest_cmp expect target-repo.git/actual\n    - '\n    - \n     +test_expect_success 'hook gets all queued symref updates' '\n     +\ttest_when_finished \"rm actual\" &&\n     +\n    @@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls s\n     +\n     +\tcat >expect <<-EOF &&\n     +\t\tprepared\n    -+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n    -+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n    -+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n    ++\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n    ++\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n    ++\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n     +\t\tcommitted\n    -+\t\trefs/heads/main $ZERO_OID refs/heads/symref\n    -+\t\t$ZERO_OID refs/heads/main refs/heads/symrefc\n    -+\t\trefs/heads/main refs/heads/branch refs/heads/symrefu\n    ++\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n    ++\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n    ++\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n     +\tEOF\n     +\n     +\tgit update-ref --no-deref --stdin <<-EOF &&\n     +\t\tstart\n     +\t\tsymref-verify refs/heads/symref refs/heads/main\n     +\t\tsymref-create refs/heads/symrefc refs/heads/main\n    -+\t\tsymref-update refs/heads/symrefu refs/heads/branch refs/heads/main\n    ++\t\tsymref-update refs/heads/symrefu refs/heads/branch ref refs/heads/main\n     +\t\tprepare\n     +\t\tcommit\n     +\tEOF\n\n\nKarthik Nayak (7):\n  refs: accept symref values in `ref_transaction[_add]_update`\n  files-backend: extract out `create_symref_lock`\n  update-ref: add support for 'symref-verify' command\n  update-ref: add support for 'symref-delete' command\n  update-ref: add support for 'symref-create' command\n  update-ref: add support for 'symref-update' command\n  ref: support symrefs in 'reference-transaction' hook\n\n Documentation/git-update-ref.txt |  34 ++-\n Documentation/githooks.txt       |  14 +-\n branch.c                         |   2 +-\n builtin/clone.c                  |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   4 +-\n builtin/receive-pack.c           |   4 +-\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             | 240 ++++++++++++++++--\n refs.c                           |  78 ++++--\n refs.h                           |  23 +-\n refs/files-backend.c             | 141 +++++++++--\n refs/refs-internal.h             |  20 ++\n refs/reftable-backend.c          |  49 +++-\n sequencer.c                      |   9 +-\n t/t0600-reffiles-backend.sh      |  32 +++\n t/t1400-update-ref.sh            | 413 ++++++++++++++++++++++++++++++-\n t/t1416-ref-transaction-hooks.sh |  41 +++\n walker.c                         |   2 +-\n 20 files changed, 1030 insertions(+), 86 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"493514","messageId":"20240426152449.228860-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:43Z","receivedAt":"2024-04-26T15:24:56Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `ref_transaction[_add]_update` functions obtain ref information and\nflags to create a `ref_update` and add it to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. In this commit, let's add the\nrequired parameters to the function and modify all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked.\n\nThe handling logic of these parameters will be added in consequent\ncommits as we add symref commands to the '--stdin' mode of\n'git-update-ref'.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 17 ++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 13 +++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 69 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..060a31616d 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n+\t\tBUG(\"Only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n+\t\tBUG(\"Only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..c792e13a64 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         update to point to. This takes precedence over new_oid when\n+ *         set. If the reference is a regular reference, it will be\n+ *         converted to a symbolic reference.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Will only be taken into account when the reference is a symbolic\n+ *         reference.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +735,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..3040d4797c 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,18 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set and the reference is a symbolic ref, check that the\n+\t * reference previously pointed to this value.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +185,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 2c19846385..af1b25692b 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -616,7 +616,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1248,7 +1248,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3764,8 +3764,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"493515","messageId":"20240426152449.228860-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 2/7] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:44Z","receivedAt":"2024-04-26T15:24:56Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nSplit this into two individual functions `create_and_commit_symref` and\n`create_symref_locked`. This way we can create the symref lock and\ncommit it at different times. This will be used to provide symref\nsupport in `git-update-ref(1)`.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 40 +++++++++++++++++++++++++++-------------\n 1 file changed, 27 insertions(+), 13 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..2420dac2aa 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,26 +1920,39 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n+\t\treturn error(\"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\n+\t/* no error check; commit_ref will check ferror */\n+\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n \treturn 0;\n }\n \n@@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"493517","messageId":"20240426152449.228860-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 3/7] update-ref: add support for 'symref-verify' command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:45Z","receivedAt":"2024-04-26T15:24:58Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we added the required base for adding symref\ncommands to the '--stdin' mode provided by 'git-update-ref(1)'. Using\nthem, add a new 'symref-verify' command to verify symrefs.\n\nThe 'symref-verify' command allows users to verify if a provided <ref>\ncontains the provided <old-target> without changing the <ref>. If\n<old-target> is not provided, the command will verify that the <ref>\ndoesn't exist. Since we're checking for symbolic refs, this command will\nonly work with the 'no-deref' mode. This is because any dereferenced\nsymbolic ref will point to an object and not a ref and the regular\n'verify' command can be used in such situations.\n\nAdd and use `ref_update_is_null_new_value`, a helper function which is\nused to check if there is a new_value in a reference update. The new\nvalue could either be a symref target `new_target` or a OID `new_oid`.\nWe also add tests to test the command in both the regular stdin mode and\nalso with the '-z' flag.\n\nWe also disable the reference-transaction hook for symref-updates which\nwill be tackled in its own commit.\n\nAdd required tests for symref support in 'verify' while also adding\nreflog checks for the pre-existing 'verify' tests.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |  7 +++\n builtin/update-ref.c             | 80 +++++++++++++++++++++++----\n refs.c                           | 30 +++++++++--\n refs.h                           |  1 +\n refs/files-backend.c             | 43 +++++++++++++++\n refs/refs-internal.h             |  7 +++\n refs/reftable-backend.c          | 21 +++++++-\n t/t1400-update-ref.sh            | 93 +++++++++++++++++++++++++++++++-\n 8 files changed, 264 insertions(+), 18 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 374a2ebd2b..9fe78b3501 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-verify SP <ref> [SP <old-target>] LF\n \toption SP <opt> LF\n \tstart LF\n \tprepare LF\n@@ -86,6 +87,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-verify SP <ref> [NUL <old-target>] NUL\n \toption SP <opt> NUL\n \tstart NUL\n \tprepare NUL\n@@ -117,6 +119,11 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-verify::\n+\tVerify symbolic <ref> against <old-target> but do not change it.\n+\tIf <old-target> is missing, the ref must not exist.  Can only be\n+\tused in `no-deref` mode.\n+\n option::\n \tModify the behavior of the next command naming a <ref>.\n \tThe only valid option is `no-deref` to avoid dereferencing\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 21fdbf6ac8..419b28169b 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -76,6 +76,29 @@ static char *parse_refname(const char **next)\n \treturn strbuf_detach(&ref, NULL);\n }\n \n+/*\n+ * Wrapper around parse_refname which skips the next delimiter.\n+ */\n+static char *parse_next_refname(const char **next)\n+{\n+\tif (line_termination) {\n+\t\t/* Without -z, consume SP and use next argument */\n+\t\tif (!**next || **next == line_termination)\n+\t\t\treturn NULL;\n+\t\tif (**next != ' ')\n+\t\t\tdie(\"expected SP but got: %s\", *next);\n+\t} else {\n+\t\t/* With -z, read the next NUL-terminated line */\n+\t\tif (**next)\n+\t\t\treturn NULL;\n+\t}\n+\t/* Skip the delimiter */\n+\t(*next)++;\n+\n+\treturn parse_refname(next);\n+}\n+\n+\n /*\n  * The value being parsed is <old-oid> (as opposed to <new-oid>; the\n  * difference affects which error messages are generated):\n@@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n \t\tdie(\"verify %s: extra input: %s\", refname, next);\n \n \tif (ref_transaction_verify(transaction, refname, &old_oid,\n-\t\t\t\t   update_flags, &err))\n+\t\t\t\t   NULL, update_flags, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tstrbuf_release(&err);\n+}\n+\n+static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct object_id old_oid;\n+\tchar *refname, *old_target;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+\t\tdie(\"symref-verify: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-verify: missing <ref>\");\n+\n+\t/*\n+\t * old_ref is optional, but we want to differentiate between\n+\t * a NULL and zero value.\n+\t */\n+\told_target = parse_next_refname(&next);\n+\tif (!old_target)\n+\t\told_oid = *null_oid();\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_verify(transaction, refname,\n+\t\t\t\t   old_target ? NULL : &old_oid,\n+\t\t\t\t   old_target, update_flags, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n \tfree(refname);\n+\tfree(old_target);\n \tstrbuf_release(&err);\n }\n \n@@ -380,15 +439,16 @@ static const struct parse_cmd {\n \tunsigned args;\n \tenum update_refs_state state;\n } command[] = {\n-\t{ \"update\",  parse_cmd_update,  3, UPDATE_REFS_OPEN },\n-\t{ \"create\",  parse_cmd_create,  2, UPDATE_REFS_OPEN },\n-\t{ \"delete\",  parse_cmd_delete,  2, UPDATE_REFS_OPEN },\n-\t{ \"verify\",  parse_cmd_verify,  2, UPDATE_REFS_OPEN },\n-\t{ \"option\",  parse_cmd_option,  1, UPDATE_REFS_OPEN },\n-\t{ \"start\",   parse_cmd_start,   0, UPDATE_REFS_STARTED },\n-\t{ \"prepare\", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },\n-\t{ \"abort\",   parse_cmd_abort,   0, UPDATE_REFS_CLOSED },\n-\t{ \"commit\",  parse_cmd_commit,  0, UPDATE_REFS_CLOSED },\n+\t{ \"update\",        parse_cmd_update,        3, UPDATE_REFS_OPEN },\n+\t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n+\t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n+\t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n+\t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n+\t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\n+\t{ \"prepare\",       parse_cmd_prepare,       0, UPDATE_REFS_PREPARED },\n+\t{ \"abort\",         parse_cmd_abort,         0, UPDATE_REFS_CLOSED },\n+\t{ \"commit\",        parse_cmd_commit,        0, UPDATE_REFS_CLOSED },\n };\n \n static void update_refs_stdin(void)\ndiff --git a/refs.c b/refs.c\nindex 060a31616d..0e1013b5ab 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((void *)transaction->updates[i]->old_target);\n+\t\tfree((void *)transaction->updates[i]->new_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tif (new_target)\n+\t\tupdate->new_target = xstrdup(new_target);\n+\tif (old_target)\n+\t\tupdate->old_target = xstrdup(old_target);\n+\tif (new_oid && flags & REF_HAVE_NEW)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif (old_oid && flags & REF_HAVE_OLD)\n \t\toidcpy(&update->old_oid, old_oid);\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n@@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!old_oid)\n-\t\tBUG(\"verify called with old_oid set to NULL\");\n+\tif (!old_target && !old_oid)\n+\t\tBUG(\"verify called with old_oid and old_target set to NULL\");\n+\tif (old_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"verify cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n-\t\t\t\t      NULL, NULL,\n+\t\t\t\t      NULL, old_target,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n \n+\t\t/*\n+\t\t * Skip reference transaction for symbolic refs.\n+\t\t */\n+\t\tif (update->new_target || update->old_target)\n+\t\t\tcontinue;\n+\n \t\tstrbuf_reset(&buf);\n \t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n \t\t\t    oid_to_hex(&update->old_oid),\n@@ -2802,3 +2818,7 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+int ref_update_is_null_new_value(struct ref_update *update) {\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\ndiff --git a/refs.h b/refs.h\nindex c792e13a64..27b9aeaf54 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -780,6 +780,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n int ref_transaction_verify(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 2420dac2aa..53197fa3af 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+/*\n+ * Check whether the REF_HAVE_OLD and old_target values stored in\n+ * update are consistent with ref, which is the symbolic reference's\n+ * current value. If everything is OK, return 0; otherwise, write an\n+ * error message to err and return -1.\n+ */\n+static int check_old_target(struct ref_update *update, char *ref,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (!(update->flags & REF_HAVE_OLD) ||\n+\t    !strcmp(update->old_target, ref))\n+\t\treturn 0;\n+\n+\tif (!strcmp(update->old_target, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference already exists\",\n+\t\t\t    original_update_refname(update));\n+\telse if (!strcmp(ref, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    ref, update->old_target);\n+\n+\treturn -1;\n+}\n+\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n+\t\t\t}\n+\n+\t\t\t/*\n+\t\t\t * For symref verification, we need to check the reference value\n+\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n+\t\t\t * verifying a dereferenced symref, we then check the oid.\n+\t\t\t */\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 3040d4797c..23e65f65e8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -748,4 +748,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_is_null_new_value(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..a2474245aa 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif ((u->flags & REF_HAVE_OLD) && u->old_target) {\n+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n+\t\t\t\tif (!strcmp(u->old_target, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"provided target is empty\",\n+\t\t\t\t\t\t    original_update_refname(u));\n+\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    u->old_target);\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"is at %s but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    referent.buf, u->old_target);\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t    \"reference already exists\"),\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex ec3443cc87..34b29eeac8 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -890,17 +890,23 @@ test_expect_success 'stdin update/create/verify combination works' '\n '\n \n test_expect_success 'stdin verify succeeds for correct value' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \tgit rev-parse $m >expect &&\n \techo \"verify $m $m\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n \tgit rev-parse $m >actual &&\n-\ttest_cmp expect actual\n+\ttest_cmp expect actual &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify succeeds for missing reference' '\n+\ttest-tool ref-store main for-each-reflog-ent $m >before &&\n \techo \"verify refs/heads/missing $Z\" >stdin &&\n \tgit update-ref --stdin <stdin &&\n-\ttest_must_fail git rev-parse --verify -q refs/heads/missing\n+\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\ttest-tool ref-store main for-each-reflog-ent $m >after &&\n+\ttest_cmp before after\n '\n \n test_expect_success 'stdin verify treats no value as missing' '\n@@ -1641,4 +1647,87 @@ test_expect_success PIPE 'transaction flushes status updates' '\n \ttest_cmp expected actual\n '\n \n+create_stdin_buf () {\n+\tif test \"$1\" = \"-z\"\n+\tthen\n+\t\tshift\n+\t\tprintf \"$F\" \"$@\" >stdin\n+\telse\n+\t\techo \"$@\" >stdin\n+\tfi\n+}\n+\n+for type in \"\" \"-z\"\n+do\n+\n+\ttest_expect_success \"stdin ${type} symref-verify fails without --no-deref\" '\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: symref-verify: cannot operate with deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" \"$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err  &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: $a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: symref-verify refs/heads/symref: extra input:  $a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify succeeds for correct value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$a\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\t\ttest_cmp before after\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify no value is treated as zero value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify succeeds for dangling reference\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref2\" &&\n+\t\ttest_must_fail git symbolic-ref refs/heads/nonexistent &&\n+\t\tgit symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref2\" \"refs/heads/nonexistent\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify succeeds for missing reference\" '\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >before &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/missing\" \"$Z\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git rev-parse --verify -q refs/heads/missing &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >after &&\n+\t\ttest_cmp before after\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify fails for wrong value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$b\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-verify fails for mistaken null value\" '\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"$Z\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+done\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493518","messageId":"20240426152449.228860-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 4/7] update-ref: add support for 'symref-delete' command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:46Z","receivedAt":"2024-04-26T15:24:58Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd a new command 'symref-delete' to allow deletions of symbolic refs in\na transaction via the '--stdin' mode of the 'git-update-ref' command.\nThe 'symref-delete' command can, when given an <old-target>, delete the\nprovided <ref> only when it points to <old-target>. This will only work\nwhen used with the 'no-deref' mode as it doesn't make sense to deref a\nsymref during deletion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt | 11 ++++--\n builtin/fetch.c                  |  2 +-\n builtin/receive-pack.c           |  3 +-\n builtin/update-ref.c             | 33 ++++++++++++++++-\n refs.c                           | 12 ++++---\n refs.h                           |  4 ++-\n refs/files-backend.c             |  2 +-\n refs/reftable-backend.c          |  2 +-\n t/t1400-update-ref.sh            | 61 +++++++++++++++++++++++++++++++-\n 9 files changed, 117 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 9fe78b3501..2924b9437e 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-delete SP <ref> [SP <old-target>] LF\n \tsymref-verify SP <ref> [SP <old-target>] LF\n \toption SP <opt> LF\n \tstart LF\n@@ -87,6 +88,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-delete SP <ref> [NUL <old-target>] NUL\n \tsymref-verify SP <ref> [NUL <old-target>] NUL\n \toption SP <opt> NUL\n \tstart NUL\n@@ -112,13 +114,18 @@ create::\n \texist.  The given <new-oid> may not be zero.\n \n delete::\n-\tDelete <ref> after verifying it exists with <old-oid>, if\n-\tgiven.  If given, <old-oid> may not be zero.\n+\tDelete <ref> after verifying it exists with <old-oid>, if given.\n+\tIf given, <old-oid> may not be zero.  If instead, ref:<old-target>\n+\tis provided, verify that the symbolic ref <ref> targets\n+\t<old-target> before deleting it.\n \n verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-delete::\n+\tDelete <ref> after verifying it exists with <old-target>, if given.\n+\n symref-verify::\n \tVerify symbolic <ref> against <old-target> but do not change it.\n \tIf <old-target> is missing, the ref must not exist.  Can only be\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 66840b7c5b..d02592efca 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -1383,7 +1383,7 @@ static int prune_refs(struct display_state *display_state,\n \t\tif (transaction) {\n \t\t\tfor (ref = stale_refs; ref; ref = ref->next) {\n \t\t\t\tresult = ref_transaction_delete(transaction, ref->name, NULL, 0,\n-\t\t\t\t\t\t\t\t\"fetch: prune\", &err);\n+\t\t\t\t\t\t\t\tNULL, \"fetch: prune\", &err);\n \t\t\t\tif (result)\n \t\t\t\t\tgoto cleanup;\n \t\t\t}\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex b150ef39a8..9a4667d57d 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1576,7 +1576,8 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_delete(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   old_oid,\n-\t\t\t\t\t   0, \"push\", &err)) {\n+\t\t\t\t\t   0, NULL,\n+\t\t\t\t\t   \"push\", &err)) {\n \t\t\trp_error(\"%s\", err.buf);\n \t\t\tret = \"failed to delete\";\n \t\t} else {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 419b28169b..8fef3aed0a 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -293,7 +293,7 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \n \tif (ref_transaction_delete(transaction, refname,\n \t\t\t\t   have_old ? &old_oid : NULL,\n-\t\t\t\t   update_flags, msg, &err))\n+\t\t\t\t   update_flags, NULL, msg, &err))\n \t\tdie(\"%s\", err.buf);\n \n \tupdate_flags = default_flags;\n@@ -301,6 +301,36 @@ static void parse_cmd_delete(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+\n+static void parse_cmd_symref_delete(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *old_target;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+\t\tdie(\"symref-delete: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-delete: missing <ref>\");\n+\n+\told_target = parse_next_refname(&next);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-delete %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_delete(transaction, refname, NULL,\n+\t\t\t\t   update_flags, old_target, msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(old_target);\n+\tstrbuf_release(&err);\n+}\n+\n+\n static void parse_cmd_verify(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -443,6 +473,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\n \t{ \"start\",         parse_cmd_start,         0, UPDATE_REFS_STARTED },\ndiff --git a/refs.c b/refs.c\nindex 0e1013b5ab..6b7c46bfd8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -979,7 +979,7 @@ int refs_delete_ref(struct ref_store *refs, const char *msg,\n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n \t    ref_transaction_delete(transaction, refname, old_oid,\n-\t\t\t\t   flags, msg, &err) ||\n+\t\t\t\t   flags, NULL, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tref_transaction_free(transaction);\n@@ -1318,14 +1318,18 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_target,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err)\n {\n \tif (old_oid && is_null_oid(old_oid))\n \t\tBUG(\"delete called with old_oid set to zeros\");\n+\tif (old_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"delete cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      NULL, old_target, flags,\n \t\t\t\t      msg, err);\n }\n \n@@ -2752,7 +2756,7 @@ int refs_delete_refs(struct ref_store *refs, const char *logmsg,\n \n \tfor_each_string_list_item(item, refnames) {\n \t\tret = ref_transaction_delete(transaction, item->string,\n-\t\t\t\t\t     NULL, flags, msg, &err);\n+\t\t\t\t\t     NULL, flags, NULL, msg, &err);\n \t\tif (ret) {\n \t\t\twarning(_(\"could not delete reference %s: %s\"),\n \t\t\t\titem->string, err.buf);\ndiff --git a/refs.h b/refs.h\nindex 27b9aeaf54..4be4930f04 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -766,7 +766,9 @@ int ref_transaction_create(struct ref_transaction *transaction,\n int ref_transaction_delete(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *old_oid,\n-\t\t\t   unsigned int flags, const char *msg,\n+\t\t\t   unsigned int flags,\n+\t\t\t   const char *old_target,\n+\t\t\t   const char *msg,\n \t\t\t   struct strbuf *err);\n \n /*\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 53197fa3af..fc5037fe5a 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2516,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex a2474245aa..2b2cbca8c0 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1120,7 +1120,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 34b29eeac8..8efddac013 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1689,7 +1689,7 @@ do\n \t\ttest_cmp before after\n \t'\n \n-\ttest_expect_success \"stdin ${type} symref-verify no value is treated as zero value\" '\n+\ttest_expect_success \"stdin ${type} symref-verify fails with no value\" '\n \t\tgit symbolic-ref refs/heads/symref >expect &&\n \t\tcreate_stdin_buf ${type} \"symref-verify refs/heads/symref\" \"\" &&\n \t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n@@ -1728,6 +1728,65 @@ do\n \t\ttest_cmp expect actual\n \t'\n \n+\ttest_expect_success \"stdin ${type} symref-delete fails without --no-deref\" '\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: symref-delete: cannot operate with deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete fails with no ref\" '\n+\t\tcreate_stdin_buf ${type} \"symref-delete \" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tgrep \"fatal: symref-delete: missing <ref>\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" \"$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: $a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: symref-delete refs/heads/symref: extra input:  $a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete fails with wrong old value\" '\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$m\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test_have_prereq REFTABLE\n+\t\tthen\n+\t\t\tgrep \"fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected refs/heads/main\" err\n+\t\telse\n+\t\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}\" err\n+\t\tfi &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete works with right old value\" '\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"$a\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git rev-parse --verify -q refs/heads/symref\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete works with empty old value\" '\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref\" \"\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git rev-parse --verify -q $b\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-delete succeeds for dangling reference\" '\n+\t\ttest_must_fail git symbolic-ref refs/heads/nonexistent &&\n+\t\tgit symbolic-ref refs/heads/symref2 refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-delete refs/heads/symref2\" \"refs/heads/nonexistent\" &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\ttest_must_fail git symbolic-ref -d refs/heads/symref2\n+\t'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"493519","messageId":"20240426152449.228860-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 5/7] update-ref: add support for 'symref-create' command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:47Z","receivedAt":"2024-04-26T15:25:00Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd 'symref-create' command to the '--stdin' mode 'git-update-ref' to\nallow creation of symbolic refs in a transaction. The 'symref-create'\ncommand takes in a <new-target>, which the created <ref> will point to.\n\nAlso, support the 'core.prefersymlinkrefs' config, wherein if the config\nis set and the filesystem supports symlinks, we create the symbolic ref\nas a symlink. We fallback to creating a regular symref if creating the\nsymlink is unsuccessful.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt | 10 ++++-\n builtin/clone.c                  |  2 +-\n builtin/update-ref.c             | 35 ++++++++++++++++-\n refs.c                           |  9 +++--\n refs.h                           |  1 +\n refs/files-backend.c             | 42 +++++++++++++++++++++\n refs/reftable-backend.c          | 23 +++++++++--\n t/t0600-reffiles-backend.sh      | 32 ++++++++++++++++\n t/t1400-update-ref.sh            | 65 ++++++++++++++++++++++++++++++++\n 9 files changed, 210 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 2924b9437e..7a33f70767 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-create SP <ref> SP <new-target> LF\n \tsymref-delete SP <ref> [SP <old-target>] LF\n \tsymref-verify SP <ref> [SP <old-target>] LF\n \toption SP <opt> LF\n@@ -88,6 +89,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-create SP <ref> NUL <new-target> NUL\n \tsymref-delete SP <ref> [NUL <old-target>] NUL\n \tsymref-verify SP <ref> [NUL <old-target>] NUL\n \toption SP <opt> NUL\n@@ -111,7 +113,9 @@ update::\n \n create::\n \tCreate <ref> with <new-oid> after verifying it does not\n-\texist.  The given <new-oid> may not be zero.\n+\texist.  The given <new-oid> may not be zero.  If instead\n+\tref:<new-target> is provided, a symbolic ref is created\n+\twhich targets <new-target>.\n \n delete::\n \tDelete <ref> after verifying it exists with <old-oid>, if given.\n@@ -123,6 +127,10 @@ verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\n \n+symref-create::\n+\tCreate symbolic ref <ref> with <new-target> after verifying\n+\tit does not exist.  Can only be used in `no-deref` mode.\n+\n symref-delete::\n \tDelete <ref> after verifying it exists with <old-target>, if given.\n \ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 74ec14542e..c0eed8e795 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -547,7 +547,7 @@ static void write_remote_refs(const struct ref *local_refs)\n \t\tif (!r->peer_ref)\n \t\t\tcontinue;\n \t\tif (ref_transaction_create(t, r->peer_ref->name, &r->old_oid,\n-\t\t\t\t\t   0, NULL, &err))\n+\t\t\t\t\t   NULL, 0, NULL, &err))\n \t\t\tdie(\"%s\", err.buf);\n \t}\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 8fef3aed0a..ae68ffde5e 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -257,7 +257,7 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"create %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_create(transaction, refname, &new_oid,\n+\tif (ref_transaction_create(transaction, refname, &new_oid, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\n@@ -267,6 +267,38 @@ static void parse_cmd_create(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+\n+static void parse_cmd_symref_create(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tchar *refname, *new_target;\n+\n+\tif (!(update_flags & REF_NO_DEREF))\n+\t\tdie(\"symref-create: cannot operate with deref mode\");\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-create: missing <ref>\");\n+\n+\tnew_target = parse_next_refname(&next);\n+\tif (!new_target)\n+\t\tdie(\"symref-create %s: missing <new-target>\", refname);\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-create %s: extra input: %s\", refname, next);\n+\n+\tif (ref_transaction_create(transaction, refname, NULL, new_target,\n+\t\t\t\t   update_flags | create_reflog_flag,\n+\t\t\t\t   msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(new_target);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_delete(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -473,6 +505,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\n \t{ \"option\",        parse_cmd_option,        1, UPDATE_REFS_OPEN },\ndiff --git a/refs.c b/refs.c\nindex 6b7c46bfd8..42cb4126a7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1303,15 +1303,18 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n-\tif (!new_oid || is_null_oid(new_oid)) {\n-\t\tstrbuf_addf(err, \"'%s' has a null OID\", refname);\n+\tif ((!new_oid || is_null_oid(new_oid)) && !new_target) {\n+\t\tstrbuf_addf(err, \"'%s' has a null OID or no new target\", refname);\n \t\treturn 1;\n \t}\n+\tif (new_target && !(flags & REF_NO_DEREF))\n+\t\tBUG(\"create cannot operate on symrefs with deref mode\");\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      null_oid(), new_target, NULL, flags,\n \t\t\t\t      msg, err);\n }\n \ndiff --git a/refs.h b/refs.h\nindex 4be4930f04..bde8606213 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -752,6 +752,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n int ref_transaction_create(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n+\t\t\t   const char *new_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex fc5037fe5a..f5e271a442 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2610,6 +2610,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n+\tif (update->new_target) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t}\n+\n+\n \tif ((update->flags & REF_HAVE_NEW) &&\n \t    !(update->flags & REF_DELETING) &&\n \t    !(update->flags & REF_LOG_ONLY)) {\n@@ -2905,6 +2926,18 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n+\t\t\tif (update->new_target) {\n+\t\t\t\t/*\n+\t\t\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t\t\t * that the correct value is added to the reflog.\n+\t\t\t\t */\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n+\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n+\t\t\t\t\tupdate->new_oid = *null_oid();\n+\t\t\t\t}\n+\t\t\t}\n+\n \t\t\tif (files_log_ref_write(refs,\n \t\t\t\t\t\tlock->ref_name,\n \t\t\t\t\t\t&lock->old_oid,\n@@ -2922,6 +2955,15 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next updated. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 2b2cbca8c0..e203c697f2 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -1062,7 +1062,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1104,6 +1104,12 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n \n+\t\t\tif (u->new_target)\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n+\t\t\t\t\t/* for dangling symrefs we gracefully set the oid to zero */\n+\t\t\t\t\tu->new_oid = *null_oid();\n+\n \t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n \t\t\tlog = &logs[logs_nr++];\n \t\t\tmemset(log, 0, sizeof(*log));\n@@ -1120,7 +1126,18 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n+\t\tif (u->flags & REF_HAVE_NEW && u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\ndiff --git a/t/t0600-reffiles-backend.sh b/t/t0600-reffiles-backend.sh\nindex 64214340e7..c5061c26cf 100755\n--- a/t/t0600-reffiles-backend.sh\n+++ b/t/t0600-reffiles-backend.sh\n@@ -472,4 +472,36 @@ test_expect_success POSIXPERM 'git reflog expire honors core.sharedRepository' '\n \tesac\n '\n \n+test_expect_success SYMLINKS 'symref transaction supports symlinks' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs true &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tsymref-create TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_symlink .git/TESTSYMREFONE &&\n+\ttest \"$(test_readlink .git/TESTSYMREFONE)\" = refs/heads/new\n+'\n+\n+test_expect_success 'symref transaction supports false symlink config' '\n+\ttest_when_finished \"git symbolic-ref -d TESTSYMREFONE\" &&\n+\tgit update-ref refs/heads/new @ &&\n+\ttest_config core.prefersymlinkrefs false &&\n+\tcat >stdin <<-EOF &&\n+\tstart\n+\tsymref-create TESTSYMREFONE refs/heads/new\n+\tprepare\n+\tcommit\n+\tEOF\n+\tgit update-ref --no-deref --stdin <stdin &&\n+\ttest_path_is_file .git/TESTSYMREFONE &&\n+\tgit symbolic-ref TESTSYMREFONE >actual &&\n+\techo refs/heads/new >expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 8efddac013..452fc1da50 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1787,6 +1787,71 @@ do\n \t\ttest_must_fail git symbolic-ref -d refs/heads/symref2\n \t'\n \n+\ttest_expect_success \"stdin ${type} symref-create fails without --no-deref\" '\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: symref-create: cannot operate with deref mode\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" \"$a\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: $a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: symref-create refs/heads/symref: extra input:  $a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create fails with no target\" '\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create fails with empty target\" '\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create works\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} create dangling symref ref works\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"refs/heads/unkown\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo refs/heads/unkown >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create does not create reflogs by default\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest_must_fail git reflog exists refs/symref\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-create reflogs with --create-reflog\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-create refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --create-reflog --stdin ${type} --no-deref <stdin &&\n+\t\tgit symbolic-ref refs/heads/symref >expect &&\n+\t\techo $a >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\tgit reflog exists refs/heads/symref\n+\t'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"493520","messageId":"20240426152449.228860-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 6/7] update-ref: add support for 'symref-update' command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:48Z","receivedAt":"2024-04-26T15:25:00Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nAdd 'symref-update' command to the '--stdin' mode of 'git-update-ref' to\nallow updates of symbolic refs. The 'symref-update' command takes in a\n<new-target>, which the <ref> will be updated to. If the <ref> doesn't\nexist it will be created.\n\nIt also optionally takes either an `ref <old-target>` or `oid\n<old-oid>`. If the <old-target> is provided, it checks to see if the\n<ref> targets the <old-target> before the update. If <old-oid> is provided\nit checks <ref> to ensure that it is a regular ref and <old-oid> is the\nOID before the update. This by extension also means that this when a\nzero <old-oid> is provided, it ensures that the ref didn't exist before.\n\nThis command will also support deref mode, to ensure that we can update\ndereferenced regular refs to symrefs.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/git-update-ref.txt |   6 +\n builtin/update-ref.c             |  91 +++++++++++++-\n refs/files-backend.c             |  14 +--\n refs/reftable-backend.c          |   3 +-\n t/t1400-update-ref.sh            | 196 +++++++++++++++++++++++++++++++\n 5 files changed, 299 insertions(+), 11 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt\nindex 7a33f70767..0cd4c37820 100644\n--- a/Documentation/git-update-ref.txt\n+++ b/Documentation/git-update-ref.txt\n@@ -65,6 +65,7 @@ performs all modifications together.  Specify commands of the form:\n \tcreate SP <ref> SP <new-oid> LF\n \tdelete SP <ref> [SP <old-oid>] LF\n \tverify SP <ref> [SP <old-oid>] LF\n+\tsymref-update SP <ref> SP <new-target> [SP (ref SP <old-target> | oid SP <old-oid>)] LF\n \tsymref-create SP <ref> SP <new-target> LF\n \tsymref-delete SP <ref> [SP <old-target>] LF\n \tsymref-verify SP <ref> [SP <old-target>] LF\n@@ -89,6 +90,7 @@ quoting:\n \tcreate SP <ref> NUL <new-oid> NUL\n \tdelete SP <ref> NUL [<old-oid>] NUL\n \tverify SP <ref> NUL [<old-oid>] NUL\n+\tsymref-update SP <ref> NUL <new-target> [NUL (ref NUL <old-target> | oid NUL <old-oid>)] NUL\n \tsymref-create SP <ref> NUL <new-target> NUL\n \tsymref-delete SP <ref> [NUL <old-target>] NUL\n \tsymref-verify SP <ref> [NUL <old-target>] NUL\n@@ -123,6 +125,10 @@ delete::\n \tis provided, verify that the symbolic ref <ref> targets\n \t<old-target> before deleting it.\n \n+symref-update::\n+\tSet <ref> to <new-target> after verifying <old-target> or <old-oid>,\n+\tif given.  Can be used to delete or create symrefs too.\n+\n verify::\n \tVerify <ref> against <old-oid> but do not change it.  If\n \t<old-oid> is zero or missing, the ref must not exist.\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex ae68ffde5e..5001d80ae4 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -98,6 +98,41 @@ static char *parse_next_refname(const char **next)\n \treturn parse_refname(next);\n }\n \n+/*\n+ * Wrapper around parse_arg which skips the next delimiter.\n+ */\n+static char *parse_next_arg(const char **next)\n+{\n+\tstruct strbuf arg = STRBUF_INIT;\n+\n+\tif (line_termination) {\n+\t\t/* Without -z, consume SP and use next argument */\n+\t\tif (!**next || **next == line_termination)\n+\t\t\treturn NULL;\n+\t\tif (**next != ' ')\n+\t\t\tdie(\"expected SP but got: %s\", *next);\n+\t} else {\n+\t\t/* With -z, read the next NUL-terminated line */\n+\t\tif (**next)\n+\t\t\treturn NULL;\n+\t}\n+\t/* Skip the delimiter */\n+\t(*next)++;\n+\n+\tif (line_termination) {\n+\t\t/* Without -z, use the next argument */\n+\t\t*next = parse_arg(*next, &arg);\n+\t} else {\n+\t\t/* With -z, use everything up to the next NUL */\n+\t\tstrbuf_addstr(&arg, *next);\n+\t\t*next += arg.len;\n+\t}\n+\n+\tif (arg.len)\n+\t\treturn strbuf_detach(&arg, NULL);\n+\treturn NULL;\n+}\n+\n \n /*\n  * The value being parsed is <old-oid> (as opposed to <new-oid>; the\n@@ -225,8 +260,8 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tif (*next != line_termination)\n \t\tdie(\"update %s: extra input: %s\", refname, next);\n \n-\tif (ref_transaction_update(transaction, refname,\n-\t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\tif (ref_transaction_update(transaction, refname, &new_oid,\n+\t\t\t\t   have_old ? &old_oid : NULL,\n \t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n@@ -237,6 +272,57 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \tstrbuf_release(&err);\n }\n \n+static void parse_cmd_symref_update(struct ref_transaction *transaction,\n+\t\t\t\t    const char *next, const char *end)\n+{\n+\tchar *refname, *new_target, *old_arg;\n+\tchar *old_target = NULL;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tstruct object_id old_oid;\n+\tint have_old = 0;\n+\n+\trefname = parse_refname(&next);\n+\tif (!refname)\n+\t\tdie(\"symref-update: missing <ref>\");\n+\n+\tnew_target = parse_next_refname(&next);\n+\tif (!new_target)\n+\t\tdie(\"symref-update %s: missing <new-target>\", refname);\n+\n+\told_arg = parse_next_arg(&next);\n+\tif (old_arg) {\n+\t\told_target = parse_next_refname(&next);\n+\t\tif (!old_target)\n+\t\t\tdie(\"symref-update %s: expected old value\", refname);\n+\n+\t\tif (!strcmp(old_arg, \"oid\") &&\n+\t\t    !repo_get_oid(the_repository, old_target, &old_oid)) {\n+\t\t\told_target = NULL;\n+\t\t\thave_old = 1;\n+\t\t} else if (strcmp(old_arg, \"ref\"))\n+\t\t\tdie(\"symref-update %s: invalid arg '%s' for old value\", refname, old_arg);\n+\t}\n+\n+\n+\tif (*next != line_termination)\n+\t\tdie(\"symref-update %s: extra input: %s\", refname, next);\n+\n+\n+\tif (ref_transaction_update(transaction, refname, NULL,\n+\t\t\t\t   have_old ? &old_oid : NULL,\n+\t\t\t\t   new_target, old_target,\n+\t\t\t\t   update_flags |= create_reflog_flag,\n+\t\t\t\t   msg, &err))\n+\t\tdie(\"%s\", err.buf);\n+\n+\tupdate_flags = default_flags;\n+\tfree(refname);\n+\tfree(old_arg);\n+\tfree(old_target);\n+\tfree(new_target);\n+\tstrbuf_release(&err);\n+}\n+\n static void parse_cmd_create(struct ref_transaction *transaction,\n \t\t\t     const char *next, const char *end)\n {\n@@ -505,6 +591,7 @@ static const struct parse_cmd {\n \t{ \"create\",        parse_cmd_create,        2, UPDATE_REFS_OPEN },\n \t{ \"delete\",        parse_cmd_delete,        2, UPDATE_REFS_OPEN },\n \t{ \"verify\",        parse_cmd_verify,        2, UPDATE_REFS_OPEN },\n+\t{ \"symref-update\", parse_cmd_symref_update, 4, UPDATE_REFS_OPEN },\n \t{ \"symref-create\", parse_cmd_symref_create, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-delete\", parse_cmd_symref_delete, 2, UPDATE_REFS_OPEN },\n \t{ \"symref-verify\", parse_cmd_symref_verify, 2, UPDATE_REFS_OPEN },\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex f5e271a442..59d1ab3eeb 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2386,7 +2386,8 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_target, update->old_target,\n+\t\t\tupdate->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2610,7 +2611,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif (update->new_target) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n \t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n@@ -2628,12 +2629,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t * phase of the transaction only needs to commit the lock.\n \t\t */\n \t\tupdate->flags |= REF_NEEDS_COMMIT;\n-\t}\n-\n-\n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex e203c697f2..a00f55802a 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -908,7 +908,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\t\tu->old_target, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 452fc1da50..3cfef9fbe3 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -1360,6 +1360,7 @@ test_expect_success 'fails with duplicate HEAD update' '\n '\n \n test_expect_success 'fails with duplicate ref update via symref' '\n+\ttest_when_finished \"git symbolic-ref -d refs/heads/symref2\" &&\n \tgit branch target2 $A &&\n \tgit symbolic-ref refs/heads/symref2 refs/heads/target2 &&\n \tcat >stdin <<-EOF &&\n@@ -1852,6 +1853,201 @@ do\n \t\tgit reflog exists refs/heads/symref\n \t'\n \n+\ttest_expect_success \"stdin ${type} symref-update fails with too many arguments\" '\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"ref\" \"$a\" \"$a\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test \"$type\" = \"-z\"\n+\t\tthen\n+\t\t\tgrep \"fatal: unknown command: $a\" err\n+\t\telse\n+\t\t\tgrep \"fatal: symref-update refs/heads/symref: extra input:  $a\" err\n+\t\tfi\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update fails with wrong old value argument\" '\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"foo\" \"$a\" \"$a\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tgrep \"fatal: symref-update refs/heads/symref: invalid arg ${SQ}foo${SQ} for old value\" err\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update creates with zero old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"oid\" \"$Z\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update creates with no old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update creates dangling\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\ttest_must_fail git rev-parse refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"refs/heads/nonexistent\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo refs/heads/nonexistent >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update fails with wrong old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" \"ref\" \"$b\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin 2>err &&\n+\t\tif test_have_prereq REFTABLE\n+\t\tthen\n+\t\t\tgrep \"fatal: verifying symref target: ${SQ}refs/heads/symref${SQ}: is at $a but expected $b\" err\n+\t\telse\n+\t\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: is at $a but expected $b\" err\n+\t\tfi &&\n+\t\ttest_must_fail git rev-parse --verify -q $c\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update updates dangling ref\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\ttest_must_fail git rev-parse refs/heads/nonexistent &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update updates dangling ref with old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\ttest_must_fail git rev-parse refs/heads/nonexistent &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"ref\" \"refs/heads/nonexistent\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update fails update dangling ref with wrong old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\ttest_must_fail git rev-parse refs/heads/nonexistent &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/nonexistent &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"ref\" \"refs/heads/wrongref\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo refs/heads/nonexistent >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update works with right old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" \"ref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $m >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update works with no old value\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" >stdin &&\n+\t\tgit update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $m >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update fails with empty old ref-target\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$m\" \"ref\" \"\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} --no-deref <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update creates (with deref)\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref --stdin ${type} <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\t\tgrep \"$Z $(git rev-parse $a)\" actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update regular ref to symref with correct old-oid\" '\n+\t\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\t\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" \"oid\" \"$(git rev-parse $a)\" >stdin &&\n+\t\tgit update-ref --stdin ${type} <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\t\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update regular ref to symref fails with wrong old-oid\" '\n+\t\ttest_when_finished \"git update-ref -d refs/heads/regularref\" &&\n+\t\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" \"oid\" \"$(git rev-parse refs/heads/target2)\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\techo $(git rev-parse $a) >expect &&\n+\t\tgit rev-parse refs/heads/regularref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update existing symref with zero old-oid\" '\n+\t\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/symref\" &&\n+\t\tgit symbolic-ref refs/heads/symref refs/heads/target2 &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" \"oid\" \"$Z\" >stdin &&\n+\t\ttest_must_fail git update-ref --stdin ${type} <stdin 2>err &&\n+\t\tgrep \"fatal: cannot lock ref ${SQ}refs/heads/symref${SQ}: reference already exists\" err &&\n+\t\techo refs/heads/target2 >expect &&\n+\t\tgit symbolic-ref refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update regular ref to symref (with deref)\" '\n+\t\ttest_when_finished \"git symbolic-ref -d refs/heads/symref\" &&\n+\t\ttest_when_finished \"git update-ref -d --no-deref refs/heads/symref2\" &&\n+\t\tgit update-ref refs/heads/symref2 $a &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/symref refs/heads/symref2 &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/symref\" \"$a\" >stdin &&\n+\t\tgit update-ref ${type} --stdin <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/symref2 >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\techo refs/heads/symref2 >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/symref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/symref >actual &&\n+\t\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+\t'\n+\n+\ttest_expect_success \"stdin ${type} symref-update regular ref to symref\" '\n+\t\ttest_when_finished \"git symbolic-ref -d --no-recurse refs/heads/regularref\" &&\n+\t\tgit update-ref --no-deref refs/heads/regularref $a &&\n+\t\tcreate_stdin_buf ${type} \"symref-update refs/heads/regularref\" \"$a\" >stdin &&\n+\t\tgit update-ref ${type} --stdin <stdin &&\n+\t\techo $a >expect &&\n+\t\tgit symbolic-ref --no-recurse refs/heads/regularref >actual &&\n+\t\ttest_cmp expect actual &&\n+\t\ttest-tool ref-store main for-each-reflog-ent refs/heads/regularref >actual &&\n+\t\tgrep \"$(git rev-parse $a) $(git rev-parse $a)\" actual\n+\t'\n+\n done\n \n test_done\n-- \n2.43.GIT\n\n"},{"id":"493521","messageId":"20240426152449.228860-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v4 7/7] ref: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-26T15:24:49Z","receivedAt":"2024-04-26T15:25:02Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In the previous commits, we added symref support for\nvarious commands in `git-update-ref`. While it allowed us to now\nmanipulate symbolic refs via `git-update-ref`, it didn't activate the\n'reference-transaction' hook.\n\nLet's activate the hook for symbolic reference updates too. There is no\nnew format described for this and we stick to the existing format of:\n    <old-value> SP <new-value> SP <ref-name> LF\nbut now, <old-value> and <new-value> could also denote references\ninstead of objects, where the format is similar to that in\n'git-update-ref', i.e. 'ref:<ref-target>'.\n\nWhile this seems to be backward incompatible, it is okay, since the only\nway the `reference-transaction` hook has refs in its output is when\n`git-update-ref` is used to manipulate symrefs. Also the documentation\nfor reference-transaction hook always stated that support for symbolic\nreferences may be added in the future.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt       | 14 +++++++----\n refs.c                           | 21 ++++++++--------\n t/t1416-ref-transaction-hooks.sh | 41 ++++++++++++++++++++++++++++++++\n 3 files changed, 61 insertions(+), 15 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..0bf8ca87a6 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also cover symbolic references.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects, denoted via the\n+`ref:<ref-target>` format.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 42cb4126a7..9a510744a7 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2365,18 +2365,19 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tstruct ref_update *update = transaction->updates[i];\n+\t\tstrbuf_reset(&buf);\n \n-\t\t/*\n-\t\t * Skip reference transaction for symbolic refs.\n-\t\t */\n-\t\tif (update->new_target || update->old_target)\n-\t\t\tcontinue;\n+\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n \n-\t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..0a7e86062e 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,45 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+# This test doesn't add a check for symref 'delete' since there is a\n+# variation between the ref backends WRT 'delete'. In the files backend,\n+# 'delete' also triggers an additional transaction update on the\n+# packed-refs backend, which constitutes additional reflog entries.\n+test_expect_success 'hook gets all queued symref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\tgit update-ref refs/heads/branch $POST_OID &&\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\tgit symbolic-ref refs/heads/symrefu refs/heads/main &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tcat >expect <<-EOF &&\n+\t\tprepared\n+\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n+\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n+\t\tcommitted\n+\t\tref:refs/heads/main $ZERO_OID refs/heads/symref\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symrefc\n+\t\tref:refs/heads/main ref:refs/heads/branch refs/heads/symrefu\n+\tEOF\n+\n+\tgit update-ref --no-deref --stdin <<-EOF &&\n+\t\tstart\n+\t\tsymref-verify refs/heads/symref refs/heads/main\n+\t\tsymref-create refs/heads/symrefc refs/heads/main\n+\t\tsymref-update refs/heads/symrefu refs/heads/branch ref refs/heads/main\n+\t\tprepare\n+\t\tcommit\n+\tEOF\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493527","messageId":"xmqq1q6rc44n.fsf@gitster.g","threadId":"61235","inReplyTo":"20240426152449.228860-2-knayak@gitlab.com","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-26T19:31:36Z","receivedAt":"2024-04-26T19:31:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n> Subject: Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`\n>\n> The `ref_transaction[_add]_update` functions obtain ref information and\n> flags to create a `ref_update` and add it to the transaction at hand.\n\nJust a very minor irritation, but ref_transaction_add_update() is a\nfunction used internally in the ref subsystem and is exported only\nbecause its visibility needs to cross file boundaries between refs.c\nand refs/*backend.c files.\n\nIt would be better to only mention ref_transaction_update() in the\ntitle, and talk about the need to make matching adjustment to\nref_transaction_add_update(), which is an internal function, in the\nbody of the log message.\n\nThis is an unrelated #leftoverbits tangent, but while trying to find\nout the reason why \"[_add]\" in the title looked irritating to me, I\nnoticed that builtin/show-ref.c includes <refs/refs-internal.h>.  I\ndo not know what it uses from the \"internal\" implementation detail,\nbut the API may have to be cleaned up so that a random \"client\"\ncaller do not have to do so.\n\nThe patch itself looked good.  Thanks.\n\n"},{"id":"493531","messageId":"20240426204145.GC13703@coredump.intra.peff.net","threadId":"61235","inReplyTo":"CAOLa=ZRk8QR4qkbkRm6AirapdrAqz6bG-tXfY3zRQU_9XkJM1Q@mail.gmail.com","subject":"Re: [PATCH v3 0/8] refs: add symref support to 'git-update-ref'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-26T20:41:45Z","receivedAt":"2024-04-26T20:41:46Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Wed, Apr 24, 2024 at 09:25:27AM -0700, Karthik Nayak wrote:\n\n> This also brings light onto the previous versions we were considering:\n> \n>     symref-update SP <ref> SP <new-target> [SP (<old-target> | <old-oid>)] LF\n> \n> There is also some ambiguity here which we missed, especially when we\n> support dangling refs. If we're updating a dangling ref <ref>, and we\n> provide an old value. Then there is uncertainty around whether the\n> provided value is actually a <old-target> or if it's an <old-oid>.\n> \n> For non dangling ref symref, we first parse it as an <old-target> and\n> since the <old-target> would exist, we can move on.\n> \n> So I see two ways to go about this,\n> \n> 1. In the symref-update function, we need to parse and see if <ref> is a\n> regular ref or a symref, if it is symref, we simply set the provided old\n> value as <old-target>, if not, we set it as <old-oid>. This seems clunky\n> because we'll be parsing the ref and trying to understand its type in\n> 'update-ref.c', before the actual update.\n\nI think this avoids the \"mischief\" case I mentioned because it is about\nlooking at what is in <ref> currently, not spooky action from a\npossibly-unrelated \"refs/heads/ref\". But in general, I think it is a\ngood thing if we can tell what the caller is asking for based only on\nthe syntax of the request, without taking into account repository state.\nIt just makes things easier to reason about.\n\n> 2. We change the syntax to something like\n> \n>     symref-update SP <ref> SP <new-ref> [SP (ref <old-target> | oid\n> <old-oid>)] LF\n> \n> this would remove any ambiguity since the user specifies the data type\n> they're providing.\n\nYeah, I was going to suggest that it could be resolved with any syntax\nthat would not be a valid oid name. Certainly check-ref-format places\nsome restrictions there (e.g., no \"^\") but name resolution relies on\nthat, too (so foo^{tree} is a valid name). Probably something like\n\"^foo\" is unambiguous, but it's ugly and hard to explain. ;)\n\nBut I think your \"ref <old-target>\" solves that. Resolved names can have\nspaces in them, but only after a \"^\" (e.g., \"foo^{/some regex}\") or \":\"\n(e.g., \"foo:path with spaces\"). So seeing just \"ref\" by itself, followed\nby a space, I think is unambiguous. And it looks pretty.\n\nIt gets a little tricky when the field delimiter is also space, and the\nitem in question is not the final field. See below.\n\n> Also on a sidenote, it's worth considering that with the direction of\n> [2], we could also extrapolate to introduce {verify, update, create,\n> delete} v2, which support both symrefs and regular refs. But require\n> explicit types from the user:\n> \n>     update-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n> [(oid <old-oid> | ref <old-target>)] NUL\n> \tcreate-v2 SP <ref> NUL (oid <new-oid> | ref <new-target>) NUL\n> \tdelete-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n> \tverify-v2 SP <ref> NUL [(oid <old-oid> | ref <old-target>)] NUL\n> \n> This is similar to the v3 patches I've currently sent out, in that it\n> would also allow cross operations between regular refs and symrefs.\n\nSo I _think_ that \"<oid> | ref <symref-target>\" is unambiguous. In which\ncase you could just have:\n\n  update SP <ref> NUL (<new-oid> | ref <new-target>) NUL [(<old-oid> | ref <old-target>)]\n\nwhich is backwards-compatible.\n\nWith the NUL separator it's easy to parse, because \"ref \" with a\ntrailing space always means a ref, even in the \"new\" field. But with\nspaces instead, it gets weird. If you have:\n\n  update refs/heads/foo refs refs/heads/bar\n\nit can either be creating a symref to \"bar\" (with no \"old\" specifier) or\nit could be pointing it at the resolved-name \"refs\", which the old value\ncoming from \"bar\".\n\nI guess one option would be to only allow \"ref\" syntax in \"-z\" mode, but\nthat is probably getting to be a bit weird and hard to explain.\n\n-Peff\n"},{"id":"493532","messageId":"20240426211529.GD13703@coredump.intra.peff.net","threadId":"61235","inReplyTo":"xmqq1q6rc44n.fsf@gitster.g","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-26T21:15:29Z","receivedAt":"2024-04-26T21:15:31Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:\n\n> This is an unrelated #leftoverbits tangent, but while trying to find\n> out the reason why \"[_add]\" in the title looked irritating to me, I\n> noticed that builtin/show-ref.c includes <refs/refs-internal.h>.  I\n> do not know what it uses from the \"internal\" implementation detail,\n> but the API may have to be cleaned up so that a random \"client\"\n> caller do not have to do so.\n\nThere are two issues. One is the use of refs_read_raw_ref(), added by\nPatrick's 9080a7f178 (builtin/show-ref: add new mode to check for\nreference existence, 2023-10-31). And it argues there why the regular\nAPI is unsufficient (mostly because it does not protect errno).\n\nBut the more interesting one is a call to refname_is_safe(), added\nrecently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,\n2024-02-07). Looking at that commit, the intent was to allow pseudo-refs\nby loosening the conditional that checked \"HEAD\" to allow \"FOO_BAR\" but\nnot \"foobar\" outside of \"refs/\". We enforce the all-caps pseudoref\nsyntax in is_refname_safe().\n\nThe proper API there is I think check_ref_format() with ALLOW_ONELEVEL.\nBut you shouldn't need to do that, because the refs code should be\nchecking the names itself (using check_ref_format() usually, but falling\nback to refname_is_safe() if the ALLOW_BAD_NAME flag is passed).\n\nAnd I think there _is_ a bug there. The idea of those two functions is\nthat check_ref_format() would allow a subset of what refname_is_safe()\ndoes. We'd fall back to the latter when deleting, but not otherwise\nallow creation or updates.\n\nHowever, it looks like check_ref_format() doesn't enforce the pseudo-ref\nsyntax. It will happily resolve this:\n\n  git rev-parse HEAD >.git/foo\n  git rev-parse foo\n\nand even update it:\n\n  git update-ref foo HEAD\n\nthough curiously we will refuse to delete it:\n\n  $ git update-ref -d foo\n  error: refusing to update ref with bad name 'foo'\n\nsince that sets the ALLOW_BAD_NAME flag!\n\nIMHO these should _all_ be forbidden, because we only want to allow the\nmore limited pseudoref names everywhere (and never mischievous ones like\n\"config\" or whatever). And once we are doing that, then show-ref has no\nneed to check the format. It can just call read_ref() and it either gets\nan answer or doesn't.\n\nI don't know if that is a #leftoverbit though. It perhaps more\ncomplicated than that.\n\n-Peff\n"},{"id":"493533","messageId":"xmqqmspf9537.fsf@gitster.g","threadId":"61235","inReplyTo":"20240426152449.228860-3-knayak@gitlab.com","subject":"Re: [PATCH v4 2/7] files-backend: extract out `create_symref_lock`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-26T21:39:08Z","receivedAt":"2024-04-26T21:39:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> The function `create_symref_locked` creates a symref by creating a\n> '<symref>.lock' file and then committing the symref lock, which creates\n> the final symref.\n>\n> Split this into two individual functions `create_and_commit_symref` and\n> `create_symref_locked`. This way we can create the symref lock and\n> commit it at different times. This will be used to provide symref\n> support in `git-update-ref(1)`.\n\nIt is a confusing way to describe what this patch did, though.  If\nyou truly splitted create_symref_locked() into two, you would have\nfunctions A and B, and existing callers of create_symref_locked()\nwould be changed to call A() and then B().  I do not think such a\nsplit would make sense in this case, but the above description gives\nan impression that it was what you did.\n\nIn reality, an early part of create_symref_locked() was made into a\nseparate helper function that can be called from callers other than\ncreate_symref_locked(), and because the helper got a name too\nsimilar to the original, you had to rename create_symref_locked() to\ncreate_and_commit_symref().  The existing callers of it are not\naffected, modulo the name change.\n\nPerhaps\n\n    Split the early half of create_symref_locked() into a new helper\n    funciton create_symref_lock().  Because the name of the new\n    function is too similar to the original, rename the original to\n    create_and_commit_symref() to avoid confusion.\n\n    The new helper will be used to ...\n\nor something?\n\n> -static int create_symref_locked(struct files_ref_store *refs,\n> -\t\t\t\tstruct ref_lock *lock, const char *refname,\n> -\t\t\t\tconst char *target, const char *logmsg)\n> +static int create_symref_lock(struct files_ref_store *refs,\n> +\t\t\t      struct ref_lock *lock, const char *refname,\n> +\t\t\t      const char *target)\n>  {\n> +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> +\t\treturn error(\"unable to fdopen %s: %s\",\n> +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> +\n> +\t/* no error check; commit_ref will check ferror */\n> +\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n\nThis was a bit puzzling (see below).\n\n> +\treturn 0;\n> +}\n> +\n> +static int create_and_commit_symref(struct files_ref_store *refs,\n> +\t\t\t\t    struct ref_lock *lock, const char *refname,\n> +\t\t\t\t    const char *target, const char *logmsg)\n> +{\n> +\tint ret;\n> +\n>  \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n>  \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>  \t\treturn 0;\n>  \t}\n\n    Offtopic: we might want to start planning to deprecate creation\n    of \"symlink refs\".  Linus originally used a symlink for\n    .git/HEAD, but 9f0bb90d (core.prefersymlinkrefs: use symlinks\n    for .git/HEAD, 2006-05-02) made it default not to use of\n    symbolic links.  As long as we preserve the ability to work on a\n    repository whose HEAD still uses a symbolic link, I'd hope\n    nothing would break (#leftoverbits).\n\nLet me rearrange this hunk to show the original first:\n\n> -\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> -\t\treturn error(\"unable to fdopen %s: %s\",\n> -\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> -\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n> -\t/* no error check; commit_ref will check ferror */\n> -\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n> -\tif (commit_ref(lock) < 0)\n> -\t\treturn error(\"unable to write symref for %s: %s\", refname,\n> -\t\t\t     strerror(errno));\n\nThe original in create_symref_locked() created a lockfile, called\nupdate_symref_reflog(), and called commit_ref() to commit the thing.\n\nThe \"no error check\" comment is about detecting an error while\nwriting into the lock file.  It came from 370e5ad6 (create_symref:\nuse existing ref-lock code, 2015-12-29).  Because the fprintf() call\nwas immediately followed by commit_ref(), and the code assumed that\ncommit_ref() will check ferror(), we do not bother checking if the\nfprintf() call fails to write the contents correctly.\n\n> +\tret = create_symref_lock(refs, lock, refname, target);\n> +\tif (!ret) {\n> +\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>  \n> +\t\tif (commit_ref(lock) < 0)\n> +\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n> +\t\t\t\t     strerror(errno));\n> +\t}\n\nThe new code lets create_symref_lock() to create a lockfile, and\ndoes the rest here.  commit_ref() does call commit_lock_file(),\nwhich eventually passes the control to close_tempfile() and a\nwrite error can be detected there.\n\nBut the point of this patch is that the creation of the locked\nsymref file PLUS writing its new contents (which is done by\ncreate_symref_lock()) can be done way ahead of the remainder that\neventually does commit_ref().  So it smells a bit[*] dubious that we\nstill leave the error from fprintf() ignored in the \"early half\" in\nthe rearranged code.\n\n\tSide note: it is a \"bit\", as it is unlikely that we will do\n\tsomething to clear the ferror() from the (FILE *) in the\n\tmeantime.\n\n> @@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n>  \t\treturn -1;\n>  \t}\n>  \n> -\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n> +\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n> +\n>  \tunlock_ref(lock);\n>  \treturn ret;\n>  }\n\nThis hunk shows the \"original function was renamed; there is no\nother changes visible to the caller\" nature of this rearrangement.\n\nThe extra blank line is probably a nice touch.\n"},{"id":"493537","messageId":"xmqq5xw37n6t.fsf@gitster.g","threadId":"61235","inReplyTo":"20240426152449.228860-4-knayak@gitlab.com","subject":"Re: [PATCH v4 3/7] update-ref: add support for 'symref-verify' command","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-26T22:51:06Z","receivedAt":"2024-04-26T22:51:09Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> In the previous commits, we added the required base for adding symref\n> commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using\n> them, add a new 'symref-verify' command to verify symrefs.\n>\n> The 'symref-verify' command allows users to verify if a provided <ref>\n> contains the provided <old-target> without changing the <ref>. If\n> <old-target> is not provided, the command will verify that the <ref>\n> doesn't exist. Since we're checking for symbolic refs, this command will\n> only work with the 'no-deref' mode. This is because any dereferenced\n> symbolic ref will point to an object and not a ref and the regular\n> 'verify' command can be used in such situations.\n\nAll makes sense, but a naïve reader may find it helpful if you\nexplained why having \"verify\" command is a good idea in the first\nplace (\"I can just do 'git symoblic-ref' to read the current value,\nand see if it is what I expect\").  Presumably the value of \"verify\"\nis that you can have it in a transaction and fail other operations\nin the same transaction if the symref moved from what you expected\nit to point at?\n\n> Add and use `ref_update_is_null_new_value`, a helper function which is\n> used to check if there is a new_value in a reference update. The new\n> value could either be a symref target `new_target` or a OID `new_oid`.\n> We also add tests to test the command in both the regular stdin mode and\n> also with the '-z' flag.\n\nThis looks out of place, primarily because the helper function is\n*NOT* used in this step.  Without any actual user, and with the name\nthat says only what it checks without hinting why a caller may want\nto check the condition it checks, it is hard to guess if it is a\ngood idea to have such a helper.\n\n\"If a ref_update object specifies no new-oid and no new-target, it\nis not about updating but just validating\" is how the callers are\nexpected to use it, then instead of is_null_new_value that says\nwhat it checks, something like is_verify_only that says what the\ncaller may want to use it for would be a more friendly name for\nreaders and future developers.\n\n> @@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n>  \t\tdie(\"verify %s: extra input: %s\", refname, next);\n>  \n>  \tif (ref_transaction_verify(transaction, refname, &old_oid,\n> -\t\t\t\t   update_flags, &err))\n> +\t\t\t\t   NULL, update_flags, &err))\n>  \n>  \tupdate_flags = default_flags;\n>  \tfree(refname);\n>  \tstrbuf_release(&err);\n>  }\n\nThe only damage by this patch to parse_cmd_verify() is that\nref_transaction_verify() gained another parameter NULL, but with the\ndefault \"--diff-algorithm=myers\" algorithm, it is very hard to see.\n\nThe \"--patience\" algorithm does a much beter job on this hunk.\n\nAnd the following function is entirely new.\n\n> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n> +\t\t\t\t    const char *next, const char *end)\n> +{\n> +\tstruct strbuf err = STRBUF_INIT;\n> +\tstruct object_id old_oid;\n> +\tchar *refname, *old_target;\n> +\n> +\tif (!(update_flags & REF_NO_DEREF))\n> +\t\tdie(\"symref-verify: cannot operate with deref mode\");\n> +\n> +\trefname = parse_refname(&next);\n> +\tif (!refname)\n> +\t\tdie(\"symref-verify: missing <ref>\");\n> +\n> +\t/*\n> +\t * old_ref is optional, but we want to differentiate between\n> +\t * a NULL and zero value.\n> +\t */\n> +\told_target = parse_next_refname(&next);\n> +\tif (!old_target)\n> +\t\told_oid = *null_oid();\n\nIn many existing code paths, we do not do structure assignment like\nthis. Instead we do\n\n\t\toidcpy(&old_oid, null_oid());\n\nWe can see an existing example in a common context in a hunk for\nrefs.c in this patch.\n\n> +\tif (*next != line_termination)\n> +\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n> +\n> +\tif (ref_transaction_verify(transaction, refname,\n> +\t\t\t\t   old_target ? NULL : &old_oid,\n> +\t\t\t\t   old_target, update_flags, &err))\n> +\t\tdie(\"%s\", err.buf);\n\nAre static analyzers smart enough to notice that we will not be\nusing old_oid uninitialized here?  Just wondering.\n\nAnyway.  This ensures ref_transaction_verify() gets either\nold_target or old_oid, but never both at the same time.  The caller\nto ref_transaction_verify() in the previous function passed NULL for\nold_target but it always had a non-NULL old_oid so that is perfectly\nfine.\n\n> +\tupdate_flags = default_flags;\n> +\tfree(refname);\n> +\tfree(old_target);\n> +\tstrbuf_release(&err);\n> +}\n\n> diff --git a/refs.c b/refs.c\n> index 060a31616d..0e1013b5ab 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n>  \n>  \tfor (i = 0; i < transaction->nr; i++) {\n>  \t\tfree(transaction->updates[i]->msg);\n> +\t\tfree((void *)transaction->updates[i]->old_target);\n> +\t\tfree((void *)transaction->updates[i]->new_target);\n>  \t\tfree(transaction->updates[i]);\n>  \t}\n>  \tfree(transaction->updates);\n> @@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n>  \n>  \tupdate->flags = flags;\n>  \n> -\tif (flags & REF_HAVE_NEW)\n> +\tif (new_target)\n> +\t\tupdate->new_target = xstrdup(new_target);\n> +\tif (old_target)\n> +\t\tupdate->old_target = xstrdup(old_target);\n\nPresumably \"update\" structure, when freshly initialized, has NULL in\nboth of these _target members?  Otherwise ref_transaction_free()\nwould get in trouble, so double checking.\n\n> +\tif (new_oid && flags & REF_HAVE_NEW)\n>  \t\toidcpy(&update->new_oid, new_oid);\n> -\tif (flags & REF_HAVE_OLD)\n> +\tif (old_oid && flags & REF_HAVE_OLD)\n>  \t\toidcpy(&update->old_oid, old_oid);\n\nSince we can ask to work on a symbolic ref, new_oid / old_oid can be\nNULL when REF_HAVE_NEW / REF_HAVE_OLD bit is on for _target members.\n\nMakes me wonder if the code becomes easier to follow if the flag\nbits are split into four (_NEW -> _NEW_OID + _NEW_TARGET), but let's\nnot worry about that for now.\n\n> @@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>  \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n>  \n>  \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n> +\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n\n> @@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n>  int ref_transaction_verify(struct ref_transaction *transaction,\n>  \t\t\t   const char *refname,\n>  \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *old_target,\n>  \t\t\t   unsigned int flags,\n>  \t\t\t   struct strbuf *err)\n>  {\n> -\tif (!old_oid)\n> -\t\tBUG(\"verify called with old_oid set to NULL\");\n> +\tif (!old_target && !old_oid)\n> +\t\tBUG(\"verify called with old_oid and old_target set to NULL\");\n\nIs it normal if you get _both_ set, or is it equally a BUG()?\nThe parse_*_verify() codepaths we saw earlier both made sure\nonly one of the two is non-NULL, and it is unclear what should\nhappen if both are non-NULL.\n\n> +\tif (old_target && !(flags & REF_NO_DEREF))\n> +\t\tBUG(\"verify cannot operate on symrefs with deref mode\");\n>  \treturn ref_transaction_update(transaction, refname,\n>  \t\t\t\t      NULL, old_oid,\n> -\t\t\t\t      NULL, NULL,\n> +\t\t\t\t      NULL, old_target,\n>  \t\t\t\t      flags, NULL, err);\n>  }\n\nSo this queues an ref_update object whose .new_oid and .new_target\nare NULL, and .old_oid and .old_target are what the caller gave us\nto check.  The NULLs in .new* members hopefully do not mean \"delete\nthis thing\" ;-)\n\n> @@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n>  \tfor (i = 0; i < transaction->nr; i++) {\n>  \t\tstruct ref_update *update = transaction->updates[i];\n>  \n> +\t\t/*\n> +\t\t * Skip reference transaction for symbolic refs.\n> +\t\t */\n> +\t\tif (update->new_target || update->old_target)\n> +\t\t\tcontinue;\n\nIs that a final design, or will the hooks have a chance to interfere?\n\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 2420dac2aa..53197fa3af 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)\n>  \treturn update->refname;\n>  }\n>  \n> +/*\n> + * Check whether the REF_HAVE_OLD and old_target values stored in\n> + * update are consistent with ref, which is the symbolic reference's\n> + * current value. If everything is OK, return 0; otherwise, write an\n> + * error message to err and return -1.\n> + */\n> +static int check_old_target(struct ref_update *update, char *ref,\n> +\t\t\t    struct strbuf *err)\n> +{\n> +\tif (!(update->flags & REF_HAVE_OLD) ||\n> +\t    !strcmp(update->old_target, ref))\n> +\t\treturn 0;\n\nEarlier on the assignment side for \"update\" structure we saw above,\nthe guard was (old_target && flags & REF_HAVE_OLD), but here we\nassume old_target is valid, which feels a bit asymmetric.\n\nYes, I can see that the caller does not call us when !old_target,\nbut still...  Perhaps\n\n\tif ((update->flags & REF_HAVE_OLD) && !update->old_target)\n\t\tBUG(...);\n\nor something?  Or alternatively, perhaps !!update->old_target should\nbe the only thing we should check and ignore REF_HAVE_OLD bit?  I am\nnot sure, but it smells like that the non-NULL-ness of old_target is\nthe only thing that matters (if it is not NULL, very early in the\ncontrol flow somebody would have set REF_HAVE_OLD bit to flags, no?).\n\nIt brings me back to my earlier question.  Does REF_HAVE_OLD bit\nserve a useful purpose in this code?\n\n> +\tif (!strcmp(update->old_target, \"\"))\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"reference already exists\",\n> +\t\t\t    original_update_refname(update));\n> +\telse if (!strcmp(ref, \"\"))\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    update->old_target);\n\nSo... for old_target and ref, an empty string is a special value?\nHow?  Shouldn't that be documented in the comment before the\nfunction?\n\n> +\telse\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    ref, update->old_target);\n> +\n> +\treturn -1;\n> +}\n> +\n>  /*\n>   * Check whether the REF_HAVE_OLD and old_oid values stored in update\n>   * are consistent with oid, which is the reference's current value. If\n> @@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>  \t\t\t\t\tgoto out;\n>  \t\t\t\t}\n> +\t\t\t}\n> +\n> +\t\t\t/*\n> +\t\t\t * For symref verification, we need to check the reference value\n> +\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n> +\t\t\t * verifying a dereferenced symref, we then check the oid.\n> +\t\t\t */\n> +\t\t\tif (update->old_target) {\n> +\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n> +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n\nWe come here only when update->type has REF_ISSYMREF bit on (we\nlearned that value by calling lock_raw_ref()), and know referent.buf\nhas the current \"target\" value.  That is consumed as \"ref\" parameter\nto check_old_target() we just saw.  OK.\n\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 6104471199..a2474245aa 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>  \t\t * individual refs. But the error messages match what the files\n>  \t\t * backend returns, which keeps our tests happy.\n>  \t\t */\n> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n> +\t\tif ((u->flags & REF_HAVE_OLD) && u->old_target) {\n> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n> +\t\t\t\tif (!strcmp(u->old_target, \"\"))\n> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n> +\t\t\t\t\t\t    \"provided target is empty\",\n> +\t\t\t\t\t\t    original_update_refname(u));\n> +\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    u->old_target);\n> +\t\t\t\telse\n> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    referent.buf, u->old_target);\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto done;\n> +\t\t\t}\n\nAgain, the puzzling \"empty string\"s are handled here.\n\n"},{"id":"493557","messageId":"CAOLa=ZTRZ37X-u3cPMKRbyEW2pWw0hpdQf=fdiFJMvFz2Oh5kw@mail.gmail.com","threadId":"61235","inReplyTo":"xmqq1q6rc44n.fsf@gitster.g","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-28T19:36:12Z","receivedAt":"2024-04-28T19:36:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>> Subject: Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`\n>>\n>> The `ref_transaction[_add]_update` functions obtain ref information and\n>> flags to create a `ref_update` and add it to the transaction at hand.\n>\n> Just a very minor irritation, but ref_transaction_add_update() is a\n> function used internally in the ref subsystem and is exported only\n> because its visibility needs to cross file boundaries between refs.c\n> and refs/*backend.c files.\n\nYes that is true. I'll amend this for the next version. Thanks\n"},{"id":"493558","messageId":"CAOLa=ZS8wZD88B2CFYUOKX3BkHAoWgA65qYJXV+83v-s=A-woQ@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqmspf9537.fsf@gitster.g","subject":"Re: [PATCH v4 2/7] files-backend: extract out `create_symref_lock`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-28T19:57:58Z","receivedAt":"2024-04-28T19:57:59Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The function `create_symref_locked` creates a symref by creating a\n>> '<symref>.lock' file and then committing the symref lock, which creates\n>> the final symref.\n>>\n>> Split this into two individual functions `create_and_commit_symref` and\n>> `create_symref_locked`. This way we can create the symref lock and\n>> commit it at different times. This will be used to provide symref\n>> support in `git-update-ref(1)`.\n>\n> It is a confusing way to describe what this patch did, though.  If\n> you truly splitted create_symref_locked() into two, you would have\n> functions A and B, and existing callers of create_symref_locked()\n> would be changed to call A() and then B().  I do not think such a\n> split would make sense in this case, but the above description gives\n> an impression that it was what you did.\n>\n> In reality, an early part of create_symref_locked() was made into a\n> separate helper function that can be called from callers other than\n> create_symref_locked(), and because the helper got a name too\n> similar to the original, you had to rename create_symref_locked() to\n> create_and_commit_symref().  The existing callers of it are not\n> affected, modulo the name change.\n>\n> Perhaps\n>\n>     Split the early half of create_symref_locked() into a new helper\n>     funciton create_symref_lock().  Because the name of the new\n>     function is too similar to the original, rename the original to\n>     create_and_commit_symref() to avoid confusion.\n>\n>     The new helper will be used to ...\n>\n> or something?\n>\n\nThanks. I agree with what you're saying. I would also s/Split/Extract\nperhaps because it drives the point better.\n\n>> -static int create_symref_locked(struct files_ref_store *refs,\n>> -\t\t\t\tstruct ref_lock *lock, const char *refname,\n>> -\t\t\t\tconst char *target, const char *logmsg)\n>> +static int create_symref_lock(struct files_ref_store *refs,\n>> +\t\t\t      struct ref_lock *lock, const char *refname,\n>> +\t\t\t      const char *target)\n>>  {\n>> +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n>> +\t\treturn error(\"unable to fdopen %s: %s\",\n>> +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> +\n>> +\t/* no error check; commit_ref will check ferror */\n>> +\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n>\n> This was a bit puzzling (see below).\n>\n>> +\treturn 0;\n>> +}\n>> +\n>> +static int create_and_commit_symref(struct files_ref_store *refs,\n>> +\t\t\t\t    struct ref_lock *lock, const char *refname,\n>> +\t\t\t\t    const char *target, const char *logmsg)\n>> +{\n>> +\tint ret;\n>> +\n>>  \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n>>  \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>>  \t\treturn 0;\n>>  \t}\n>\n>     Offtopic: we might want to start planning to deprecate creation\n>     of \"symlink refs\".  Linus originally used a symlink for\n>     .git/HEAD, but 9f0bb90d (core.prefersymlinkrefs: use symlinks\n>     for .git/HEAD, 2006-05-02) made it default not to use of\n>     symbolic links.  As long as we preserve the ability to work on a\n>     repository whose HEAD still uses a symbolic link, I'd hope\n>     nothing would break (#leftoverbits).\n>\n> Let me rearrange this hunk to show the original first:\n>\n>> -\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n>> -\t\treturn error(\"unable to fdopen %s: %s\",\n>> -\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> -\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>> -\t/* no error check; commit_ref will check ferror */\n>> -\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n>> -\tif (commit_ref(lock) < 0)\n>> -\t\treturn error(\"unable to write symref for %s: %s\", refname,\n>> -\t\t\t     strerror(errno));\n>\n> The original in create_symref_locked() created a lockfile, called\n> update_symref_reflog(), and called commit_ref() to commit the thing.\n>\n> The \"no error check\" comment is about detecting an error while\n> writing into the lock file.  It came from 370e5ad6 (create_symref:\n> use existing ref-lock code, 2015-12-29).  Because the fprintf() call\n> was immediately followed by commit_ref(), and the code assumed that\n> commit_ref() will check ferror(), we do not bother checking if the\n> fprintf() call fails to write the contents correctly.\n>\n>> +\tret = create_symref_lock(refs, lock, refname, target);\n>> +\tif (!ret) {\n>> +\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n>>\n>> +\t\tif (commit_ref(lock) < 0)\n>> +\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n>> +\t\t\t\t     strerror(errno));\n>> +\t}\n>\n> The new code lets create_symref_lock() to create a lockfile, and\n> does the rest here.  commit_ref() does call commit_lock_file(),\n> which eventually passes the control to close_tempfile() and a\n> write error can be detected there.\n>\n> But the point of this patch is that the creation of the locked\n> symref file PLUS writing its new contents (which is done by\n> create_symref_lock()) can be done way ahead of the remainder that\n> eventually does commit_ref().  So it smells a bit[*] dubious that we\n> still leave the error from fprintf() ignored in the \"early half\" in\n> the rearranged code.\n>\n> \tSide note: it is a \"bit\", as it is unlikely that we will do\n> \tsomething to clear the ferror() from the (FILE *) in the\n> \tmeantime.\n>\n\nYou're right. I would say that perhaps it is a bit more than a 'bit\ndubious' since `commit_ref()` being called after, is no longer a\nguarantee. I'll go ahead and add error handling here.\n\n>> @@ -1960,7 +1973,8 @@ static int files_create_symref(struct ref_store *ref_store,\n>>  \t\treturn -1;\n>>  \t}\n>>\n>> -\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n>> +\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n>> +\n>>  \tunlock_ref(lock);\n>>  \treturn ret;\n>>  }\n>\n> This hunk shows the \"original function was renamed; there is no\n> other changes visible to the caller\" nature of this rearrangement.\n>\n> The extra blank line is probably a nice touch.\n\nThanks. I'm sure it's not the best idea to introduce whitespace, but this\nfelt more readable here.\n"},{"id":"493559","messageId":"CAOLa=ZT4yVEuZXmiTVB2tf0qaTPCPn=0TcJRc89knZQWZBbFTg@mail.gmail.com","threadId":"61235","inReplyTo":"xmqq5xw37n6t.fsf@gitster.g","subject":"Re: [PATCH v4 3/7] update-ref: add support for 'symref-verify' command","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-28T22:28:41Z","receivedAt":"2024-04-28T22:28:44Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> In the previous commits, we added the required base for adding symref\n>> commands to the '--stdin' mode provided by 'git-update-ref(1)'. Using\n>> them, add a new 'symref-verify' command to verify symrefs.\n>>\n>> The 'symref-verify' command allows users to verify if a provided <ref>\n>> contains the provided <old-target> without changing the <ref>. If\n>> <old-target> is not provided, the command will verify that the <ref>\n>> doesn't exist. Since we're checking for symbolic refs, this command will\n>> only work with the 'no-deref' mode. This is because any dereferenced\n>> symbolic ref will point to an object and not a ref and the regular\n>> 'verify' command can be used in such situations.\n>\n> All makes sense, but a naïve reader may find it helpful if you\n> explained why having \"verify\" command is a good idea in the first\n> place (\"I can just do 'git symoblic-ref' to read the current value,\n> and see if it is what I expect\").  Presumably the value of \"verify\"\n> is that you can have it in a transaction and fail other operations\n> in the same transaction if the symref moved from what you expected\n> it to point at?\n>\n\nI would say none of the commits drive this point, and I would go ahead\nand add something on these lines to each of them. I think it would add\ngood value to readers.\n\n>> Add and use `ref_update_is_null_new_value`, a helper function which is\n>> used to check if there is a new_value in a reference update. The new\n>> value could either be a symref target `new_target` or a OID `new_oid`.\n>> We also add tests to test the command in both the regular stdin mode and\n>> also with the '-z' flag.\n>\n> This looks out of place, primarily because the helper function is\n> *NOT* used in this step.  Without any actual user, and with the name\n> that says only what it checks without hinting why a caller may want\n> to check the condition it checks, it is hard to guess if it is a\n> good idea to have such a helper.\n>\n\nI think over the revision, its usage from this commit was removed. It\nmakes sense to move it to a commit where its used, I'll do that.\n\n> \"If a ref_update object specifies no new-oid and no new-target, it\n> is not about updating but just validating\" is how the callers are\n> expected to use it, then instead of is_null_new_value that says\n> what it checks, something like is_verify_only that says what the\n> caller may want to use it for would be a more friendly name for\n> readers and future developers.\n\nThis is true for the old-oid and old-target. That is, when they are set\nto null, we're validating.\n\nWith the new-oid and new-target, if they're null, it usually signifies\ndeletion. We could rename it to 'is_delete_only', but that would also\nneed checking the 'REF_HAVE_NEW' flag. So we could ideally change it to\n\n```\nint ref_update_is_delete_only(struct ref_update *update) {\n\treturn (update->flags & REF_HAVE_NEW) && !update->new_target &&\nis_null_oid(&update->new_oid);\n}\n```\n\nI'm okay with making this change.\n\n>> @@ -297,11 +320,47 @@ static void parse_cmd_verify(struct ref_transaction *transaction,\n>>  \t\tdie(\"verify %s: extra input: %s\", refname, next);\n>>\n>>  \tif (ref_transaction_verify(transaction, refname, &old_oid,\n>> -\t\t\t\t   update_flags, &err))\n>> +\t\t\t\t   NULL, update_flags, &err))\n>>\n>>  \tupdate_flags = default_flags;\n>>  \tfree(refname);\n>>  \tstrbuf_release(&err);\n>>  }\n>\n> The only damage by this patch to parse_cmd_verify() is that\n> ref_transaction_verify() gained another parameter NULL, but with the\n> default \"--diff-algorithm=myers\" algorithm, it is very hard to see.\n>\n> The \"--patience\" algorithm does a much beter job on this hunk.\n>\n> And the following function is entirely new.\n>\n>> +static void parse_cmd_symref_verify(struct ref_transaction *transaction,\n>> +\t\t\t\t    const char *next, const char *end)\n>> +{\n>> +\tstruct strbuf err = STRBUF_INIT;\n>> +\tstruct object_id old_oid;\n>> +\tchar *refname, *old_target;\n>> +\n>> +\tif (!(update_flags & REF_NO_DEREF))\n>> +\t\tdie(\"symref-verify: cannot operate with deref mode\");\n>> +\n>> +\trefname = parse_refname(&next);\n>> +\tif (!refname)\n>> +\t\tdie(\"symref-verify: missing <ref>\");\n>> +\n>> +\t/*\n>> +\t * old_ref is optional, but we want to differentiate between\n>> +\t * a NULL and zero value.\n>> +\t */\n>> +\told_target = parse_next_refname(&next);\n>> +\tif (!old_target)\n>> +\t\told_oid = *null_oid();\n>\n> In many existing code paths, we do not do structure assignment like\n> this. Instead we do\n>\n> \t\toidcpy(&old_oid, null_oid());\n>\n> We can see an existing example in a common context in a hunk for\n> refs.c in this patch.\n>\n\nYeah, makes sense to switch this. Will do.\n\n>> +\tif (*next != line_termination)\n>> +\t\tdie(\"symref-verify %s: extra input: %s\", refname, next);\n>> +\n>> +\tif (ref_transaction_verify(transaction, refname,\n>> +\t\t\t\t   old_target ? NULL : &old_oid,\n>> +\t\t\t\t   old_target, update_flags, &err))\n>> +\t\tdie(\"%s\", err.buf);\n>\n> Are static analyzers smart enough to notice that we will not be\n> using old_oid uninitialized here?  Just wondering.\n\nYup, at least the clang LSP server seems to detect and not bug me about\nit.\n\n> Anyway.  This ensures ref_transaction_verify() gets either\n> old_target or old_oid, but never both at the same time.  The caller\n> to ref_transaction_verify() in the previous function passed NULL for\n> old_target but it always had a non-NULL old_oid so that is perfectly\n> fine.\n>\n>> +\tupdate_flags = default_flags;\n>> +\tfree(refname);\n>> +\tfree(old_target);\n>> +\tstrbuf_release(&err);\n>> +}\n>\n>> diff --git a/refs.c b/refs.c\n>> index 060a31616d..0e1013b5ab 100644\n>> --- a/refs.c\n>> +++ b/refs.c\n>> @@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n>>\n>>  \tfor (i = 0; i < transaction->nr; i++) {\n>>  \t\tfree(transaction->updates[i]->msg);\n>> +\t\tfree((void *)transaction->updates[i]->old_target);\n>> +\t\tfree((void *)transaction->updates[i]->new_target);\n>>  \t\tfree(transaction->updates[i]);\n>>  \t}\n>>  \tfree(transaction->updates);\n>> @@ -1247,9 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n>>\n>>  \tupdate->flags = flags;\n>>\n>> -\tif (flags & REF_HAVE_NEW)\n>> +\tif (new_target)\n>> +\t\tupdate->new_target = xstrdup(new_target);\n>> +\tif (old_target)\n>> +\t\tupdate->old_target = xstrdup(old_target);\n>\n> Presumably \"update\" structure, when freshly initialized, has NULL in\n> both of these _target members?  Otherwise ref_transaction_free()\n> would get in trouble, so double checking.\n>\n\nThis is a good point. My understanding was that FLEX_ALLOC_MEM should\nset everything to 0.\n\n>> +\tif (new_oid && flags & REF_HAVE_NEW)\n>>  \t\toidcpy(&update->new_oid, new_oid);\n>> -\tif (flags & REF_HAVE_OLD)\n>> +\tif (old_oid && flags & REF_HAVE_OLD)\n>>  \t\toidcpy(&update->old_oid, old_oid);\n>\n> Since we can ask to work on a symbolic ref, new_oid / old_oid can be\n> NULL when REF_HAVE_NEW / REF_HAVE_OLD bit is on for _target members.\n>\n> Makes me wonder if the code becomes easier to follow if the flag\n> bits are split into four (_NEW -> _NEW_OID + _NEW_TARGET), but let's\n> not worry about that for now.\n>\n\nThe intersection of this is quite low currently, so I'm not really sure\nif there's added benefit. I did start that way before, but perhaps with\nthe iterations in the last few version, maybe it makes the code simpler.\n\n>> @@ -1286,6 +1292,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>>  \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n>>\n>>  \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n>> +\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n>\n>> @@ -1325,14 +1332,17 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n>>  int ref_transaction_verify(struct ref_transaction *transaction,\n>>  \t\t\t   const char *refname,\n>>  \t\t\t   const struct object_id *old_oid,\n>> +\t\t\t   const char *old_target,\n>>  \t\t\t   unsigned int flags,\n>>  \t\t\t   struct strbuf *err)\n>>  {\n>> -\tif (!old_oid)\n>> -\t\tBUG(\"verify called with old_oid set to NULL\");\n>> +\tif (!old_target && !old_oid)\n>> +\t\tBUG(\"verify called with old_oid and old_target set to NULL\");\n>\n> Is it normal if you get _both_ set, or is it equally a BUG()?\n> The parse_*_verify() codepaths we saw earlier both made sure\n> only one of the two is non-NULL, and it is unclear what should\n> happen if both are non-NULL.\n>\n\nIt is a bug and this is caught in `ref_transaction_add_update`.\nIntroduced in the first patch of the series.\n\n>> +\tif (old_target && !(flags & REF_NO_DEREF))\n>> +\t\tBUG(\"verify cannot operate on symrefs with deref mode\");\n>>  \treturn ref_transaction_update(transaction, refname,\n>>  \t\t\t\t      NULL, old_oid,\n>> -\t\t\t\t      NULL, NULL,\n>> +\t\t\t\t      NULL, old_target,\n>>  \t\t\t\t      flags, NULL, err);\n>>  }\n>\n> So this queues an ref_update object whose .new_oid and .new_target\n> are NULL, and .old_oid and .old_target are what the caller gave us\n> to check.  The NULLs in .new* members hopefully do not mean \"delete\n> this thing\" ;-)\n>\n\nSo the 'new_oid' being set to zero should be the delete this thing\nqueue.\n\n>> @@ -2349,6 +2359,12 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n>>  \tfor (i = 0; i < transaction->nr; i++) {\n>>  \t\tstruct ref_update *update = transaction->updates[i];\n>>\n>> +\t\t/*\n>> +\t\t * Skip reference transaction for symbolic refs.\n>> +\t\t */\n>> +\t\tif (update->new_target || update->old_target)\n>> +\t\t\tcontinue;\n>\n> Is that a final design, or will the hooks have a chance to interfere?\n>\n\nThe last patch adds hook support.\n\n>> diff --git a/refs/files-backend.c b/refs/files-backend.c\n>> index 2420dac2aa..53197fa3af 100644\n>> --- a/refs/files-backend.c\n>> +++ b/refs/files-backend.c\n>> @@ -2425,6 +2425,37 @@ static const char *original_update_refname(struct ref_update *update)\n>>  \treturn update->refname;\n>>  }\n>>\n>> +/*\n>> + * Check whether the REF_HAVE_OLD and old_target values stored in\n>> + * update are consistent with ref, which is the symbolic reference's\n>> + * current value. If everything is OK, return 0; otherwise, write an\n>> + * error message to err and return -1.\n>> + */\n>> +static int check_old_target(struct ref_update *update, char *ref,\n>> +\t\t\t    struct strbuf *err)\n>> +{\n>> +\tif (!(update->flags & REF_HAVE_OLD) ||\n>> +\t    !strcmp(update->old_target, ref))\n>> +\t\treturn 0;\n>\n> Earlier on the assignment side for \"update\" structure we saw above,\n> the guard was (old_target && flags & REF_HAVE_OLD), but here we\n> assume old_target is valid, which feels a bit asymmetric.\n>\n> Yes, I can see that the caller does not call us when !old_target,\n> but still...  Perhaps\n>\n> \tif ((update->flags & REF_HAVE_OLD) && !update->old_target)\n> \t\tBUG(...);\n>\n\nI will add something like this.\n\n> or something?  Or alternatively, perhaps !!update->old_target should\n> be the only thing we should check and ignore REF_HAVE_OLD bit?  I am\n> not sure, but it smells like that the non-NULL-ness of old_target is\n> the only thing that matters (if it is not NULL, very early in the\n> control flow somebody would have set REF_HAVE_OLD bit to flags, no?).\n>\n> It brings me back to my earlier question.  Does REF_HAVE_OLD bit\n> serve a useful purpose in this code?\n>\n\nI checked and it doesn't, it can be removed from usage in this code.\nWill cleanup this part.\n\n>> +\tif (!strcmp(update->old_target, \"\"))\n>> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t    \"reference already exists\",\n>> +\t\t\t    original_update_refname(update));\n>> +\telse if (!strcmp(ref, \"\"))\n>> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t    \"reference is missing but expected %s\",\n>> +\t\t\t    original_update_refname(update),\n>> +\t\t\t    update->old_target);\n>\n> So... for old_target and ref, an empty string is a special value?\n> How?  Shouldn't that be documented in the comment before the\n> function?\n>\n>> +\telse\n>> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t    \"is at %s but expected %s\",\n>> +\t\t\t    original_update_refname(update),\n>> +\t\t\t    ref, update->old_target);\n>> +\n>> +\treturn -1;\n>> +}\n>> +\n>>  /*\n>>   * Check whether the REF_HAVE_OLD and old_oid values stored in update\n>>   * are consistent with oid, which is the reference's current value. If\n>> @@ -2528,6 +2559,18 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>  \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>>  \t\t\t\t\tgoto out;\n>>  \t\t\t\t}\n>> +\t\t\t}\n>> +\n>> +\t\t\t/*\n>> +\t\t\t * For symref verification, we need to check the reference value\n>> +\t\t\t * rather than the oid. If we're dealing with regular refs or we're\n>> +\t\t\t * verifying a dereferenced symref, we then check the oid.\n>> +\t\t\t */\n>> +\t\t\tif (update->old_target) {\n>> +\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n>> +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>> +\t\t\t\t\tgoto out;\n>> +\t\t\t\t}\n>\n> We come here only when update->type has REF_ISSYMREF bit on (we\n> learned that value by calling lock_raw_ref()), and know referent.buf\n> has the current \"target\" value.  That is consumed as \"ref\" parameter\n> to check_old_target() we just saw.  OK.\n>\n>> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n>> index 6104471199..a2474245aa 100644\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -938,7 +938,26 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>  \t\t * individual refs. But the error messages match what the files\n>>  \t\t * backend returns, which keeps our tests happy.\n>>  \t\t */\n>> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>> +\t\tif ((u->flags & REF_HAVE_OLD) && u->old_target) {\n>> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n>> +\t\t\t\tif (!strcmp(u->old_target, \"\"))\n>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>> +\t\t\t\t\t\t    \"provided target is empty\",\n>> +\t\t\t\t\t\t    original_update_refname(u));\n>> +\t\t\t\telse if (!strcmp(referent.buf, \"\"))\n>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    u->old_target);\n>> +\t\t\t\telse\n>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    referent.buf, u->old_target);\n>> +\t\t\t\tret = -1;\n>> +\t\t\t\tgoto done;\n>> +\t\t\t}\n>\n> Again, the puzzling \"empty string\"s are handled here.\n\nFor here and above, this too is dead code and no longer needed,\nold_target being empty string is left over code from before we decided\nto use zero_oid for deleting. I'll remove it. Thanks.\n"},{"id":"493607","messageId":"Zi9GIN1KrfIEjwRz@tanuki","threadId":"61235","inReplyTo":"20240426211529.GD13703@coredump.intra.peff.net","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-04-29T07:02:56Z","receivedAt":"2024-04-29T07:03:03Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, Apr 26, 2024 at 05:15:29PM -0400, Jeff King wrote:\n> On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:\n> \n> > This is an unrelated #leftoverbits tangent, but while trying to find\n> > out the reason why \"[_add]\" in the title looked irritating to me, I\n> > noticed that builtin/show-ref.c includes <refs/refs-internal.h>.  I\n> > do not know what it uses from the \"internal\" implementation detail,\n> > but the API may have to be cleaned up so that a random \"client\"\n> > caller do not have to do so.\n> \n> There are two issues. One is the use of refs_read_raw_ref(), added by\n> Patrick's 9080a7f178 (builtin/show-ref: add new mode to check for\n> reference existence, 2023-10-31). And it argues there why the regular\n> API is unsufficient (mostly because it does not protect errno).\n> \n> But the more interesting one is a call to refname_is_safe(), added\n> recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,\n> 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs\n> by loosening the conditional that checked \"HEAD\" to allow \"FOO_BAR\" but\n> not \"foobar\" outside of \"refs/\". We enforce the all-caps pseudoref\n> syntax in is_refname_safe().\n> \n> The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.\n> But you shouldn't need to do that, because the refs code should be\n> checking the names itself (using check_ref_format() usually, but falling\n> back to refname_is_safe() if the ALLOW_BAD_NAME flag is passed).\n> \n> And I think there _is_ a bug there. The idea of those two functions is\n> that check_ref_format() would allow a subset of what refname_is_safe()\n> does. We'd fall back to the latter when deleting, but not otherwise\n> allow creation or updates.\n> \n> However, it looks like check_ref_format() doesn't enforce the pseudo-ref\n> syntax. It will happily resolve this:\n> \n>   git rev-parse HEAD >.git/foo\n>   git rev-parse foo\n> \n> and even update it:\n> \n>   git update-ref foo HEAD\n> \n> though curiously we will refuse to delete it:\n> \n>   $ git update-ref -d foo\n>   error: refusing to update ref with bad name 'foo'\n> \n> since that sets the ALLOW_BAD_NAME flag!\n> \n> IMHO these should _all_ be forbidden, because we only want to allow the\n> more limited pseudoref names everywhere (and never mischievous ones like\n> \"config\" or whatever). And once we are doing that, then show-ref has no\n> need to check the format. It can just call read_ref() and it either gets\n> an answer or doesn't.\n> \n> I don't know if that is a #leftoverbit though. It perhaps more\n> complicated than that.\n\nYeah, this is something that I've repeatedly stumbled over myself. If I\nremember correctly, the plan was to clean up and consolidate all these\ndifferent functions we have for checking ref names such that they become\neasier to use and hopefully lead to more consistent behaviour.\n\nIn any case, I very much agree that git-update-ref(1) should refuse to\nwrite refs with names that are known-bad. There should probably be an\nescape hatch though that at least allows you to _delete_ those, or\notherwise there is no way to remove such a ref in the reftable repo.\nWell, except for meddling with the binary format, but I doubt that\nanybody would really want to do that.\n\nPatrick\n"},{"id":"493609","messageId":"20240429075524.GA3908618@coredump.intra.peff.net","threadId":"61235","inReplyTo":"Zi9GIN1KrfIEjwRz@tanuki","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-29T07:55:24Z","receivedAt":"2024-04-29T07:55:27Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Apr 29, 2024 at 09:02:56AM +0200, Patrick Steinhardt wrote:\n\n> > IMHO these should _all_ be forbidden, because we only want to allow the\n> > more limited pseudoref names everywhere (and never mischievous ones like\n> > \"config\" or whatever). And once we are doing that, then show-ref has no\n> > need to check the format. It can just call read_ref() and it either gets\n> > an answer or doesn't.\n> > \n> > I don't know if that is a #leftoverbit though. It perhaps more\n> > complicated than that.\n> \n> Yeah, this is something that I've repeatedly stumbled over myself. If I\n> remember correctly, the plan was to clean up and consolidate all these\n> different functions we have for checking ref names such that they become\n> easier to use and hopefully lead to more consistent behaviour.\n\nI think the code changes here aren't too bad (modulo some\nhead-scratching I had to do around per-worktree refs). I'll post a\nseries in a moment (split off from here since we're far off topic from\nthe original thread).\n\n> In any case, I very much agree that git-update-ref(1) should refuse to\n> write refs with names that are known-bad. There should probably be an\n> escape hatch though that at least allows you to _delete_ those, or\n> otherwise there is no way to remove such a ref in the reftable repo.\n> Well, except for meddling with the binary format, but I doubt that\n> anybody would really want to do that.\n\nYeah, ironically deleting is the one thing you cannot do with them even\nright now. ;) That is because the supposedly-looser refname_is_safe() is\nactually tighter.\n\nIt would be tough to loosen it safely, since you don't want to allow\ndeleting arbitrary files in .git. Or worse, escaping .git via clever\npaths, symlinks, etc. So I think at most you'd want some kind of \"trust\nme, for this command don't bother with ref format checks\" flag.\n\nOr alternatively, refname_is_safe() should become a per-backend thing.\nAnd then reftables can say \"everything is safe\", because the ref names\nare never used as paths (I think, anyway; I didn't follow all of the\ndiscussion around per-worktree refs, pseudorefs, etc).\n\n-Peff\n"},{"id":"493623","messageId":"ea3ee800-af0c-46e5-9032-391d82a4f046@gmail.com","threadId":"61235","inReplyTo":"20240429075524.GA3908618@coredump.intra.peff.net","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T09:29:01Z","receivedAt":"2024-04-29T09:29:06Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 29/04/2024 08:55, Jeff King wrote:\n> On Mon, Apr 29, 2024 at 09:02:56AM +0200, Patrick Steinhardt wrote:\n> \n>> In any case, I very much agree that git-update-ref(1) should refuse to\n>> write refs with names that are known-bad. There should probably be an\n>> escape hatch though that at least allows you to _delete_ those, or\n>> otherwise there is no way to remove such a ref in the reftable repo.\n>> Well, except for meddling with the binary format, but I doubt that\n>> anybody would really want to do that.\n> \n> Yeah, ironically deleting is the one thing you cannot do with them even\n> right now. ;) That is because the supposedly-looser refname_is_safe() is\n> actually tighter.\n> \n> It would be tough to loosen it safely, since you don't want to allow\n> deleting arbitrary files in .git. Or worse, escaping .git via clever\n> paths, symlinks, etc. So I think at most you'd want some kind of \"trust\n> me, for this command don't bother with ref format checks\" flag.\n\nCurrently \"git update-ref config HEAD\" fails because the config file \ndoes not look like a ref - could we do similar check when deleting \nreferences at least for the files backend for arguments outside refs/?\n\nBest Wishes\n\nPhillip\n"},{"id":"493624","messageId":"b2977495-3b4a-4f2a-85dc-d0568f34f2e9@gmail.com","threadId":"61235","inReplyTo":"20240426211529.GD13703@coredump.intra.peff.net","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T09:32:58Z","receivedAt":"2024-04-29T09:33:01Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Peff\n\nOn 26/04/2024 22:15, Jeff King wrote:\n> On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:\n> \n> But the more interesting one is a call to refname_is_safe(), added\n> recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,\n> 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs\n> by loosening the conditional that checked \"HEAD\" to allow \"FOO_BAR\" but\n> not \"foobar\" outside of \"refs/\". We enforce the all-caps pseudoref\n> syntax in is_refname_safe().\n> \n> The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.\n\nALLOW_ONELEVEL just disables the check that the refname contains a '/' \nand I think it is aimed at checking branch and tag names without a \nrefs/{heads,tags} prefix. If we want to move away from using \nrefname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that only \nallows the name to contain '[A-Z_]' if there is no '/'.\n\nBest Wishes\n\nPhillip\n"},{"id":"493626","messageId":"73632d75-b82d-4853-93fe-3460fdda3325@gmail.com","threadId":"61235","inReplyTo":"20240426152449.228860-2-knayak@gitlab.com","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-04-29T13:38:55Z","receivedAt":"2024-04-29T13:38:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 26/04/2024 16:24, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The `ref_transaction[_add]_update` functions obtain ref information and\n> flags to create a `ref_update` and add it to the transaction at hand.\n> \n> To extend symref support in transactions, we need to also accept the\n> old and new ref targets and process it.\n\ns/it/them/\n\n> In this commit, let's add the\n\nThis commit adds?\n\n> required parameters to the function and modify all call sites.\n> \n> The two parameters added are `new_target` and `old_target`. The\n> `new_target` is used to denote what the reference should point to when\n> the transaction is applied.\n> \n> The `old_target` denotes the value the reference must have before the\n> update. Some functions allow this parameter to be NULL, meaning that the\n> old value of the reference is not checked.\n> \n> The handling logic of these parameters will be added in consequent\n> commits as we add symref commands to the '--stdin' mode of\n> 'git-update-ref'.\n> \n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n\nThanks for updating the documentation, I've left a couple of comments below\n\n> diff --git a/refs.h b/refs.h\n> index d278775e08..c792e13a64 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>    *         before the update. A copy of this value is made in the\n>    *         transaction.\n>    *\n> + *     new_target -- the target reference that the reference will be\n> + *         update to point to.\n\ns/update/updated/\n\n> This takes precedence over new_oid when set.\n\nI thought it was a bug to set both new_oid and new_target.\n\n> If the reference is a regular reference, it will be\n> + *         converted to a symbolic reference.\n > + *\n> + *     old_target -- the reference that the reference must be pointing to.\n> + *         Will only be taken into account when the reference is a symbolic\n> + *         reference.\n\nDoes this last sentence mean it is not possible to assert that it is \ncurrently a symbolic reference? I thought the point of being able to \nspecify the old value of a ref when updating was to ensure it hadn't \nchanged since it was read. This contradicts the documentation in the \nnext hunk and the description in the commit message.\n\n>    *     flags -- flags affecting the update, passed to\n>    *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n>    *         REF_FORCE_CREATE_REFLOG. See those constants for more\n> @@ -713,7 +722,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>    * beforehand. The old value is checked after the lock is taken to\n>    * prevent races. If the old value doesn't agree with old_oid, the\n>    * whole transaction fails. If old_oid is NULL, then the previous\n> - * value is not checked.\n> + * value is not checked. If `old_target` is not NULL, treat the reference\n> + * as a symbolic ref and validate that its target before the update is\n> + * `old_target`. If the `new_target` is not NULL, then the reference\n> + * will be updated to a symbolic ref which targets `new_target`.\n\nThis looks good and describes the behavior I'd expected to see.\n\nBest Wishes\n\nPhillip\n"},{"id":"493633","messageId":"CAOLa=ZQ1HTRqsCrhw=PRYaAY0-d_w2Ufxf-ucBCz4P2ws9hFcA@mail.gmail.com","threadId":"61235","inReplyTo":"73632d75-b82d-4853-93fe-3460fdda3325@gmail.com","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-29T14:01:33Z","receivedAt":"2024-04-29T14:01:38Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> On 26/04/2024 16:24, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The `ref_transaction[_add]_update` functions obtain ref information and\n>> flags to create a `ref_update` and add it to the transaction at hand.\n>>\n>> To extend symref support in transactions, we need to also accept the\n>> old and new ref targets and process it.\n>\n> s/it/them/\n>\n>> In this commit, let's add the\n>\n> This commit adds?\n>\n\nThanks, will add both the above.\n\n>> required parameters to the function and modify all call sites.\n>>\n>> The two parameters added are `new_target` and `old_target`. The\n>> `new_target` is used to denote what the reference should point to when\n>> the transaction is applied.\n>>\n>> The `old_target` denotes the value the reference must have before the\n>> update. Some functions allow this parameter to be NULL, meaning that the\n>> old value of the reference is not checked.\n>>\n>> The handling logic of these parameters will be added in consequent\n>> commits as we add symref commands to the '--stdin' mode of\n>> 'git-update-ref'.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>\n> Thanks for updating the documentation, I've left a couple of comments below\n>\n\nThanks for the review.\n\n>> diff --git a/refs.h b/refs.h\n>> index d278775e08..c792e13a64 100644\n>> --- a/refs.h\n>> +++ b/refs.h\n>> @@ -648,6 +648,15 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>>    *         before the update. A copy of this value is made in the\n>>    *         transaction.\n>>    *\n>> + *     new_target -- the target reference that the reference will be\n>> + *         update to point to.\n>\n> s/update/updated/\n>\n>> This takes precedence over new_oid when set.\n>\n> I thought it was a bug to set both new_oid and new_target.\n>\n\nYup. fixed both of these.\n\n>\n>> If the reference is a regular reference, it will be\n>> + *         converted to a symbolic reference.\n>  > + *\n>> + *     old_target -- the reference that the reference must be pointing to.\n>> + *         Will only be taken into account when the reference is a symbolic\n>> + *         reference.\n>\n> Does this last sentence mean it is not possible to assert that it is\n> currently a symbolic reference? I thought the point of being able to\n> specify the old value of a ref when updating was to ensure it hadn't\n> changed since it was read. This contradicts the documentation in the\n> next hunk and the description in the commit message.\n>\n\nI see how this is vague, I was trying to imply that the old_target is\nused to check a symref's old_value. But actually, if this is set and the\nref is a regular ref, we do fail the check. So this is wrong. Let me\njust strip the last time.\n"},{"id":"493647","messageId":"xmqqo79sxhug.fsf@gitster.g","threadId":"61235","inReplyTo":"b2977495-3b4a-4f2a-85dc-d0568f34f2e9@gmail.com","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-04-29T16:18:47Z","receivedAt":"2024-04-29T16:18:55Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"phillip.wood123@gmail.com writes:\n\n> ALLOW_ONELEVEL just disables the check that the refname contains a '/'\n> and I think it is aimed at checking branch and tag names without a\n> refs/{heads,tags} prefix. If we want to move away from using\n> refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that\n> only allows the name to contain '[A-Z_]' if there is no '/'.\n\nMakes sense.\n\nI wonder if we eventually can get rid of ALLOW_ONELEVEL, though.  If\nall callers that use ALLOW_ONELEVEL know under which prefix they\nplan to hang the refname they are checking (if the refname passed\nthe check), we can force the check to be performed always on the\nfull refname, and it will become easier to make the check more\nconsistent---as the check will have full context information.\n\nFor example, with ALLOW_ONELEVEL the check may say \"HEAD\" is OK, but\nif we get rid of ALLOW_ONELEVEL and force the callers to always test\nthe full refname, we may say \"refs/heads/HEAD\" is not acceptable,\nneither is \"refs/tags/HEAD\", but \"refs/remotes/origin/HEAD\" is good.\n\n"},{"id":"493746","messageId":"CAOLa=ZSNYs17bM9v3r4s4Mvy6zvdFFvrx=j_JWoaKbPWtuGssw@mail.gmail.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"Re: [PATCH v4 0/7] add symref-* commands to 'git-update-ref --stdin'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-04-30T10:14:03Z","receivedAt":"2024-04-30T10:14:05Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> The 'git-update-ref(1)' command allows transactional reference updates.\n> But currently only supports regular reference updates. Meaning, if one\n> wants to update HEAD (symbolic ref) in a transaction, there is no tool\n> to do so.\n>\n> One option to obtain transactional updates for the HEAD ref is to\n> manually create the HEAD.lock file and commit. This is intrusive, where\n> the user needs to mimic internal git behavior. Also, this only works\n> when using the files backend.\n>\n> At GitLab, we've been using the manual process till date, to allow users\n> to set and change their default branch. But with the introduction of\n> reftables as a reference backend, this becomes a necessity to be solved\n> within git.\n>\n> This patch series goes about introducing a set of commands\n> symref-{create,verify,delete,update} to work with symrefs complimenting\n> the existing commands for the regular refs in the '--stdin' mode of\n> 'git-update-ref'.\n>\n> The 'symref-verify' command can be used to verify if a symref exists and\n> its existing value.\n>\n> The 'symref-create' command can be used to create a new symref.\n>\n> The 'symref-delete' command can be used to delete an existing symref while\n> optionally checking its existing value.\n>\n> The 'symref-update' command can be used to update a symref, create a symref,\n> delete a symref or even convert an existing regular ref to a symref. Wherein\n> like the regular 'update' command, the zero OID can be used to create/delete\n> a symref.\n>\n> While this series adds the commands and the required ground work, it only\n> is accessile within the '--stdin' mode of 'git-update-ref'. However, it makes\n> it easy to extend it further to the command line too, which will be present\n> in a follow up series.\n>\n\nAlso, I'll be pushing a new version with reduced scope soon. The idea\nbeing that we can review/merge the part which adds the symref support to\nthe refs library first and then we can have a followup for exposing the\nsame to the users via git-update-ref. This should make it also easier\nfor reviewers.\n\n[snip]\n"},{"id":"493748","messageId":"20240430103002.GD1279403@coredump.intra.peff.net","threadId":"61235","inReplyTo":"b2977495-3b4a-4f2a-85dc-d0568f34f2e9@gmail.com","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-30T10:30:02Z","receivedAt":"2024-04-30T10:30:04Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Apr 29, 2024 at 10:32:58AM +0100, phillip.wood123@gmail.com wrote:\n\n> On 26/04/2024 22:15, Jeff King wrote:\n> > On Fri, Apr 26, 2024 at 12:31:36PM -0700, Junio C Hamano wrote:\n> > \n> > But the more interesting one is a call to refname_is_safe(), added\n> > recently by Phillip's 1dbe401563 (show-ref --verify: accept pseudorefs,\n> > 2024-02-07). Looking at that commit, the intent was to allow pseudo-refs\n> > by loosening the conditional that checked \"HEAD\" to allow \"FOO_BAR\" but\n> > not \"foobar\" outside of \"refs/\". We enforce the all-caps pseudoref\n> > syntax in is_refname_safe().\n> > \n> > The proper API there is I think check_ref_format() with ALLOW_ONELEVEL.\n> \n> ALLOW_ONELEVEL just disables the check that the refname contains a '/' and I\n> think it is aimed at checking branch and tag names without a\n> refs/{heads,tags} prefix. If we want to move away from using\n> refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that only\n> allows the name to contain '[A-Z_]' if there is no '/'.\n\nRight, I think that was the original reason for ALLOW_ONELEVEL. But then\nlots of callers ended up using it because it was the only way to allow\n\"HEAD\" or \"FETCH_HEAD\" to work alongside regular refs.\n\nSee the series I posted elsewhere which adds a new flag to cover that\ncase.\n\n-Peff\n"},{"id":"493749","messageId":"20240430103337.GE1279403@coredump.intra.peff.net","threadId":"61235","inReplyTo":"xmqqo79sxhug.fsf@gitster.g","subject":"Re: [PATCH v4 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2024-04-30T10:33:37Z","receivedAt":"2024-04-30T10:33:38Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Mon, Apr 29, 2024 at 09:18:47AM -0700, Junio C Hamano wrote:\n\n> phillip.wood123@gmail.com writes:\n> \n> > ALLOW_ONELEVEL just disables the check that the refname contains a '/'\n> > and I think it is aimed at checking branch and tag names without a\n> > refs/{heads,tags} prefix. If we want to move away from using\n> > refname_is_safe() perhaps we could add an ALLOW_PSEUDOREF flag that\n> > only allows the name to contain '[A-Z_]' if there is no '/'.\n> \n> Makes sense.\n> \n> I wonder if we eventually can get rid of ALLOW_ONELEVEL, though.  If\n> all callers that use ALLOW_ONELEVEL know under which prefix they\n> plan to hang the refname they are checking (if the refname passed\n> the check), we can force the check to be performed always on the\n> full refname, and it will become easier to make the check more\n> consistent---as the check will have full context information.\n> \n> For example, with ALLOW_ONELEVEL the check may say \"HEAD\" is OK, but\n> if we get rid of ALLOW_ONELEVEL and force the callers to always test\n> the full refname, we may say \"refs/heads/HEAD\" is not acceptable,\n> neither is \"refs/tags/HEAD\", but \"refs/remotes/origin/HEAD\" is good.\n\nOne case I ran into while working on my series is refspec parsing, where\nwe feed the left and right halves to check_refname_format(), albeit with\na special flag that allows the \"*\" wildcard character. And there we are\nOK with one-level names because they will be passed through the\ndwim_ref() lookup.\n\nI don't know if there are other spots that do something similar. Most of\nthem would, I imagine, just take any input and leave it to the ref code\nto enforce syntax after the dwim-ref has happened.\n\n-Peff\n"},{"id":"493837","messageId":"20240501202229.2695774-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240426152449.228860-1-knayak@gitlab.com","subject":"[PATCH v5 0/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:22Z","receivedAt":"2024-05-01T20:22:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe patch series takes over from the existing patch series, wherein we\nintroduced symref-* commands to git-update-ref. Since there was a ton of\ndiscussions on the UX of the patch series and its application, I thought it\nwould be best to shorten the series and split it into multiple smaller series.\n\nThis series adds transactional support for symrefs in the reference db. Then\nwe switch refs_create_symref() to start using transactions for symref updates.\nThis allows us to deprecate the create_symref code in the ref_storage_be\ninterface and remove all associated code which is no longer used.\n\nThe split was primarily done so we can merge the non-user facing parts of the\nprevious series. While pertaining the user facing components into another set\nof patches wherein deeper discussion on the UX can be held without worrying\nabout the internal implementation. Also by using this new functionality in a\npre-existing command, we can leverage the existing tests to catch any\ninconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for\ndangling symrefs, which I've modified my patch to do the same.\n\nWe also modify the reference transaction hook to support symrefs. For any symref\nupdate the reference transaction hook will output the value with a 'ref:' prefix.\n\nPrevious versions:\nV1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\nV2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\nV3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/\nV4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\n\nChanges over v4 are:\n- Dropped the patches for adding support in git-update-ref.\n- Added changes to use transaction in 'refs_create_symref()' and\n  deprecate 'create_symref'.\n- Cleaned up the commit messages, documentation and comments.\n- Added better handling, like if 'old_target' is set, the value of the\n  reference is checked, irrelevant of its type.\n\nRange diff:\n\n1:  4a56e3ede4 ! 1:  a354190905 refs: accept symref values in `ref_transaction[_add]_update`\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    refs: accept symref values in `ref_transaction[_add]_update`\n    +    refs: accept symref values in `ref_transaction_update()`\n     \n    -    The `ref_transaction[_add]_update` functions obtain ref information and\n    -    flags to create a `ref_update` and add it to the transaction at hand.\n    +    The function `ref_transaction_update()` obtains ref information and\n    +    flags to create a `ref_update` and add them to the transaction at hand.\n     \n         To extend symref support in transactions, we need to also accept the\n    -    old and new ref targets and process it. In this commit, let's add the\n    -    required parameters to the function and modify all call sites.\n    +    old and new ref targets and process it. This commit adds the required\n    +    parameters to the function and modifies all call sites.\n     \n         The two parameters added are `new_target` and `old_target`. The\n         `new_target` is used to denote what the reference should point to when\n    -    the transaction is applied.\n    +    the transaction is applied. Some functions allow this parameter to be\n    +    NULL, meaning that the reference is not changed.\n     \n         The `old_target` denotes the value the reference must have before the\n         update. Some functions allow this parameter to be NULL, meaning that the\n         old value of the reference is not checked.\n     \n    -    The handling logic of these parameters will be added in consequent\n    -    commits as we add symref commands to the '--stdin' mode of\n    -    'git-update-ref'.\n    +    We also update the internal function `ref_transaction_add_update()`\n    +    similarly to take the two new parameters.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \t\tBUG(\"update called for transaction that is not open\");\n      \n     +\tif (old_oid && !is_null_oid(old_oid) && old_target)\n    -+\t\tBUG(\"Only one of old_oid and old_target should be non NULL\");\n    ++\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n     +\tif (new_oid && !is_null_oid(new_oid) && new_target)\n    -+\t\tBUG(\"Only one of new_oid and new_target should be non NULL\");\n    ++\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n     +\n      \tFLEX_ALLOC_STR(update, refname, refname);\n      \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n    @@ refs.h: struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n       *         transaction.\n       *\n     + *     new_target -- the target reference that the reference will be\n    -+ *         update to point to. This takes precedence over new_oid when\n    -+ *         set. If the reference is a regular reference, it will be\n    -+ *         converted to a symbolic reference.\n    ++ *         updated to point to. If the reference is a regular reference,\n    ++ *         it will be converted to a symbolic reference. Cannot be set\n    ++ *         together with `new_oid`. A copy of this value is made in the\n    ++ *         transaction.\n     + *\n     + *     old_target -- the reference that the reference must be pointing to.\n    -+ *         Will only be taken into account when the reference is a symbolic\n    -+ *         reference.\n    ++ *         Canont be set together with `old_oid`. A copy of this value is\n    ++ *         made in the transaction.\n     + *\n       *     flags -- flags affecting the update, passed to\n       *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n    @@ refs/refs-internal.h: struct ref_update {\n     +\t/*\n     +\t * If set, point the reference to this value. This can also be\n     +\t * used to convert regular references to become symbolic refs.\n    ++\t * Cannot be set together with `new_oid`.\n     +\t */\n     +\tconst char *new_target;\n     +\n     +\t/*\n    -+\t * If set and the reference is a symbolic ref, check that the\n    -+\t * reference previously pointed to this value.\n    ++\t * If set, check that the reference previously pointed to this\n    ++\t * value. Cannot be set together with `old_oid`.\n     +\t */\n     +\tconst char *old_target;\n     +\n2:  496bf14f28 ! 2:  7dff21dbef files-backend: extract out `create_symref_lock`\n    @@ Metadata\n     Author: Karthik Nayak <karthik.188@gmail.com>\n     \n      ## Commit message ##\n    -    files-backend: extract out `create_symref_lock`\n    +    files-backend: extract out `create_symref_lock()`\n     \n    -    The function `create_symref_locked` creates a symref by creating a\n    +    The function `create_symref_locked()` creates a symref by creating a\n         '<symref>.lock' file and then committing the symref lock, which creates\n         the final symref.\n     \n    -    Split this into two individual functions `create_and_commit_symref` and\n    -    `create_symref_locked`. This way we can create the symref lock and\n    -    commit it at different times. This will be used to provide symref\n    -    support in `git-update-ref(1)`.\n    +    Extract the early half of `create_symref_locked()` into a new helper\n    +    function `create_symref_lock()`. Because the name of the new function is\n    +    too similar to the original, rename the original to\n    +    `create_and_commit_symref()` to avoid confusion.\n    +\n    +    The new function `create_symref_locked()` can be used to create the\n    +    symref lock in a separate step from that of committing it. This allows\n    +    to add transactional support for symrefs, where the lock would be\n    +    created in the preparation step and the lock would be committed in the\n    +    finish step.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     +\t\treturn error(\"unable to fdopen %s: %s\",\n     +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n     +\n    -+\t/* no error check; commit_ref will check ferror */\n    -+\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n    ++\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n    ++\t\treturn error(\"unable to fprintf %s: %s\",\n    ++\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n     +\treturn 0;\n     +}\n     +\n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     -\tif (commit_ref(lock) < 0)\n     -\t\treturn error(\"unable to write symref for %s: %s\", refname,\n     -\t\t\t     strerror(errno));\n    - \treturn 0;\n    +-\treturn 0;\n    ++\treturn ret;\n      }\n      \n    + static int files_create_symref(struct ref_store *ref_store,\n     @@ refs/files-backend.c: static int files_create_symref(struct ref_store *ref_store,\n      \t\treturn -1;\n      \t}\n3:  6337859cbb < -:  ---------- update-ref: add support for 'symref-verify' command\n4:  e611cb5a8c < -:  ---------- update-ref: add support for 'symref-delete' command\n5:  37f8be2f3f < -:  ---------- update-ref: add support for 'symref-create' command\n6:  b385f4d0d7 < -:  ---------- update-ref: add support for 'symref-update' command\n7:  ef335e47d1 < -:  ---------- ref: support symrefs in 'reference-transaction' hook\n-:  ---------- > 3:  901a586683 refs: support symrefs in 'reference-transaction' hook\n-:  ---------- > 4:  6c97f6a660 refs: add support for transactional symref updates\n-:  ---------- > 5:  5b55406430 refs: use transaction in `refs_create_symref()`\n-:  ---------- > 6:  9e25816e68 refs: rename `refs_create_symref()` to `refs_update_symref()`\n-:  ---------- > 7:  3836e25932 refs: remove `create_symref` and associated dead code\n\n\nKarthik Nayak (7):\n  refs: accept symref values in `ref_transaction_update()`\n  files-backend: extract out `create_symref_lock()`\n  refs: support symrefs in 'reference-transaction' hook\n  refs: add support for transactional symref updates\n  refs: use transaction in `refs_create_symref()`\n  refs: rename `refs_create_symref()` to `refs_update_symref()`\n  refs: remove `create_symref` and associated dead code\n\n Documentation/githooks.txt       |  14 ++-\n branch.c                         |   2 +-\n builtin/branch.c                 |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   2 +-\n builtin/receive-pack.c           |   1 +\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             |   1 +\n builtin/worktree.c               |   2 +-\n refs.c                           |  87 +++++++++++----\n refs.h                           |  20 +++-\n refs/debug.c                     |  13 ---\n refs/files-backend.c             | 185 +++++++++++++++++++------------\n refs/packed-backend.c            |   1 -\n refs/refs-internal.h             |  26 ++++-\n refs/reftable-backend.c          | 159 +++++++++-----------------\n sequencer.c                      |   9 +-\n t/helper/test-ref-store.c        |   2 +-\n t/t0610-reftable-basics.sh       |   2 +-\n t/t1416-ref-transaction-hooks.sh |  23 ++++\n walker.c                         |   2 +-\n 22 files changed, 321 insertions(+), 240 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"493838","messageId":"20240501202229.2695774-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 1/7] refs: accept symref values in `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:23Z","receivedAt":"2024-05-01T20:22:36Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `ref_transaction_update()` obtains ref information and\nflags to create a `ref_update` and add them to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. This commit adds the required\nparameters to the function and modifies all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied. Some functions allow this parameter to be\nNULL, meaning that the reference is not changed.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked.\n\nWe also update the internal function `ref_transaction_add_update()`\nsimilarly to take the two new parameters.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 18 +++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 71 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..47bc9dd103 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n+\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n+\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..c7851bf587 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         updated to point to. If the reference is a regular reference,\n+ *         it will be converted to a symbolic reference. Cannot be set\n+ *         together with `new_oid`. A copy of this value is made in the\n+ *         transaction.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Canont be set together with `old_oid`. A copy of this value is\n+ *         made in the transaction.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..108f4ec419 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,19 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t * Cannot be set together with `new_oid`.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set, check that the reference previously pointed to this\n+\t * value. Cannot be set together with `old_oid`.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 88de4dc20f..61e007d85f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"493839","messageId":"20240501202229.2695774-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:24Z","receivedAt":"2024-05-01T20:22:36Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked()` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nExtract the early half of `create_symref_locked()` into a new helper\nfunction `create_symref_lock()`. Because the name of the new function is\ntoo similar to the original, rename the original to\n`create_and_commit_symref()` to avoid confusion.\n\nThe new function `create_symref_locked()` can be used to create the\nsymref lock in a separate step from that of committing it. This allows\nto add transactional support for symrefs, where the lock would be\ncreated in the preparation step and the lock would be committed in the\nfinish step.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 43 +++++++++++++++++++++++++++++--------------\n 1 file changed, 29 insertions(+), 14 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..878601ced0 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,27 +1920,41 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n+\t\treturn error(\"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\n+\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n+\t\treturn error(\"unable to fprintf %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n-\treturn 0;\n+\treturn ret;\n }\n \n static int files_create_symref(struct ref_store *ref_store,\n@@ -1960,7 +1974,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"493840","messageId":"20240501202229.2695774-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:25Z","receivedAt":"2024-05-01T20:22:37Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In a previous commit, we added the `old_target` and\n`new_target` fields to the `reference_transaction_update()`. In\nfollowing commits we'll also add the code to handle symref's in the\nreference backends.\n\nSupport symrefs also in the 'reference-transaction' hook, by modifying\nthe current format:\n    <old-oid> SP <new-oid> SP <ref-name> LF\nto be be:\n    <old-value> SP <new-value> SP <ref-name> LF\nwhere for regular refs the output would not change and remain the same.\nBut when either 'old-value' or 'new-value' is a symref, we print the ref\nas 'ref:<ref-target>'.\n\nThis does break backward compatibility, but the 'reference-transaction'\nhook's documentation always stated that support for symbolic references\nmay be added in the future.\n\nWe do not add any tests in this commit since there is no git command\nwhich activates this flow, in an upcoming commit, we'll start using\ntransaction based symref updates as the default, we'll add tests there\nfor the hook too.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt | 14 +++++++++-----\n refs.c                     | 16 ++++++++++++----\n 2 files changed, 21 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..06e997131b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also supports symbolic reference updates.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects. A reference will be\n+denoted with a 'ref:' prefix, like `ref:<ref-target>`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 47bc9dd103..5dfe93060a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2350,10 +2350,18 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = transaction->updates[i];\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\n+\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n+\n+\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\n-- \n2.43.GIT\n\n"},{"id":"493841","messageId":"20240501202229.2695774-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:26Z","receivedAt":"2024-05-01T20:22:38Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe reference backends currently support transactional reference\nupdates. While this is exposed to users via 'git-update-ref' and its\n'--stdin' mode, it is also used internally within various commands.\n\nHowever, we never supported transactional updates of symrefs. Let's add\nsupport for symrefs in both the 'files' and the 'reftable' backend.\n\nHere, we add and use `ref_update_is_null_new_value()`, a helper function\nwhich is used to check if there is a new_value in a reference update.\nThe new value could either be a symref target `new_target` or a OID\n`new_oid`.\n\nWith this, now transactional updates (verify, create, delete, update)\ncan be used for:\n- regular refs\n- symbolic refs\n- conversion of regular to symbolic refs and vice versa\n\nThis also allows us to expose this to users via new commands in\n'git-update-ref' in the future.\n\nWe do not add reflog for dangling symref updates, because currently\n'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\nwould be best to keep this behavior consistent as we would move it to\nstart using transaction based updates in the following commit.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  17 +++++-\n refs/files-backend.c    | 112 +++++++++++++++++++++++++++++++++++-----\n refs/refs-internal.h    |   7 +++\n refs/reftable-backend.c |  71 +++++++++++++++++++------\n 4 files changed, 176 insertions(+), 31 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 5dfe93060a..a4dca08244 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((char *)transaction->updates[i]->new_target);\n+\t\tfree((char *)transaction->updates[i]->old_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tif (new_target)\n+\t\tupdate->new_target = xstrdup(new_target);\n+\tif (old_target)\n+\t\tupdate->old_target = xstrdup(old_target);\n+\tif (new_oid && flags & REF_HAVE_NEW)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif (old_oid && flags & REF_HAVE_OLD)\n \t\toidcpy(&update->old_oid, old_oid);\n+\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -1286,6 +1293,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -2810,3 +2818,8 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+int ref_update_is_null_new_value(struct ref_update *update)\n+{\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 878601ced0..85c4af7e89 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2426,6 +2426,36 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+/*\n+ * Check whether the old_target values stored in update are consistent\n+ * with current_target, which is the symbolic reference's current value.\n+ * If everything is OK, return 0; otherwise, write an error message to\n+ * err and return -1.\n+ */\n+static int check_old_target(struct ref_update *update,\n+\t\t\t    const char *current_target,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (!update->old_target)\n+\t\tBUG(\"called without old_target set\");\n+\n+\tif (!strcmp(update->old_target, current_target))\n+\t\treturn 0;\n+\n+\tif (!strcmp(current_target, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    current_target, update->old_target);\n+\n+\treturn -1;\n+}\n+\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2486,7 +2516,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\n@@ -2529,7 +2559,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t\t}\n+\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n+\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -2550,7 +2587,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t} else {\n \t\tstruct ref_update *parent_update;\n \n-\t\tif (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t/*\n+\t\t * Even if the ref is a regular ref, if `old_target` is set, we\n+\t\t * check the referent value. Ideally `old_target` should only\n+\t\t * be set for symrefs, but we're strict about its usage.\n+\t\t */\n+\t\tif (update->old_target) {\n+\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n \t\t}\n@@ -2568,9 +2615,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\n@@ -2863,12 +2928,26 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n-\t\t\tif (files_log_ref_write(refs,\n-\t\t\t\t\t\tlock->ref_name,\n-\t\t\t\t\t\t&lock->old_oid,\n-\t\t\t\t\t\t&update->new_oid,\n-\t\t\t\t\t\tupdate->msg, update->flags,\n-\t\t\t\t\t\terr)) {\n+\t\t\tint create_reflog = 1;\n+\n+\t\t\tif (update->new_target) {\n+\t\t\t\t/*\n+\t\t\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t\t\t * that the correct value is added to the reflog.\n+\t\t\t\t */\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n+\t\t\t\t\t/* for dangling symrefs we skip creating a reflog entry. */\n+\t\t\t\t\tcreate_reflog = 0;\n+\t\t\t\t}\n+\t\t\t}\n+\n+\t\t\tif (create_reflog && files_log_ref_write(refs,\n+\t\t\t\t\t\t\t\t lock->ref_name,\n+\t\t\t\t\t\t\t\t &lock->old_oid,\n+\t\t\t\t\t\t\t\t &update->new_oid,\n+\t\t\t\t\t\t\t\t update->msg, update->flags,\n+\t\t\t\t\t\t\t\t err)) {\n \t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n \n \t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n@@ -2880,6 +2959,15 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next update. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 108f4ec419..9578665243 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_is_null_new_value(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..5e8a696d40 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -907,8 +907,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t * intertwined with the locking in files-backend.c.\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n-\t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\ttransaction, referent.buf, new_flags,\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\tu->old_target, u->msg);\n+\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif (u->old_target) {\n+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n+\t\t\t\tif (!strcmp(referent.buf, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    u->old_target);\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"is at %s but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    referent.buf, u->old_target);\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t    \"reference already exists\"),\n@@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1084,24 +1101,44 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t   (u->flags & REF_FORCE_CREATE_REFLOG ||\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n+\t\t\tint create_reflog = 1;\n \n-\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n-\t\t\tlog = &logs[logs_nr++];\n-\t\t\tmemset(log, 0, sizeof(*log));\n-\n-\t\t\tfill_reftable_log_record(log);\n-\t\t\tlog->update_index = ts;\n-\t\t\tlog->refname = xstrdup(u->refname);\n-\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tlog->value.update.message =\n-\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\tif (u->new_target)\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n+\t\t\t\t\t/* for dangling symrefs we skip creating reflog */\n+\t\t\t\t\tcreate_reflog = 0;\n+\n+\t\t\tif (create_reflog) {\n+\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n+\t\t\t\tlog = &logs[logs_nr++];\n+\t\t\t\tmemset(log, 0, sizeof(*log));\n+\n+\t\t\t\tfill_reftable_log_record(log);\n+\t\t\t\tlog->update_index = ts;\n+\t\t\t\tlog->refname = xstrdup(u->refname);\n+\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tlog->value.update.message =\n+\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\t}\n \t\t}\n \n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->flags & REF_HAVE_NEW && u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\n-- \n2.43.GIT\n\n"},{"id":"493842","messageId":"20240501202229.2695774-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 5/7] refs: use transaction in `refs_create_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:27Z","receivedAt":"2024-05-01T20:22:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function updates a symref to a given new\ntarget. To do this, it uses a ref-backend specific function\n`create_symref()`.\n\nIn this previous commit, we introduce symref support in transactions.\nThis means we can now use transactions to perform symref updates and not\nhave to resort to `create_symref()`. Doing this allows us to remove and\ncleanup `create_symref()`, which we will do in the following commit.\n\nModify the expected error message for a test in\n't/t0610-reftable-basics.sh', since the error is now thrown from the\n'refs.c'. This is because in transactional updates, F/D conflicts are\ncaught before we're in the reference backend.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                           | 24 +++++++++++++++++-------\n t/t0610-reftable-basics.sh       |  2 +-\n t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++\n 3 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex a4dca08244..1b67c87f47 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2291,14 +2291,24 @@ int refs_create_symref(struct ref_store *refs,\n \t\t       const char *refs_heads_master,\n \t\t       const char *logmsg)\n {\n-\tchar *msg;\n-\tint retval;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret = 0;\n \n-\tmsg = normalize_reflog_message(logmsg);\n-\tretval = refs->be->create_symref(refs, ref_target, refs_heads_master,\n-\t\t\t\t\t msg);\n-\tfree(msg);\n-\treturn retval;\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n+\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t\t\t\t   logmsg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n+\t\tret = error(\"%s\", err.buf);\n+\t}\n+\n+\tstrbuf_release(&err);\n+\tif (transaction)\n+\t\tref_transaction_free(transaction);\n+\n+\treturn ret;\n }\n \n int create_symref(const char *ref_target, const char *refs_heads_master,\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 178791e086..9e8d22bcbd 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl\n \tgit init repo &&\n \ttest_commit -C repo A &&\n \tcat >expect <<-EOF &&\n-\terror: unable to write symref for refs/heads: file/directory conflict\n+\terror: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}\n \tEOF\n \ttest_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&\n \ttest_cmp expect err\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..4433ac2177 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+test_expect_success 'hook captures git-symbolic-ref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\n+\tcat >expect <<-EOF &&\n+\t\tprepared\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\t\tcommitted\n+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493843","messageId":"20240501202229.2695774-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:28Z","receivedAt":"2024-05-01T20:22:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function is used to update/create a symref.\nBut it doesn't check the old target of the symref, if existing. It force\nupdates the symref. In this regard, the name `refs_create_symref()` is a\nbit misleading. So let's rename it to `refs_update_symref()`. This is\nakin to how 'git-update-ref(1)' also allows us to create apart from\nupdate.\n\nWhile we're here, rename the arguments in the function to clarify what\nthey actually signify and reduce confusion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/branch.c          |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 12 +++++-------\n refs.h                    |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 5 files changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex dd3e3a7dc0..4491f7a20c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_create_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7c6c72536b..480202c517 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_create_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 1b67c87f47..1215b0fae2 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2286,10 +2286,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)\n \treturn peel_object(base, peeled) ? -1 : 0;\n }\n \n-int refs_create_symref(struct ref_store *refs,\n-\t\t       const char *ref_target,\n-\t\t       const char *refs_heads_master,\n-\t\t       const char *logmsg)\n+int refs_update_symref(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2297,8 +2295,8 @@ int refs_create_symref(struct ref_store *refs,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n-\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n@@ -2314,7 +2312,7 @@ int refs_create_symref(struct ref_store *refs,\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n-\treturn refs_create_symref(get_main_ref_store(the_repository), ref_target,\n+\treturn refs_update_symref(get_main_ref_store(the_repository), ref_target,\n \t\t\t\t  refs_heads_master, logmsg);\n }\n \ndiff --git a/refs.h b/refs.h\nindex c7851bf587..71cc1c58e0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int copy_existing_ref(const char *oldref, const char *newref,\n \t\t\tconst char *logmsg);\n \n-int refs_create_symref(struct ref_store *refs, const char *refname,\n+int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n int create_symref(const char *refname, const char *target, const char *logmsg);\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 82bbf6e2e6..4651e4ced7 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_create_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.43.GIT\n\n"},{"id":"493844","messageId":"20240501202229.2695774-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v5 7/7] refs: remove `create_symref` and associated dead code","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-01T20:22:29Z","receivedAt":"2024-05-01T20:22:44Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we converted `refs_create_symref()` to utilize\ntransactions to perform symref updates. Earlier `refs_create_symref()`\nused `create_symref()` to do the same.\n\nThis means, we can now remove `create_symref()` and any code associated\nwith it which is no longer used. We remove `create_symref()` code from\nall the reference backends and also remove it entirely from the\n`ref_storage_be` struct.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/debug.c            | 13 -------\n refs/files-backend.c    | 64 ------------------------------\n refs/packed-backend.c   |  1 -\n refs/refs-internal.h    |  5 ---\n refs/reftable-backend.c | 86 -----------------------------------------\n 5 files changed, 169 deletions(-)\n\ndiff --git a/refs/debug.c b/refs/debug.c\nindex c7531b17f0..8be316bb67 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o\n \treturn res;\n }\n \n-static int debug_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *ref_name, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;\n-\tint res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,\n-\t\t\t\t\t\t logmsg);\n-\ttrace_printf_key(&trace_refs, \"create_symref: %s -> %s \\\"%s\\\": %d\\n\", ref_name,\n-\t\ttarget, logmsg, res);\n-\treturn res;\n-}\n-\n static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,\n \t\t\t    const char *newref, const char *logmsg)\n {\n@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {\n \t.initial_transaction_commit = debug_initial_transaction_commit,\n \n \t.pack_refs = debug_pack_refs,\n-\t.create_symref = debug_create_symref,\n \t.rename_ref = debug_rename_ref,\n \t.copy_ref = debug_copy_ref,\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 85c4af7e89..7597760f63 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)\n \treturn ret;\n }\n \n-static void update_symref_reflog(struct files_ref_store *refs,\n-\t\t\t\t struct ref_lock *lock, const char *refname,\n-\t\t\t\t const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct object_id new_oid;\n-\n-\tif (logmsg &&\n-\t    refs_resolve_ref_unsafe(&refs->base, target,\n-\t\t\t\t    RESOLVE_REF_READING, &new_oid, NULL) &&\n-\t    files_log_ref_write(refs, refname, &lock->old_oid,\n-\t\t\t\t&new_oid, logmsg, 0, &err)) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t}\n-}\n-\n static int create_symref_lock(struct files_ref_store *refs,\n \t\t\t      struct ref_lock *lock, const char *refname,\n \t\t\t      const char *target)\n@@ -1934,52 +1917,6 @@ static int create_symref_lock(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int create_and_commit_symref(struct files_ref_store *refs,\n-\t\t\t\t    struct ref_lock *lock, const char *refname,\n-\t\t\t\t    const char *target, const char *logmsg)\n-{\n-\tint ret;\n-\n-\tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\t\treturn 0;\n-\t}\n-\n-\tret = create_symref_lock(refs, lock, refname, target);\n-\tif (!ret) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\n-\t\tif (commit_ref(lock) < 0)\n-\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t\t     strerror(errno));\n-\t}\n-\n-\treturn ret;\n-}\n-\n-static int files_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *refname, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct files_ref_store *refs =\n-\t\tfiles_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_lock *lock;\n-\tint ret;\n-\n-\tlock = lock_ref_oid_basic(refs, refname, &err);\n-\tif (!lock) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t\treturn -1;\n-\t}\n-\n-\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n-\n-\tunlock_ref(lock);\n-\treturn ret;\n-}\n-\n static int files_reflog_exists(struct ref_store *ref_store,\n \t\t\t       const char *refname)\n {\n@@ -3394,7 +3331,6 @@ struct ref_storage_be refs_be_files = {\n \t.initial_transaction_commit = files_initial_transaction_commit,\n \n \t.pack_refs = files_pack_refs,\n-\t.create_symref = files_create_symref,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4e826c05ff..a937e7dbfc 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {\n \t.initial_transaction_commit = packed_initial_transaction_commit,\n \n \t.pack_refs = packed_pack_refs,\n-\t.create_symref = NULL,\n \t.rename_ref = NULL,\n \t.copy_ref = NULL,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 9578665243..fa46548a2f 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n-typedef int create_symref_fn(struct ref_store *ref_store,\n-\t\t\t     const char *ref_target,\n-\t\t\t     const char *refs_heads_master,\n-\t\t\t     const char *logmsg);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -690,7 +686,6 @@ struct ref_storage_be {\n \tref_transaction_commit_fn *initial_transaction_commit;\n \n \tpack_refs_fn *pack_refs;\n-\tcreate_symref_fn *create_symref;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 5e8a696d40..d4221898e4 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1269,91 +1269,6 @@ struct write_create_symref_arg {\n \tconst char *logmsg;\n };\n \n-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n-{\n-\tstruct write_create_symref_arg *create = cb_data;\n-\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)create->refname,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)create->target,\n-\t\t.update_index = ts,\n-\t};\n-\tstruct reftable_log_record log = {0};\n-\tstruct object_id new_oid;\n-\tstruct object_id old_oid;\n-\tint ret;\n-\n-\treftable_writer_set_limits(writer, ts, ts);\n-\n-\tret = reftable_writer_add_ref(writer, &ref);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Note that it is important to try and resolve the reference before we\n-\t * write the log entry. This is because `should_write_log()` will munge\n-\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n-\t * repository because it would be written into the config. As HEAD will\n-\t * not resolve for new repositories this ordering will ensure that this\n-\t * never happens.\n-\t */\n-\tif (!create->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n-\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&create->refs->base, create->refname))\n-\t\treturn 0;\n-\n-\tfill_reftable_log_record(&log);\n-\tlog.refname = xstrdup(create->refname);\n-\tlog.update_index = ts;\n-\tlog.value.update.message = xstrndup(create->logmsg,\n-\t\t\t\t\t    create->refs->write_options.block_size / 2);\n-\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n-\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n-\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n-\n-\tret = reftable_writer_add_log(writer, &log);\n-\treftable_log_record_release(&log);\n-\treturn ret;\n-}\n-\n-static int reftable_be_create_symref(struct ref_store *ref_store,\n-\t\t\t\t     const char *refname,\n-\t\t\t\t     const char *target,\n-\t\t\t\t     const char *logmsg)\n-{\n-\tstruct reftable_ref_store *refs =\n-\t\treftable_be_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct reftable_stack *stack = stack_for(refs, refname, &refname);\n-\tstruct write_create_symref_arg arg = {\n-\t\t.refs = refs,\n-\t\t.stack = stack,\n-\t\t.refname = refname,\n-\t\t.target = target,\n-\t\t.logmsg = logmsg,\n-\t};\n-\tint ret;\n-\n-\tret = refs->err;\n-\tif (ret < 0)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_reload(stack);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_add(stack, &write_create_symref_table, &arg);\n-\n-done:\n-\tassert(ret != REFTABLE_API_ERROR);\n-\tif (ret)\n-\t\terror(\"unable to write symref for %s: %s\", refname,\n-\t\t      reftable_error_str(ret));\n-\treturn ret;\n-}\n-\n struct write_copy_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2261,7 +2176,6 @@ struct ref_storage_be refs_be_reftable = {\n \t.initial_transaction_commit = reftable_be_initial_transaction_commit,\n \n \t.pack_refs = reftable_be_pack_refs,\n-\t.create_symref = reftable_be_create_symref,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.43.GIT\n\n"},{"id":"493848","messageId":"xmqq7cgdjig4.fsf@gitster.g","threadId":"61235","inReplyTo":"20240501202229.2695774-3-knayak@gitlab.com","subject":"Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-01T22:06:19Z","receivedAt":"2024-05-01T22:06:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> +\t\treturn error(\"unable to fdopen %s: %s\",\n> +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> +\n> +\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n> +\t\treturn error(\"unable to fprintf %s: %s\",\n> +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n\nerror() is end-user facing, so \"fprintf\" is probably a bit too\nprecise?  \"fprintf\" -> \"write to\"\n\nAlso we may want to make them (not just this new message but other\nerror() messages in related code paths) localizable but that is\nprobably beyond the scope of this topic.\n"},{"id":"493852","messageId":"xmqqy18tgmkl.fsf@gitster.g","threadId":"61235","inReplyTo":"20240501202229.2695774-4-knayak@gitlab.com","subject":"Re: [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-01T23:05:30Z","receivedAt":"2024-05-01T23:05:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> +\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n\nAlthough the precedence rule does not require it,\n\n\t\tif ((update->flags & REF_HAVE_OLD) && update_old_target)\n\nis probably easier to read.\n\n> +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n> +\t\telse\n> +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n\nSo the promise this code assumes is that .old_target member is\nnon-NULL if and only if the ref originally is a symbolic ref?\n\nAnd if the \"we do not care what the original value is, whether it is\na normal ref or a symbolic one\" case, .old_oid would be all '\\0' and\nREF_HAVE_OLD bit is not set?\n\nIf we can write it like so:\n\n\tif (!(update->flags & REF_HAVE_OLD))\n\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n\telse if (update->old_target)\n\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n\telse\n\t\tstrbuf_addf(&buf, \"ref:%s \", oid_to_hex(update->old_oid));\n\nit may make the intent of the code a lot more clear.  If we are\noperating in \"!HAVE_OLD\" mode, we show 0{40}.  Otherwise, old_target\nis non-NULL when the thing is symbolic, and if old_target is NULL,\nit is not symbolic and has its own value.\n\nThe same comment applies to the other side.\n\n> +\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n> +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n> +\t\telse\n> +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n\n\n> +\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n>  \n>  \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n>  \t\t\tif (errno != EPIPE) {\n"},{"id":"493853","messageId":"xmqqbk5pgke0.fsf@gitster.g","threadId":"61235","inReplyTo":"20240501202229.2695774-5-knayak@gitlab.com","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-01T23:52:39Z","receivedAt":"2024-05-01T23:52:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> The reference backends currently support transactional reference\n> updates. While this is exposed to users via 'git-update-ref' and its\n> '--stdin' mode, it is also used internally within various commands.\n>\n> However, we never supported transactional updates of symrefs. Let's add\n> support for symrefs in both the 'files' and the 'reftable' backend.\n>\n> Here, we add and use `ref_update_is_null_new_value()`, a helper function\n> which is used to check if there is a new_value in a reference update.\n\nI know you want to express a condition where you answer yes to \"Is\nthe new value specified in this ref update NULL?\", but \"is\" at that\nposition in the name somehow sounds awkward.  Any of\n\n\tref_update_has_null_new_value\n        ref_update_with_no_new_value\n        ref_update_without_new_value\n\nmight be nicer to ears.\n\n> We do not add reflog for dangling symref updates, because currently\n> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\n> would be best to keep this behavior consistent as we would move it to\n> start using transaction based updates in the following commit.\n\nIf we are not changing the behaviour, does it deserve a four-line\nparagraph?  It is not like we describe every no changes (i.e. \"we\ncould break the behaviour by introducing this and that bugs, but we\ndid not\" is not something we usually say in proposed log messages).\n\nAt most, if you want to highlight that behaviour, I would expect a\nbrief mention like:\n\n    Note that a dangling symref update does not record a new reflog\n    entry, which is unchanged before and after this commit.\n\nAs a reflog entry records name of the object that is pointed by the\nref (either directly or indirectly) before and after an operation,\nan operation that involve a dangling reflog that does not point at\nany object cannot be expressed in a reflog, no?  It is way too late\nto change this, but it would have been interesting if the design of\nreflog had a room to log the change of symbolic ref target as well\nas object names.  It would have allowed us to say \"HEAD at time T\npointed at refs/heads/main (which did not exist)\", \"HEAD at time T+1\ndirectly pointed at commit X (detached)\", \"HEAD at time T+2 pointed\nat refs/heads/next\", etc. and allowed us to much more cleanly\nsupport \"previous branch\".\n\n> @@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(\n>  \n>  \tupdate->flags = flags;\n>  \n> -\tif (flags & REF_HAVE_NEW)\n> +\tif (new_target)\n> +\t\tupdate->new_target = xstrdup(new_target);\n> +\tif (old_target)\n> +\t\tupdate->old_target = xstrdup(old_target);\n\n\"Is the assumption that *update is 0-initialized?\" was the first\nquestion that came to my mind.\n\nDoing an unconditional\n\n\tupdate->new_target = xstrdup_or_null(new_target);\n\tupdate->old_target = xstrdup_or_null(old_target);\n\nwould convey the intention much more clearly without having readers\nguess the answer to the above question.\n\n> +\tif (new_oid && flags & REF_HAVE_NEW)\n\nEven though syntactically not required, \n\n\tif (new_oid && (flags & REF_HAVE_NEW))\n\nor better yet\n\n\tif ((flags & REF_HAVE_NEW) && new_oid)\n\nwould be easier to see.\n\n>  \t\toidcpy(&update->new_oid, new_oid);\n\nAgain is the expectation that update->new_oid is initialized to\nall-0?  I am wondering if we want an else clause, i.e.\n\n\tif (!(flags & REF_HAVE_NEW))\n\t\toidcpy(&update->new_oid, null_oid());\n\telse\n\t\toidcpy(&update->new_oid, new_oid ? new_oid : null_oid());\n\nto clarify the intention of the code, since the way you wrote the\nconsumer of thes two members and REF_HAVE_NEW bit in the previous\nstep implied that the new_oid member gets used even when REF_HAVE_*\nbit is off, only for its null_oid() value.\n\nI'll stop here for now.\n\nThanks.\n"},{"id":"493854","messageId":"xmqq34r1gj47.fsf@gitster.g","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"Re: [PATCH v5 0/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T00:20:08Z","receivedAt":"2024-05-02T00:20:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> The patch series takes over from the existing patch series, wherein we\n> introduced symref-* commands to git-update-ref. Since there was a ton of\n> discussions on the UX of the patch series and its application, I thought it\n> would be best to shorten the series and split it into multiple smaller series.\n>\n> This series adds transactional support for symrefs in the reference db. Then\n> we switch refs_create_symref() to start using transactions for symref updates.\n> This allows us to deprecate the create_symref code in the ref_storage_be\n> interface and remove all associated code which is no longer used.\n>\n> The split was primarily done so we can merge the non-user facing parts of the\n> previous series. While pertaining the user facing components into another set\n> of patches wherein deeper discussion on the UX can be held without worrying\n> about the internal implementation.\n\nThis split probably makes sense in the context of the evolution of\nthis series.  If this were without any prior discussion, a change to\nthe internal mechanism, without showing how the end-user facing half\nwould use it fully, would have been hard to evaluate, but now that\nwe know where the new mechanism wants to take us, we can fairly\nevaluate it alone without the end-user facing part.\n\nI've read only the earlier half of the series but so far the pieces\nmake sense to me.\n"},{"id":"493869","messageId":"CAOLa=ZQrjYRq14KYsGASD+ua5fNNQCjnO0JbL4W7RTbXc1veJg@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqy18tgmkl.fsf@gitster.g","subject":"Re: [PATCH v5 3/7] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T05:32:46Z","receivedAt":"2024-05-02T05:32:48Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> +\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n>\n> Although the precedence rule does not require it,\n>\n> \t\tif ((update->flags & REF_HAVE_OLD) && update_old_target)\n>\n> is probably easier to read.\n>\n\nWill add.\n\n>> +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n>> +\t\telse\n>> +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n>\n> So the promise this code assumes is that .old_target member is\n> non-NULL if and only if the ref originally is a symbolic ref?\n>\n\nYes, for old_target this is correct. new_target could be set for a ref\nto convert it to a symbolic ref.\n\n> And if the \"we do not care what the original value is, whether it is\n> a normal ref or a symbolic one\" case, .old_oid would be all '\\0' and\n> REF_HAVE_OLD bit is not set?\n>\n\nYup that's accurate.\n\n> If we can write it like so:\n>\n> \tif (!(update->flags & REF_HAVE_OLD))\n> \t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n> \telse if (update->old_target)\n> \t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n> \telse\n> \t\tstrbuf_addf(&buf, \"ref:%s \", oid_to_hex(update->old_oid));\n>\n> it may make the intent of the code a lot more clear.  If we are\n> operating in \"!HAVE_OLD\" mode, we show 0{40}.  Otherwise, old_target\n> is non-NULL when the thing is symbolic, and if old_target is NULL,\n> it is not symbolic and has its own value.\n>\n> The same comment applies to the other side.\n>\n\nI see how it makes it clearer, but I think the intent with the existing\ncode was clear too. I'll add this change to my local for the next\nversion.\n\n>> +\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n>> +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n>> +\t\telse\n>> +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n>\n>\n>> +\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n>>\n>>  \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n>>  \t\t\tif (errno != EPIPE) {\n"},{"id":"493870","messageId":"CAOLa=ZQkTM26wmGgj3=Pz9p7-nFAWQdxCA=RjaJVSm2m8zn7jQ@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqbk5pgke0.fsf@gitster.g","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T05:50:47Z","receivedAt":"2024-05-02T05:50:49Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The reference backends currently support transactional reference\n>> updates. While this is exposed to users via 'git-update-ref' and its\n>> '--stdin' mode, it is also used internally within various commands.\n>>\n>> However, we never supported transactional updates of symrefs. Let's add\n>> support for symrefs in both the 'files' and the 'reftable' backend.\n>>\n>> Here, we add and use `ref_update_is_null_new_value()`, a helper function\n>> which is used to check if there is a new_value in a reference update.\n>\n> I know you want to express a condition where you answer yes to \"Is\n> the new value specified in this ref update NULL?\", but \"is\" at that\n> position in the name somehow sounds awkward.  Any of\n>\n> \tref_update_has_null_new_value\n>         ref_update_with_no_new_value\n>         ref_update_without_new_value\n>\n> might be nicer to ears.\n>\n\nYes, thanks with this, I do agree that `ref_update_has_null_new_value`\nsounds better.\n\n>> We do not add reflog for dangling symref updates, because currently\n>> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\n>> would be best to keep this behavior consistent as we would move it to\n>> start using transaction based updates in the following commit.\n>\n> If we are not changing the behaviour, does it deserve a four-line\n> paragraph?  It is not like we describe every no changes (i.e. \"we\n> could break the behaviour by introducing this and that bugs, but we\n> did not\" is not something we usually say in proposed log messages).\n>\n> At most, if you want to highlight that behaviour, I would expect a\n> brief mention like:\n>\n>     Note that a dangling symref update does not record a new reflog\n>     entry, which is unchanged before and after this commit.\n>\n> As a reflog entry records name of the object that is pointed by the\n> ref (either directly or indirectly) before and after an operation,\n> an operation that involve a dangling reflog that does not point at\n> any object cannot be expressed in a reflog, no?  It is way too late\n> to change this, but it would have been interesting if the design of\n> reflog had a room to log the change of symbolic ref target as well\n> as object names.  It would have allowed us to say \"HEAD at time T\n> pointed at refs/heads/main (which did not exist)\", \"HEAD at time T+1\n> directly pointed at commit X (detached)\", \"HEAD at time T+2 pointed\n> at refs/heads/next\", etc. and allowed us to much more cleanly\n> support \"previous branch\".\n>\n\nWhile I agree that four lines may seem excessive, I think it is indeed\nan important point to note. Mostly because this shows that when doing\ndangling symref updates, there is no record of this update. The best\nsituation would be like you mentioned, to record the symref target\nchanges. But even with the current design, it would have been nice to at\nleast acknowledge that there was some update done to the symref. By\nhaving zero-oid for the new and old value in the reflog. But seems like\nwe can't do that either.\n\n>> @@ -1247,10 +1249,15 @@ struct ref_update *ref_transaction_add_update(\n>>\n>>  \tupdate->flags = flags;\n>>\n>> -\tif (flags & REF_HAVE_NEW)\n>> +\tif (new_target)\n>> +\t\tupdate->new_target = xstrdup(new_target);\n>> +\tif (old_target)\n>> +\t\tupdate->old_target = xstrdup(old_target);\n>\n> \"Is the assumption that *update is 0-initialized?\" was the first\n> question that came to my mind.\n>\n> Doing an unconditional\n>\n> \tupdate->new_target = xstrdup_or_null(new_target);\n> \tupdate->old_target = xstrdup_or_null(old_target);\n>\n> would convey the intention much more clearly without having readers\n> guess the answer to the above question.\n>\n\nRight, I didn't catch the nuance last time, thanks for the explanation.\n\n>> +\tif (new_oid && flags & REF_HAVE_NEW)\n>\n> Even though syntactically not required,\n>\n> \tif (new_oid && (flags & REF_HAVE_NEW))\n>\n> or better yet\n>\n> \tif ((flags & REF_HAVE_NEW) && new_oid)\n>\n> would be easier to see.\n>\n>>  \t\toidcpy(&update->new_oid, new_oid);\n>\n> Again is the expectation that update->new_oid is initialized to\n> all-0?  I am wondering if we want an else clause, i.e.\n>\n> \tif (!(flags & REF_HAVE_NEW))\n> \t\toidcpy(&update->new_oid, null_oid());\n> \telse\n> \t\toidcpy(&update->new_oid, new_oid ? new_oid : null_oid());\n>\n> to clarify the intention of the code, since the way you wrote the\n> consumer of thes two members and REF_HAVE_NEW bit in the previous\n> step implied that the new_oid member gets used even when REF_HAVE_*\n> bit is off, only for its null_oid() value.\n>\n\nYes I understand what you're saying, but since we're doing a\n`FLEX_ALLOC_MEM` right above this code, I thought the fact that the\n'update' struct is 0 initialized is known. With this, and the fact that\nhere `update->new_oid` is a static variable, while `new_oid` is a\npointer. I think being too verbose is not required.\n\n> I'll stop here for now.\n>\n> Thanks.\n>\n\nThanks Junio for taking the time to review.\n"},{"id":"493871","messageId":"CAOLa=ZT+JyJQzfuQmseZSc1mzFghHriX5fRaua3V7XWJues-_g@mail.gmail.com","threadId":"61235","inReplyTo":"xmqq34r1gj47.fsf@gitster.g","subject":"Re: [PATCH v5 0/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T05:53:31Z","receivedAt":"2024-05-02T05:53:33Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The patch series takes over from the existing patch series, wherein we\n>> introduced symref-* commands to git-update-ref. Since there was a ton of\n>> discussions on the UX of the patch series and its application, I thought it\n>> would be best to shorten the series and split it into multiple smaller series.\n>>\n>> This series adds transactional support for symrefs in the reference db. Then\n>> we switch refs_create_symref() to start using transactions for symref updates.\n>> This allows us to deprecate the create_symref code in the ref_storage_be\n>> interface and remove all associated code which is no longer used.\n>>\n>> The split was primarily done so we can merge the non-user facing parts of the\n>> previous series. While pertaining the user facing components into another set\n>> of patches wherein deeper discussion on the UX can be held without worrying\n>> about the internal implementation.\n>\n> This split probably makes sense in the context of the evolution of\n> this series.  If this were without any prior discussion, a change to\n> the internal mechanism, without showing how the end-user facing half\n> would use it fully, would have been hard to evaluate, but now that\n> we know where the new mechanism wants to take us, we can fairly\n> evaluate it alone without the end-user facing part.\n>\n> I've read only the earlier half of the series but so far the pieces\n> make sense to me.\n\nYes I agree, I think it's also nice to see how a bunch of code can be\nremoved to use this generic functionality.\n\nI'll wait for a day/two for other reviews. Thanks for your review.\n"},{"id":"493890","messageId":"ZjNFBZOlcQNVYm8O@tanuki","threadId":"61235","inReplyTo":"xmqq7cgdjig4.fsf@gitster.g","subject":"Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T07:47:17Z","receivedAt":"2024-05-02T07:47:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:\n> Karthik Nayak <karthik.188@gmail.com> writes:\n> \n> > +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n> > +\t\treturn error(\"unable to fdopen %s: %s\",\n> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> > +\n> > +\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n> > +\t\treturn error(\"unable to fprintf %s: %s\",\n> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n> \n> error() is end-user facing, so \"fprintf\" is probably a bit too\n> precise?  \"fprintf\" -> \"write to\"\n> \n> Also we may want to make them (not just this new message but other\n> error() messages in related code paths) localizable but that is\n> probably beyond the scope of this topic.\n\nIt only occurred to me now, but shouldn't we also support passing in a\n`struct strbuf *err` here? The transactional code doesn't want us to\nprint error messages to `stderr`, but always supplies a buffer.\n\nPatrick\n"},{"id":"493891","messageId":"ZjNFC6A5j5hMQ5VA@tanuki","threadId":"61235","inReplyTo":"CAOLa=ZQkTM26wmGgj3=Pz9p7-nFAWQdxCA=RjaJVSm2m8zn7jQ@mail.gmail.com","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T07:47:23Z","receivedAt":"2024-05-02T07:47:27Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, May 02, 2024 at 05:50:47AM +0000, Karthik Nayak wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> > Karthik Nayak <karthik.188@gmail.com> writes:\n> >> From: Karthik Nayak <karthik.188@gmail.com>\n> >> We do not add reflog for dangling symref updates, because currently\n> >> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\n> >> would be best to keep this behavior consistent as we would move it to\n> >> start using transaction based updates in the following commit.\n> >\n> > If we are not changing the behaviour, does it deserve a four-line\n> > paragraph?  It is not like we describe every no changes (i.e. \"we\n> > could break the behaviour by introducing this and that bugs, but we\n> > did not\" is not something we usually say in proposed log messages).\n> >\n> > At most, if you want to highlight that behaviour, I would expect a\n> > brief mention like:\n> >\n> >     Note that a dangling symref update does not record a new reflog\n> >     entry, which is unchanged before and after this commit.\n> >\n> > As a reflog entry records name of the object that is pointed by the\n> > ref (either directly or indirectly) before and after an operation,\n> > an operation that involve a dangling reflog that does not point at\n> > any object cannot be expressed in a reflog, no?  It is way too late\n> > to change this, but it would have been interesting if the design of\n> > reflog had a room to log the change of symbolic ref target as well\n> > as object names.  It would have allowed us to say \"HEAD at time T\n> > pointed at refs/heads/main (which did not exist)\", \"HEAD at time T+1\n> > directly pointed at commit X (detached)\", \"HEAD at time T+2 pointed\n> > at refs/heads/next\", etc. and allowed us to much more cleanly\n> > support \"previous branch\".\n> >\n> \n> While I agree that four lines may seem excessive, I think it is indeed\n> an important point to note. Mostly because this shows that when doing\n> dangling symref updates, there is no record of this update. The best\n> situation would be like you mentioned, to record the symref target\n> changes. But even with the current design, it would have been nice to at\n> least acknowledge that there was some update done to the symref. By\n> having zero-oid for the new and old value in the reflog. But seems like\n> we can't do that either.\n\nI wouldn't say we can't do that. We already do log when symrefs become\ndangling when updating references via HEAD by logging a zero OID as new\nOID. That is, if we have \"HEAD -> refs/heads/foo\" and you delete the\nlatter, then we create a new reflog message for \"HEAD\" with zero OID as\nnew OID.\n\nI would claim that the current behaviour where we don't create a reflog\nentry when updating a ref to become dangling is a mere bug. I think it's\nfair to declare this a #leftoverbit and handle it in a follow-up patch\nseries. But it would be nice to say so in an in-code comment.\n\nPatrick\n"},{"id":"493892","messageId":"ZjNFEHA9QgQlMl5F@tanuki","threadId":"61235","inReplyTo":"20240501202229.2695774-6-knayak@gitlab.com","subject":"Re: [PATCH v5 5/7] refs: use transaction in `refs_create_symref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T07:47:28Z","receivedAt":"2024-05-02T07:47:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 01, 2024 at 10:22:27PM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The `refs_create_symref()` function updates a symref to a given new\n> target. To do this, it uses a ref-backend specific function\n> `create_symref()`.\n> \n> In this previous commit, we introduce symref support in transactions.\n> This means we can now use transactions to perform symref updates and not\n> have to resort to `create_symref()`. Doing this allows us to remove and\n\nNit: \"not have to\" -> \"don't have to\"\n\n[snip]\n> diff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\n> index 178791e086..9e8d22bcbd 100755\n> --- a/t/t0610-reftable-basics.sh\n> +++ b/t/t0610-reftable-basics.sh\n> @@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl\n>  \tgit init repo &&\n>  \ttest_commit -C repo A &&\n>  \tcat >expect <<-EOF &&\n> -\terror: unable to write symref for refs/heads: file/directory conflict\n> +\terror: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}\n>  \tEOF\n>  \ttest_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&\n>  \ttest_cmp expect err\n\nNice. Not only do we have less code to worry about, but the error\nmessage is better, too.\n\n> diff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\n> index 2092488090..4433ac2177 100755\n> --- a/t/t1416-ref-transaction-hooks.sh\n> +++ b/t/t1416-ref-transaction-hooks.sh\n> @@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '\n>  \ttest_cmp expect target-repo.git/actual\n>  '\n>  \n> +test_expect_success 'hook captures git-symbolic-ref updates' '\n> +\ttest_when_finished \"rm actual\" &&\n> +\n> +\ttest_hook reference-transaction <<-\\EOF &&\n> +\t\techo \"$*\" >>actual\n> +\t\twhile read -r line\n> +\t\tdo\n> +\t\t\tprintf \"%s\\n\" \"$line\"\n> +\t\tdone >>actual\n> +\tEOF\n> +\n> +\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n> +\n> +\tcat >expect <<-EOF &&\n> +\t\tprepared\n> +\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n> +\t\tcommitted\n> +\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n> +\tEOF\n\nNit: the contents of the heredoc should be indented one level less.\n\nPatrick\n"},{"id":"493893","messageId":"ZjNFFreAkolYMCd_@tanuki","threadId":"61235","inReplyTo":"20240501202229.2695774-7-knayak@gitlab.com","subject":"Re: [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T07:47:34Z","receivedAt":"2024-05-02T07:47:38Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 01, 2024 at 10:22:28PM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The `refs_create_symref()` function is used to update/create a symref.\n> But it doesn't check the old target of the symref, if existing. It force\n> updates the symref. In this regard, the name `refs_create_symref()` is a\n> bit misleading. So let's rename it to `refs_update_symref()`. This is\n> akin to how 'git-update-ref(1)' also allows us to create apart from\n> update.\n\nArguably, as we are already updating all callsites anyway, I don't see a\nreason why we shouldn't also update the function to accept the old OID\nor old target so that callers can make raceless updates.\n\nThe only problem is that we don't have any users yet, and consequently\nwe have no way to verify that it works as intended. So maybe this is\nbetter left for a future patch series, unless we have places where we\ncan reasonably update the callers to pass in the old value, as well.\n\nPatrick\n"},{"id":"493894","messageId":"ZjNFG5Ztnoj9DMzB@tanuki","threadId":"61235","inReplyTo":"20240501202229.2695774-8-knayak@gitlab.com","subject":"Re: [PATCH v5 7/7] refs: remove `create_symref` and associated dead code","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-02T07:47:39Z","receivedAt":"2024-05-02T07:47:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Wed, May 01, 2024 at 10:22:29PM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> In the previous commits, we converted `refs_create_symref()` to utilize\n> transactions to perform symref updates. Earlier `refs_create_symref()`\n> used `create_symref()` to do the same.\n> \n> This means, we can now remove `create_symref()` and any code associated\n> with it which is no longer used. We remove `create_symref()` code from\n> all the reference backends and also remove it entirely from the\n> `ref_storage_be` struct.\n\nVery nice.\n\nPatrick\n"},{"id":"493914","messageId":"CAOLa=ZS7Co01QegZcJ6uqRoTfQ6d7dXNMy6ZyajsonC2TOV1EQ@mail.gmail.com","threadId":"61235","inReplyTo":"ZjNFBZOlcQNVYm8O@tanuki","subject":"Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T11:05:37Z","receivedAt":"2024-05-02T11:05:39Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>>\n>> > +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n>> > +\t\treturn error(\"unable to fdopen %s: %s\",\n>> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> > +\n>> > +\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n>> > +\t\treturn error(\"unable to fprintf %s: %s\",\n>> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>>\n>> error() is end-user facing, so \"fprintf\" is probably a bit too\n>> precise?  \"fprintf\" -> \"write to\"\n>>\n>> Also we may want to make them (not just this new message but other\n>> error() messages in related code paths) localizable but that is\n>> probably beyond the scope of this topic.\n>\n> It only occurred to me now, but shouldn't we also support passing in a\n> `struct strbuf *err` here? The transactional code doesn't want us to\n> print error messages to `stderr`, but always supplies a buffer.\n>\n> Patrick\n\nYes I think that would fit better with the existing transaction code.\n"},{"id":"493915","messageId":"CAOLa=ZQfm0xgOBuOi8__SKqcj4DnTBoPLgvBtArv4asuZ7UUcw@mail.gmail.com","threadId":"61235","inReplyTo":"ZjNFC6A5j5hMQ5VA@tanuki","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T11:10:03Z","receivedAt":"2024-05-02T11:10:05Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Thu, May 02, 2024 at 05:50:47AM +0000, Karthik Nayak wrote:\n>> Junio C Hamano <gitster@pobox.com> writes:\n>> > Karthik Nayak <karthik.188@gmail.com> writes:\n>> >> From: Karthik Nayak <karthik.188@gmail.com>\n>> >> We do not add reflog for dangling symref updates, because currently\n>> >> 'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\n>> >> would be best to keep this behavior consistent as we would move it to\n>> >> start using transaction based updates in the following commit.\n>> >\n>> > If we are not changing the behaviour, does it deserve a four-line\n>> > paragraph?  It is not like we describe every no changes (i.e. \"we\n>> > could break the behaviour by introducing this and that bugs, but we\n>> > did not\" is not something we usually say in proposed log messages).\n>> >\n>> > At most, if you want to highlight that behaviour, I would expect a\n>> > brief mention like:\n>> >\n>> >     Note that a dangling symref update does not record a new reflog\n>> >     entry, which is unchanged before and after this commit.\n>> >\n>> > As a reflog entry records name of the object that is pointed by the\n>> > ref (either directly or indirectly) before and after an operation,\n>> > an operation that involve a dangling reflog that does not point at\n>> > any object cannot be expressed in a reflog, no?  It is way too late\n>> > to change this, but it would have been interesting if the design of\n>> > reflog had a room to log the change of symbolic ref target as well\n>> > as object names.  It would have allowed us to say \"HEAD at time T\n>> > pointed at refs/heads/main (which did not exist)\", \"HEAD at time T+1\n>> > directly pointed at commit X (detached)\", \"HEAD at time T+2 pointed\n>> > at refs/heads/next\", etc. and allowed us to much more cleanly\n>> > support \"previous branch\".\n>> >\n>>\n>> While I agree that four lines may seem excessive, I think it is indeed\n>> an important point to note. Mostly because this shows that when doing\n>> dangling symref updates, there is no record of this update. The best\n>> situation would be like you mentioned, to record the symref target\n>> changes. But even with the current design, it would have been nice to at\n>> least acknowledge that there was some update done to the symref. By\n>> having zero-oid for the new and old value in the reflog. But seems like\n>> we can't do that either.\n>\n> I wouldn't say we can't do that. We already do log when symrefs become\n> dangling when updating references via HEAD by logging a zero OID as new\n> OID. That is, if we have \"HEAD -> refs/heads/foo\" and you delete the\n> latter, then we create a new reflog message for \"HEAD\" with zero OID as\n> new OID.\n>\n> I would claim that the current behaviour where we don't create a reflog\n> entry when updating a ref to become dangling is a mere bug. I think it's\n> fair to declare this a #leftoverbit and handle it in a follow-up patch\n> series. But it would be nice to say so in an in-code comment.\n>\n\nI think _can't_ wasn't the best terminology. My previous series actually\nadded a reflog, but I noticed a bunch of tests were failing and I think\nit made sense to keep the existing behaviour.\n\nBut addressing it as a bug would definitely be a good way to go and fix\nthis, I'll add a comment in the code for now.\n"},{"id":"493916","messageId":"CAOLa=ZSJABxcW643hmpsot5M-_-h-_XHpNAgoof9HCXwj1zhKQ@mail.gmail.com","threadId":"61235","inReplyTo":"ZjNFFreAkolYMCd_@tanuki","subject":"Re: [PATCH v5 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-02T11:34:22Z","receivedAt":"2024-05-02T11:34:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 01, 2024 at 10:22:28PM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The `refs_create_symref()` function is used to update/create a symref.\n>> But it doesn't check the old target of the symref, if existing. It force\n>> updates the symref. In this regard, the name `refs_create_symref()` is a\n>> bit misleading. So let's rename it to `refs_update_symref()`. This is\n>> akin to how 'git-update-ref(1)' also allows us to create apart from\n>> update.\n>\n> Arguably, as we are already updating all callsites anyway, I don't see a\n> reason why we shouldn't also update the function to accept the old OID\n> or old target so that callers can make raceless updates.\n>\n> The only problem is that we don't have any users yet, and consequently\n> we have no way to verify that it works as intended. So maybe this is\n> better left for a future patch series, unless we have places where we\n> can reasonably update the callers to pass in the old value, as well.\n>\n> Patrick\n\nYeah that did run through my mind too, but without a usecase, its hard\nto justify a change and also like you mentioned harder to write tests\nfor.\n"},{"id":"493918","messageId":"xmqqwmocfbld.fsf@gitster.g","threadId":"61235","inReplyTo":"CAOLa=ZQkTM26wmGgj3=Pz9p7-nFAWQdxCA=RjaJVSm2m8zn7jQ@mail.gmail.com","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T16:00:14Z","receivedAt":"2024-05-02T16:00:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> ... By\n> having zero-oid for the new and old value in the reflog. But seems like\n> we can't do that either.\n\nEven if you could, the transition from refs/heads/main to refs/heads/next\nwould not be captured.\n\nIn any case, that is not something this step changes.  My assumption\nis that for most of developers it would be unexpected for the theme\nto \"allow symbolic updates to be transactional\" to change it, hence\n\"we do not explain what we did not change\".\n\nThanks.\n"},{"id":"493923","messageId":"xmqq8r0sf9ah.fsf@gitster.g","threadId":"61235","inReplyTo":"ZjNFBZOlcQNVYm8O@tanuki","subject":"Re: [PATCH v5 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T16:49:58Z","receivedAt":"2024-05-02T16:50:04Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 01, 2024 at 03:06:19PM -0700, Junio C Hamano wrote:\n>> Karthik Nayak <karthik.188@gmail.com> writes:\n>> \n>> > +\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n>> > +\t\treturn error(\"unable to fdopen %s: %s\",\n>> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> > +\n>> > +\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n>> > +\t\treturn error(\"unable to fprintf %s: %s\",\n>> > +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n>> \n>> error() is end-user facing, so \"fprintf\" is probably a bit too\n>> precise?  \"fprintf\" -> \"write to\"\n>> \n>> Also we may want to make them (not just this new message but other\n>> error() messages in related code paths) localizable but that is\n>> probably beyond the scope of this topic.\n>\n> It only occurred to me now, but shouldn't we also support passing in a\n> `struct strbuf *err` here? The transactional code doesn't want us to\n> print error messages to `stderr`, but always supplies a buffer.\n\nSounds sensible.  Thanks.\n"},{"id":"493924","messageId":"xmqq4jbgf980.fsf@gitster.g","threadId":"61235","inReplyTo":"ZjNFC6A5j5hMQ5VA@tanuki","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T16:51:27Z","receivedAt":"2024-05-02T16:51:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> I wouldn't say we can't do that. We already do log when symrefs become\n> dangling when updating references via HEAD by logging a zero OID as new\n> OID. That is, if we have \"HEAD -> refs/heads/foo\" and you delete the\n> latter, then we create a new reflog message for \"HEAD\" with zero OID as\n> new OID.\n>\n> I would claim that the current behaviour where we don't create a reflog\n> entry when updating a ref to become dangling is a mere bug. I think it's\n> fair to declare this a #leftoverbit and handle it in a follow-up patch\n> series. But it would be nice to say so in an in-code comment.\n\nI like that.  Thanks.\n"},{"id":"493926","messageId":"xmqqzft8dukq.fsf@gitster.g","threadId":"61235","inReplyTo":"ZjNFG5Ztnoj9DMzB@tanuki","subject":"Re: [PATCH v5 7/7] refs: remove `create_symref` and associated dead code","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T16:53:09Z","receivedAt":"2024-05-02T16:53:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Wed, May 01, 2024 at 10:22:29PM +0200, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>> \n>> In the previous commits, we converted `refs_create_symref()` to utilize\n>> transactions to perform symref updates. Earlier `refs_create_symref()`\n>> used `create_symref()` to do the same.\n>> \n>> This means, we can now remove `create_symref()` and any code associated\n>> with it which is no longer used. We remove `create_symref()` code from\n>> all the reference backends and also remove it entirely from the\n>> `ref_storage_be` struct.\n>\n> Very nice.\n\nIndeed.\n\nThe last paragraph reads well without \"This means, \", by the way.\n"},{"id":"493931","messageId":"xmqqbk5odrrr.fsf@gitster.g","threadId":"61235","inReplyTo":"20240501202229.2695774-5-knayak@gitlab.com","subject":"Re: [PATCH v5 4/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-02T17:53:44Z","receivedAt":"2024-05-02T17:53:47Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> @@ -2863,12 +2928,26 @@ static int files_transaction_finish(struct ref_store *ref_store,\n>  \n>  \t\tif (update->flags & REF_NEEDS_COMMIT ||\n>  \t\t    update->flags & REF_LOG_ONLY) {\n> -\t\t\tif (files_log_ref_write(refs,\n> -\t\t\t\t\t\tlock->ref_name,\n> -\t\t\t\t\t\t&lock->old_oid,\n> -\t\t\t\t\t\t&update->new_oid,\n> -\t\t\t\t\t\tupdate->msg, update->flags,\n> -\t\t\t\t\t\terr)) {\n> +\t\t\tint create_reflog = 1;\n> +\n> +\t\t\tif (update->new_target) {\n> +\t\t\t\t/*\n> +\t\t\t\t * We want to get the resolved OID for the target, to ensure\n> +\t\t\t\t * that the correct value is added to the reflog.\n> +\t\t\t\t */\n> +\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n> +\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n> +\t\t\t\t\t/* for dangling symrefs we skip creating a reflog entry. */\n> +\t\t\t\t\tcreate_reflog = 0;\n> +\t\t\t\t}\n> +\t\t\t}\n> +\n> +\t\t\tif (create_reflog && files_log_ref_write(refs,\n> +\t\t\t\t\t\t\t\t lock->ref_name,\n> +\t\t\t\t\t\t\t\t &lock->old_oid,\n> +\t\t\t\t\t\t\t\t &update->new_oid,\n> +\t\t\t\t\t\t\t\t update->msg, update->flags,\n> +\t\t\t\t\t\t\t\t err)) {\n>  \t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n>  \n>  \t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n\nThis hunk is overly wide.\n\nYou could of course fix it mechanically (e.g., by rewrapping overly\nwide comment block, wrapping an expression after &&-), but a code\npath that is too deeply indented may be a sign that the function may\nwant to be split into calls to a smaller helper function for\nreadability.\n"},{"id":"493991","messageId":"20240503124115.252413-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240501202229.2695774-1-knayak@gitlab.com","subject":"[PATCH v6 0/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:08Z","receivedAt":"2024-05-03T12:41:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe patch series takes over from the existing patch series, wherein we\nintroduced symref-* commands to git-update-ref. Since there was a ton of\ndiscussions on the UX of the patch series and its application, I thought it\nwould be best to shorten the series and split it into multiple smaller series.\n\nThis series adds transactional support for symrefs in the reference db. Then\nwe switch refs_create_symref() to start using transactions for symref updates.\nThis allows us to deprecate the create_symref code in the ref_storage_be\ninterface and remove all associated code which is no longer used.\n\nThe split was primarily done so we can merge the non-user facing parts of the\nprevious series. While pertaining the user facing components into another set\nof patches wherein deeper discussion on the UX can be held without worrying\nabout the internal implementation. Also by using this new functionality in a\npre-existing command, we can leverage the existing tests to catch any\ninconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for\ndangling symrefs, which I've modified my patch to do the same.\n\nWe also modify the reference transaction hook to support symrefs. For any symref\nupdate the reference transaction hook will output the value with a 'ref:' prefix.\n\nPrevious versions:\nV1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\nV2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\nV3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/\nV4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\nV5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com\n\nChanges over v5 are:\n- More user friendly error messages.\n- `create_symref_lock` now writes to an err buf, instead of directly to stderr.\n- Refactor code to make it easier to read around logical operations.\n- Cleanup commit message and fix typos.\n\nThanks to all reviewers!\n\nRange diff:\n\n1:  a354190905 = 1:  a354190905 refs: accept symref values in `ref_transaction_update()`\n2:  7dff21dbef ! 2:  0d9c5b9804 files-backend: extract out `create_symref_lock()`\n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     -\t\t\t\tconst char *target, const char *logmsg)\n     +static int create_symref_lock(struct files_ref_store *refs,\n     +\t\t\t      struct ref_lock *lock, const char *refname,\n    -+\t\t\t      const char *target)\n    ++\t\t\t      const char *target, struct strbuf *err)\n      {\n    -+\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n    -+\t\treturn error(\"unable to fdopen %s: %s\",\n    ++\tif (!fdopen_lock_file(&lock->lk, \"w\")) {\n    ++\t\tstrbuf_addf(err, \"unable to fdopen %s: %s\",\n     +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n    ++\t\treturn -1;\n    ++\t}\n     +\n    -+\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0)\n    -+\t\treturn error(\"unable to fprintf %s: %s\",\n    ++\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0) {\n    ++\t\tstrbuf_addf(err, \"unable to write to %s: %s\",\n     +\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n    ++\t\treturn -1;\n    ++\t}\n    ++\n     +\treturn 0;\n     +}\n     +\n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     +\t\t\t\t    struct ref_lock *lock, const char *refname,\n     +\t\t\t\t    const char *target, const char *logmsg)\n     +{\n    ++\tstruct strbuf err = STRBUF_INIT;\n     +\tint ret;\n     +\n      \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     -\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n     -\t\treturn error(\"unable to fdopen %s: %s\",\n     -\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n    -+\tret = create_symref_lock(refs, lock, refname, target);\n    ++\tret = create_symref_lock(refs, lock, refname, target, &err);\n     +\tif (!ret) {\n     +\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n      \n    @@ refs/files-backend.c: static void update_symref_reflog(struct files_ref_store *r\n     +\t\tif (commit_ref(lock) < 0)\n     +\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n     +\t\t\t\t     strerror(errno));\n    ++\t} else {\n    ++\t\treturn error(\"%s\", err.buf);\n     +\t}\n      \n     -\t/* no error check; commit_ref will check ferror */\n3:  901a586683 ! 3:  e0219ffd31 refs: support symrefs in 'reference-transaction' hook\n    @@ refs.c: static int run_transaction_hook(struct ref_transaction *transaction,\n     -\t\t\t    oid_to_hex(&update->new_oid),\n     -\t\t\t    update->refname);\n     +\n    -+\t\tif (update->flags & REF_HAVE_OLD && update->old_target)\n    ++\t\tif (!(update->flags & REF_HAVE_OLD))\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n    ++\t\telse if (update->old_target)\n     +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n     +\t\telse\n     +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n     +\n    -+\t\tif (update->flags & REF_HAVE_NEW && update->new_target)\n    ++\t\tif (!(update->flags & REF_HAVE_NEW))\n    ++\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n    ++\t\telse if (update->new_target)\n     +\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n     +\t\telse\n     +\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n4:  6c97f6a660 ! 4:  b22c59c722 refs: add support for transactional symref updates\n    @@ Commit message\n         However, we never supported transactional updates of symrefs. Let's add\n         support for symrefs in both the 'files' and the 'reftable' backend.\n     \n    -    Here, we add and use `ref_update_is_null_new_value()`, a helper function\n    -    which is used to check if there is a new_value in a reference update.\n    -    The new value could either be a symref target `new_target` or a OID\n    -    `new_oid`.\n    +    Here, we add and use `ref_update_has_null_new_value()`, a helper\n    +    function which is used to check if there is a new_value in a reference\n    +    update. The new value could either be a symref target `new_target` or a\n    +    OID `new_oid`.\n     \n         With this, now transactional updates (verify, create, delete, update)\n         can be used for:\n    @@ Commit message\n         This also allows us to expose this to users via new commands in\n         'git-update-ref' in the future.\n     \n    -    We do not add reflog for dangling symref updates, because currently\n    -    'git-symbolic-ref' doesn't add reflog for dangling symref updates and it\n    -    would be best to keep this behavior consistent as we would move it to\n    -    start using transaction based updates in the following commit.\n    +    Note that a dangling symref update does not record a new reflog entry,\n    +    which is unchanged before and after this commit.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \tupdate->flags = flags;\n      \n     -\tif (flags & REF_HAVE_NEW)\n    -+\tif (new_target)\n    -+\t\tupdate->new_target = xstrdup(new_target);\n    -+\tif (old_target)\n    -+\t\tupdate->old_target = xstrdup(old_target);\n    -+\tif (new_oid && flags & REF_HAVE_NEW)\n    ++\tupdate->new_target = xstrdup_or_null(new_target);\n    ++\tupdate->old_target = xstrdup_or_null(old_target);\n    ++\tif ((flags & REF_HAVE_NEW) && new_oid)\n      \t\toidcpy(&update->new_oid, new_oid);\n     -\tif (flags & REF_HAVE_OLD)\n    -+\tif (old_oid && flags & REF_HAVE_OLD)\n    ++\tif ((flags & REF_HAVE_OLD) && old_oid)\n      \t\toidcpy(&update->old_oid, old_oid);\n     +\n      \tupdate->msg = normalize_reflog_message(msg);\n    @@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char\n      \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n      }\n     +\n    -+int ref_update_is_null_new_value(struct ref_update *update)\n    ++int ref_update_has_null_new_value(struct ref_update *update)\n     +{\n     +\treturn !update->new_target && is_null_oid(&update->new_oid);\n     +}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n      \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n      \n     -\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n    -+\tif ((update->flags & REF_HAVE_NEW) && ref_update_is_null_new_value(update))\n    ++\tif ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))\n      \t\tupdate->flags |= REF_DELETING;\n      \n      \tif (head_ref) {\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     -\t    !(update->flags & REF_DELETING) &&\n     -\t    !(update->flags & REF_LOG_ONLY)) {\n     +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n    -+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target)) {\n    ++\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n     +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\tgoto out;\n     +\t\t}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n      \t\tif (!(update->type & REF_ISSYMREF) &&\n      \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n      \t\t\t/*\n    +@@ refs/files-backend.c: static int files_transaction_prepare(struct ref_store *ref_store,\n    + \treturn ret;\n    + }\n    + \n    ++static int parse_and_write_reflog(struct files_ref_store *refs,\n    ++\t\t\t\t  struct ref_update *update,\n    ++\t\t\t\t  struct ref_lock *lock,\n    ++\t\t\t\t  struct strbuf *err)\n    ++{\n    ++\tif (update->new_target) {\n    ++\t\t/*\n    ++\t\t * We want to get the resolved OID for the target, to ensure\n    ++\t\t * that the correct value is added to the reflog.\n    ++\t\t */\n    ++\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n    ++\t\t\t\t\t     RESOLVE_REF_READING,\n    ++\t\t\t\t\t     &update->new_oid, NULL)) {\n    ++\t\t\t/*\n    ++\t\t\t * TODO: currently we skip creating reflogs for dangling\n    ++\t\t\t * symref updates. It would be nice to capture this as\n    ++\t\t\t * zero oid updates however.\n    ++\t\t\t */\n    ++\t\t\treturn 0;\n    ++\t\t}\n    ++\t}\n    ++\n    ++\tif (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,\n    ++\t\t\t\t&update->new_oid, update->msg, update->flags, err)) {\n    ++\t\tchar *old_msg = strbuf_detach(err, NULL);\n    ++\n    ++\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n    ++\t\t\t    lock->ref_name, old_msg);\n    ++\t\tfree(old_msg);\n    ++\t\tunlock_ref(lock);\n    ++\t\tupdate->backend_data = NULL;\n    ++\t\treturn -1;\n    ++\t}\n    ++\n    ++\treturn 0;\n    ++}\n    ++\n    + static int files_transaction_finish(struct ref_store *ref_store,\n    + \t\t\t\t    struct ref_transaction *transaction,\n    + \t\t\t\t    struct strbuf *err)\n     @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_store,\n      \n      \t\tif (update->flags & REF_NEEDS_COMMIT ||\n    @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n     -\t\t\t\t\t\t&update->new_oid,\n     -\t\t\t\t\t\tupdate->msg, update->flags,\n     -\t\t\t\t\t\terr)) {\n    -+\t\t\tint create_reflog = 1;\n    -+\n    -+\t\t\tif (update->new_target) {\n    -+\t\t\t\t/*\n    -+\t\t\t\t * We want to get the resolved OID for the target, to ensure\n    -+\t\t\t\t * that the correct value is added to the reflog.\n    -+\t\t\t\t */\n    -+\t\t\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n    -+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &update->new_oid, NULL)) {\n    -+\t\t\t\t\t/* for dangling symrefs we skip creating a reflog entry. */\n    -+\t\t\t\t\tcreate_reflog = 0;\n    -+\t\t\t\t}\n    -+\t\t\t}\n    -+\n    -+\t\t\tif (create_reflog && files_log_ref_write(refs,\n    -+\t\t\t\t\t\t\t\t lock->ref_name,\n    -+\t\t\t\t\t\t\t\t &lock->old_oid,\n    -+\t\t\t\t\t\t\t\t &update->new_oid,\n    -+\t\t\t\t\t\t\t\t update->msg, update->flags,\n    -+\t\t\t\t\t\t\t\t err)) {\n    - \t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n    - \n    - \t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n    -@@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_store,\n    +-\t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n    +-\n    +-\t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n    +-\t\t\t\t\t    lock->ref_name, old_msg);\n    +-\t\t\t\tfree(old_msg);\n    +-\t\t\t\tunlock_ref(lock);\n    +-\t\t\t\tupdate->backend_data = NULL;\n    ++\t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n    + \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n      \t\t\t\tgoto cleanup;\n      \t\t\t}\n      \t\t}\n    @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re\n     + * takes into consideration that the update could be a regular\n     + * ref or a symbolic ref.\n     + */\n    -+int ref_update_is_null_new_value(struct ref_update *update);\n    ++int ref_update_has_null_new_value(struct ref_update *update);\n     +\n      #endif /* REFS_REFS_INTERNAL_H */\n     \n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n      \t\t\t * when the reference in question doesn't exist.\n      \t\t\t */\n     -\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n    -+\t\t\t if (u->flags & REF_HAVE_NEW && !ref_update_is_null_new_value(u)) {\n    ++\t\t\t if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {\n      \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n      \t\t\t\t\t\t\t\t&current_oid, err);\n      \t\t\t\t if (ret)\n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n     +\t\t\t\tret = -1;\n     +\t\t\t\tgoto done;\n     +\t\t\t}\n    -+\t\t} else if (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n    ++\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n      \t\t\tif (is_null_oid(&u->old_oid))\n      \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n      \t\t\t\t\t    \"reference already exists\"),\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n      \t\t *   the given ref.\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n    -+\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && ref_update_is_null_new_value(u)) {\n    ++\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n      \t\t\tstruct reftable_log_record log = {0};\n      \t\t\tstruct reftable_iterator it = {0};\n      \n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n      \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n      \t\t\tstruct reftable_log_record *log;\n     +\t\t\tint create_reflog = 1;\n    ++\n    ++\t\t\tif (u->new_target) {\n    ++\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n    ++\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL)) {\n    ++\t\t\t\t\t/*\n    ++\t\t\t\t\t * TODO: currently we skip creating reflogs for dangling\n    ++\t\t\t\t\t * symref updates. It would be nice to capture this as\n    ++\t\t\t\t\t * zero oid updates however.\n    ++\t\t\t\t\t */\n    ++\t\t\t\t\tcreate_reflog = 0;\n    ++\t\t\t\t}\n    ++\t\t\t}\n      \n     -\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n     -\t\t\tlog = &logs[logs_nr++];\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n     -\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n     -\t\t\tlog->value.update.message =\n     -\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n    -+\t\t\tif (u->new_target)\n    -+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n    -+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL))\n    -+\t\t\t\t\t/* for dangling symrefs we skip creating reflog */\n    -+\t\t\t\t\tcreate_reflog = 0;\n    -+\n     +\t\t\tif (create_reflog) {\n     +\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n     +\t\t\t\tlog = &logs[logs_nr++];\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n      \t\t\tcontinue;\n      \n     -\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n    -+\t\tif (u->flags & REF_HAVE_NEW && u->new_target) {\n    ++\t\tif (u->new_target) {\n     +\t\t\tstruct reftable_ref_record ref = {\n     +\t\t\t\t.refname = (char *)u->refname,\n     +\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n     +\t\t\tret = reftable_writer_add_ref(writer, &ref);\n     +\t\t\tif (ret < 0)\n     +\t\t\t\tgoto done;\n    -+\t\t} else if (u->flags & REF_HAVE_NEW && ref_update_is_null_new_value(u)) {\n    ++\t\t} else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {\n      \t\t\tstruct reftable_ref_record ref = {\n      \t\t\t\t.refname = (char *)u->refname,\n      \t\t\t\t.update_index = ts,\n5:  5b55406430 ! 5:  636bf5ce98 refs: use transaction in `refs_create_symref()`\n    @@ Commit message\n         target. To do this, it uses a ref-backend specific function\n         `create_symref()`.\n     \n    -    In this previous commit, we introduce symref support in transactions.\n    -    This means we can now use transactions to perform symref updates and not\n    -    have to resort to `create_symref()`. Doing this allows us to remove and\n    -    cleanup `create_symref()`, which we will do in the following commit.\n    +    In the previous commits, we introduced symref support in transactions.\n    +    This means we can now use transactions to perform symref updates and\n    +    don't have to resort to `create_symref()`. Doing this allows us to\n    +    remove and cleanup `create_symref()`, which we will do in the following\n    +    commit.\n     \n         Modify the expected error message for a test in\n    -    't/t0610-reftable-basics.sh', since the error is now thrown from the\n    +    't/t0610-reftable-basics.sh', since the error is now thrown from\n         'refs.c'. This is because in transactional updates, F/D conflicts are\n         caught before we're in the reference backend.\n     \n    @@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls s\n     +\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n     +\n     +\tcat >expect <<-EOF &&\n    -+\t\tprepared\n    -+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n    -+\t\tcommitted\n    -+\t\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n    ++\tprepared\n    ++\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n    ++\tcommitted\n    ++\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n     +\tEOF\n     +\n     +\ttest_cmp expect actual\n6:  9e25816e68 = 6:  07fb23374f refs: rename `refs_create_symref()` to `refs_update_symref()`\n7:  3836e25932 ! 7:  5c05813bcc refs: remove `create_symref` and associated dead code\n    @@ Commit message\n         transactions to perform symref updates. Earlier `refs_create_symref()`\n         used `create_symref()` to do the same.\n     \n    -    This means, we can now remove `create_symref()` and any code associated\n    -    with it which is no longer used. We remove `create_symref()` code from\n    -    all the reference backends and also remove it entirely from the\n    -    `ref_storage_be` struct.\n    +    We can now remove `create_symref()` and any code associated with it\n    +    which is no longer used. We remove `create_symref()` code from all the\n    +    reference backends and also remove it entirely from the `ref_storage_be`\n    +    struct.\n     \n         Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n     \n    @@ refs/files-backend.c: static int create_ref_symlink(struct ref_lock *lock, const\n     -\n      static int create_symref_lock(struct files_ref_store *refs,\n      \t\t\t      struct ref_lock *lock, const char *refname,\n    - \t\t\t      const char *target)\n    + \t\t\t      const char *target, struct strbuf *err)\n     @@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs,\n      \treturn 0;\n      }\n    @@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs\n     -\t\t\t\t    struct ref_lock *lock, const char *refname,\n     -\t\t\t\t    const char *target, const char *logmsg)\n     -{\n    +-\tstruct strbuf err = STRBUF_INIT;\n     -\tint ret;\n     -\n     -\tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n    @@ refs/files-backend.c: static int create_symref_lock(struct files_ref_store *refs\n     -\t\treturn 0;\n     -\t}\n     -\n    --\tret = create_symref_lock(refs, lock, refname, target);\n    +-\tret = create_symref_lock(refs, lock, refname, target, &err);\n     -\tif (!ret) {\n     -\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n     -\n     -\t\tif (commit_ref(lock) < 0)\n     -\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n     -\t\t\t\t     strerror(errno));\n    +-\t} else {\n    +-\t\treturn error(\"%s\", err.buf);\n     -\t}\n     -\n     -\treturn ret;\n\n\nKarthik Nayak (7):\n  refs: accept symref values in `ref_transaction_update()`\n  files-backend: extract out `create_symref_lock()`\n  refs: support symrefs in 'reference-transaction' hook\n  refs: add support for transactional symref updates\n  refs: use transaction in `refs_create_symref()`\n  refs: rename `refs_create_symref()` to `refs_update_symref()`\n  refs: remove `create_symref` and associated dead code\n\n Documentation/githooks.txt       |  14 +-\n branch.c                         |   2 +-\n builtin/branch.c                 |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   2 +-\n builtin/receive-pack.c           |   1 +\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             |   1 +\n builtin/worktree.c               |   2 +-\n refs.c                           |  89 +++++++++----\n refs.h                           |  20 ++-\n refs/debug.c                     |  13 --\n refs/files-backend.c             | 213 +++++++++++++++++++------------\n refs/packed-backend.c            |   1 -\n refs/refs-internal.h             |  26 +++-\n refs/reftable-backend.c          | 165 +++++++++---------------\n sequencer.c                      |   9 +-\n t/helper/test-ref-store.c        |   2 +-\n t/t0610-reftable-basics.sh       |   2 +-\n t/t1416-ref-transaction-hooks.sh |  23 ++++\n walker.c                         |   2 +-\n 22 files changed, 351 insertions(+), 246 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"493993","messageId":"20240503124115.252413-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:09Z","receivedAt":"2024-05-03T12:41:20Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `ref_transaction_update()` obtains ref information and\nflags to create a `ref_update` and add them to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. This commit adds the required\nparameters to the function and modifies all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied. Some functions allow this parameter to be\nNULL, meaning that the reference is not changed.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked.\n\nWe also update the internal function `ref_transaction_add_update()`\nsimilarly to take the two new parameters.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 18 +++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 71 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..47bc9dd103 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n+\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n+\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..c7851bf587 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         updated to point to. If the reference is a regular reference,\n+ *         it will be converted to a symbolic reference. Cannot be set\n+ *         together with `new_oid`. A copy of this value is made in the\n+ *         transaction.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Canont be set together with `old_oid`. A copy of this value is\n+ *         made in the transaction.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..108f4ec419 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,19 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t * Cannot be set together with `new_oid`.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set, check that the reference previously pointed to this\n+\t * value. Cannot be set together with `old_oid`.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 88de4dc20f..61e007d85f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"493992","messageId":"20240503124115.252413-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 2/7] files-backend: extract out `create_symref_lock()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:10Z","receivedAt":"2024-05-03T12:41:21Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked()` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nExtract the early half of `create_symref_locked()` into a new helper\nfunction `create_symref_lock()`. Because the name of the new function is\ntoo similar to the original, rename the original to\n`create_and_commit_symref()` to avoid confusion.\n\nThe new function `create_symref_locked()` can be used to create the\nsymref lock in a separate step from that of committing it. This allows\nto add transactional support for symrefs, where the lock would be\ncreated in the preparation step and the lock would be committed in the\nfinish step.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 37 insertions(+), 14 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..74a713090c 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,27 +1920,49 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target, struct strbuf *err)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\")) {\n+\t\tstrbuf_addf(err, \"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0) {\n+\t\tstrbuf_addf(err, \"unable to write to %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target, &err);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t} else {\n+\t\treturn error(\"%s\", err.buf);\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n-\treturn 0;\n+\treturn ret;\n }\n \n static int files_create_symref(struct ref_store *ref_store,\n@@ -1960,7 +1982,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"493994","messageId":"20240503124115.252413-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 3/7] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:11Z","receivedAt":"2024-05-03T12:41:22Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In a previous commit, we added the `old_target` and\n`new_target` fields to the `reference_transaction_update()`. In\nfollowing commits we'll also add the code to handle symref's in the\nreference backends.\n\nSupport symrefs also in the 'reference-transaction' hook, by modifying\nthe current format:\n    <old-oid> SP <new-oid> SP <ref-name> LF\nto be be:\n    <old-value> SP <new-value> SP <ref-name> LF\nwhere for regular refs the output would not change and remain the same.\nBut when either 'old-value' or 'new-value' is a symref, we print the ref\nas 'ref:<ref-target>'.\n\nThis does break backward compatibility, but the 'reference-transaction'\nhook's documentation always stated that support for symbolic references\nmay be added in the future.\n\nWe do not add any tests in this commit since there is no git command\nwhich activates this flow, in an upcoming commit, we'll start using\ntransaction based symref updates as the default, we'll add tests there\nfor the hook too.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt | 14 +++++++++-----\n refs.c                     | 20 ++++++++++++++++----\n 2 files changed, 25 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..06e997131b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also supports symbolic reference updates.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects. A reference will be\n+denoted with a 'ref:' prefix, like `ref:<ref-target>`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex 47bc9dd103..203a101988 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2350,10 +2350,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = transaction->updates[i];\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\n+\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n+\n+\t\tif (!(update->flags & REF_HAVE_NEW))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\n-- \n2.43.GIT\n\n"},{"id":"493995","messageId":"20240503124115.252413-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:12Z","receivedAt":"2024-05-03T12:41:23Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe reference backends currently support transactional reference\nupdates. While this is exposed to users via 'git-update-ref' and its\n'--stdin' mode, it is also used internally within various commands.\n\nHowever, we never supported transactional updates of symrefs. Let's add\nsupport for symrefs in both the 'files' and the 'reftable' backend.\n\nHere, we add and use `ref_update_has_null_new_value()`, a helper\nfunction which is used to check if there is a new_value in a reference\nupdate. The new value could either be a symref target `new_target` or a\nOID `new_oid`.\n\nWith this, now transactional updates (verify, create, delete, update)\ncan be used for:\n- regular refs\n- symbolic refs\n- conversion of regular to symbolic refs and vice versa\n\nThis also allows us to expose this to users via new commands in\n'git-update-ref' in the future.\n\nNote that a dangling symref update does not record a new reflog entry,\nwhich is unchanged before and after this commit.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  15 ++++-\n refs/files-backend.c    | 137 ++++++++++++++++++++++++++++++++++------\n refs/refs-internal.h    |   7 ++\n refs/reftable-backend.c |  77 +++++++++++++++++-----\n 4 files changed, 198 insertions(+), 38 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 203a101988..acb153b6f8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((char *)transaction->updates[i]->new_target);\n+\t\tfree((char *)transaction->updates[i]->old_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1247,10 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tupdate->new_target = xstrdup_or_null(new_target);\n+\tupdate->old_target = xstrdup_or_null(old_target);\n+\tif ((flags & REF_HAVE_NEW) && new_oid)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif ((flags & REF_HAVE_OLD) && old_oid)\n \t\toidcpy(&update->old_oid, old_oid);\n+\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -1286,6 +1291,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -2814,3 +2820,8 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+int ref_update_has_null_new_value(struct ref_update *update)\n+{\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 74a713090c..e1f0ca74c0 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2434,6 +2434,36 @@ static const char *original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+/*\n+ * Check whether the old_target values stored in update are consistent\n+ * with current_target, which is the symbolic reference's current value.\n+ * If everything is OK, return 0; otherwise, write an error message to\n+ * err and return -1.\n+ */\n+static int check_old_target(struct ref_update *update,\n+\t\t\t    const char *current_target,\n+\t\t\t    struct strbuf *err)\n+{\n+\tif (!update->old_target)\n+\t\tBUG(\"called without old_target set\");\n+\n+\tif (!strcmp(update->old_target, current_target))\n+\t\treturn 0;\n+\n+\tif (!strcmp(current_target, \"\"))\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    original_update_refname(update),\n+\t\t\t    current_target, update->old_target);\n+\n+\treturn -1;\n+}\n+\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2494,7 +2524,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\n@@ -2537,7 +2567,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t\t}\n+\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n+\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -2558,7 +2595,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t} else {\n \t\tstruct ref_update *parent_update;\n \n-\t\tif (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t/*\n+\t\t * Even if the ref is a regular ref, if `old_target` is set, we\n+\t\t * check the referent value. Ideally `old_target` should only\n+\t\t * be set for symrefs, but we're strict about its usage.\n+\t\t */\n+\t\tif (update->old_target) {\n+\t\t\tif (check_old_target(update, referent.buf, err)) {\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n \t\t}\n@@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\n@@ -2841,6 +2906,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \treturn ret;\n }\n \n+static int parse_and_write_reflog(struct files_ref_store *refs,\n+\t\t\t\t  struct ref_update *update,\n+\t\t\t\t  struct ref_lock *lock,\n+\t\t\t\t  struct strbuf *err)\n+{\n+\tif (update->new_target) {\n+\t\t/*\n+\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t * that the correct value is added to the reflog.\n+\t\t */\n+\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t     RESOLVE_REF_READING,\n+\t\t\t\t\t     &update->new_oid, NULL)) {\n+\t\t\t/*\n+\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t * zero oid updates however.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,\n+\t\t\t\t&update->new_oid, update->msg, update->flags, err)) {\n+\t\tchar *old_msg = strbuf_detach(err, NULL);\n+\n+\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n+\t\t\t    lock->ref_name, old_msg);\n+\t\tfree(old_msg);\n+\t\tunlock_ref(lock);\n+\t\tupdate->backend_data = NULL;\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\t    struct ref_transaction *transaction,\n \t\t\t\t    struct strbuf *err)\n@@ -2871,23 +2973,20 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n-\t\t\tif (files_log_ref_write(refs,\n-\t\t\t\t\t\tlock->ref_name,\n-\t\t\t\t\t\t&lock->old_oid,\n-\t\t\t\t\t\t&update->new_oid,\n-\t\t\t\t\t\tupdate->msg, update->flags,\n-\t\t\t\t\t\terr)) {\n-\t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n-\n-\t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n-\t\t\t\t\t    lock->ref_name, old_msg);\n-\t\t\t\tfree(old_msg);\n-\t\t\t\tunlock_ref(lock);\n-\t\t\t\tupdate->backend_data = NULL;\n+\t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next update. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 108f4ec419..03eda70ea4 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -749,4 +749,11 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_has_null_new_value(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..54c4d8b771 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -856,7 +856,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -907,8 +907,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t * intertwined with the locking in files-backend.c.\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n-\t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\ttransaction, referent.buf, new_flags,\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\tu->old_target, u->msg);\n+\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif (u->old_target) {\n+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n+\t\t\t\tif (!strcmp(referent.buf, \"\"))\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    u->old_target);\n+\t\t\t\telse\n+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t\t\t\t    \"is at %s but expected %s\",\n+\t\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t    referent.buf, u->old_target);\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t    \"reference already exists\"),\n@@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1084,24 +1101,50 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t   (u->flags & REF_FORCE_CREATE_REFLOG ||\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n+\t\t\tint create_reflog = 1;\n+\n+\t\t\tif (u->new_target) {\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL)) {\n+\t\t\t\t\t/*\n+\t\t\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t\t\t * zero oid updates however.\n+\t\t\t\t\t */\n+\t\t\t\t\tcreate_reflog = 0;\n+\t\t\t\t}\n+\t\t\t}\n \n-\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n-\t\t\tlog = &logs[logs_nr++];\n-\t\t\tmemset(log, 0, sizeof(*log));\n-\n-\t\t\tfill_reftable_log_record(log);\n-\t\t\tlog->update_index = ts;\n-\t\t\tlog->refname = xstrdup(u->refname);\n-\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tlog->value.update.message =\n-\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\tif (create_reflog) {\n+\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n+\t\t\t\tlog = &logs[logs_nr++];\n+\t\t\t\tmemset(log, 0, sizeof(*log));\n+\n+\t\t\t\tfill_reftable_log_record(log);\n+\t\t\t\tlog->update_index = ts;\n+\t\t\t\tlog->refname = xstrdup(u->refname);\n+\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tlog->value.update.message =\n+\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\t}\n \t\t}\n \n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\n-- \n2.43.GIT\n\n"},{"id":"493996","messageId":"20240503124115.252413-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 5/7] refs: use transaction in `refs_create_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:13Z","receivedAt":"2024-05-03T12:41:24Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function updates a symref to a given new\ntarget. To do this, it uses a ref-backend specific function\n`create_symref()`.\n\nIn the previous commits, we introduced symref support in transactions.\nThis means we can now use transactions to perform symref updates and\ndon't have to resort to `create_symref()`. Doing this allows us to\nremove and cleanup `create_symref()`, which we will do in the following\ncommit.\n\nModify the expected error message for a test in\n't/t0610-reftable-basics.sh', since the error is now thrown from\n'refs.c'. This is because in transactional updates, F/D conflicts are\ncaught before we're in the reference backend.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                           | 24 +++++++++++++++++-------\n t/t0610-reftable-basics.sh       |  2 +-\n t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++\n 3 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex acb153b6f8..195861b09b 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2289,14 +2289,24 @@ int refs_create_symref(struct ref_store *refs,\n \t\t       const char *refs_heads_master,\n \t\t       const char *logmsg)\n {\n-\tchar *msg;\n-\tint retval;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret = 0;\n \n-\tmsg = normalize_reflog_message(logmsg);\n-\tretval = refs->be->create_symref(refs, ref_target, refs_heads_master,\n-\t\t\t\t\t msg);\n-\tfree(msg);\n-\treturn retval;\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n+\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t\t\t\t   logmsg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n+\t\tret = error(\"%s\", err.buf);\n+\t}\n+\n+\tstrbuf_release(&err);\n+\tif (transaction)\n+\t\tref_transaction_free(transaction);\n+\n+\treturn ret;\n }\n \n int create_symref(const char *ref_target, const char *refs_heads_master,\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 178791e086..9e8d22bcbd 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl\n \tgit init repo &&\n \ttest_commit -C repo A &&\n \tcat >expect <<-EOF &&\n-\terror: unable to write symref for refs/heads: file/directory conflict\n+\terror: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}\n \tEOF\n \ttest_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&\n \ttest_cmp expect err\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..067fd57290 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+test_expect_success 'hook captures git-symbolic-ref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\n+\tcat >expect <<-EOF &&\n+\tprepared\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tcommitted\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"493997","messageId":"20240503124115.252413-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 6/7] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:14Z","receivedAt":"2024-05-03T12:41:25Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function is used to update/create a symref.\nBut it doesn't check the old target of the symref, if existing. It force\nupdates the symref. In this regard, the name `refs_create_symref()` is a\nbit misleading. So let's rename it to `refs_update_symref()`. This is\nakin to how 'git-update-ref(1)' also allows us to create apart from\nupdate.\n\nWhile we're here, rename the arguments in the function to clarify what\nthey actually signify and reduce confusion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/branch.c          |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 12 +++++-------\n refs.h                    |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 5 files changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex dd3e3a7dc0..4491f7a20c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_create_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7c6c72536b..480202c517 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_create_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 195861b09b..3645b805c1 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2284,10 +2284,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)\n \treturn peel_object(base, peeled) ? -1 : 0;\n }\n \n-int refs_create_symref(struct ref_store *refs,\n-\t\t       const char *ref_target,\n-\t\t       const char *refs_heads_master,\n-\t\t       const char *logmsg)\n+int refs_update_symref(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2295,8 +2293,8 @@ int refs_create_symref(struct ref_store *refs,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n-\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n@@ -2312,7 +2310,7 @@ int refs_create_symref(struct ref_store *refs,\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n-\treturn refs_create_symref(get_main_ref_store(the_repository), ref_target,\n+\treturn refs_update_symref(get_main_ref_store(the_repository), ref_target,\n \t\t\t\t  refs_heads_master, logmsg);\n }\n \ndiff --git a/refs.h b/refs.h\nindex c7851bf587..71cc1c58e0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int copy_existing_ref(const char *oldref, const char *newref,\n \t\t\tconst char *logmsg);\n \n-int refs_create_symref(struct ref_store *refs, const char *refname,\n+int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n int create_symref(const char *refname, const char *target, const char *logmsg);\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 82bbf6e2e6..4651e4ced7 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_create_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.43.GIT\n\n"},{"id":"493998","messageId":"20240503124115.252413-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v6 7/7] refs: remove `create_symref` and associated dead code","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-03T12:41:15Z","receivedAt":"2024-05-03T12:41:26Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we converted `refs_create_symref()` to utilize\ntransactions to perform symref updates. Earlier `refs_create_symref()`\nused `create_symref()` to do the same.\n\nWe can now remove `create_symref()` and any code associated with it\nwhich is no longer used. We remove `create_symref()` code from all the\nreference backends and also remove it entirely from the `ref_storage_be`\nstruct.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/debug.c            | 13 -------\n refs/files-backend.c    | 67 --------------------------------\n refs/packed-backend.c   |  1 -\n refs/refs-internal.h    |  5 ---\n refs/reftable-backend.c | 86 -----------------------------------------\n 5 files changed, 172 deletions(-)\n\ndiff --git a/refs/debug.c b/refs/debug.c\nindex c7531b17f0..8be316bb67 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o\n \treturn res;\n }\n \n-static int debug_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *ref_name, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;\n-\tint res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,\n-\t\t\t\t\t\t logmsg);\n-\ttrace_printf_key(&trace_refs, \"create_symref: %s -> %s \\\"%s\\\": %d\\n\", ref_name,\n-\t\ttarget, logmsg, res);\n-\treturn res;\n-}\n-\n static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,\n \t\t\t    const char *newref, const char *logmsg)\n {\n@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {\n \t.initial_transaction_commit = debug_initial_transaction_commit,\n \n \t.pack_refs = debug_pack_refs,\n-\t.create_symref = debug_create_symref,\n \t.rename_ref = debug_rename_ref,\n \t.copy_ref = debug_copy_ref,\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e1f0ca74c0..3ce260d07d 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)\n \treturn ret;\n }\n \n-static void update_symref_reflog(struct files_ref_store *refs,\n-\t\t\t\t struct ref_lock *lock, const char *refname,\n-\t\t\t\t const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct object_id new_oid;\n-\n-\tif (logmsg &&\n-\t    refs_resolve_ref_unsafe(&refs->base, target,\n-\t\t\t\t    RESOLVE_REF_READING, &new_oid, NULL) &&\n-\t    files_log_ref_write(refs, refname, &lock->old_oid,\n-\t\t\t\t&new_oid, logmsg, 0, &err)) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t}\n-}\n-\n static int create_symref_lock(struct files_ref_store *refs,\n \t\t\t      struct ref_lock *lock, const char *refname,\n \t\t\t      const char *target, struct strbuf *err)\n@@ -1939,55 +1922,6 @@ static int create_symref_lock(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int create_and_commit_symref(struct files_ref_store *refs,\n-\t\t\t\t    struct ref_lock *lock, const char *refname,\n-\t\t\t\t    const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret;\n-\n-\tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\t\treturn 0;\n-\t}\n-\n-\tret = create_symref_lock(refs, lock, refname, target, &err);\n-\tif (!ret) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\n-\t\tif (commit_ref(lock) < 0)\n-\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t\t     strerror(errno));\n-\t} else {\n-\t\treturn error(\"%s\", err.buf);\n-\t}\n-\n-\treturn ret;\n-}\n-\n-static int files_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *refname, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct files_ref_store *refs =\n-\t\tfiles_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_lock *lock;\n-\tint ret;\n-\n-\tlock = lock_ref_oid_basic(refs, refname, &err);\n-\tif (!lock) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t\treturn -1;\n-\t}\n-\n-\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n-\n-\tunlock_ref(lock);\n-\treturn ret;\n-}\n-\n static int files_reflog_exists(struct ref_store *ref_store,\n \t\t\t       const char *refname)\n {\n@@ -3413,7 +3347,6 @@ struct ref_storage_be refs_be_files = {\n \t.initial_transaction_commit = files_initial_transaction_commit,\n \n \t.pack_refs = files_pack_refs,\n-\t.create_symref = files_create_symref,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4e826c05ff..a937e7dbfc 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {\n \t.initial_transaction_commit = packed_initial_transaction_commit,\n \n \t.pack_refs = packed_pack_refs,\n-\t.create_symref = NULL,\n \t.rename_ref = NULL,\n \t.copy_ref = NULL,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 03eda70ea4..0c620d4bce 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n-typedef int create_symref_fn(struct ref_store *ref_store,\n-\t\t\t     const char *ref_target,\n-\t\t\t     const char *refs_heads_master,\n-\t\t\t     const char *logmsg);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -690,7 +686,6 @@ struct ref_storage_be {\n \tref_transaction_commit_fn *initial_transaction_commit;\n \n \tpack_refs_fn *pack_refs;\n-\tcreate_symref_fn *create_symref;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 54c4d8b771..999b5bca21 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1275,91 +1275,6 @@ struct write_create_symref_arg {\n \tconst char *logmsg;\n };\n \n-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n-{\n-\tstruct write_create_symref_arg *create = cb_data;\n-\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)create->refname,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)create->target,\n-\t\t.update_index = ts,\n-\t};\n-\tstruct reftable_log_record log = {0};\n-\tstruct object_id new_oid;\n-\tstruct object_id old_oid;\n-\tint ret;\n-\n-\treftable_writer_set_limits(writer, ts, ts);\n-\n-\tret = reftable_writer_add_ref(writer, &ref);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Note that it is important to try and resolve the reference before we\n-\t * write the log entry. This is because `should_write_log()` will munge\n-\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n-\t * repository because it would be written into the config. As HEAD will\n-\t * not resolve for new repositories this ordering will ensure that this\n-\t * never happens.\n-\t */\n-\tif (!create->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n-\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&create->refs->base, create->refname))\n-\t\treturn 0;\n-\n-\tfill_reftable_log_record(&log);\n-\tlog.refname = xstrdup(create->refname);\n-\tlog.update_index = ts;\n-\tlog.value.update.message = xstrndup(create->logmsg,\n-\t\t\t\t\t    create->refs->write_options.block_size / 2);\n-\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n-\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n-\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n-\n-\tret = reftable_writer_add_log(writer, &log);\n-\treftable_log_record_release(&log);\n-\treturn ret;\n-}\n-\n-static int reftable_be_create_symref(struct ref_store *ref_store,\n-\t\t\t\t     const char *refname,\n-\t\t\t\t     const char *target,\n-\t\t\t\t     const char *logmsg)\n-{\n-\tstruct reftable_ref_store *refs =\n-\t\treftable_be_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct reftable_stack *stack = stack_for(refs, refname, &refname);\n-\tstruct write_create_symref_arg arg = {\n-\t\t.refs = refs,\n-\t\t.stack = stack,\n-\t\t.refname = refname,\n-\t\t.target = target,\n-\t\t.logmsg = logmsg,\n-\t};\n-\tint ret;\n-\n-\tret = refs->err;\n-\tif (ret < 0)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_reload(stack);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_add(stack, &write_create_symref_table, &arg);\n-\n-done:\n-\tassert(ret != REFTABLE_API_ERROR);\n-\tif (ret)\n-\t\terror(\"unable to write symref for %s: %s\", refname,\n-\t\t      reftable_error_str(ret));\n-\treturn ret;\n-}\n-\n struct write_copy_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2267,7 +2182,6 @@ struct ref_storage_be refs_be_reftable = {\n \t.initial_transaction_commit = reftable_be_initial_transaction_commit,\n \n \t.pack_refs = reftable_be_pack_refs,\n-\t.create_symref = reftable_be_create_symref,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.43.GIT\n\n"},{"id":"494016","messageId":"xmqq5xvualp8.fsf@gitster.g","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"Re: [PATCH v6 0/7] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-03T16:45:23Z","receivedAt":"2024-05-03T16:45:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Previous versions:\n> V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\n> V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\n> V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/\n> V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\n> V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com\n>\n> Changes over v5 are:\n> - More user friendly error messages.\n> - `create_symref_lock` now writes to an err buf, instead of directly to stderr.\n> - Refactor code to make it easier to read around logical operations.\n\nAll changes I saw in the range-diff and diff output with v5 looked\nsensible improvements.\n\nWill wait for comments and then hopefully we can mark it for 'next'\nsometime next week, if nothing happens.\n\nThanks.\n"},{"id":"494069","messageId":"xmqqplu24hne.fsf@gitster.g","threadId":"61235","inReplyTo":"20240503124115.252413-8-knayak@gitlab.com","subject":"Re: [PATCH v6 7/7] refs: remove `create_symref` and associated dead code","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-03T23:09:25Z","receivedAt":"2024-05-03T23:09:29Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> From: Karthik Nayak <karthik.188@gmail.com>\n>\n> In the previous commits, we converted `refs_create_symref()` to utilize\n> transactions to perform symref updates. Earlier `refs_create_symref()`\n> used `create_symref()` to do the same.\n>\n> We can now remove `create_symref()` and any code associated with it\n> which is no longer used. We remove `create_symref()` code from all the\n> reference backends and also remove it entirely from the `ref_storage_be`\n> struct.\n>\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n\nThis has serious semantic conflicts with in-flight topics.  I think\nI resolved them all correctly while merging it in 'seen', but please\ndouble check the result after I push it out.\n\nThis comment equally applies to the \"force all callers to use\nget_main_ref_store() on the_repository and remove functions that\nimplicitly used the main ref store of the_repository\" topic by\nPatrick, but we really should devise a bit more smoother way to cope\nwith out of tree and in-flight topics.  For example, as the new\nrefs_update_symref() function works exactly like the existing\nrefs_create_symref() function, after renaming all the in-base (i.e.,\nin-tree at the point this topic forks from) users to call the new\nfunction, instead of just removing the original one, it would have\nbeen nice to guide authors of other in-flight topics by (1) causing\nbuild failure and at the same time (2) telling them what they need\nto do to adjust to the new world order.  This patch does only (1)\nbut does a poor job for (2).  We may want to establish a better\nconvention than just outright removing and breaking others' topics.\n\nThanks.\n\n\n"},{"id":"494077","messageId":"CAOLa=ZRUe23MnkBYergXr0YTSwV43f1nJj53diGF6wYHFkJy6w@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqplu24hne.fsf@gitster.g","subject":"Re: [PATCH v6 7/7] refs: remove `create_symref` and associated dead code","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-04T09:30:09Z","receivedAt":"2024-05-04T09:30:10Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> In the previous commits, we converted `refs_create_symref()` to utilize\n>> transactions to perform symref updates. Earlier `refs_create_symref()`\n>> used `create_symref()` to do the same.\n>>\n>> We can now remove `create_symref()` and any code associated with it\n>> which is no longer used. We remove `create_symref()` code from all the\n>> reference backends and also remove it entirely from the `ref_storage_be`\n>> struct.\n>>\n>> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n>> ---\n>\n> This has serious semantic conflicts with in-flight topics.  I think\n> I resolved them all correctly while merging it in 'seen', but please\n> double check the result after I push it out.\n>\n\nI had a look at the commit pushed to 'seen', looked good to me. Thanks.\n\n> This comment equally applies to the \"force all callers to use\n> get_main_ref_store() on the_repository and remove functions that\n> implicitly used the main ref store of the_repository\" topic by\n> Patrick, but we really should devise a bit more smoother way to cope\n> with out of tree and in-flight topics.  For example, as the new\n> refs_update_symref() function works exactly like the existing\n> refs_create_symref() function, after renaming all the in-base (i.e.,\n> in-tree at the point this topic forks from) users to call the new\n> function, instead of just removing the original one, it would have\n> been nice to guide authors of other in-flight topics by (1) causing\n> build failure and at the same time (2) telling them what they need\n> to do to adjust to the new world order.  This patch does only (1)\n> but does a poor job for (2).  We may want to establish a better\n> convention than just outright removing and breaking others' topics.\n>\n> Thanks.\n\nJust so I can do better next time, what is your suggestion for doing (2)\nbetter?\n"},{"id":"494079","messageId":"7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZSwtOQXwbgregzKMtVA30wUCH8R=8D7u1+KGnsGEDD1oA@mail.gmail.com","subject":"Re: [PATCH v2 1/7] refs: accept symref values in `ref_transaction[_add]_update`","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-04T15:15:39Z","receivedAt":"2024-05-04T15:15:49Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nSorry for the slow response on this\n\nOn 19/04/2024 16:47, Karthik Nayak wrote:\n>> either way it would be helpful to add some assertions to detect mistakes\n>>\n>> \tif (old_oid && old_ref)\n>> \t\tBUG(\"Only one of old_oid and old_ref should be non NULL\");\n>> \tif (new_oid && new_ref)\n>> \t\tBUG(\"Only one of new_oid and new_ref should be non NULL\");\n>>\n> \n> I have slightly modified it to:\n> \n>   \tif (old_oid && !is_null_oid(old_oid) && old_ref)\n>   \t\tBUG(\"Only one of old_oid and old_ref should be non NULL\");\n>   \tif (new_oid && !is_null_oid(new_oid) && new_ref)\n>   \t\tBUG(\"Only one of new_oid and new_ref should be non NULL\");\n> \n> But I agree, this is needed and have added it.\n\nI'm confused as to why we want to allow old_ref in conjunction with \nold_oid being the null oid. Reading the documentation a null oid means \n\"this ref must not exist\" and NULL means \"I don't care about the current \nstate of the ref\" so NULL and the null oid are not equivalent.\n\nBest Wishes\n\nPhillip\n\n> \n>>> diff --git a/refs.h b/refs.h\n>>> index d278775e08..645fe9fdb8 100644\n>>> --- a/refs.h\n>>> +++ b/refs.h\n>>> @@ -696,13 +696,19 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>>>     */\n>>>    #define REF_SKIP_REFNAME_VERIFICATION (1 << 11)\n>>>\n>>> +/*\n>>> + * The reference update is considered to be done on a symbolic reference. This\n>>> + * ensures that we verify, delete, create and update the ref correspondingly.\n>>> + */\n>>> +#define REF_SYMREF_UPDATE (1 << 12)\n>>\n>> I'm confused as to why we need this as I assumed that we could use the\n>> presence of old_ref/new_ref to determine that the caller wants to update\n>> symbolic ref. Having this flag means that there are more possibilities\n>> to misuse the new API setting this flag but providing NULL for old_ref\n>> and new_ref.\n>>\n> \n> I think I started with this flag but as the direction of the series\n> changed and I moved using zero_oid values for deletion or for using the\n> verify command, this is not really needed anymore. I just tried removing\n> all the code around the flags and fixing up things and all the tests\n> still pass. Thanks for brining this up.\n> \n> Patrick Steinhardt <ps@pks.im> writes:\n>>> I'm confused as to why we need this as I assumed that we could use the\n>>> presence of old_ref/new_ref to determine that the caller wants to update\n>>> symbolic ref. Having this flag means that there are more possibilities to\n>>> misuse the new API setting this flag but providing NULL for old_ref and\n>>> new_ref.\n>>\n>> In my opinion the same comment applies to `REF_HAVE_NEW` and\n>> `REF_HAVE_OLD`, which I found to be redundant, as well. Those may make\n>> sense in the internals when the object IDs are stored as non-pointers,\n>> but queueing ref updates only accepts pointers anyway.\n>>\n> \n> Yeah like you mentioned, since we're dealing with pointers, checking the\n> if its set is enough indication, which doesn't work with the static OID\n> values.\n"},{"id":"494080","messageId":"37875fc1-19d5-40bf-9d92-bf6356e2049d@gmail.com","threadId":"61235","inReplyTo":"20240503124115.252413-2-knayak@gitlab.com","subject":"Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-04T15:18:48Z","receivedAt":"2024-05-04T15:18:57Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 03/05/2024 13:41, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The function `ref_transaction_update()` obtains ref information and\n> flags to create a `ref_update` and add them to the transaction at hand.\n> \n> To extend symref support in transactions, we need to also accept the\n> old and new ref targets and process it. This commit adds the required\n> parameters to the function and modifies all call sites.\n> \n> The two parameters added are `new_target` and `old_target`. The\n> `new_target` is used to denote what the reference should point to when\n> the transaction is applied. Some functions allow this parameter to be\n> NULL, meaning that the reference is not changed.\n> \n> The `old_target` denotes the value the reference must have before the\n> update. Some functions allow this parameter to be NULL, meaning that the\n> old value of the reference is not checked.\n> \n> We also update the internal function `ref_transaction_add_update()`\n> similarly to take the two new parameters.\n\nThe documentation for the new parameters looks good to me now - thanks \nfor updating it. I'm confused about the assertions though as I mentioned \nin my other message [1].\n\nBest Wishes\n\nPhillip\n\n[1] \nhttps://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com\n\n> Signed-off-by: Karthik Nayak <karthik.188@gmail.com>\n> ---\n>   branch.c                |  2 +-\n>   builtin/fast-import.c   |  5 +++--\n>   builtin/fetch.c         |  2 +-\n>   builtin/receive-pack.c  |  1 +\n>   builtin/replace.c       |  2 +-\n>   builtin/tag.c           |  1 +\n>   builtin/update-ref.c    |  1 +\n>   refs.c                  | 22 +++++++++++++++++-----\n>   refs.h                  | 18 +++++++++++++++++-\n>   refs/files-backend.c    | 12 ++++++------\n>   refs/refs-internal.h    | 14 ++++++++++++++\n>   refs/reftable-backend.c |  4 ++--\n>   sequencer.c             |  9 +++++----\n>   walker.c                |  2 +-\n>   14 files changed, 71 insertions(+), 24 deletions(-)\n> \n> diff --git a/branch.c b/branch.c\n> index e4a738fc7b..48af4c3ceb 100644\n> --- a/branch.c\n> +++ b/branch.c\n> @@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n>   \tif (!transaction ||\n>   \t\tref_transaction_update(transaction, ref.buf,\n>   \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n> -\t\t\t\t\t0, msg, &err) ||\n> +\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n>   \t\tref_transaction_commit(transaction, &err))\n>   \t\tdie(\"%s\", err.buf);\n>   \tref_transaction_free(transaction);\n> diff --git a/builtin/fast-import.c b/builtin/fast-import.c\n> index dc5a9d32dd..297dfb91a1 100644\n> --- a/builtin/fast-import.c\n> +++ b/builtin/fast-import.c\n> @@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n>   \ttransaction = ref_transaction_begin(&err);\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n> -\t\t\t\t   0, msg, &err) ||\n> +\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n>   \t    ref_transaction_commit(transaction, &err)) {\n>   \t\tref_transaction_free(transaction);\n>   \t\terror(\"%s\", err.buf);\n> @@ -1675,7 +1675,8 @@ static void dump_tags(void)\n>   \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n>   \n>   \t\tif (ref_transaction_update(transaction, ref_name.buf,\n> -\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n> +\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n> +\t\t\t\t\t   0, msg, &err)) {\n>   \t\t\tfailure |= error(\"%s\", err.buf);\n>   \t\t\tgoto cleanup;\n>   \t\t}\n> diff --git a/builtin/fetch.c b/builtin/fetch.c\n> index 5857d860db..66840b7c5b 100644\n> --- a/builtin/fetch.c\n> +++ b/builtin/fetch.c\n> @@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n>   \n>   \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n>   \t\t\t\t     check_old ? &ref->old_oid : NULL,\n> -\t\t\t\t     0, msg, &err);\n> +\t\t\t\t     NULL, NULL, 0, msg, &err);\n>   \tif (ret) {\n>   \t\tret = STORE_REF_ERROR_OTHER;\n>   \t\tgoto out;\n> diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\n> index e8d7df14b6..b150ef39a8 100644\n> --- a/builtin/receive-pack.c\n> +++ b/builtin/receive-pack.c\n> @@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n>   \t\tif (ref_transaction_update(transaction,\n>   \t\t\t\t\t   namespaced_name,\n>   \t\t\t\t\t   new_oid, old_oid,\n> +\t\t\t\t\t   NULL, NULL,\n>   \t\t\t\t\t   0, \"push\",\n>   \t\t\t\t\t   &err)) {\n>   \t\t\trp_error(\"%s\", err.buf);\n> diff --git a/builtin/replace.c b/builtin/replace.c\n> index da59600ad2..7690687b0e 100644\n> --- a/builtin/replace.c\n> +++ b/builtin/replace.c\n> @@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n>   \ttransaction = ref_transaction_begin(&err);\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n> -\t\t\t\t   0, NULL, &err) ||\n> +\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n>   \t    ref_transaction_commit(transaction, &err))\n>   \t\tres = error(\"%s\", err.buf);\n>   \n> diff --git a/builtin/tag.c b/builtin/tag.c\n> index 9a33cb50b4..40a65fdebc 100644\n> --- a/builtin/tag.c\n> +++ b/builtin/tag.c\n> @@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n>   \ttransaction = ref_transaction_begin(&err);\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n> +\t\t\t\t   NULL, NULL,\n>   \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n>   \t\t\t\t   reflog_msg.buf, &err) ||\n>   \t    ref_transaction_commit(transaction, &err)) {\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index e46afbc46d..21fdbf6ac8 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n>   \n>   \tif (ref_transaction_update(transaction, refname,\n>   \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n> +\t\t\t\t   NULL, NULL,\n>   \t\t\t\t   update_flags | create_reflog_flag,\n>   \t\t\t\t   msg, &err))\n>   \t\tdie(\"%s\", err.buf);\n> diff --git a/refs.c b/refs.c\n> index 55d2e0b2cb..47bc9dd103 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n>   \t\tconst char *refname, unsigned int flags,\n>   \t\tconst struct object_id *new_oid,\n>   \t\tconst struct object_id *old_oid,\n> +\t\tconst char *new_target, const char *old_target,\n>   \t\tconst char *msg)\n>   {\n>   \tstruct ref_update *update;\n> @@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n>   \tif (transaction->state != REF_TRANSACTION_OPEN)\n>   \t\tBUG(\"update called for transaction that is not open\");\n>   \n> +\tif (old_oid && !is_null_oid(old_oid) && old_target)\n> +\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n> +\tif (new_oid && !is_null_oid(new_oid) && new_target)\n> +\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n> +\n>   \tFLEX_ALLOC_STR(update, refname, refname);\n>   \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n>   \ttransaction->updates[transaction->nr++] = update;\n> @@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>   \t\t\t   const char *refname,\n>   \t\t\t   const struct object_id *new_oid,\n>   \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *new_target,\n> +\t\t\t   const char *old_target,\n>   \t\t\t   unsigned int flags, const char *msg,\n>   \t\t\t   struct strbuf *err)\n>   {\n> @@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>   \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n>   \n>   \tref_transaction_add_update(transaction, refname, flags,\n> -\t\t\t\t   new_oid, old_oid, msg);\n> +\t\t\t\t   new_oid, old_oid, new_target,\n> +\t\t\t\t   old_target, msg);\n>   \treturn 0;\n>   }\n>   \n> @@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n>   \t\treturn 1;\n>   \t}\n>   \treturn ref_transaction_update(transaction, refname, new_oid,\n> -\t\t\t\t      null_oid(), flags, msg, err);\n> +\t\t\t\t      null_oid(), NULL, NULL, flags,\n> +\t\t\t\t      msg, err);\n>   }\n>   \n>   int ref_transaction_delete(struct ref_transaction *transaction,\n> @@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n>   \t\tBUG(\"delete called with old_oid set to zeros\");\n>   \treturn ref_transaction_update(transaction, refname,\n>   \t\t\t\t      null_oid(), old_oid,\n> -\t\t\t\t      flags, msg, err);\n> +\t\t\t\t      NULL, NULL, flags,\n> +\t\t\t\t      msg, err);\n>   }\n>   \n>   int ref_transaction_verify(struct ref_transaction *transaction,\n> @@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n>   \t\tBUG(\"verify called with old_oid set to NULL\");\n>   \treturn ref_transaction_update(transaction, refname,\n>   \t\t\t\t      NULL, old_oid,\n> +\t\t\t\t      NULL, NULL,\n>   \t\t\t\t      flags, NULL, err);\n>   }\n>   \n> @@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n>   \n>   \tt = ref_store_transaction_begin(refs, &err);\n>   \tif (!t ||\n> -\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n> -\t\t\t\t   &err) ||\n> +\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n> +\t\t\t\t   flags, msg, &err) ||\n>   \t    ref_transaction_commit(t, &err)) {\n>   \t\tret = 1;\n>   \t\tref_transaction_free(t);\n> diff --git a/refs.h b/refs.h\n> index d278775e08..c7851bf587 100644\n> --- a/refs.h\n> +++ b/refs.h\n> @@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>    *         before the update. A copy of this value is made in the\n>    *         transaction.\n>    *\n> + *     new_target -- the target reference that the reference will be\n> + *         updated to point to. If the reference is a regular reference,\n> + *         it will be converted to a symbolic reference. Cannot be set\n> + *         together with `new_oid`. A copy of this value is made in the\n> + *         transaction.\n> + *\n> + *     old_target -- the reference that the reference must be pointing to.\n> + *         Canont be set together with `old_oid`. A copy of this value is\n> + *         made in the transaction.\n> + *\n>    *     flags -- flags affecting the update, passed to\n>    *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n>    *         REF_FORCE_CREATE_REFLOG. See those constants for more\n> @@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n>    * beforehand. The old value is checked after the lock is taken to\n>    * prevent races. If the old value doesn't agree with old_oid, the\n>    * whole transaction fails. If old_oid is NULL, then the previous\n> - * value is not checked.\n> + * value is not checked. If `old_target` is not NULL, treat the reference\n> + * as a symbolic ref and validate that its target before the update is\n> + * `old_target`. If the `new_target` is not NULL, then the reference\n> + * will be updated to a symbolic ref which targets `new_target`.\n> + * Together, these allow us to update between regular refs and symrefs.\n>    *\n>    * See the above comment \"Reference transaction updates\" for more\n>    * information.\n> @@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n>   \t\t\t   const char *refname,\n>   \t\t\t   const struct object_id *new_oid,\n>   \t\t\t   const struct object_id *old_oid,\n> +\t\t\t   const char *new_target,\n> +\t\t\t   const char *old_target,\n>   \t\t\t   unsigned int flags, const char *msg,\n>   \t\t\t   struct strbuf *err);\n>   \n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index a098d14ea0..e4d0aa3d41 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n>   \tref_transaction_add_update(\n>   \t\t\ttransaction, r->name,\n>   \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n> -\t\t\tnull_oid(), &r->oid, NULL);\n> +\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n>   \tif (ref_transaction_commit(transaction, &err))\n>   \t\tgoto cleanup;\n>   \n> @@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n>   \t\t * packed-refs transaction:\n>   \t\t */\n>   \t\tif (ref_transaction_update(transaction, iter->refname,\n> -\t\t\t\t\t   iter->oid, NULL,\n> +\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n>   \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n>   \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n>   \t\t\t    iter->refname, err.buf);\n> @@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n>   \t\t\ttransaction, \"HEAD\",\n>   \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n>   \t\t\t&update->new_oid, &update->old_oid,\n> -\t\t\tupdate->msg);\n> +\t\t\tNULL, NULL, update->msg);\n>   \n>   \t/*\n>   \t * Add \"HEAD\". This insertion is O(N) in the transaction\n> @@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n>   \tnew_update = ref_transaction_add_update(\n>   \t\t\ttransaction, referent, new_flags,\n>   \t\t\t&update->new_oid, &update->old_oid,\n> -\t\t\tupdate->msg);\n> +\t\t\tNULL, NULL, update->msg);\n>   \n>   \tnew_update->parent_update = update;\n>   \n> @@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n>   \t\t\t\t\tpacked_transaction, update->refname,\n>   \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n>   \t\t\t\t\t&update->new_oid, NULL,\n> -\t\t\t\t\tNULL);\n> +\t\t\t\t\tNULL, NULL, NULL);\n>   \t\t}\n>   \t}\n>   \n> @@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n>   \t\tref_transaction_add_update(packed_transaction, update->refname,\n>   \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n>   \t\t\t\t\t   &update->new_oid, &update->old_oid,\n> -\t\t\t\t\t   NULL);\n> +\t\t\t\t\t   NULL, NULL, NULL);\n>   \t}\n>   \n>   \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\n> diff --git a/refs/refs-internal.h b/refs/refs-internal.h\n> index 56641aa57a..108f4ec419 100644\n> --- a/refs/refs-internal.h\n> +++ b/refs/refs-internal.h\n> @@ -124,6 +124,19 @@ struct ref_update {\n>   \t */\n>   \tstruct object_id old_oid;\n>   \n> +\t/*\n> +\t * If set, point the reference to this value. This can also be\n> +\t * used to convert regular references to become symbolic refs.\n> +\t * Cannot be set together with `new_oid`.\n> +\t */\n> +\tconst char *new_target;\n> +\n> +\t/*\n> +\t * If set, check that the reference previously pointed to this\n> +\t * value. Cannot be set together with `old_oid`.\n> +\t */\n> +\tconst char *old_target;\n> +\n>   \t/*\n>   \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n>   \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n> @@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n>   \t\tconst char *refname, unsigned int flags,\n>   \t\tconst struct object_id *new_oid,\n>   \t\tconst struct object_id *old_oid,\n> +\t\tconst char *new_target, const char *old_target,\n>   \t\tconst char *msg);\n>   \n>   /*\n> diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\n> index 1cda48c504..6104471199 100644\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>   \t\t\tnew_update = ref_transaction_add_update(\n>   \t\t\t\t\ttransaction, \"HEAD\",\n>   \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n> -\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n> +\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n>   \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n>   \t\t}\n>   \n> @@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>   \t\t\t\t */\n>   \t\t\t\tnew_update = ref_transaction_add_update(\n>   \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n> -\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n> +\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n>   \t\t\t\tnew_update->parent_update = u;\n>   \n>   \t\t\t\t/*\n> diff --git a/sequencer.c b/sequencer.c\n> index 88de4dc20f..61e007d85f 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, \"HEAD\",\n>   \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n> -\t\t\t\t   null_oid() : from,\n> +\t\t\t\t   null_oid() : from, NULL, NULL,\n>   \t\t\t\t   0, sb.buf, &err) ||\n>   \t    ref_transaction_commit(transaction, &err)) {\n>   \t\tref_transaction_free(transaction);\n> @@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,\n>   \tif (!transaction ||\n>   \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n>   \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n> -\t\t\t\t   0, sb.buf, err) ||\n> +\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n>   \t    ref_transaction_commit(transaction, err)) {\n>   \t\tret = -1;\n>   \t}\n> @@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)\n>   \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n>   \t\terror(_(\"could not read HEAD\"));\n>   \t\tret = -1;\n> -\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n> -\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n> +\t} else if (ref_transaction_update(transaction, ref_name.buf,\n> +\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n> +\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n>   \t\t   ref_transaction_commit(transaction, &err)) {\n>   \t\terror(\"%s\", err.buf);\n>   \t\tret = -1;\n> diff --git a/walker.c b/walker.c\n> index c0fd632d92..1b3df43906 100644\n> --- a/walker.c\n> +++ b/walker.c\n> @@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n>   \t\tstrbuf_reset(&refname);\n>   \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n>   \t\tif (ref_transaction_update(transaction, refname.buf,\n> -\t\t\t\t\t   oids + i, NULL, 0,\n> +\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n>   \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n>   \t\t\t\t\t   &err)) {\n>   \t\t\terror(\"%s\", err.buf);\n"},{"id":"494090","messageId":"17c71b18-20b4-4bbd-b52c-c29ef8d3860e@gmail.com","threadId":"61235","inReplyTo":"20240503124115.252413-5-knayak@gitlab.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-05T14:09:39Z","receivedAt":"2024-05-05T14:09:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nI've left a few comments below - the most important one is about the \nerror messages in the reftable backend, non of the others are worth \nre-rolling for on their own.\n\nOn 03/05/2024 13:41, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The reference backends currently support transactional reference\n> updates. While this is exposed to users via 'git-update-ref' and its\n> '--stdin' mode, it is also used internally within various commands.\n> \n> However, we never supported transactional updates of symrefs. Let's adds\n\ns/we never supported/we do not support/\n\ns/Let's/This commit/\n\n> support for symrefs in both the 'files' and the 'reftable' backend.\n\ns/backend/backends/\n\n> Here, we add and use `ref_update_has_null_new_value()`, a helper\n> function which is used to check if there is a new_value in a reference\n> update. The new value could either be a symref target `new_target` or a\n> OID `new_oid`.\n> \n> With this, now transactional updates (verify, create, delete, update)\n\ns/With this, //\n\n> can be used for:\n> - regular refs\n> - symbolic refs\n> - conversion of regular to symbolic refs and vice versa\n\nExcellent\n\n> This also allows us to expose this to users via new commands in\n> 'git-update-ref' in the future.\n\nI'm slightly concerned that splitting out the update-ref changes means \nwe don't have any test coverage of the new code beyond the part that is \nused by refs_create_symref()\n\n> Note that a dangling symref update does not record a new reflog entry,\n> which is unchanged before and after this commit.\n> \n> +/*\n> + * Check whether the old_target values stored in update are consistent\n> + * with current_target, which is the symbolic reference's current value.\n> + * If everything is OK, return 0; otherwise, write an error message to\n> + * err and return -1.\n> + */\n> +static int check_old_target(struct ref_update *update,\n> +\t\t\t    const char *current_target,\n> +\t\t\t    struct strbuf *err)\n> +{\n> +\tif (!update->old_target)\n> +\t\tBUG(\"called without old_target set\");\n> +\n> +\tif (!strcmp(update->old_target, current_target))\n> +\t\treturn 0;\n> +\n> +\tif (!strcmp(current_target, \"\"))\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    update->old_target);\n> +\telse\n> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n> +\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t    original_update_refname(update),\n> +\t\t\t    current_target, update->old_target);\n> +\n> +\treturn -1;\n> +}\n> @@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>   \t\t}\n>   \t}\n>   \n> -\tif ((update->flags & REF_HAVE_NEW) &&\n> -\t    !(update->flags & REF_DELETING) &&\n> -\t    !(update->flags & REF_LOG_ONLY)) {\n> +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n> +\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n\nThis line looks quite long\n\n> --- a/refs/reftable-backend.c\n> +++ b/refs/reftable-backend.c\n> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>   \t\t * individual refs. But the error messages match what the files\n>   \t\t * backend returns, which keeps our tests happy.\n>   \t\t */\n> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n> +\t\tif (u->old_target) {\n> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n> +\t\t\t\tif (!strcmp(referent.buf, \"\"))\n> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    u->old_target);\n> +\t\t\t\telse\n> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n> +\t\t\t\t\t\t    original_update_refname(u),\n> +\t\t\t\t\t\t    referent.buf, u->old_target);\n\nThe messages in this function differ from the equivalent messages in \ncheck_old_target() from the files backend above. This is potentially \nconfusing to users, creates more work for translators and makes it hard \nto write tests that are independent of the backend. Can we export \ncheck_old_target() so it can be reused here. If not we should reword \nthese messages to match the other messages all of which talk about not \nbeing able to lock the ref.\n\n> +\t\t\t\tret = -1;\n> +\t\t\t\tgoto done;\n> +\t\t\t}\n> +\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n>   \t\t\tif (is_null_oid(&u->old_oid))\n>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>   \t\t\t\t\t    \"reference already exists\"),\n> @@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n>   \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n>   \t\t *   the given ref.\n>   \t\t */\n> -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n> +\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n\nThe old line was already quite long and the new one is even longer - \nperhaps we could break it after the second \"&&\"\n\n> +\t\t\tif (create_reflog) {\n> +\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n> +\t\t\t\tlog = &logs[logs_nr++];\n> +\t\t\t\tmemset(log, 0, sizeof(*log));\n> +\n> +\t\t\t\tfill_reftable_log_record(log);\n> +\t\t\t\tlog->update_index = ts;\n> +\t\t\t\tlog->refname = xstrdup(u->refname);\n> +\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n> +\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n\nBoth these lines would benefit from being folded\n\nBest Wishes\n\nPhillip\n"},{"id":"494091","messageId":"CAOLa=ZTRg+eg+ExWYvcxkHEiX=W7BJ-tW8Ssq+ViHaUh7P8CKQ@mail.gmail.com","threadId":"61235","inReplyTo":"37875fc1-19d5-40bf-9d92-bf6356e2049d@gmail.com","subject":"Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-05T15:10:39Z","receivedAt":"2024-05-05T15:10:41Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Hey Phillip,\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> On 03/05/2024 13:41, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The function `ref_transaction_update()` obtains ref information and\n>> flags to create a `ref_update` and add them to the transaction at hand.\n>>\n>> To extend symref support in transactions, we need to also accept the\n>> old and new ref targets and process it. This commit adds the required\n>> parameters to the function and modifies all call sites.\n>>\n>> The two parameters added are `new_target` and `old_target`. The\n>> `new_target` is used to denote what the reference should point to when\n>> the transaction is applied. Some functions allow this parameter to be\n>> NULL, meaning that the reference is not changed.\n>>\n>> The `old_target` denotes the value the reference must have before the\n>> update. Some functions allow this parameter to be NULL, meaning that the\n>> old value of the reference is not checked.\n>>\n>> We also update the internal function `ref_transaction_add_update()`\n>> similarly to take the two new parameters.\n>\n> The documentation for the new parameters looks good to me now - thanks\n> for updating it. I'm confused about the assertions though as I mentioned\n> in my other message [1].\n>\n> Best Wishes\n>\n> Phillip\n>\n> [1]\n> https://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com\n>\n\nResponding here since this is a newer thread.\n\nThis is done because in files-backend we split symref updates (see\n`split_symref_update`) and add a new one for the target reference. Here\nwe pass along the update struct. This update struct is memset to 0 and\nthis is after the checks we do. So the 'new_oid' here would be set to 0\n(null oid) even if the 'new_target' value is set. This made more sense\nin the earlier set of patches, but probably a diff like this should work\nfor this series and can be amended later as needed (in the series which\nadds the symref-* commands).\n\ndiff --git a/refs.c b/refs.c\nindex 3645b805c1..20d26da372 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1238,9 +1238,9 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n\n-\tif (old_oid && !is_null_oid(old_oid) && old_target)\n+\tif (old_oid && old_target)\n \t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n-\tif (new_oid && !is_null_oid(new_oid) && new_target)\n+\tif (new_oid && new_target)\n \t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n\n \tFLEX_ALLOC_STR(update, refname, refname);\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 3ce260d07d..a718164798 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2328,8 +2328,9 @@ static int split_symref_update(struct ref_update *update,\n\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n-\t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_target ? NULL : &update->new_oid,\n+\t\t\tupdate->old_target ? NULL : &update->old_oid,\n+\t\t\tupdate->new_target, update->old_target, update->msg);\n\n \tnew_update->parent_update = update;\n\nI think it makes sense to make it fool proof and add this, I'll wait for\nmore reviews and re-roll in a day or so.\n\nThanks for following through.\n\n[snip]\n"},{"id":"494092","messageId":"d774f2dc-eeae-4542-852f-674bb4fd0497@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZTRg+eg+ExWYvcxkHEiX=W7BJ-tW8Ssq+ViHaUh7P8CKQ@mail.gmail.com","subject":"Re: [PATCH v6 1/7] refs: accept symref values in `ref_transaction_update()`","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-05T15:19:39Z","receivedAt":"2024-05-05T15:19:42Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 05/05/2024 16:10, Karthik Nayak wrote:\n> Hey Phillip,\n> \n> Phillip Wood <phillip.wood123@gmail.com> writes:\n> \n>> Hi Karthik\n>>\n>> On 03/05/2024 13:41, Karthik Nayak wrote:\n>>> From: Karthik Nayak <karthik.188@gmail.com>\n>>>\n>>> The function `ref_transaction_update()` obtains ref information and\n>>> flags to create a `ref_update` and add them to the transaction at hand.\n>>>\n>>> To extend symref support in transactions, we need to also accept the\n>>> old and new ref targets and process it. This commit adds the required\n>>> parameters to the function and modifies all call sites.\n>>>\n>>> The two parameters added are `new_target` and `old_target`. The\n>>> `new_target` is used to denote what the reference should point to when\n>>> the transaction is applied. Some functions allow this parameter to be\n>>> NULL, meaning that the reference is not changed.\n>>>\n>>> The `old_target` denotes the value the reference must have before the\n>>> update. Some functions allow this parameter to be NULL, meaning that the\n>>> old value of the reference is not checked.\n>>>\n>>> We also update the internal function `ref_transaction_add_update()`\n>>> similarly to take the two new parameters.\n>>\n>> The documentation for the new parameters looks good to me now - thanks\n>> for updating it. I'm confused about the assertions though as I mentioned\n>> in my other message [1].\n>>\n>> Best Wishes\n>>\n>> Phillip\n>>\n>> [1]\n>> https://www.lore.kernel.org/git/7ca8c2c4-a9cc-4bec-b13c-95d7854b664b@gmail.com\n>>\n> \n> Responding here since this is a newer thread.\n> \n> This is done because in files-backend we split symref updates (see\n> `split_symref_update`) and add a new one for the target reference. Here\n> we pass along the update struct. This update struct is memset to 0 and\n> this is after the checks we do. So the 'new_oid' here would be set to 0\n> (null oid) even if the 'new_target' value is set. This made more sense\n> in the earlier set of patches, but probably a diff like this should work\n> for this series and can be amended later as needed (in the series which\n> adds the symref-* commands).\n\nThanks for the explanation - it would be good to fix this because the \nassertions don't catch misuses of ref_transaction_update() at the moment \nwhen old_oid points to the null oid and old_target is also set.\n\nBest Wishes\n\nPhillip\n\n> diff --git a/refs.c b/refs.c\n> index 3645b805c1..20d26da372 100644\n> --- a/refs.c\n> +++ b/refs.c\n> @@ -1238,9 +1238,9 @@ struct ref_update *ref_transaction_add_update(\n>   \tif (transaction->state != REF_TRANSACTION_OPEN)\n>   \t\tBUG(\"update called for transaction that is not open\");\n> \n> -\tif (old_oid && !is_null_oid(old_oid) && old_target)\n> +\tif (old_oid && old_target)\n>   \t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n> -\tif (new_oid && !is_null_oid(new_oid) && new_target)\n> +\tif (new_oid && new_target)\n>   \t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n> \n>   \tFLEX_ALLOC_STR(update, refname, refname);\n> diff --git a/refs/files-backend.c b/refs/files-backend.c\n> index 3ce260d07d..a718164798 100644\n> --- a/refs/files-backend.c\n> +++ b/refs/files-backend.c\n> @@ -2328,8 +2328,9 @@ static int split_symref_update(struct ref_update *update,\n> \n>   \tnew_update = ref_transaction_add_update(\n>   \t\t\ttransaction, referent, new_flags,\n> -\t\t\t&update->new_oid, &update->old_oid,\n> -\t\t\tNULL, NULL, update->msg);\n> +\t\t\tupdate->new_target ? NULL : &update->new_oid,\n> +\t\t\tupdate->old_target ? NULL : &update->old_oid,\n> +\t\t\tupdate->new_target, update->old_target, update->msg);\n> \n>   \tnew_update->parent_update = update;\n> \n> I think it makes sense to make it fool proof and add this, I'll wait for\n> more reviews and re-roll in a day or so.\n> \n> Thanks for following through.\n> \n> [snip]\n"},{"id":"494094","messageId":"CAOLa=ZR5a0Tj1abM4z4SAVm5cw5+EA0qyoOMs8QH8MoS6eJGiQ@mail.gmail.com","threadId":"61235","inReplyTo":"17c71b18-20b4-4bbd-b52c-c29ef8d3860e@gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-05T16:09:45Z","receivedAt":"2024-05-05T16:09:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Hello Phillip,\n\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> I've left a few comments below - the most important one is about the\n> error messages in the reftable backend, non of the others are worth\n> re-rolling for on their own.\n>\n> On 03/05/2024 13:41, Karthik Nayak wrote:\n>> From: Karthik Nayak <karthik.188@gmail.com>\n>>\n>> The reference backends currently support transactional reference\n>> updates. While this is exposed to users via 'git-update-ref' and its\n>> '--stdin' mode, it is also used internally within various commands.\n>>\n>> However, we never supported transactional updates of symrefs. Let's adds\n>\n> s/we never supported/we do not support/\n>\n> s/Let's/This commit/\n>\n>> support for symrefs in both the 'files' and the 'reftable' backend.\n>\n> s/backend/backends/\n>\n>> Here, we add and use `ref_update_has_null_new_value()`, a helper\n>> function which is used to check if there is a new_value in a reference\n>> update. The new value could either be a symref target `new_target` or a\n>> OID `new_oid`.\n>>\n>> With this, now transactional updates (verify, create, delete, update)\n>\n> s/With this, //\n>\n\nThanks, will make the above changes.\n\n>> can be used for:\n>> - regular refs\n>> - symbolic refs\n>> - conversion of regular to symbolic refs and vice versa\n>\n> Excellent\n>\n>> This also allows us to expose this to users via new commands in\n>> 'git-update-ref' in the future.\n>\n> I'm slightly concerned that splitting out the update-ref changes means\n> we don't have any test coverage of the new code beyond the part that is\n> used by refs_create_symref()\n>\n\nThis is definitely true. But I also caught a bunch of edge cases this\nway because the tests which indirectly use 'refs_create_symref()' are\nquite intensive.\n\n>> Note that a dangling symref update does not record a new reflog entry,\n>> which is unchanged before and after this commit.\n>>\n>> +/*\n>> + * Check whether the old_target values stored in update are consistent\n>> + * with current_target, which is the symbolic reference's current value.\n>> + * If everything is OK, return 0; otherwise, write an error message to\n>> + * err and return -1.\n>> + */\n>> +static int check_old_target(struct ref_update *update,\n>> +\t\t\t    const char *current_target,\n>> +\t\t\t    struct strbuf *err)\n>> +{\n>> +\tif (!update->old_target)\n>> +\t\tBUG(\"called without old_target set\");\n>> +\n>> +\tif (!strcmp(update->old_target, current_target))\n>> +\t\treturn 0;\n>> +\n>> +\tif (!strcmp(current_target, \"\"))\n>> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t    \"reference is missing but expected %s\",\n>> +\t\t\t    original_update_refname(update),\n>> +\t\t\t    update->old_target);\n>> +\telse\n>> +\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>> +\t\t\t    \"is at %s but expected %s\",\n>> +\t\t\t    original_update_refname(update),\n>> +\t\t\t    current_target, update->old_target);\n>> +\n>> +\treturn -1;\n>> +}\n>> @@ -2576,9 +2623,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n>>   \t\t}\n>>   \t}\n>>\n>> -\tif ((update->flags & REF_HAVE_NEW) &&\n>> -\t    !(update->flags & REF_DELETING) &&\n>> -\t    !(update->flags & REF_LOG_ONLY)) {\n>> +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n>> +\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n>\n> This line looks quite long\n>\n\nSomehow I find it much easier to read these longer lines, unless there\nis a logical operator, but I'll split the line mid way with the\narguments.\n\n>> --- a/refs/reftable-backend.c\n>> +++ b/refs/reftable-backend.c\n>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>   \t\t * individual refs. But the error messages match what the files\n>>   \t\t * backend returns, which keeps our tests happy.\n>>   \t\t */\n>> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>> +\t\tif (u->old_target) {\n>> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n>> +\t\t\t\tif (!strcmp(referent.buf, \"\"))\n>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    u->old_target);\n>> +\t\t\t\telse\n>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n>> +\t\t\t\t\t\t    original_update_refname(u),\n>> +\t\t\t\t\t\t    referent.buf, u->old_target);\n>\n> The messages in this function differ from the equivalent messages in\n> check_old_target() from the files backend above. This is potentially\n> confusing to users, creates more work for translators and makes it hard\n> to write tests that are independent of the backend. Can we export\n> check_old_target() so it can be reused here. If not we should reword\n> these messages to match the other messages all of which talk about not\n> being able to lock the ref.\n>\n\nThis is very intentional, the way the backends work at this point are\nquite different and while in the files backend, we talk about locking a\nparticular ref. In the reftable backend we do not lock single refs. We\nlock tables. So keeping it consistent doesn't make sense here.\n\nHowever, we could make the files backend similar to this one, I would be\nokay doing that.\n\n>> +\t\t\t\tret = -1;\n>> +\t\t\t\tgoto done;\n>> +\t\t\t}\n>> +\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n>>   \t\t\tif (is_null_oid(&u->old_oid))\n>>   \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>>   \t\t\t\t\t    \"reference already exists\"),\n>> @@ -1043,7 +1060,7 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n>>   \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n>>   \t\t *   the given ref.\n>>   \t\t */\n>> -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n>> +\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n>\n> The old line was already quite long and the new one is even longer -\n> perhaps we could break it after the second \"&&\"\n>\n\nI will break it both the && I think that is easier on the eyes.\n\n>> +\t\t\tif (create_reflog) {\n>> +\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n>> +\t\t\t\tlog = &logs[logs_nr++];\n>> +\t\t\t\tmemset(log, 0, sizeof(*log));\n>> +\n>> +\t\t\t\tfill_reftable_log_record(log);\n>> +\t\t\t\tlog->update_index = ts;\n>> +\t\t\t\tlog->refname = xstrdup(u->refname);\n>> +\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n>> +\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n>\n> Both these lines would benefit from being folded\n>\n> Best Wishes\n>\n> Phillip\n\n\nThanks for the review.\n\nKarthik\n"},{"id":"494120","messageId":"ZjiIjsGhRDHxo-vR@tanuki","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"Re: [PATCH v6 0/7] refs: add support for transactional symref updates","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-05-06T07:36:46Z","receivedAt":"2024-05-06T07:36:53Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Fri, May 03, 2024 at 02:41:08PM +0200, Karthik Nayak wrote:\n> From: Karthik Nayak <karthik.188@gmail.com>\n> \n> The patch series takes over from the existing patch series, wherein we\n> introduced symref-* commands to git-update-ref. Since there was a ton of\n> discussions on the UX of the patch series and its application, I thought it\n> would be best to shorten the series and split it into multiple smaller series.\n> \n> This series adds transactional support for symrefs in the reference db. Then\n> we switch refs_create_symref() to start using transactions for symref updates.\n> This allows us to deprecate the create_symref code in the ref_storage_be\n> interface and remove all associated code which is no longer used.\n> \n> The split was primarily done so we can merge the non-user facing parts of the\n> previous series. While pertaining the user facing components into another set\n> of patches wherein deeper discussion on the UX can be held without worrying\n> about the internal implementation. Also by using this new functionality in a\n> pre-existing command, we can leverage the existing tests to catch any\n> inconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for\n> dangling symrefs, which I've modified my patch to do the same.\n> \n> We also modify the reference transaction hook to support symrefs. For any symref\n> update the reference transaction hook will output the value with a 'ref:' prefix.\n> \n> Previous versions:\n> V1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com/\n> V2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com/\n> V3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com/\n> V4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\n> V5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com\n> \n> Changes over v5 are:\n> - More user friendly error messages.\n> - `create_symref_lock` now writes to an err buf, instead of directly to stderr.\n> - Refactor code to make it easier to read around logical operations.\n> - Cleanup commit message and fix typos.\n\nJust for the record, I don't really have much to add to this series\nbesides what has already been said.\n\nThanks!\n\nPatrick\n"},{"id":"494140","messageId":"6e1f09fc-747c-4704-87b5-a7b2ba18d78e@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZR5a0Tj1abM4z4SAVm5cw5+EA0qyoOMs8QH8MoS6eJGiQ@mail.gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-06T09:35:40Z","receivedAt":"2024-05-06T09:35:43Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 05/05/2024 17:09, Karthik Nayak wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> On 03/05/2024 13:41, Karthik Nayak wrote:\n>>> --- a/refs/reftable-backend.c\n>>> +++ b/refs/reftable-backend.c\n>>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>>    \t\t * individual refs. But the error messages match what the files\n>>>    \t\t * backend returns, which keeps our tests happy.\n>>>    \t\t */\n>>> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>>> +\t\tif (u->old_target) {\n>>> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n>>> +\t\t\t\tif (!strcmp(referent.buf, \"\"))\n>>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>>> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>>> +\t\t\t\t\t\t    original_update_refname(u),\n>>> +\t\t\t\t\t\t    u->old_target);\n>>> +\t\t\t\telse\n>>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>>> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n>>> +\t\t\t\t\t\t    original_update_refname(u),\n>>> +\t\t\t\t\t\t    referent.buf, u->old_target);\n>>\n>> The messages in this function differ from the equivalent messages in\n>> check_old_target() from the files backend above. This is potentially\n>> confusing to users, creates more work for translators and makes it hard\n>> to write tests that are independent of the backend. Can we export\n>> check_old_target() so it can be reused here. If not we should reword\n>> these messages to match the other messages all of which talk about not\n>> being able to lock the ref.\n>>\n> \n> This is very intentional, the way the backends work at this point are\n> quite different and while in the files backend, we talk about locking a\n> particular ref.\n\nI agree that the existing messages could be improved - these messages \nare returned when checking the old value of the ref so talking about \nbeing unable to lock the ref is not helpful as the important information \nis that the old value does not match the expected value. However that is \nnot dependent on the backend or on whether the expected value is a \nsymref or an oid so it feels a bit random to make these two messages \ndifferent.\n\n> In the reftable backend we do not lock single refs. We\n> lock tables. So keeping it consistent doesn't make sense here.\n\nWhere an update is prevented by another process holding a lock I think \nthat the granularity of the lock that prevents the ref from being \nupdated is not particularly relevant as far as the user is concerned. As \nfar as I can see the existing error messages in the reftable backend try \nto be consistent with the messages in the files backend.\n\n> However, we could make the files backend similar to this one, I would be\n> okay doing that.\n\nI would be very happy to see the messages improved for both backends \nwhen the old value does not match the expected (oid or symref) value. I \ndo think we should have consistent error messages in this case that are \nessentially independent of the backend and type of the expected value.\n\nBest Wishes\n\nPhillip\n"},{"id":"494141","messageId":"dff080ee-946c-4a44-a448-294ae8ee241f@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZR5a0Tj1abM4z4SAVm5cw5+EA0qyoOMs8QH8MoS6eJGiQ@mail.gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-06T09:54:56Z","receivedAt":"2024-05-06T09:54:59Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 05/05/2024 17:09, Karthik Nayak wrote:\n> Phillip Wood <phillip.wood123@gmail.com> writes:\n>> I'm slightly concerned that splitting out the update-ref changes means\n>> we don't have any test coverage of the new code beyond the part that is\n>> used by refs_create_symref()\n>>\n> \n> This is definitely true. But I also caught a bunch of edge cases this\n> way because the tests which indirectly use 'refs_create_symref()' are\n> quite intensive.\n\nI forgot to say in my last mail that this is good to know. So it sounds \nlike the only new code that isn't being exercised by the tests is the \ncheck for the old value?\n\nBest Wishes\n\nPhillip\n"},{"id":"494144","messageId":"CAOLa=ZRo00Mty-RFw5sgsckGj=qbgkwaynyqGiEXR5YhuRCMLA@mail.gmail.com","threadId":"61235","inReplyTo":"6e1f09fc-747c-4704-87b5-a7b2ba18d78e@gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-06T11:19:54Z","receivedAt":"2024-05-06T11:19:56Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> On 05/05/2024 17:09, Karthik Nayak wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> On 03/05/2024 13:41, Karthik Nayak wrote:\n>>>> --- a/refs/reftable-backend.c\n>>>> +++ b/refs/reftable-backend.c\n>>>> @@ -938,7 +940,22 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n>>>>    \t\t * individual refs. But the error messages match what the files\n>>>>    \t\t * backend returns, which keeps our tests happy.\n>>>>    \t\t */\n>>>> -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>>>> +\t\tif (u->old_target) {\n>>>> +\t\t\tif (strcmp(referent.buf, u->old_target)) {\n>>>> +\t\t\t\tif (!strcmp(referent.buf, \"\"))\n>>>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>>>> +\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>>>> +\t\t\t\t\t\t    original_update_refname(u),\n>>>> +\t\t\t\t\t\t    u->old_target);\n>>>> +\t\t\t\telse\n>>>> +\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>>>> +\t\t\t\t\t\t    \"is at %s but expected %s\",\n>>>> +\t\t\t\t\t\t    original_update_refname(u),\n>>>> +\t\t\t\t\t\t    referent.buf, u->old_target);\n>>>\n>>> The messages in this function differ from the equivalent messages in\n>>> check_old_target() from the files backend above. This is potentially\n>>> confusing to users, creates more work for translators and makes it hard\n>>> to write tests that are independent of the backend. Can we export\n>>> check_old_target() so it can be reused here. If not we should reword\n>>> these messages to match the other messages all of which talk about not\n>>> being able to lock the ref.\n>>>\n>>\n>> This is very intentional, the way the backends work at this point are\n>> quite different and while in the files backend, we talk about locking a\n>> particular ref.\n>\n> I agree that the existing messages could be improved - these messages\n> are returned when checking the old value of the ref so talking about\n> being unable to lock the ref is not helpful as the important information\n> is that the old value does not match the expected value. However that is\n> not dependent on the backend or on whether the expected value is a\n> symref or an oid so it feels a bit random to make these two messages\n> different.\n>\n>> In the reftable backend we do not lock single refs. We\n>> lock tables. So keeping it consistent doesn't make sense here.\n>\n> Where an update is prevented by another process holding a lock I think\n> that the granularity of the lock that prevents the ref from being\n> updated is not particularly relevant as far as the user is concerned. As\n> far as I can see the existing error messages in the reftable backend try\n> to be consistent with the messages in the files backend.\n>\n\nWell, I agree. But we do have to note, that the files backend always had\n`check_old_oid` which has messages along the lines of 'cannot lock ref\n...' and now the new `check_old_target` will not be consistent with\nthose messages. Which is OK, since, like you mentioned, these messages\ncould be improved.\n\n>> However, we could make the files backend similar to this one, I would be\n>> okay doing that.\n>\n> I would be very happy to see the messages improved for both backends\n> when the old value does not match the expected (oid or symref) value. I\n> do think we should have consistent error messages in this case that are\n> essentially independent of the backend and type of the expected value.\n>\n> Best Wishes\n>\n> Phillip\n\nOverall, I think we're reaching the same consensus, i.e. to export this\nfunctionality to a generic function and shared between the both\nbackends. I will make this change. I think the message used in the\nreftable backend along the lines of 'verifying symref target ...' is\nverbose yet generic enough, so I'll keep those messages.\n\nThanks\nKarthik\n"},{"id":"494145","messageId":"CAOLa=ZSMFVDy31W+5=iu2AWjyaR+Jy_BR5txd-bGnHu1iRsL9w@mail.gmail.com","threadId":"61235","inReplyTo":"dff080ee-946c-4a44-a448-294ae8ee241f@gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-06T11:22:34Z","receivedAt":"2024-05-06T11:22:36Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Hello,\n\n\nPhillip Wood <phillip.wood123@gmail.com> writes:\n\n> Hi Karthik\n>\n> On 05/05/2024 17:09, Karthik Nayak wrote:\n>> Phillip Wood <phillip.wood123@gmail.com> writes:\n>>> I'm slightly concerned that splitting out the update-ref changes means\n>>> we don't have any test coverage of the new code beyond the part that is\n>>> used by refs_create_symref()\n>>>\n>>\n>> This is definitely true. But I also caught a bunch of edge cases this\n>> way because the tests which indirectly use 'refs_create_symref()' are\n>> quite intensive.\n>\n> I forgot to say in my last mail that this is good to know. So it sounds\n> like the only new code that isn't being exercised by the tests is the\n> check for the old value?\n>\n> Best Wishes\n>\n> Phillip\n\nThat's correct. I think testing that _currently_ would require us to\nprobably expose and test via the unit testing library. I plan to follow\nthis patch series soon with the symref-* ones. While that's not the best\nargument for not having full test coverage, I hope it is an OK state to\nbe since that path has no users as of this point.\n\nKarthik\n"},{"id":"494157","messageId":"6cb8c364-a7a9-4773-82e1-ba2899fcb353@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZSMFVDy31W+5=iu2AWjyaR+Jy_BR5txd-bGnHu1iRsL9w@mail.gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-06T13:17:37Z","receivedAt":"2024-05-06T13:17:41Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\nOn 06/05/2024 12:22, Karthik Nayak wrote:\n> \n> That's correct. I think testing that _currently_ would require us to\n> probably expose and test via the unit testing library. I plan to follow\n> this patch series soon with the symref-* ones. While that's not the best\n> argument for not having full test coverage, I hope it is an OK state to\n> be since that path has no users as of this point.\n\nThat seems reasonable - we have good test coverage for the code we're \ncurrently using and you're clearly committed to adding tests for the \nrest when you extend \"git update-ref\" to accommodate symrefs.\n\nThanks\n\nPhillip\n"},{"id":"494158","messageId":"cd84d359-79e2-439d-bae3-b4450aba7adb@gmail.com","threadId":"61235","inReplyTo":"CAOLa=ZRo00Mty-RFw5sgsckGj=qbgkwaynyqGiEXR5YhuRCMLA@mail.gmail.com","subject":"Re: [PATCH v6 4/7] refs: add support for transactional symref updates","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-06T13:19:21Z","receivedAt":"2024-05-06T13:19:24Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 06/05/2024 12:19, Karthik Nayak wrote:\n>\n> Overall, I think we're reaching the same consensus, i.e. to export this\n> functionality to a generic function and shared between the both\n> backends. I will make this change. I think the message used in the\n> reftable backend along the lines of 'verifying symref target ...' is\n> verbose yet generic enough, so I'll keep those messages.\n\nThat sounds good to me\n\nThanks\n\nPhillip\n"},{"id":"494239","messageId":"20240507060035.28602-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240503124115.252413-1-knayak@gitlab.com","subject":"[PATCH v7 0/8] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:27Z","receivedAt":"2024-05-07T06:00:44Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe patch series takes over from the existing patch series, wherein we\nintroduced symref-* commands to git-update-ref. Since there was a ton of\ndiscussions on the UX of the patch series and its application, I thought it\nwould be best to shorten the series and split it into multiple smaller series.\n\nThis series adds transactional support for symrefs in the reference db. Then\nwe switch refs_create_symref() to start using transactions for symref updates.\nThis allows us to deprecate the create_symref code in the ref_storage_be\ninterface and remove all associated code which is no longer used.\n\nThe split was primarily done so we can merge the non-user facing parts of the\nprevious series. While pertaining the user facing components into another set\nof patches wherein deeper discussion on the UX can be held without worrying\nabout the internal implementation. Also by using this new functionality in a\npre-existing command, we can leverage the existing tests to catch any\ninconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for\ndangling symrefs, which I've modified my patch to do the same.\n\nWe also modify the reference transaction hook to support symrefs. For any symref\nupdate the reference transaction hook will output the value with a 'ref:' prefix.\n\nPrevious versions:\nV1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com\nV2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com\nV3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com\nV4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\nV5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com\nV6: https://lore.kernel.org/r/20240503124115.252413-1-knayak@gitlab.com\n\nThanks for all the reviews!\n\nChanges since v6:\n* Made the check for old/new oid & target more stricter by removing\nthe exception for null oid's.\n* Extracted `ref_update_check_old_target` and `original_update_refname`\nto refs.c\n* ^This allowed us to generalize the error messages for non-matching\nold_target values between files and reftable backend.\n* Better line wrapping in code.\n* Fixed some grammar in commit messages.\n\nJunio, I've rebased my patches on top of, not sure if there is something else I\nmissed here:\n- ps/ci-python-2-deprecation (2a686e00d2)\n- ps/reftable-write-options (f5545b173b)\n- ps/pseudo-ref-terminology (ad302a68fd)\n\nRange diff against v6:\n\n1:  a354190905 ! 1:  2a09350661 refs: accept symref values in `ref_transaction_update()`\n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \tif (transaction->state != REF_TRANSACTION_OPEN)\n      \t\tBUG(\"update called for transaction that is not open\");\n      \n    -+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n    ++\tif (old_oid && old_target)\n     +\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n    -+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n    ++\tif (new_oid && new_target)\n     +\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n     +\n      \tFLEX_ALLOC_STR(update, refname, refname);\n2:  0d9c5b9804 = 2:  9e76b768b9 files-backend: extract out `create_symref_lock()`\n3:  e0219ffd31 = 3:  c1d1c5a651 refs: support symrefs in 'reference-transaction' hook\n-:  ---------- > 4:  d7d455c9b6 refs: move `original_update_refname` to 'refs.c'\n4:  b22c59c722 ! 5:  1c00cd69c6 refs: add support for transactional symref updates\n    @@ Commit message\n         updates. While this is exposed to users via 'git-update-ref' and its\n         '--stdin' mode, it is also used internally within various commands.\n     \n    -    However, we never supported transactional updates of symrefs. Let's add\n    -    support for symrefs in both the 'files' and the 'reftable' backend.\n    +    However, we do not support transactional updates of symrefs. This commit\n    +    adds support for symrefs in both the 'files' and the 'reftable' backend.\n     \n         Here, we add and use `ref_update_has_null_new_value()`, a helper\n         function which is used to check if there is a new_value in a reference\n         update. The new value could either be a symref target `new_target` or a\n         OID `new_oid`.\n     \n    -    With this, now transactional updates (verify, create, delete, update)\n    -    can be used for:\n    +    We also add another common function `ref_update_check_old_target` which\n    +    will be used to check if the update's old_target corresponds to a\n    +    reference's current target.\n    +\n    +    Now transactional updates (verify, create, delete, update) can be used\n    +    for:\n         - regular refs\n         - symbolic refs\n         - conversion of regular to symbolic refs and vice versa\n    @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \n      \tref_transaction_add_update(transaction, refname, flags,\n      \t\t\t\t   new_oid, old_oid, new_target,\n    -@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n    - {\n    - \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n    +@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)\n    + \treturn update->refname;\n      }\n    -+\n    + \n     +int ref_update_has_null_new_value(struct ref_update *update)\n     +{\n     +\treturn !update->new_target && is_null_oid(&update->new_oid);\n     +}\n    -\n    - ## refs/files-backend.c ##\n    -@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)\n    - \treturn update->refname;\n    - }\n    - \n    -+/*\n    -+ * Check whether the old_target values stored in update are consistent\n    -+ * with current_target, which is the symbolic reference's current value.\n    -+ * If everything is OK, return 0; otherwise, write an error message to\n    -+ * err and return -1.\n    -+ */\n    -+static int check_old_target(struct ref_update *update,\n    -+\t\t\t    const char *current_target,\n    -+\t\t\t    struct strbuf *err)\n    ++\n    ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n    ++\t\t\t\tstruct strbuf *err)\n     +{\n     +\tif (!update->old_target)\n     +\t\tBUG(\"called without old_target set\");\n     +\n    -+\tif (!strcmp(update->old_target, current_target))\n    ++\tif (!strcmp(referent, update->old_target))\n     +\t\treturn 0;\n     +\n    -+\tif (!strcmp(current_target, \"\"))\n    -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\tif (!strcmp(referent, \"\"))\n    ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t    \"reference is missing but expected %s\",\n    -+\t\t\t    original_update_refname(update),\n    ++\t\t\t    ref_update_original_update_refname(update),\n     +\t\t\t    update->old_target);\n     +\telse\n    -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t    \"is at %s but expected %s\",\n    -+\t\t\t    original_update_refname(update),\n    -+\t\t\t    current_target, update->old_target);\n    -+\n    ++\t\t\t    ref_update_original_update_refname(update),\n    ++\t\t\t    referent, update->old_target);\n     +\treturn -1;\n     +}\n    -+\n    - /*\n    -  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n    -  * are consistent with oid, which is the reference's current value. If\n    +\n    + ## refs/files-backend.c ##\n    +@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,\n    + \n    + \tnew_update = ref_transaction_add_update(\n    + \t\t\ttransaction, referent, new_flags,\n    +-\t\t\t&update->new_oid, &update->old_oid,\n    +-\t\t\tNULL, NULL, update->msg);\n    ++\t\t\tupdate->new_target ? NULL : &update->new_oid,\n    ++\t\t\tupdate->old_target ? NULL : &update->old_oid,\n    ++\t\t\tupdate->new_target, update->old_target, update->msg);\n    + \n    + \tnew_update->parent_update = update;\n    + \n     @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,\n      \n      \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     +\t\t\t}\n     +\n     +\t\t\tif (update->old_target) {\n    -+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n    ++\t\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n     +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\t\t\tgoto out;\n     +\t\t\t\t}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     +\t\t * be set for symrefs, but we're strict about its usage.\n     +\t\t */\n     +\t\tif (update->old_target) {\n    -+\t\t\tif (check_old_target(update, referent.buf, err)) {\n    ++\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n     +\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\t\tgoto out;\n     +\t\t\t}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     -\t    !(update->flags & REF_DELETING) &&\n     -\t    !(update->flags & REF_LOG_ONLY)) {\n     +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n    -+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n    ++\t\tif (create_symref_lock(refs, lock, update->refname,\n    ++\t\t\t\t       update->new_target, err)) {\n     +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\tgoto out;\n     +\t\t}\n    @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n      \t\t\tif (commit_ref(lock)) {\n     \n      ## refs/refs-internal.h ##\n    -@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n    +@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor\n       */\n    - struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n    + const char *ref_update_original_update_refname(struct ref_update *update);\n      \n     +/*\n     + * Helper function to check if the new value is null, this\n    @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re\n     + * ref or a symbolic ref.\n     + */\n     +int ref_update_has_null_new_value(struct ref_update *update);\n    ++\n    ++/*\n    ++ * Check whether the old_target values stored in update are consistent\n    ++ * with the referent, which is the symbolic reference's current value.\n    ++ * If everything is OK, return 0; otherwise, write an error message to\n    ++ * err and return -1.\n    ++ */\n    ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n    ++\t\t\t\tstruct strbuf *err);\n     +\n      #endif /* REFS_REFS_INTERNAL_H */\n     \n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n     +\t\tif (u->old_target) {\n    -+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n    -+\t\t\t\tif (!strcmp(referent.buf, \"\"))\n    -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n    -+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n    -+\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    u->old_target);\n    -+\t\t\t\telse\n    -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n    -+\t\t\t\t\t\t    \"is at %s but expected %s\",\n    -+\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    referent.buf, u->old_target);\n    ++\t\t\tif (ref_update_check_old_target(referent.buf, u, err)) {\n     +\t\t\t\tret = -1;\n     +\t\t\t\tgoto done;\n     +\t\t\t}\n     +\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n      \t\t\tif (is_null_oid(&u->old_oid))\n      \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n    - \t\t\t\t\t    \"reference already exists\"),\n    + \t\t\t\t\t\t   \"reference already exists\"),\n     @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n      \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n      \t\t *   the given ref.\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n    -+\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n    ++\t\tif ((u->flags & REF_HAVE_NEW) &&\n    ++\t\t    !(u->type & REF_ISSYMREF) &&\n    ++\t\t    ref_update_has_null_new_value(u)) {\n      \t\t\tstruct reftable_log_record log = {0};\n      \t\t\tstruct reftable_iterator it = {0};\n      \n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n     -\t\t\tlog = &logs[logs_nr++];\n     -\t\t\tmemset(log, 0, sizeof(*log));\n     -\n    --\t\t\tfill_reftable_log_record(log);\n    +-\t\t\tfill_reftable_log_record(log, &committer_ident);\n     -\t\t\tlog->update_index = ts;\n     -\t\t\tlog->refname = xstrdup(u->refname);\n     -\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n     +\t\t\t\tlog = &logs[logs_nr++];\n     +\t\t\t\tmemset(log, 0, sizeof(*log));\n     +\n    -+\t\t\t\tfill_reftable_log_record(log);\n    ++\t\t\t\tfill_reftable_log_record(log, &committer_ident);\n     +\t\t\t\tlog->update_index = ts;\n     +\t\t\t\tlog->refname = xstrdup(u->refname);\n    -+\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n    -+\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n    ++\t\t\t\tmemcpy(log->value.update.new_hash,\n    ++\t\t\t\t       u->new_oid.hash, GIT_MAX_RAWSZ);\n    ++\t\t\t\tmemcpy(log->value.update.old_hash,\n    ++\t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n     +\t\t\t\tlog->value.update.message =\n     +\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n     +\t\t\t}\n5:  636bf5ce98 ! 6:  027106cfe6 refs: use transaction in `refs_create_symref()`\n    @@ refs.c: int refs_create_symref(struct ref_store *refs,\n      \n      int create_symref(const char *ref_target, const char *refs_heads_master,\n     \n    - ## t/t0610-reftable-basics.sh ##\n    -@@ t/t0610-reftable-basics.sh: test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl\n    - \tgit init repo &&\n    - \ttest_commit -C repo A &&\n    - \tcat >expect <<-EOF &&\n    --\terror: unable to write symref for refs/heads: file/directory conflict\n    -+\terror: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}\n    - \tEOF\n    - \ttest_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&\n    - \ttest_cmp expect err\n    -\n      ## t/t1416-ref-transaction-hooks.sh ##\n     @@ t/t1416-ref-transaction-hooks.sh: test_expect_success 'interleaving hook calls succeed' '\n      \ttest_cmp expect target-repo.git/actual\n6:  07fb23374f = 7:  6939406143 refs: rename `refs_create_symref()` to `refs_update_symref()`\n7:  5c05813bcc ! 8:  bc787b903b refs: remove `create_symref` and associated dead code\n    @@ refs/reftable-backend.c: struct write_create_symref_arg {\n     -\t\t.value.symref = (char *)create->target,\n     -\t\t.update_index = ts,\n     -\t};\n    +-\tstruct ident_split committer_ident = {0};\n     -\tstruct reftable_log_record log = {0};\n     -\tstruct object_id new_oid;\n     -\tstruct object_id old_oid;\n    +-\tconst char *committer_info;\n     -\tint ret;\n     -\n     -\treftable_writer_set_limits(writer, ts, ts);\n     -\n    +-\tret = refs_verify_refname_available(&create->refs->base, create->refname,\n    +-\t\t\t\t\t    NULL, NULL, create->err);\n    +-\tif (ret < 0)\n    +-\t\treturn ret;\n    +-\n     -\tret = reftable_writer_add_ref(writer, &ref);\n     -\tif (ret)\n     -\t\treturn ret;\n    @@ refs/reftable-backend.c: struct write_create_symref_arg {\n     -\t    !should_write_log(&create->refs->base, create->refname))\n     -\t\treturn 0;\n     -\n    --\tfill_reftable_log_record(&log);\n    +-\tcommitter_info = git_committer_info(0);\n    +-\tif (split_ident_line(&committer_ident, committer_info, strlen(committer_info)))\n    +-\t\tBUG(\"failed splitting committer info\");\n    +-\n    +-\tfill_reftable_log_record(&log, &committer_ident);\n     -\tlog.refname = xstrdup(create->refname);\n     -\tlog.update_index = ts;\n     -\tlog.value.update.message = xstrndup(create->logmsg,\n    @@ refs/reftable-backend.c: struct write_create_symref_arg {\n     -\tstruct reftable_ref_store *refs =\n     -\t\treftable_be_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n     -\tstruct reftable_stack *stack = stack_for(refs, refname, &refname);\n    +-\tstruct strbuf err = STRBUF_INIT;\n     -\tstruct write_create_symref_arg arg = {\n     -\t\t.refs = refs,\n     -\t\t.stack = stack,\n     -\t\t.refname = refname,\n     -\t\t.target = target,\n     -\t\t.logmsg = logmsg,\n    +-\t\t.err = &err,\n     -\t};\n     -\tint ret;\n     -\n    @@ refs/reftable-backend.c: struct write_create_symref_arg {\n     -\n     -done:\n     -\tassert(ret != REFTABLE_API_ERROR);\n    --\tif (ret)\n    --\t\terror(\"unable to write symref for %s: %s\", refname,\n    --\t\t      reftable_error_str(ret));\n    +-\tif (ret) {\n    +-\t\tif (err.len)\n    +-\t\t\terror(\"%s\", err.buf);\n    +-\t\telse\n    +-\t\t\terror(\"unable to write symref for %s: %s\", refname,\n    +-\t\t\t      reftable_error_str(ret));\n    +-\t}\n    +-\n    +-\tstrbuf_release(&err);\n     -\treturn ret;\n     -}\n     -\n\nKarthik Nayak (8):\n  refs: accept symref values in `ref_transaction_update()`\n  files-backend: extract out `create_symref_lock()`\n  refs: support symrefs in 'reference-transaction' hook\n  refs: move `original_update_refname` to 'refs.c'\n  refs: add support for transactional symref updates\n  refs: use transaction in `refs_create_symref()`\n  refs: rename `refs_create_symref()` to `refs_update_symref()`\n  refs: remove `create_symref` and associated dead code\n\n Documentation/githooks.txt       |  14 ++-\n branch.c                         |   2 +-\n builtin/branch.c                 |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   2 +-\n builtin/receive-pack.c           |   1 +\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             |   1 +\n builtin/worktree.c               |   2 +-\n refs.c                           | 119 ++++++++++++++----\n refs.h                           |  20 ++-\n refs/debug.c                     |  13 --\n refs/files-backend.c             | 208 +++++++++++++++++--------------\n refs/packed-backend.c            |   1 -\n refs/refs-internal.h             |  40 +++++-\n refs/reftable-backend.c          | 202 +++++++++---------------------\n sequencer.c                      |   9 +-\n t/helper/test-ref-store.c        |   2 +-\n t/t1416-ref-transaction-hooks.sh |  23 ++++\n walker.c                         |   2 +-\n 21 files changed, 373 insertions(+), 298 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"494240","messageId":"20240507060035.28602-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 1/8] refs: accept symref values in `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:28Z","receivedAt":"2024-05-07T06:00:46Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `ref_transaction_update()` obtains ref information and\nflags to create a `ref_update` and add them to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. This commit adds the required\nparameters to the function and modifies all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied. Some functions allow this parameter to be\nNULL, meaning that the reference is not changed.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked.\n\nWe also update the internal function `ref_transaction_add_update()`\nsimilarly to take the two new parameters.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 18 +++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 71 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 7c3c7465a4..d7df137904 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1268,6 +1268,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1275,6 +1276,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && old_target)\n+\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && new_target)\n+\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1293,6 +1299,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1327,7 +1335,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1342,7 +1351,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1355,7 +1365,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1368,6 +1379,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1382,8 +1394,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex 8255989e7e..9abbfd35a2 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         updated to point to. If the reference is a regular reference,\n+ *         it will be converted to a symbolic reference. Cannot be set\n+ *         together with `new_oid`. A copy of this value is made in the\n+ *         transaction.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Canont be set together with `old_oid`. A copy of this value is\n+ *         made in the transaction.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex ea927c516d..7e432cf026 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1197,7 +1197,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1291,7 +1291,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2308,7 +2308,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2371,7 +2371,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2762,7 +2762,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3047,7 +3047,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..108f4ec419 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,19 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t * Cannot be set together with `new_oid`.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set, check that the reference previously pointed to this\n+\t * value. Cannot be set together with `old_oid`.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 10ee92e1f7..f48ab5ad22 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -870,7 +870,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -949,7 +949,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 88de4dc20f..61e007d85f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"494241","messageId":"20240507060035.28602-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 2/8] files-backend: extract out `create_symref_lock()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:29Z","receivedAt":"2024-05-07T06:00:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked()` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nExtract the early half of `create_symref_locked()` into a new helper\nfunction `create_symref_lock()`. Because the name of the new function is\ntoo similar to the original, rename the original to\n`create_and_commit_symref()` to avoid confusion.\n\nThe new function `create_symref_locked()` can be used to create the\nsymref lock in a separate step from that of committing it. This allows\nto add transactional support for symrefs, where the lock would be\ncreated in the preparation step and the lock would be committed in the\nfinish step.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 37 insertions(+), 14 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 7e432cf026..40cc715ea7 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1919,27 +1919,49 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target, struct strbuf *err)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\")) {\n+\t\tstrbuf_addf(err, \"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0) {\n+\t\tstrbuf_addf(err, \"unable to write to %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target, &err);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t} else {\n+\t\treturn error(\"%s\", err.buf);\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n-\treturn 0;\n+\treturn ret;\n }\n \n static int files_create_symref(struct ref_store *ref_store,\n@@ -1959,7 +1981,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"494242","messageId":"20240507060035.28602-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 3/8] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:30Z","receivedAt":"2024-05-07T06:00:48Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In a previous commit, we added the `old_target` and\n`new_target` fields to the `reference_transaction_update()`. In\nfollowing commits we'll also add the code to handle symref's in the\nreference backends.\n\nSupport symrefs also in the 'reference-transaction' hook, by modifying\nthe current format:\n    <old-oid> SP <new-oid> SP <ref-name> LF\nto be be:\n    <old-value> SP <new-value> SP <ref-name> LF\nwhere for regular refs the output would not change and remain the same.\nBut when either 'old-value' or 'new-value' is a symref, we print the ref\nas 'ref:<ref-target>'.\n\nThis does break backward compatibility, but the 'reference-transaction'\nhook's documentation always stated that support for symbolic references\nmay be added in the future.\n\nWe do not add any tests in this commit since there is no git command\nwhich activates this flow, in an upcoming commit, we'll start using\ntransaction based symref updates as the default, we'll add tests there\nfor the hook too.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt | 14 +++++++++-----\n refs.c                     | 20 ++++++++++++++++----\n 2 files changed, 25 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..06e997131b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also supports symbolic reference updates.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects. A reference will be\n+denoted with a 'ref:' prefix, like `ref:<ref-target>`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex d7df137904..2bb3e09425 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2366,10 +2366,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = transaction->updates[i];\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\n+\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n+\n+\t\tif (!(update->flags & REF_HAVE_NEW))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\n-- \n2.43.GIT\n\n"},{"id":"494243","messageId":"20240507060035.28602-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 4/8] refs: move `original_update_refname` to 'refs.c'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:31Z","receivedAt":"2024-05-07T06:00:49Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe files backend and the reftable backend implement\n`original_update_refname` to obtain the original refname of the update.\nMove it out to 'refs.c' and only expose it internally to the refs\nlibrary. This will be used in an upcoming commit to also introduce\nanother common functionality for the two backends.\n\nWe also rename the function to `ref_update_original_update_refname` to\nkeep it consistent with the upcoming other 'ref_update_*' functions\nthat'll be introduced.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  9 +++++++++\n refs/files-backend.c    | 21 +++++----------------\n refs/refs-internal.h    |  5 +++++\n refs/reftable-backend.c | 24 +++++++-----------------\n 4 files changed, 26 insertions(+), 33 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 2bb3e09425..d65e4cc41e 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2830,3 +2830,12 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+const char *ref_update_original_update_refname(struct ref_update *update)\n+{\n+\twhile (update->parent_update)\n+\t\tupdate = update->parent_update;\n+\n+\treturn update->refname;\n+}\n+\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 40cc715ea7..64d2a50e97 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2422,17 +2422,6 @@ static int split_symref_update(struct ref_update *update,\n \treturn 0;\n }\n \n-/*\n- * Return the refname under which update was originally requested.\n- */\n-static const char *original_update_refname(struct ref_update *update)\n-{\n-\twhile (update->parent_update)\n-\t\tupdate = update->parent_update;\n-\n-\treturn update->refname;\n-}\n-\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2449,16 +2438,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \tif (is_null_oid(&update->old_oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n-\t\t\t    original_update_refname(update));\n+\t\t\t    ref_update_original_update_refname(update));\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n-\t\t\t    original_update_refname(update),\n+\t\t\t    ref_update_original_update_refname(update),\n \t\t\t    oid_to_hex(&update->old_oid));\n \telse\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"is at %s but expected %s\",\n-\t\t\t    original_update_refname(update),\n+\t\t\t    ref_update_original_update_refname(update),\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n@@ -2512,7 +2501,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \t\treason = strbuf_detach(err, NULL);\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': %s\",\n-\t\t\t    original_update_refname(update), reason);\n+\t\t\t    ref_update_original_update_refname(update), reason);\n \t\tfree(reason);\n \t\tgoto out;\n \t}\n@@ -2532,7 +2521,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\tif (update->flags & REF_HAVE_OLD) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t\t\t\t    \"error reading reference\",\n-\t\t\t\t\t\t    original_update_refname(update));\n+\t\t\t\t\t\t    ref_update_original_update_refname(update));\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 108f4ec419..617b93a6c8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -749,4 +749,9 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Return the refname under which update was originally requested.\n+ */\n+const char *ref_update_original_update_refname(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex f48ab5ad22..0988f59ed4 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -622,16 +622,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \treturn ret;\n }\n \n-/*\n- * Return the refname under which update was originally requested.\n- */\n-static const char *original_update_refname(struct ref_update *update)\n-{\n-\twhile (update->parent_update)\n-\t\tupdate = update->parent_update;\n-\treturn update->refname;\n-}\n-\n struct reftable_transaction_update {\n \tstruct ref_update *update;\n \tstruct object_id current_oid;\n@@ -910,7 +900,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t/* The reference does not exist, but we expected it to. */\n \t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t    \"unable to resolve reference '%s'\"),\n-\t\t\t\t    original_update_refname(u), u->refname);\n+\t\t\t\t    ref_update_original_update_refname(u), u->refname);\n \t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n@@ -982,17 +972,17 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"reference already exists\"),\n-\t\t\t\t\t    original_update_refname(u));\n+\t\t\t\t\t\t   \"reference already exists\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u));\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"reference is missing but expected %s\"),\n-\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t   \"reference is missing but expected %s\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n \t\t\telse\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"is at %s but expected %s\"),\n-\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t   \"is at %s but expected %s\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n \t\t\tret = -1;\n-- \n2.43.GIT\n\n"},{"id":"494244","messageId":"20240507060035.28602-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 5/8] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:32Z","receivedAt":"2024-05-07T06:00:50Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe reference backends currently support transactional reference\nupdates. While this is exposed to users via 'git-update-ref' and its\n'--stdin' mode, it is also used internally within various commands.\n\nHowever, we do not support transactional updates of symrefs. This commit\nadds support for symrefs in both the 'files' and the 'reftable' backend.\n\nHere, we add and use `ref_update_has_null_new_value()`, a helper\nfunction which is used to check if there is a new_value in a reference\nupdate. The new value could either be a symref target `new_target` or a\nOID `new_oid`.\n\nWe also add another common function `ref_update_check_old_target` which\nwill be used to check if the update's old_target corresponds to a\nreference's current target.\n\nNow transactional updates (verify, create, delete, update) can be used\nfor:\n- regular refs\n- symbolic refs\n- conversion of regular to symbolic refs and vice versa\n\nThis also allows us to expose this to users via new commands in\n'git-update-ref' in the future.\n\nNote that a dangling symref update does not record a new reflog entry,\nwhich is unchanged before and after this commit.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  36 ++++++++++++-\n refs/files-backend.c    | 113 ++++++++++++++++++++++++++++++++--------\n refs/refs-internal.h    |  16 ++++++\n refs/reftable-backend.c |  71 +++++++++++++++++++------\n 4 files changed, 196 insertions(+), 40 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d65e4cc41e..f3d6b3b8e3 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1257,6 +1257,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((char *)transaction->updates[i]->new_target);\n+\t\tfree((char *)transaction->updates[i]->old_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1287,10 +1289,13 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tupdate->new_target = xstrdup_or_null(new_target);\n+\tupdate->old_target = xstrdup_or_null(old_target);\n+\tif ((flags & REF_HAVE_NEW) && new_oid)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif ((flags & REF_HAVE_OLD) && old_oid)\n \t\toidcpy(&update->old_oid, old_oid);\n+\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -1333,6 +1338,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -2839,3 +2845,29 @@ const char *ref_update_original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+int ref_update_has_null_new_value(struct ref_update *update)\n+{\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\n+\n+int ref_update_check_old_target(const char *referent, struct ref_update *update,\n+\t\t\t\tstruct strbuf *err)\n+{\n+\tif (!update->old_target)\n+\t\tBUG(\"called without old_target set\");\n+\n+\tif (!strcmp(referent, update->old_target))\n+\t\treturn 0;\n+\n+\tif (!strcmp(referent, \"\"))\n+\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    ref_update_original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    ref_update_original_update_refname(update),\n+\t\t\t    referent, update->old_target);\n+\treturn -1;\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 64d2a50e97..1d4650b7cb 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2393,8 +2393,9 @@ static int split_symref_update(struct ref_update *update,\n \n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n-\t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_target ? NULL : &update->new_oid,\n+\t\t\tupdate->old_target ? NULL : &update->old_oid,\n+\t\t\tupdate->new_target, update->old_target, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2482,7 +2483,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\n@@ -2525,7 +2526,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t\t}\n+\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n+\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -2546,7 +2554,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t} else {\n \t\tstruct ref_update *parent_update;\n \n-\t\tif (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t/*\n+\t\t * Even if the ref is a regular ref, if `old_target` is set, we\n+\t\t * check the referent value. Ideally `old_target` should only\n+\t\t * be set for symrefs, but we're strict about its usage.\n+\t\t */\n+\t\tif (update->old_target) {\n+\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n \t\t}\n@@ -2564,9 +2582,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n+\t\tif (create_symref_lock(refs, lock, update->refname,\n+\t\t\t\t       update->new_target, err)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\n@@ -2829,6 +2866,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \treturn ret;\n }\n \n+static int parse_and_write_reflog(struct files_ref_store *refs,\n+\t\t\t\t  struct ref_update *update,\n+\t\t\t\t  struct ref_lock *lock,\n+\t\t\t\t  struct strbuf *err)\n+{\n+\tif (update->new_target) {\n+\t\t/*\n+\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t * that the correct value is added to the reflog.\n+\t\t */\n+\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t     RESOLVE_REF_READING,\n+\t\t\t\t\t     &update->new_oid, NULL)) {\n+\t\t\t/*\n+\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t * zero oid updates however.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,\n+\t\t\t\t&update->new_oid, update->msg, update->flags, err)) {\n+\t\tchar *old_msg = strbuf_detach(err, NULL);\n+\n+\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n+\t\t\t    lock->ref_name, old_msg);\n+\t\tfree(old_msg);\n+\t\tunlock_ref(lock);\n+\t\tupdate->backend_data = NULL;\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\t    struct ref_transaction *transaction,\n \t\t\t\t    struct strbuf *err)\n@@ -2859,23 +2933,20 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n-\t\t\tif (files_log_ref_write(refs,\n-\t\t\t\t\t\tlock->ref_name,\n-\t\t\t\t\t\t&lock->old_oid,\n-\t\t\t\t\t\t&update->new_oid,\n-\t\t\t\t\t\tupdate->msg, update->flags,\n-\t\t\t\t\t\terr)) {\n-\t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n-\n-\t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n-\t\t\t\t\t    lock->ref_name, old_msg);\n-\t\t\t\tfree(old_msg);\n-\t\t\t\tunlock_ref(lock);\n-\t\t\t\tupdate->backend_data = NULL;\n+\t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next update. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 617b93a6c8..819157256e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -754,4 +754,20 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor\n  */\n const char *ref_update_original_update_refname(struct ref_update *update);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_has_null_new_value(struct ref_update *update);\n+\n+/*\n+ * Check whether the old_target values stored in update are consistent\n+ * with the referent, which is the symbolic reference's current value.\n+ * If everything is OK, return 0; otherwise, write an error message to\n+ * err and return -1.\n+ */\n+int ref_update_check_old_target(const char *referent, struct ref_update *update,\n+\t\t\t\tstruct strbuf *err);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 0988f59ed4..4817dc2f0b 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -887,7 +887,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -938,8 +938,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t * intertwined with the locking in files-backend.c.\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n-\t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\ttransaction, referent.buf, new_flags,\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\tu->old_target, u->msg);\n+\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -969,7 +971,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif (u->old_target) {\n+\t\t\tif (ref_update_check_old_target(referent.buf, u, err)) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n@@ -1080,7 +1087,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif ((u->flags & REF_HAVE_NEW) &&\n+\t\t    !(u->type & REF_ISSYMREF) &&\n+\t\t    ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1121,24 +1130,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t   (u->flags & REF_FORCE_CREATE_REFLOG ||\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n+\t\t\tint create_reflog = 1;\n+\n+\t\t\tif (u->new_target) {\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL)) {\n+\t\t\t\t\t/*\n+\t\t\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t\t\t * zero oid updates however.\n+\t\t\t\t\t */\n+\t\t\t\t\tcreate_reflog = 0;\n+\t\t\t\t}\n+\t\t\t}\n \n-\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n-\t\t\tlog = &logs[logs_nr++];\n-\t\t\tmemset(log, 0, sizeof(*log));\n-\n-\t\t\tfill_reftable_log_record(log, &committer_ident);\n-\t\t\tlog->update_index = ts;\n-\t\t\tlog->refname = xstrdup(u->refname);\n-\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tlog->value.update.message =\n-\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\tif (create_reflog) {\n+\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n+\t\t\t\tlog = &logs[logs_nr++];\n+\t\t\t\tmemset(log, 0, sizeof(*log));\n+\n+\t\t\t\tfill_reftable_log_record(log, &committer_ident);\n+\t\t\t\tlog->update_index = ts;\n+\t\t\t\tlog->refname = xstrdup(u->refname);\n+\t\t\t\tmemcpy(log->value.update.new_hash,\n+\t\t\t\t       u->new_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(log->value.update.old_hash,\n+\t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tlog->value.update.message =\n+\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\t}\n \t\t}\n \n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\n-- \n2.43.GIT\n\n"},{"id":"494245","messageId":"20240507060035.28602-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 6/8] refs: use transaction in `refs_create_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:33Z","receivedAt":"2024-05-07T06:00:51Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function updates a symref to a given new\ntarget. To do this, it uses a ref-backend specific function\n`create_symref()`.\n\nIn the previous commits, we introduced symref support in transactions.\nThis means we can now use transactions to perform symref updates and\ndon't have to resort to `create_symref()`. Doing this allows us to\nremove and cleanup `create_symref()`, which we will do in the following\ncommit.\n\nModify the expected error message for a test in\n't/t0610-reftable-basics.sh', since the error is now thrown from\n'refs.c'. This is because in transactional updates, F/D conflicts are\ncaught before we're in the reference backend.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                           | 24 +++++++++++++++++-------\n t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++\n 2 files changed, 40 insertions(+), 7 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex f3d6b3b8e3..7a693dbcfb 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2305,14 +2305,24 @@ int refs_create_symref(struct ref_store *refs,\n \t\t       const char *refs_heads_master,\n \t\t       const char *logmsg)\n {\n-\tchar *msg;\n-\tint retval;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret = 0;\n \n-\tmsg = normalize_reflog_message(logmsg);\n-\tretval = refs->be->create_symref(refs, ref_target, refs_heads_master,\n-\t\t\t\t\t msg);\n-\tfree(msg);\n-\treturn retval;\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n+\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t\t\t\t   logmsg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n+\t\tret = error(\"%s\", err.buf);\n+\t}\n+\n+\tstrbuf_release(&err);\n+\tif (transaction)\n+\t\tref_transaction_free(transaction);\n+\n+\treturn ret;\n }\n \n int create_symref(const char *ref_target, const char *refs_heads_master,\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..067fd57290 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+test_expect_success 'hook captures git-symbolic-ref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\n+\tcat >expect <<-EOF &&\n+\tprepared\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tcommitted\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"494246","messageId":"20240507060035.28602-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:34Z","receivedAt":"2024-05-07T06:00:52Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function is used to update/create a symref.\nBut it doesn't check the old target of the symref, if existing. It force\nupdates the symref. In this regard, the name `refs_create_symref()` is a\nbit misleading. So let's rename it to `refs_update_symref()`. This is\nakin to how 'git-update-ref(1)' also allows us to create apart from\nupdate.\n\nWhile we're here, rename the arguments in the function to clarify what\nthey actually signify and reduce confusion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/branch.c          |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 12 +++++-------\n refs.h                    |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 5 files changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex dd3e3a7dc0..4491f7a20c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_create_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7c6c72536b..480202c517 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_create_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 7a693dbcfb..85f53a2cbe 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2300,10 +2300,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)\n \treturn peel_object(base, peeled) ? -1 : 0;\n }\n \n-int refs_create_symref(struct ref_store *refs,\n-\t\t       const char *ref_target,\n-\t\t       const char *refs_heads_master,\n-\t\t       const char *logmsg)\n+int refs_update_symref(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2311,8 +2309,8 @@ int refs_create_symref(struct ref_store *refs,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n-\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n@@ -2328,7 +2326,7 @@ int refs_create_symref(struct ref_store *refs,\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n-\treturn refs_create_symref(get_main_ref_store(the_repository), ref_target,\n+\treturn refs_update_symref(get_main_ref_store(the_repository), ref_target,\n \t\t\t\t  refs_heads_master, logmsg);\n }\n \ndiff --git a/refs.h b/refs.h\nindex 9abbfd35a2..7c69e8cc0d 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int copy_existing_ref(const char *oldref, const char *newref,\n \t\t\tconst char *logmsg);\n \n-int refs_create_symref(struct ref_store *refs, const char *refname,\n+int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n int create_symref(const char *refname, const char *target, const char *logmsg);\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 82bbf6e2e6..4651e4ced7 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_create_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.43.GIT\n\n"},{"id":"494247","messageId":"20240507060035.28602-9-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v7 8/8] refs: remove `create_symref` and associated dead code","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T06:00:35Z","receivedAt":"2024-05-07T06:00:53Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we converted `refs_create_symref()` to utilize\ntransactions to perform symref updates. Earlier `refs_create_symref()`\nused `create_symref()` to do the same.\n\nWe can now remove `create_symref()` and any code associated with it\nwhich is no longer used. We remove `create_symref()` code from all the\nreference backends and also remove it entirely from the `ref_storage_be`\nstruct.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/debug.c            |  13 -----\n refs/files-backend.c    |  67 -------------------------\n refs/packed-backend.c   |   1 -\n refs/refs-internal.h    |   5 --\n refs/reftable-backend.c | 105 ----------------------------------------\n 5 files changed, 191 deletions(-)\n\ndiff --git a/refs/debug.c b/refs/debug.c\nindex c7531b17f0..8be316bb67 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o\n \treturn res;\n }\n \n-static int debug_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *ref_name, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;\n-\tint res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,\n-\t\t\t\t\t\t logmsg);\n-\ttrace_printf_key(&trace_refs, \"create_symref: %s -> %s \\\"%s\\\": %d\\n\", ref_name,\n-\t\ttarget, logmsg, res);\n-\treturn res;\n-}\n-\n static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,\n \t\t\t    const char *newref, const char *logmsg)\n {\n@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {\n \t.initial_transaction_commit = debug_initial_transaction_commit,\n \n \t.pack_refs = debug_pack_refs,\n-\t.create_symref = debug_create_symref,\n \t.rename_ref = debug_rename_ref,\n \t.copy_ref = debug_copy_ref,\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 1d4650b7cb..dc0490f0db 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1902,23 +1902,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)\n \treturn ret;\n }\n \n-static void update_symref_reflog(struct files_ref_store *refs,\n-\t\t\t\t struct ref_lock *lock, const char *refname,\n-\t\t\t\t const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct object_id new_oid;\n-\n-\tif (logmsg &&\n-\t    refs_resolve_ref_unsafe(&refs->base, target,\n-\t\t\t\t    RESOLVE_REF_READING, &new_oid, NULL) &&\n-\t    files_log_ref_write(refs, refname, &lock->old_oid,\n-\t\t\t\t&new_oid, logmsg, 0, &err)) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t}\n-}\n-\n static int create_symref_lock(struct files_ref_store *refs,\n \t\t\t      struct ref_lock *lock, const char *refname,\n \t\t\t      const char *target, struct strbuf *err)\n@@ -1938,55 +1921,6 @@ static int create_symref_lock(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int create_and_commit_symref(struct files_ref_store *refs,\n-\t\t\t\t    struct ref_lock *lock, const char *refname,\n-\t\t\t\t    const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret;\n-\n-\tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\t\treturn 0;\n-\t}\n-\n-\tret = create_symref_lock(refs, lock, refname, target, &err);\n-\tif (!ret) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\n-\t\tif (commit_ref(lock) < 0)\n-\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t\t     strerror(errno));\n-\t} else {\n-\t\treturn error(\"%s\", err.buf);\n-\t}\n-\n-\treturn ret;\n-}\n-\n-static int files_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *refname, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct files_ref_store *refs =\n-\t\tfiles_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_lock *lock;\n-\tint ret;\n-\n-\tlock = lock_ref_oid_basic(refs, refname, &err);\n-\tif (!lock) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t\treturn -1;\n-\t}\n-\n-\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n-\n-\tunlock_ref(lock);\n-\treturn ret;\n-}\n-\n static int files_reflog_exists(struct ref_store *ref_store,\n \t\t\t       const char *refname)\n {\n@@ -3373,7 +3307,6 @@ struct ref_storage_be refs_be_files = {\n \t.initial_transaction_commit = files_initial_transaction_commit,\n \n \t.pack_refs = files_pack_refs,\n-\t.create_symref = files_create_symref,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4e826c05ff..a937e7dbfc 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {\n \t.initial_transaction_commit = packed_initial_transaction_commit,\n \n \t.pack_refs = packed_pack_refs,\n-\t.create_symref = NULL,\n \t.rename_ref = NULL,\n \t.copy_ref = NULL,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 819157256e..53a6c5d842 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n-typedef int create_symref_fn(struct ref_store *ref_store,\n-\t\t\t     const char *ref_target,\n-\t\t\t     const char *refs_heads_master,\n-\t\t\t     const char *logmsg);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -690,7 +686,6 @@ struct ref_storage_be {\n \tref_transaction_commit_fn *initial_transaction_commit;\n \n \tpack_refs_fn *pack_refs;\n-\tcreate_symref_fn *create_symref;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 4817dc2f0b..d8e06767a4 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1307,110 +1307,6 @@ struct write_create_symref_arg {\n \tconst char *logmsg;\n };\n \n-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n-{\n-\tstruct write_create_symref_arg *create = cb_data;\n-\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)create->refname,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)create->target,\n-\t\t.update_index = ts,\n-\t};\n-\tstruct ident_split committer_ident = {0};\n-\tstruct reftable_log_record log = {0};\n-\tstruct object_id new_oid;\n-\tstruct object_id old_oid;\n-\tconst char *committer_info;\n-\tint ret;\n-\n-\treftable_writer_set_limits(writer, ts, ts);\n-\n-\tret = refs_verify_refname_available(&create->refs->base, create->refname,\n-\t\t\t\t\t    NULL, NULL, create->err);\n-\tif (ret < 0)\n-\t\treturn ret;\n-\n-\tret = reftable_writer_add_ref(writer, &ref);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Note that it is important to try and resolve the reference before we\n-\t * write the log entry. This is because `should_write_log()` will munge\n-\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n-\t * repository because it would be written into the config. As HEAD will\n-\t * not resolve for new repositories this ordering will ensure that this\n-\t * never happens.\n-\t */\n-\tif (!create->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n-\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&create->refs->base, create->refname))\n-\t\treturn 0;\n-\n-\tcommitter_info = git_committer_info(0);\n-\tif (split_ident_line(&committer_ident, committer_info, strlen(committer_info)))\n-\t\tBUG(\"failed splitting committer info\");\n-\n-\tfill_reftable_log_record(&log, &committer_ident);\n-\tlog.refname = xstrdup(create->refname);\n-\tlog.update_index = ts;\n-\tlog.value.update.message = xstrndup(create->logmsg,\n-\t\t\t\t\t    create->refs->write_options.block_size / 2);\n-\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n-\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n-\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n-\n-\tret = reftable_writer_add_log(writer, &log);\n-\treftable_log_record_release(&log);\n-\treturn ret;\n-}\n-\n-static int reftable_be_create_symref(struct ref_store *ref_store,\n-\t\t\t\t     const char *refname,\n-\t\t\t\t     const char *target,\n-\t\t\t\t     const char *logmsg)\n-{\n-\tstruct reftable_ref_store *refs =\n-\t\treftable_be_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct reftable_stack *stack = stack_for(refs, refname, &refname);\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct write_create_symref_arg arg = {\n-\t\t.refs = refs,\n-\t\t.stack = stack,\n-\t\t.refname = refname,\n-\t\t.target = target,\n-\t\t.logmsg = logmsg,\n-\t\t.err = &err,\n-\t};\n-\tint ret;\n-\n-\tret = refs->err;\n-\tif (ret < 0)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_reload(stack);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_add(stack, &write_create_symref_table, &arg);\n-\n-done:\n-\tassert(ret != REFTABLE_API_ERROR);\n-\tif (ret) {\n-\t\tif (err.len)\n-\t\t\terror(\"%s\", err.buf);\n-\t\telse\n-\t\t\terror(\"unable to write symref for %s: %s\", refname,\n-\t\t\t      reftable_error_str(ret));\n-\t}\n-\n-\tstrbuf_release(&err);\n-\treturn ret;\n-}\n-\n struct write_copy_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2325,7 +2221,6 @@ struct ref_storage_be refs_be_reftable = {\n \t.initial_transaction_commit = reftable_be_initial_transaction_commit,\n \n \t.pack_refs = reftable_be_pack_refs,\n-\t.create_symref = reftable_be_create_symref,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.43.GIT\n\n"},{"id":"494253","messageId":"xmqq5xvqt9ym.fsf@gitster.g","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"Re: [PATCH v7 0/8] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-07T06:25:21Z","receivedAt":"2024-05-07T06:25:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Junio, I've rebased my patches on top of, not sure if there is something else I\n> missed here:\n> - ps/ci-python-2-deprecation (2a686e00d2)\n> - ps/reftable-write-options (f5545b173b)\n> - ps/pseudo-ref-terminology (ad302a68fd)\n\nThis is a somewhat puzzling set of topics.  You are not touching\nci/lib.sh at all so I do not see the point of including the\nci-python one, for example.\n\n"},{"id":"494255","messageId":"xmqq1q6et9o6.fsf@gitster.g","threadId":"61235","inReplyTo":"xmqq5xvqt9ym.fsf@gitster.g","subject":"Re: [PATCH v7 0/8] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-07T06:31:37Z","receivedAt":"2024-05-07T06:31:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> Junio, I've rebased my patches on top of, not sure if there is something else I\n>> missed here:\n>> - ps/ci-python-2-deprecation (2a686e00d2)\n>> - ps/reftable-write-options (f5545b173b)\n>> - ps/pseudo-ref-terminology (ad302a68fd)\n>\n> This is a somewhat puzzling set of topics.  You are not touching\n> ci/lib.sh at all so I do not see the point of including the\n> ci-python one, for example.\n\nOh, it is even worse.  2a686e00d2 is not the ci-python thing at all.\nIt is a random point between 'master' and 'seen' that happens to be\na merge of that topic, so you basically based your series on top of\nrandom collection of many topics that are not even in 'next'.  More\nthan 30 of them.\n\n2a686e00d2 Merge branch 'ps/ci-python-2-deprecation' into seen\ncfae85760b Merge branch 'ow/refspec-glossary-update' into seen\n0ac6476973 Merge branch 'ps/ci-enable-minimal-fuzzers-at-gitlab' into seen\n2e6fe8d39b Merge branch 'jp/tag-trailer' into seen\n2954339080 Merge branch 'rs/external-diff-with-exit-code' into seen\nb16fe0b6f5 Merge branch 'it/refs-name-conflict' into seen\nbdec3a9a17 Merge branch 'ps/refs-without-the-repository' into seen\nb30d6b985f Merge branch 'kn/ref-transaction-symref' into seen\n33c1562652 Merge branch 'ds/doc-config-reflow' into seen\n053736edd8 Merge branch 'ie/config-includeif-hostname' into seen\ndb453e610c Merge branch 'cw/git-std-lib' into seen\n6858402556 Merge branch 'bk/complete-dirname-for-am-and-format-patch' into seen\n2ec214595a Merge branch 'bk/complete-send-email' into seen\n0810be3417 Merge branch 'jc/rerere-cleanup' into seen\na3d2847041 Merge branch 'ds/send-email-per-message-block' into seen\n919b280123 Merge branch 'tb/attr-limits' into jch\n27f675fb6d Merge branch 'ps/config-subcommands' into jch\nf5545b173b Merge branch 'ps/reftable-write-options' into jch\nc86de5db47 Merge branch 'ew/khash-to-khashl' into jch\nef2bdd9348 Merge branch 'jc/test-workaround-broken-mv' into jch\nfb794ba19c Merge branch 'jc/rev-parse-fatal-doc' into jch\n96aa517d85 Merge branch 'jt/port-ci-whitespace-check-to-gitlab' into jch\n941a03e7c9 Merge branch 'jl/git-no-advice' into jch\n56c4ee21a1 Merge branch 'ma/win32-unix-domain-socket' into jch\n9d2ea80011 Merge branch 'tb/pseudo-merge-reachability-bitmap' into jch\nad302a68fd Merge branch 'ps/pseudo-ref-terminology' into jch\n2754234e89 Merge branch 'ds/scalar-reconfigure-all-fix' into jch\nc0340717db Merge branch 'pw/rebase-i-error-message' into jch\n89b67e5799 Merge branch 'tb/path-filter-fix' into jch\nf40942e3c0 Merge branch 'la/hide-trailer-info' into jch\n7b725fb75c Merge branch 'js/cmake-with-test-tool' into jch\n1ddb41fd10 Merge branch 'js/unit-test-suite-runner' into jch\ne95b4f5420 Merge branch 'jc/no-default-attr-tree-in-bare' into jch\ned26589653 ### match next\n\nI'll not look at this round, as I know you know better than building\na series deliberately on top of these random topics and this was an\naccidental mistake you'd rather want to correct before asking folks\nto take a look at the resulting series.\n\nThanks.\n"},{"id":"494267","messageId":"20240507125859.132116-1-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507060035.28602-1-knayak@gitlab.com","subject":"[PATCH v8 0/8] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:51Z","receivedAt":"2024-05-07T12:59:08Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe patch series takes over from the existing patch series, wherein we\nintroduced symref-* commands to git-update-ref. Since there was a ton of\ndiscussions on the UX of the patch series and its application, I thought it\nwould be best to shorten the series and split it into multiple smaller series.\n\nThis series adds transactional support for symrefs in the reference db. Then\nwe switch refs_create_symref() to start using transactions for symref updates.\nThis allows us to deprecate the create_symref code in the ref_storage_be\ninterface and remove all associated code which is no longer used.\n\nThe split was primarily done so we can merge the non-user facing parts of the\nprevious series. While pertaining the user facing components into another set\nof patches wherein deeper discussion on the UX can be held without worrying\nabout the internal implementation. Also by using this new functionality in a\npre-existing command, we can leverage the existing tests to catch any\ninconsistencies. One of which was how 'git-symbolic-ref' doesn't add reflog for\ndangling symrefs, which I've modified my patch to do the same.\n\nWe also modify the reference transaction hook to support symrefs. For any symref\nupdate the reference transaction hook will output the value with a 'ref:' prefix.\n\nPrevious versions:\nV1: https://lore.kernel.org/git/20240330224623.579457-1-knayak@gitlab.com\nV2: https://lore.kernel.org/git/20240412095908.1134387-1-knayak@gitlab.com\nV3: https://lore.kernel.org/git/20240423212818.574123-1-knayak@gitlab.com\nV4: https://lore.kernel.org/r/20240426152449.228860-1-knayak@gitlab.com\nV5: https://lore.kernel.org/r/20240501202229.2695774-1-knayak@gitlab.com\nV6: https://lore.kernel.org/r/20240503124115.252413-1-knayak@gitlab.com\nV7: https://lore.kernel.org/r/20240507060035.28602-1-knayak@gitlab.com\n\nThanks for all the reviews!\n\nChanges since v7:\n* I had rebased v7 on next. I've rebased v8 on master. That's the only difference\nbetween the two versions.\n\nJunio, this might cause conflicts when merging, I think you resolved them for\nv6 and hope its the same now. Let me know if I can help otherwise somehow. \n\nChanges since v6:\n* Made the check for old/new oid & target more stricter by removing\nthe exception for null oid's.\n* Extracted `ref_update_check_old_target` and `original_update_refname`\nto refs.c\n* ^This allowed us to generalize the error messages for non-matching\nold_target values between files and reftable backend.\n* Better line wrapping in code.\n* Fixed some grammar in commit messages.\n\nRange diff against v6:\n\n1:  a354190905 ! 1:  defc1b3521 refs: accept symref values in `ref_transaction_update()`\n    @@ refs.c: struct ref_update *ref_transaction_add_update(\n      \tif (transaction->state != REF_TRANSACTION_OPEN)\n      \t\tBUG(\"update called for transaction that is not open\");\n      \n    -+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n    ++\tif (old_oid && old_target)\n     +\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n    -+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n    ++\tif (new_oid && new_target)\n     +\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n     +\n      \tFLEX_ALLOC_STR(update, refname, refname);\n2:  0d9c5b9804 = 2:  54bb78a27c files-backend: extract out `create_symref_lock()`\n3:  e0219ffd31 = 3:  c16b7c5da0 refs: support symrefs in 'reference-transaction' hook\n-:  ---------- > 4:  2d268f12cc refs: move `original_update_refname` to 'refs.c'\n4:  b22c59c722 ! 5:  7db3a2245f refs: add support for transactional symref updates\n    @@ Commit message\n         updates. While this is exposed to users via 'git-update-ref' and its\n         '--stdin' mode, it is also used internally within various commands.\n     \n    -    However, we never supported transactional updates of symrefs. Let's add\n    -    support for symrefs in both the 'files' and the 'reftable' backend.\n    +    However, we do not support transactional updates of symrefs. This commit\n    +    adds support for symrefs in both the 'files' and the 'reftable' backend.\n     \n         Here, we add and use `ref_update_has_null_new_value()`, a helper\n         function which is used to check if there is a new_value in a reference\n         update. The new value could either be a symref target `new_target` or a\n         OID `new_oid`.\n     \n    -    With this, now transactional updates (verify, create, delete, update)\n    -    can be used for:\n    +    We also add another common function `ref_update_check_old_target` which\n    +    will be used to check if the update's old_target corresponds to a\n    +    reference's current target.\n    +\n    +    Now transactional updates (verify, create, delete, update) can be used\n    +    for:\n         - regular refs\n         - symbolic refs\n         - conversion of regular to symbolic refs and vice versa\n    @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n      \n      \tref_transaction_add_update(transaction, refname, flags,\n      \t\t\t\t   new_oid, old_oid, new_target,\n    -@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n    - {\n    - \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n    +@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)\n    + \treturn update->refname;\n      }\n    -+\n    + \n     +int ref_update_has_null_new_value(struct ref_update *update)\n     +{\n     +\treturn !update->new_target && is_null_oid(&update->new_oid);\n     +}\n    -\n    - ## refs/files-backend.c ##\n    -@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)\n    - \treturn update->refname;\n    - }\n    - \n    -+/*\n    -+ * Check whether the old_target values stored in update are consistent\n    -+ * with current_target, which is the symbolic reference's current value.\n    -+ * If everything is OK, return 0; otherwise, write an error message to\n    -+ * err and return -1.\n    -+ */\n    -+static int check_old_target(struct ref_update *update,\n    -+\t\t\t    const char *current_target,\n    -+\t\t\t    struct strbuf *err)\n    ++\n    ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n    ++\t\t\t\tstruct strbuf *err)\n     +{\n     +\tif (!update->old_target)\n     +\t\tBUG(\"called without old_target set\");\n     +\n    -+\tif (!strcmp(update->old_target, current_target))\n    ++\tif (!strcmp(referent, update->old_target))\n     +\t\treturn 0;\n     +\n    -+\tif (!strcmp(current_target, \"\"))\n    -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\tif (!strcmp(referent, \"\"))\n    ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t    \"reference is missing but expected %s\",\n    -+\t\t\t    original_update_refname(update),\n    ++\t\t\t    ref_update_original_update_refname(update),\n     +\t\t\t    update->old_target);\n     +\telse\n    -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n    ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n     +\t\t\t    \"is at %s but expected %s\",\n    -+\t\t\t    original_update_refname(update),\n    -+\t\t\t    current_target, update->old_target);\n    -+\n    ++\t\t\t    ref_update_original_update_refname(update),\n    ++\t\t\t    referent, update->old_target);\n     +\treturn -1;\n     +}\n    -+\n    - /*\n    -  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n    -  * are consistent with oid, which is the reference's current value. If\n    +\n    + ## refs/files-backend.c ##\n    +@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,\n    + \n    + \tnew_update = ref_transaction_add_update(\n    + \t\t\ttransaction, referent, new_flags,\n    +-\t\t\t&update->new_oid, &update->old_oid,\n    +-\t\t\tNULL, NULL, update->msg);\n    ++\t\t\tupdate->new_target ? NULL : &update->new_oid,\n    ++\t\t\tupdate->old_target ? NULL : &update->old_oid,\n    ++\t\t\tupdate->new_target, update->old_target, update->msg);\n    + \n    + \tnew_update->parent_update = update;\n    + \n     @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,\n      \n      \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     +\t\t\t}\n     +\n     +\t\t\tif (update->old_target) {\n    -+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n    ++\t\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n     +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\t\t\tgoto out;\n     +\t\t\t\t}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     +\t\t * be set for symrefs, but we're strict about its usage.\n     +\t\t */\n     +\t\tif (update->old_target) {\n    -+\t\t\tif (check_old_target(update, referent.buf, err)) {\n    ++\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n     +\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\t\tgoto out;\n     +\t\t\t}\n    @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n     -\t    !(update->flags & REF_DELETING) &&\n     -\t    !(update->flags & REF_LOG_ONLY)) {\n     +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n    -+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n    ++\t\tif (create_symref_lock(refs, lock, update->refname,\n    ++\t\t\t\t       update->new_target, err)) {\n     +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n     +\t\t\tgoto out;\n     +\t\t}\n    @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n      \t\t\tif (commit_ref(lock)) {\n     \n      ## refs/refs-internal.h ##\n    -@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n    +@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor\n       */\n    - struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n    + const char *ref_update_original_update_refname(struct ref_update *update);\n      \n     +/*\n     + * Helper function to check if the new value is null, this\n    @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re\n     + * ref or a symbolic ref.\n     + */\n     +int ref_update_has_null_new_value(struct ref_update *update);\n    ++\n    ++/*\n    ++ * Check whether the old_target values stored in update are consistent\n    ++ * with the referent, which is the symbolic reference's current value.\n    ++ * If everything is OK, return 0; otherwise, write an error message to\n    ++ * err and return -1.\n    ++ */\n    ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n    ++\t\t\t\tstruct strbuf *err);\n     +\n      #endif /* REFS_REFS_INTERNAL_H */\n     \n    @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n     +\t\tif (u->old_target) {\n    -+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n    -+\t\t\t\tif (!strcmp(referent.buf, \"\"))\n    -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n    -+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n    -+\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    u->old_target);\n    -+\t\t\t\telse\n    -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n    -+\t\t\t\t\t\t    \"is at %s but expected %s\",\n    -+\t\t\t\t\t\t    original_update_refname(u),\n    -+\t\t\t\t\t\t    referent.buf, u->old_target);\n    ++\t\t\tif (ref_update_check_old_target(referent.buf, u, err)) {\n     +\t\t\t\tret = -1;\n     +\t\t\t\tgoto done;\n     +\t\t\t}\n     +\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n      \t\t\tif (is_null_oid(&u->old_oid))\n      \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n    - \t\t\t\t\t    \"reference already exists\"),\n    + \t\t\t\t\t\t   \"reference already exists\"),\n     @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n      \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n      \t\t *   the given ref.\n      \t\t */\n     -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n    -+\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n    ++\t\tif ((u->flags & REF_HAVE_NEW) &&\n    ++\t\t    !(u->type & REF_ISSYMREF) &&\n    ++\t\t    ref_update_has_null_new_value(u)) {\n      \t\t\tstruct reftable_log_record log = {0};\n      \t\t\tstruct reftable_iterator it = {0};\n      \n    @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n     +\t\t\t\tfill_reftable_log_record(log);\n     +\t\t\t\tlog->update_index = ts;\n     +\t\t\t\tlog->refname = xstrdup(u->refname);\n    -+\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n    -+\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n    ++\t\t\t\tmemcpy(log->value.update.new_hash,\n    ++\t\t\t\t       u->new_oid.hash, GIT_MAX_RAWSZ);\n    ++\t\t\t\tmemcpy(log->value.update.old_hash,\n    ++\t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n     +\t\t\t\tlog->value.update.message =\n     +\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n     +\t\t\t}\n5:  636bf5ce98 = 6:  354ebbe17f refs: use transaction in `refs_create_symref()`\n6:  07fb23374f = 7:  c8a23b3454 refs: rename `refs_create_symref()` to `refs_update_symref()`\n7:  5c05813bcc = 8:  fa1b8f445b refs: remove `create_symref` and associated dead code\n\n\nKarthik Nayak (8):\n  refs: accept symref values in `ref_transaction_update()`\n  files-backend: extract out `create_symref_lock()`\n  refs: support symrefs in 'reference-transaction' hook\n  refs: move `original_update_refname` to 'refs.c'\n  refs: add support for transactional symref updates\n  refs: use transaction in `refs_create_symref()`\n  refs: rename `refs_create_symref()` to `refs_update_symref()`\n  refs: remove `create_symref` and associated dead code\n\n Documentation/githooks.txt       |  14 ++-\n branch.c                         |   2 +-\n builtin/branch.c                 |   2 +-\n builtin/fast-import.c            |   5 +-\n builtin/fetch.c                  |   2 +-\n builtin/receive-pack.c           |   1 +\n builtin/replace.c                |   2 +-\n builtin/tag.c                    |   1 +\n builtin/update-ref.c             |   1 +\n builtin/worktree.c               |   2 +-\n refs.c                           | 119 ++++++++++++++----\n refs.h                           |  20 ++-\n refs/debug.c                     |  13 --\n refs/files-backend.c             | 208 +++++++++++++++++--------------\n refs/packed-backend.c            |   1 -\n refs/refs-internal.h             |  40 +++++-\n refs/reftable-backend.c          | 183 +++++++++------------------\n sequencer.c                      |   9 +-\n t/helper/test-ref-store.c        |   2 +-\n t/t0610-reftable-basics.sh       |   2 +-\n t/t1416-ref-transaction-hooks.sh |  23 ++++\n walker.c                         |   2 +-\n 22 files changed, 374 insertions(+), 280 deletions(-)\n\n-- \n2.43.GIT\n\n"},{"id":"494268","messageId":"20240507125859.132116-2-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 1/8] refs: accept symref values in `ref_transaction_update()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:52Z","receivedAt":"2024-05-07T12:59:10Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `ref_transaction_update()` obtains ref information and\nflags to create a `ref_update` and add them to the transaction at hand.\n\nTo extend symref support in transactions, we need to also accept the\nold and new ref targets and process it. This commit adds the required\nparameters to the function and modifies all call sites.\n\nThe two parameters added are `new_target` and `old_target`. The\n`new_target` is used to denote what the reference should point to when\nthe transaction is applied. Some functions allow this parameter to be\nNULL, meaning that the reference is not changed.\n\nThe `old_target` denotes the value the reference must have before the\nupdate. Some functions allow this parameter to be NULL, meaning that the\nold value of the reference is not checked.\n\nWe also update the internal function `ref_transaction_add_update()`\nsimilarly to take the two new parameters.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n branch.c                |  2 +-\n builtin/fast-import.c   |  5 +++--\n builtin/fetch.c         |  2 +-\n builtin/receive-pack.c  |  1 +\n builtin/replace.c       |  2 +-\n builtin/tag.c           |  1 +\n builtin/update-ref.c    |  1 +\n refs.c                  | 22 +++++++++++++++++-----\n refs.h                  | 18 +++++++++++++++++-\n refs/files-backend.c    | 12 ++++++------\n refs/refs-internal.h    | 14 ++++++++++++++\n refs/reftable-backend.c |  4 ++--\n sequencer.c             |  9 +++++----\n walker.c                |  2 +-\n 14 files changed, 71 insertions(+), 24 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex e4a738fc7b..48af4c3ceb 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -627,7 +627,7 @@ void create_branch(struct repository *r,\n \tif (!transaction ||\n \t\tref_transaction_update(transaction, ref.buf,\n \t\t\t\t\t&oid, forcing ? NULL : null_oid(),\n-\t\t\t\t\t0, msg, &err) ||\n+\t\t\t\t\tNULL, NULL, 0, msg, &err) ||\n \t\tref_transaction_commit(transaction, &err))\n \t\tdie(\"%s\", err.buf);\n \tref_transaction_free(transaction);\ndiff --git a/builtin/fast-import.c b/builtin/fast-import.c\nindex dc5a9d32dd..297dfb91a1 100644\n--- a/builtin/fast-import.c\n+++ b/builtin/fast-import.c\n@@ -1634,7 +1634,7 @@ static int update_branch(struct branch *b)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, b->name, &b->oid, &old_oid,\n-\t\t\t\t   0, msg, &err) ||\n+\t\t\t\t   NULL, NULL, 0, msg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n \t\terror(\"%s\", err.buf);\n@@ -1675,7 +1675,8 @@ static void dump_tags(void)\n \t\tstrbuf_addf(&ref_name, \"refs/tags/%s\", t->name);\n \n \t\tif (ref_transaction_update(transaction, ref_name.buf,\n-\t\t\t\t\t   &t->oid, NULL, 0, msg, &err)) {\n+\t\t\t\t\t   &t->oid, NULL, NULL, NULL,\n+\t\t\t\t\t   0, msg, &err)) {\n \t\t\tfailure |= error(\"%s\", err.buf);\n \t\t\tgoto cleanup;\n \t\t}\ndiff --git a/builtin/fetch.c b/builtin/fetch.c\nindex 5857d860db..66840b7c5b 100644\n--- a/builtin/fetch.c\n+++ b/builtin/fetch.c\n@@ -668,7 +668,7 @@ static int s_update_ref(const char *action,\n \n \tret = ref_transaction_update(transaction, ref->name, &ref->new_oid,\n \t\t\t\t     check_old ? &ref->old_oid : NULL,\n-\t\t\t\t     0, msg, &err);\n+\t\t\t\t     NULL, NULL, 0, msg, &err);\n \tif (ret) {\n \t\tret = STORE_REF_ERROR_OTHER;\n \t\tgoto out;\ndiff --git a/builtin/receive-pack.c b/builtin/receive-pack.c\nindex e8d7df14b6..b150ef39a8 100644\n--- a/builtin/receive-pack.c\n+++ b/builtin/receive-pack.c\n@@ -1595,6 +1595,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)\n \t\tif (ref_transaction_update(transaction,\n \t\t\t\t\t   namespaced_name,\n \t\t\t\t\t   new_oid, old_oid,\n+\t\t\t\t\t   NULL, NULL,\n \t\t\t\t\t   0, \"push\",\n \t\t\t\t\t   &err)) {\n \t\t\trp_error(\"%s\", err.buf);\ndiff --git a/builtin/replace.c b/builtin/replace.c\nindex da59600ad2..7690687b0e 100644\n--- a/builtin/replace.c\n+++ b/builtin/replace.c\n@@ -201,7 +201,7 @@ static int replace_object_oid(const char *object_ref,\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, repl, &prev,\n-\t\t\t\t   0, NULL, &err) ||\n+\t\t\t\t   NULL, NULL, 0, NULL, &err) ||\n \t    ref_transaction_commit(transaction, &err))\n \t\tres = error(\"%s\", err.buf);\n \ndiff --git a/builtin/tag.c b/builtin/tag.c\nindex 9a33cb50b4..40a65fdebc 100644\n--- a/builtin/tag.c\n+++ b/builtin/tag.c\n@@ -660,6 +660,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix)\n \ttransaction = ref_transaction_begin(&err);\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, ref.buf, &object, &prev,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,\n \t\t\t\t   reflog_msg.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex e46afbc46d..21fdbf6ac8 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -204,6 +204,7 @@ static void parse_cmd_update(struct ref_transaction *transaction,\n \n \tif (ref_transaction_update(transaction, refname,\n \t\t\t\t   &new_oid, have_old ? &old_oid : NULL,\n+\t\t\t\t   NULL, NULL,\n \t\t\t\t   update_flags | create_reflog_flag,\n \t\t\t\t   msg, &err))\n \t\tdie(\"%s\", err.buf);\ndiff --git a/refs.c b/refs.c\nindex 55d2e0b2cb..e7b7c48d92 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1228,6 +1228,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg)\n {\n \tstruct ref_update *update;\n@@ -1235,6 +1236,11 @@ struct ref_update *ref_transaction_add_update(\n \tif (transaction->state != REF_TRANSACTION_OPEN)\n \t\tBUG(\"update called for transaction that is not open\");\n \n+\tif (old_oid && old_target)\n+\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n+\tif (new_oid && new_target)\n+\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n+\n \tFLEX_ALLOC_STR(update, refname, refname);\n \tALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);\n \ttransaction->updates[transaction->nr++] = update;\n@@ -1253,6 +1259,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err)\n {\n@@ -1280,7 +1288,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n-\t\t\t\t   new_oid, old_oid, msg);\n+\t\t\t\t   new_oid, old_oid, new_target,\n+\t\t\t\t   old_target, msg);\n \treturn 0;\n }\n \n@@ -1295,7 +1304,8 @@ int ref_transaction_create(struct ref_transaction *transaction,\n \t\treturn 1;\n \t}\n \treturn ref_transaction_update(transaction, refname, new_oid,\n-\t\t\t\t      null_oid(), flags, msg, err);\n+\t\t\t\t      null_oid(), NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_delete(struct ref_transaction *transaction,\n@@ -1308,7 +1318,8 @@ int ref_transaction_delete(struct ref_transaction *transaction,\n \t\tBUG(\"delete called with old_oid set to zeros\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      null_oid(), old_oid,\n-\t\t\t\t      flags, msg, err);\n+\t\t\t\t      NULL, NULL, flags,\n+\t\t\t\t      msg, err);\n }\n \n int ref_transaction_verify(struct ref_transaction *transaction,\n@@ -1321,6 +1332,7 @@ int ref_transaction_verify(struct ref_transaction *transaction,\n \t\tBUG(\"verify called with old_oid set to NULL\");\n \treturn ref_transaction_update(transaction, refname,\n \t\t\t\t      NULL, old_oid,\n+\t\t\t\t      NULL, NULL,\n \t\t\t\t      flags, NULL, err);\n }\n \n@@ -1335,8 +1347,8 @@ int refs_update_ref(struct ref_store *refs, const char *msg,\n \n \tt = ref_store_transaction_begin(refs, &err);\n \tif (!t ||\n-\t    ref_transaction_update(t, refname, new_oid, old_oid, flags, msg,\n-\t\t\t\t   &err) ||\n+\t    ref_transaction_update(t, refname, new_oid, old_oid, NULL, NULL,\n+\t\t\t\t   flags, msg, &err) ||\n \t    ref_transaction_commit(t, &err)) {\n \t\tret = 1;\n \t\tref_transaction_free(t);\ndiff --git a/refs.h b/refs.h\nindex d278775e08..c7851bf587 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -648,6 +648,16 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  *         before the update. A copy of this value is made in the\n  *         transaction.\n  *\n+ *     new_target -- the target reference that the reference will be\n+ *         updated to point to. If the reference is a regular reference,\n+ *         it will be converted to a symbolic reference. Cannot be set\n+ *         together with `new_oid`. A copy of this value is made in the\n+ *         transaction.\n+ *\n+ *     old_target -- the reference that the reference must be pointing to.\n+ *         Canont be set together with `old_oid`. A copy of this value is\n+ *         made in the transaction.\n+ *\n  *     flags -- flags affecting the update, passed to\n  *         update_ref_lock(). Possible flags: REF_NO_DEREF,\n  *         REF_FORCE_CREATE_REFLOG. See those constants for more\n@@ -713,7 +723,11 @@ struct ref_transaction *ref_transaction_begin(struct strbuf *err);\n  * beforehand. The old value is checked after the lock is taken to\n  * prevent races. If the old value doesn't agree with old_oid, the\n  * whole transaction fails. If old_oid is NULL, then the previous\n- * value is not checked.\n+ * value is not checked. If `old_target` is not NULL, treat the reference\n+ * as a symbolic ref and validate that its target before the update is\n+ * `old_target`. If the `new_target` is not NULL, then the reference\n+ * will be updated to a symbolic ref which targets `new_target`.\n+ * Together, these allow us to update between regular refs and symrefs.\n  *\n  * See the above comment \"Reference transaction updates\" for more\n  * information.\n@@ -722,6 +736,8 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \t\t\t   const char *refname,\n \t\t\t   const struct object_id *new_oid,\n \t\t\t   const struct object_id *old_oid,\n+\t\t\t   const char *new_target,\n+\t\t\t   const char *old_target,\n \t\t\t   unsigned int flags, const char *msg,\n \t\t\t   struct strbuf *err);\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex a098d14ea0..e4d0aa3d41 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1198,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)\n \tref_transaction_add_update(\n \t\t\ttransaction, r->name,\n \t\t\tREF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,\n-\t\t\tnull_oid(), &r->oid, NULL);\n+\t\t\tnull_oid(), &r->oid, NULL, NULL, NULL);\n \tif (ref_transaction_commit(transaction, &err))\n \t\tgoto cleanup;\n \n@@ -1292,7 +1292,7 @@ static int files_pack_refs(struct ref_store *ref_store,\n \t\t * packed-refs transaction:\n \t\t */\n \t\tif (ref_transaction_update(transaction, iter->refname,\n-\t\t\t\t\t   iter->oid, NULL,\n+\t\t\t\t\t   iter->oid, NULL, NULL, NULL,\n \t\t\t\t\t   REF_NO_DEREF, NULL, &err))\n \t\t\tdie(\"failure preparing to create packed reference %s: %s\",\n \t\t\t    iter->refname, err.buf);\n@@ -2309,7 +2309,7 @@ static int split_head_update(struct ref_update *update,\n \t\t\ttransaction, \"HEAD\",\n \t\t\tupdate->flags | REF_LOG_ONLY | REF_NO_DEREF,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \t/*\n \t * Add \"HEAD\". This insertion is O(N) in the transaction\n@@ -2372,7 +2372,7 @@ static int split_symref_update(struct ref_update *update,\n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n \t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tupdate->msg);\n+\t\t\tNULL, NULL, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2763,7 +2763,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t\tpacked_transaction, update->refname,\n \t\t\t\t\tREF_HAVE_NEW | REF_NO_DEREF,\n \t\t\t\t\t&update->new_oid, NULL,\n-\t\t\t\t\tNULL);\n+\t\t\t\t\tNULL, NULL, NULL);\n \t\t}\n \t}\n \n@@ -3048,7 +3048,7 @@ static int files_initial_transaction_commit(struct ref_store *ref_store,\n \t\tref_transaction_add_update(packed_transaction, update->refname,\n \t\t\t\t\t   update->flags & ~REF_HAVE_OLD,\n \t\t\t\t\t   &update->new_oid, &update->old_oid,\n-\t\t\t\t\t   NULL);\n+\t\t\t\t\t   NULL, NULL, NULL);\n \t}\n \n \tif (packed_refs_lock(refs->packed_ref_store, 0, err)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 56641aa57a..108f4ec419 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -124,6 +124,19 @@ struct ref_update {\n \t */\n \tstruct object_id old_oid;\n \n+\t/*\n+\t * If set, point the reference to this value. This can also be\n+\t * used to convert regular references to become symbolic refs.\n+\t * Cannot be set together with `new_oid`.\n+\t */\n+\tconst char *new_target;\n+\n+\t/*\n+\t * If set, check that the reference previously pointed to this\n+\t * value. Cannot be set together with `old_oid`.\n+\t */\n+\tconst char *old_target;\n+\n \t/*\n \t * One or more of REF_NO_DEREF, REF_FORCE_CREATE_REFLOG,\n \t * REF_HAVE_NEW, REF_HAVE_OLD, or backend-specific flags.\n@@ -173,6 +186,7 @@ struct ref_update *ref_transaction_add_update(\n \t\tconst char *refname, unsigned int flags,\n \t\tconst struct object_id *new_oid,\n \t\tconst struct object_id *old_oid,\n+\t\tconst char *new_target, const char *old_target,\n \t\tconst char *msg);\n \n /*\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 1cda48c504..6104471199 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -829,7 +829,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\ttransaction, \"HEAD\",\n \t\t\t\t\tu->flags | REF_LOG_ONLY | REF_NO_DEREF,\n-\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\tstring_list_insert(&affected_refnames, new_update->refname);\n \t\t}\n \n@@ -908,7 +908,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n \t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, u->msg);\n+\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\ndiff --git a/sequencer.c b/sequencer.c\nindex 88de4dc20f..61e007d85f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -665,7 +665,7 @@ static int fast_forward_to(struct repository *r,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\",\n \t\t\t\t   to, unborn && !is_rebase_i(opts) ?\n-\t\t\t\t   null_oid() : from,\n+\t\t\t\t   null_oid() : from, NULL, NULL,\n \t\t\t\t   0, sb.buf, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tref_transaction_free(transaction);\n@@ -1298,7 +1298,7 @@ int update_head_with_reflog(const struct commit *old_head,\n \tif (!transaction ||\n \t    ref_transaction_update(transaction, \"HEAD\", new_head,\n \t\t\t\t   old_head ? &old_head->object.oid : null_oid(),\n-\t\t\t\t   0, sb.buf, err) ||\n+\t\t\t\t   NULL, NULL, 0, sb.buf, err) ||\n \t    ref_transaction_commit(transaction, err)) {\n \t\tret = -1;\n \t}\n@@ -3832,8 +3832,9 @@ static int do_label(struct repository *r, const char *name, int len)\n \t} else if (repo_get_oid(r, \"HEAD\", &head_oid)) {\n \t\terror(_(\"could not read HEAD\"));\n \t\tret = -1;\n-\t} else if (ref_transaction_update(transaction, ref_name.buf, &head_oid,\n-\t\t\t\t\t  NULL, 0, msg.buf, &err) < 0 ||\n+\t} else if (ref_transaction_update(transaction, ref_name.buf,\n+\t\t\t\t\t  &head_oid, NULL, NULL, NULL,\n+\t\t\t\t\t  0, msg.buf, &err) < 0 ||\n \t\t   ref_transaction_commit(transaction, &err)) {\n \t\terror(\"%s\", err.buf);\n \t\tret = -1;\ndiff --git a/walker.c b/walker.c\nindex c0fd632d92..1b3df43906 100644\n--- a/walker.c\n+++ b/walker.c\n@@ -324,7 +324,7 @@ int walker_fetch(struct walker *walker, int targets, char **target,\n \t\tstrbuf_reset(&refname);\n \t\tstrbuf_addf(&refname, \"refs/%s\", write_ref[i]);\n \t\tif (ref_transaction_update(transaction, refname.buf,\n-\t\t\t\t\t   oids + i, NULL, 0,\n+\t\t\t\t\t   oids + i, NULL, NULL, NULL, 0,\n \t\t\t\t\t   msg ? msg : \"fetch (unknown)\",\n \t\t\t\t\t   &err)) {\n \t\t\terror(\"%s\", err.buf);\n-- \n2.43.GIT\n\n"},{"id":"494269","messageId":"20240507125859.132116-3-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 2/8] files-backend: extract out `create_symref_lock()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:53Z","receivedAt":"2024-05-07T12:59:10Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe function `create_symref_locked()` creates a symref by creating a\n'<symref>.lock' file and then committing the symref lock, which creates\nthe final symref.\n\nExtract the early half of `create_symref_locked()` into a new helper\nfunction `create_symref_lock()`. Because the name of the new function is\ntoo similar to the original, rename the original to\n`create_and_commit_symref()` to avoid confusion.\n\nThe new function `create_symref_locked()` can be used to create the\nsymref lock in a separate step from that of committing it. This allows\nto add transactional support for symrefs, where the lock would be\ncreated in the preparation step and the lock would be committed in the\nfinish step.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/files-backend.c | 51 ++++++++++++++++++++++++++++++++------------\n 1 file changed, 37 insertions(+), 14 deletions(-)\n\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex e4d0aa3d41..74a713090c 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1920,27 +1920,49 @@ static void update_symref_reflog(struct files_ref_store *refs,\n \t}\n }\n \n-static int create_symref_locked(struct files_ref_store *refs,\n-\t\t\t\tstruct ref_lock *lock, const char *refname,\n-\t\t\t\tconst char *target, const char *logmsg)\n+static int create_symref_lock(struct files_ref_store *refs,\n+\t\t\t      struct ref_lock *lock, const char *refname,\n+\t\t\t      const char *target, struct strbuf *err)\n {\n+\tif (!fdopen_lock_file(&lock->lk, \"w\")) {\n+\t\tstrbuf_addf(err, \"unable to fdopen %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\tif (fprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target) < 0) {\n+\t\tstrbuf_addf(err, \"unable to write to %s: %s\",\n+\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+static int create_and_commit_symref(struct files_ref_store *refs,\n+\t\t\t\t    struct ref_lock *lock, const char *refname,\n+\t\t\t\t    const char *target, const char *logmsg)\n+{\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret;\n+\n \tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n \t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \t\treturn 0;\n \t}\n \n-\tif (!fdopen_lock_file(&lock->lk, \"w\"))\n-\t\treturn error(\"unable to fdopen %s: %s\",\n-\t\t\t     get_lock_file_path(&lock->lk), strerror(errno));\n+\tret = create_symref_lock(refs, lock, refname, target, &err);\n+\tif (!ret) {\n+\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n \n-\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n+\t\tif (commit_ref(lock) < 0)\n+\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n+\t\t\t\t     strerror(errno));\n+\t} else {\n+\t\treturn error(\"%s\", err.buf);\n+\t}\n \n-\t/* no error check; commit_ref will check ferror */\n-\tfprintf(get_lock_file_fp(&lock->lk), \"ref: %s\\n\", target);\n-\tif (commit_ref(lock) < 0)\n-\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t     strerror(errno));\n-\treturn 0;\n+\treturn ret;\n }\n \n static int files_create_symref(struct ref_store *ref_store,\n@@ -1960,7 +1982,8 @@ static int files_create_symref(struct ref_store *ref_store,\n \t\treturn -1;\n \t}\n \n-\tret = create_symref_locked(refs, lock, refname, target, logmsg);\n+\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n+\n \tunlock_ref(lock);\n \treturn ret;\n }\n-- \n2.43.GIT\n\n"},{"id":"494270","messageId":"20240507125859.132116-4-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 3/8] refs: support symrefs in 'reference-transaction' hook","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:54Z","receivedAt":"2024-05-07T12:59:12Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe 'reference-transaction' hook runs whenever a reference update is\nmade to the system. In a previous commit, we added the `old_target` and\n`new_target` fields to the `reference_transaction_update()`. In\nfollowing commits we'll also add the code to handle symref's in the\nreference backends.\n\nSupport symrefs also in the 'reference-transaction' hook, by modifying\nthe current format:\n    <old-oid> SP <new-oid> SP <ref-name> LF\nto be be:\n    <old-value> SP <new-value> SP <ref-name> LF\nwhere for regular refs the output would not change and remain the same.\nBut when either 'old-value' or 'new-value' is a symref, we print the ref\nas 'ref:<ref-target>'.\n\nThis does break backward compatibility, but the 'reference-transaction'\nhook's documentation always stated that support for symbolic references\nmay be added in the future.\n\nWe do not add any tests in this commit since there is no git command\nwhich activates this flow, in an upcoming commit, we'll start using\ntransaction based symref updates as the default, we'll add tests there\nfor the hook too.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n Documentation/githooks.txt | 14 +++++++++-----\n refs.c                     | 20 ++++++++++++++++----\n 2 files changed, 25 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/githooks.txt b/Documentation/githooks.txt\nindex ee9b92c90d..06e997131b 100644\n--- a/Documentation/githooks.txt\n+++ b/Documentation/githooks.txt\n@@ -486,7 +486,7 @@ reference-transaction\n This hook is invoked by any Git command that performs reference\n updates. It executes whenever a reference transaction is prepared,\n committed or aborted and may thus get called multiple times. The hook\n-does not cover symbolic references (but that may change in the future).\n+also supports symbolic reference updates.\n \n The hook takes exactly one argument, which is the current state the\n given reference transaction is in:\n@@ -503,16 +503,20 @@ given reference transaction is in:\n For each reference update that was added to the transaction, the hook\n receives on standard input a line of the format:\n \n-  <old-oid> SP <new-oid> SP <ref-name> LF\n+  <old-value> SP <new-value> SP <ref-name> LF\n \n-where `<old-oid>` is the old object name passed into the reference\n-transaction, `<new-oid>` is the new object name to be stored in the\n+where `<old-value>` is the old object name passed into the reference\n+transaction, `<new-value>` is the new object name to be stored in the\n ref and `<ref-name>` is the full name of the ref. When force updating\n the reference regardless of its current value or when the reference is\n-to be created anew, `<old-oid>` is the all-zeroes object name. To\n+to be created anew, `<old-value>` is the all-zeroes object name. To\n distinguish these cases, you can inspect the current value of\n `<ref-name>` via `git rev-parse`.\n \n+For symbolic reference updates the `<old_value>` and `<new-value>`\n+fields could denote references instead of objects. A reference will be\n+denoted with a 'ref:' prefix, like `ref:<ref-target>`.\n+\n The exit status of the hook is ignored for any state except for the\n \"prepared\" state. In the \"prepared\" state, a non-zero exit status will\n cause the transaction to be aborted. The hook will not be called with\ndiff --git a/refs.c b/refs.c\nindex e7b7c48d92..9d722d798a 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2350,10 +2350,22 @@ static int run_transaction_hook(struct ref_transaction *transaction,\n \t\tstruct ref_update *update = transaction->updates[i];\n \n \t\tstrbuf_reset(&buf);\n-\t\tstrbuf_addf(&buf, \"%s %s %s\\n\",\n-\t\t\t    oid_to_hex(&update->old_oid),\n-\t\t\t    oid_to_hex(&update->new_oid),\n-\t\t\t    update->refname);\n+\n+\t\tif (!(update->flags & REF_HAVE_OLD))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->old_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->old_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->old_oid));\n+\n+\t\tif (!(update->flags & REF_HAVE_NEW))\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(null_oid()));\n+\t\telse if (update->new_target)\n+\t\t\tstrbuf_addf(&buf, \"ref:%s \", update->new_target);\n+\t\telse\n+\t\t\tstrbuf_addf(&buf, \"%s \", oid_to_hex(&update->new_oid));\n+\n+\t\tstrbuf_addf(&buf, \"%s\\n\", update->refname);\n \n \t\tif (write_in_full(proc.in, buf.buf, buf.len) < 0) {\n \t\t\tif (errno != EPIPE) {\n-- \n2.43.GIT\n\n"},{"id":"494271","messageId":"20240507125859.132116-5-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 4/8] refs: move `original_update_refname` to 'refs.c'","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:55Z","receivedAt":"2024-05-07T12:59:13Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe files backend and the reftable backend implement\n`original_update_refname` to obtain the original refname of the update.\nMove it out to 'refs.c' and only expose it internally to the refs\nlibrary. This will be used in an upcoming commit to also introduce\nanother common functionality for the two backends.\n\nWe also rename the function to `ref_update_original_update_refname` to\nkeep it consistent with the upcoming other 'ref_update_*' functions\nthat'll be introduced.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  9 +++++++++\n refs/files-backend.c    | 21 +++++----------------\n refs/refs-internal.h    |  5 +++++\n refs/reftable-backend.c | 24 +++++++-----------------\n 4 files changed, 26 insertions(+), 33 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 9d722d798a..8de43dc0fc 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2814,3 +2814,12 @@ int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n {\n \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n }\n+\n+const char *ref_update_original_update_refname(struct ref_update *update)\n+{\n+\twhile (update->parent_update)\n+\t\tupdate = update->parent_update;\n+\n+\treturn update->refname;\n+}\n+\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 74a713090c..25e5d03496 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2423,17 +2423,6 @@ static int split_symref_update(struct ref_update *update,\n \treturn 0;\n }\n \n-/*\n- * Return the refname under which update was originally requested.\n- */\n-static const char *original_update_refname(struct ref_update *update)\n-{\n-\twhile (update->parent_update)\n-\t\tupdate = update->parent_update;\n-\n-\treturn update->refname;\n-}\n-\n /*\n  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n  * are consistent with oid, which is the reference's current value. If\n@@ -2450,16 +2439,16 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,\n \tif (is_null_oid(&update->old_oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference already exists\",\n-\t\t\t    original_update_refname(update));\n+\t\t\t    ref_update_original_update_refname(update));\n \telse if (is_null_oid(oid))\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"reference is missing but expected %s\",\n-\t\t\t    original_update_refname(update),\n+\t\t\t    ref_update_original_update_refname(update),\n \t\t\t    oid_to_hex(&update->old_oid));\n \telse\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t    \"is at %s but expected %s\",\n-\t\t\t    original_update_refname(update),\n+\t\t\t    ref_update_original_update_refname(update),\n \t\t\t    oid_to_hex(oid),\n \t\t\t    oid_to_hex(&update->old_oid));\n \n@@ -2513,7 +2502,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \t\treason = strbuf_detach(err, NULL);\n \t\tstrbuf_addf(err, \"cannot lock ref '%s': %s\",\n-\t\t\t    original_update_refname(update), reason);\n+\t\t\t    ref_update_original_update_refname(update), reason);\n \t\tfree(reason);\n \t\tgoto out;\n \t}\n@@ -2533,7 +2522,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\tif (update->flags & REF_HAVE_OLD) {\n \t\t\t\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n \t\t\t\t\t\t    \"error reading reference\",\n-\t\t\t\t\t\t    original_update_refname(update));\n+\t\t\t\t\t\t    ref_update_original_update_refname(update));\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 108f4ec419..617b93a6c8 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -749,4 +749,9 @@ void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n  */\n struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n \n+/*\n+ * Return the refname under which update was originally requested.\n+ */\n+const char *ref_update_original_update_refname(struct ref_update *update);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 6104471199..8bba5d5096 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -581,16 +581,6 @@ static int reftable_be_read_symbolic_ref(struct ref_store *ref_store,\n \treturn ret;\n }\n \n-/*\n- * Return the refname under which update was originally requested.\n- */\n-static const char *original_update_refname(struct ref_update *update)\n-{\n-\twhile (update->parent_update)\n-\t\tupdate = update->parent_update;\n-\treturn update->refname;\n-}\n-\n struct reftable_transaction_update {\n \tstruct ref_update *update;\n \tstruct object_id current_oid;\n@@ -869,7 +859,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t/* The reference does not exist, but we expected it to. */\n \t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t    \"unable to resolve reference '%s'\"),\n-\t\t\t\t    original_update_refname(u), u->refname);\n+\t\t\t\t    ref_update_original_update_refname(u), u->refname);\n \t\t\tret = -1;\n \t\t\tgoto done;\n \t\t}\n@@ -941,17 +931,17 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"reference already exists\"),\n-\t\t\t\t\t    original_update_refname(u));\n+\t\t\t\t\t\t   \"reference already exists\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u));\n \t\t\telse if (is_null_oid(&current_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"reference is missing but expected %s\"),\n-\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t   \"reference is missing but expected %s\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n \t\t\telse\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n-\t\t\t\t\t    \"is at %s but expected %s\"),\n-\t\t\t\t\t    original_update_refname(u),\n+\t\t\t\t\t\t   \"is at %s but expected %s\"),\n+\t\t\t\t\t    ref_update_original_update_refname(u),\n \t\t\t\t\t    oid_to_hex(&current_oid),\n \t\t\t\t\t    oid_to_hex(&u->old_oid));\n \t\t\tret = -1;\n-- \n2.43.GIT\n\n"},{"id":"494272","messageId":"20240507125859.132116-6-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 5/8] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:56Z","receivedAt":"2024-05-07T12:59:14Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe reference backends currently support transactional reference\nupdates. While this is exposed to users via 'git-update-ref' and its\n'--stdin' mode, it is also used internally within various commands.\n\nHowever, we do not support transactional updates of symrefs. This commit\nadds support for symrefs in both the 'files' and the 'reftable' backend.\n\nHere, we add and use `ref_update_has_null_new_value()`, a helper\nfunction which is used to check if there is a new_value in a reference\nupdate. The new value could either be a symref target `new_target` or a\nOID `new_oid`.\n\nWe also add another common function `ref_update_check_old_target` which\nwill be used to check if the update's old_target corresponds to a\nreference's current target.\n\nNow transactional updates (verify, create, delete, update) can be used\nfor:\n- regular refs\n- symbolic refs\n- conversion of regular to symbolic refs and vice versa\n\nThis also allows us to expose this to users via new commands in\n'git-update-ref' in the future.\n\nNote that a dangling symref update does not record a new reflog entry,\nwhich is unchanged before and after this commit.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                  |  36 ++++++++++++-\n refs/files-backend.c    | 113 ++++++++++++++++++++++++++++++++--------\n refs/refs-internal.h    |  16 ++++++\n refs/reftable-backend.c |  71 +++++++++++++++++++------\n 4 files changed, 196 insertions(+), 40 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex 8de43dc0fc..d0ea7573d8 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -1217,6 +1217,8 @@ void ref_transaction_free(struct ref_transaction *transaction)\n \n \tfor (i = 0; i < transaction->nr; i++) {\n \t\tfree(transaction->updates[i]->msg);\n+\t\tfree((char *)transaction->updates[i]->new_target);\n+\t\tfree((char *)transaction->updates[i]->old_target);\n \t\tfree(transaction->updates[i]);\n \t}\n \tfree(transaction->updates);\n@@ -1247,10 +1249,13 @@ struct ref_update *ref_transaction_add_update(\n \n \tupdate->flags = flags;\n \n-\tif (flags & REF_HAVE_NEW)\n+\tupdate->new_target = xstrdup_or_null(new_target);\n+\tupdate->old_target = xstrdup_or_null(old_target);\n+\tif ((flags & REF_HAVE_NEW) && new_oid)\n \t\toidcpy(&update->new_oid, new_oid);\n-\tif (flags & REF_HAVE_OLD)\n+\tif ((flags & REF_HAVE_OLD) && old_oid)\n \t\toidcpy(&update->old_oid, old_oid);\n+\n \tupdate->msg = normalize_reflog_message(msg);\n \treturn update;\n }\n@@ -1286,6 +1291,7 @@ int ref_transaction_update(struct ref_transaction *transaction,\n \tflags &= REF_TRANSACTION_UPDATE_ALLOWED_FLAGS;\n \n \tflags |= (new_oid ? REF_HAVE_NEW : 0) | (old_oid ? REF_HAVE_OLD : 0);\n+\tflags |= (new_target ? REF_HAVE_NEW : 0) | (old_target ? REF_HAVE_OLD : 0);\n \n \tref_transaction_add_update(transaction, refname, flags,\n \t\t\t\t   new_oid, old_oid, new_target,\n@@ -2823,3 +2829,29 @@ const char *ref_update_original_update_refname(struct ref_update *update)\n \treturn update->refname;\n }\n \n+int ref_update_has_null_new_value(struct ref_update *update)\n+{\n+\treturn !update->new_target && is_null_oid(&update->new_oid);\n+}\n+\n+int ref_update_check_old_target(const char *referent, struct ref_update *update,\n+\t\t\t\tstruct strbuf *err)\n+{\n+\tif (!update->old_target)\n+\t\tBUG(\"called without old_target set\");\n+\n+\tif (!strcmp(referent, update->old_target))\n+\t\treturn 0;\n+\n+\tif (!strcmp(referent, \"\"))\n+\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t    \"reference is missing but expected %s\",\n+\t\t\t    ref_update_original_update_refname(update),\n+\t\t\t    update->old_target);\n+\telse\n+\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n+\t\t\t    \"is at %s but expected %s\",\n+\t\t\t    ref_update_original_update_refname(update),\n+\t\t\t    referent, update->old_target);\n+\treturn -1;\n+}\ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 25e5d03496..2d1525b240 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -2394,8 +2394,9 @@ static int split_symref_update(struct ref_update *update,\n \n \tnew_update = ref_transaction_add_update(\n \t\t\ttransaction, referent, new_flags,\n-\t\t\t&update->new_oid, &update->old_oid,\n-\t\t\tNULL, NULL, update->msg);\n+\t\t\tupdate->new_target ? NULL : &update->new_oid,\n+\t\t\tupdate->old_target ? NULL : &update->old_oid,\n+\t\t\tupdate->new_target, update->old_target, update->msg);\n \n \tnew_update->parent_update = update;\n \n@@ -2483,7 +2484,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \n \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n \n-\tif ((update->flags & REF_HAVE_NEW) && is_null_oid(&update->new_oid))\n+\tif ((update->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(update))\n \t\tupdate->flags |= REF_DELETING;\n \n \tif (head_ref) {\n@@ -2526,7 +2527,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\t\tgoto out;\n \t\t\t\t}\n-\t\t\t} else if (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t\t}\n+\n+\t\t\tif (update->old_target) {\n+\t\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n+\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n+\t\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto out;\n \t\t\t}\n@@ -2547,7 +2555,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t} else {\n \t\tstruct ref_update *parent_update;\n \n-\t\tif (check_old_oid(update, &lock->old_oid, err)) {\n+\t\t/*\n+\t\t * Even if the ref is a regular ref, if `old_target` is set, we\n+\t\t * check the referent value. Ideally `old_target` should only\n+\t\t * be set for symrefs, but we're strict about its usage.\n+\t\t */\n+\t\tif (update->old_target) {\n+\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n+\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t} else if  (check_old_oid(update, &lock->old_oid, err)) {\n \t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\tgoto out;\n \t\t}\n@@ -2565,9 +2583,28 @@ static int lock_ref_for_update(struct files_ref_store *refs,\n \t\t}\n \t}\n \n-\tif ((update->flags & REF_HAVE_NEW) &&\n-\t    !(update->flags & REF_DELETING) &&\n-\t    !(update->flags & REF_LOG_ONLY)) {\n+\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n+\t\tif (create_symref_lock(refs, lock, update->refname,\n+\t\t\t\t       update->new_target, err)) {\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (close_ref_gently(lock)) {\n+\t\t\tstrbuf_addf(err, \"couldn't close '%s.lock'\",\n+\t\t\t\t    update->refname);\n+\t\t\tret = TRANSACTION_GENERIC_ERROR;\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\t/*\n+\t\t * Once we have created the symref lock, the commit\n+\t\t * phase of the transaction only needs to commit the lock.\n+\t\t */\n+\t\tupdate->flags |= REF_NEEDS_COMMIT;\n+\t} else if ((update->flags & REF_HAVE_NEW) &&\n+\t\t   !(update->flags & REF_DELETING) &&\n+\t\t   !(update->flags & REF_LOG_ONLY)) {\n \t\tif (!(update->type & REF_ISSYMREF) &&\n \t\t    oideq(&lock->old_oid, &update->new_oid)) {\n \t\t\t/*\n@@ -2830,6 +2867,43 @@ static int files_transaction_prepare(struct ref_store *ref_store,\n \treturn ret;\n }\n \n+static int parse_and_write_reflog(struct files_ref_store *refs,\n+\t\t\t\t  struct ref_update *update,\n+\t\t\t\t  struct ref_lock *lock,\n+\t\t\t\t  struct strbuf *err)\n+{\n+\tif (update->new_target) {\n+\t\t/*\n+\t\t * We want to get the resolved OID for the target, to ensure\n+\t\t * that the correct value is added to the reflog.\n+\t\t */\n+\t\tif (!refs_resolve_ref_unsafe(&refs->base, update->new_target,\n+\t\t\t\t\t     RESOLVE_REF_READING,\n+\t\t\t\t\t     &update->new_oid, NULL)) {\n+\t\t\t/*\n+\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t * zero oid updates however.\n+\t\t\t */\n+\t\t\treturn 0;\n+\t\t}\n+\t}\n+\n+\tif (files_log_ref_write(refs, lock->ref_name, &lock->old_oid,\n+\t\t\t\t&update->new_oid, update->msg, update->flags, err)) {\n+\t\tchar *old_msg = strbuf_detach(err, NULL);\n+\n+\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n+\t\t\t    lock->ref_name, old_msg);\n+\t\tfree(old_msg);\n+\t\tunlock_ref(lock);\n+\t\tupdate->backend_data = NULL;\n+\t\treturn -1;\n+\t}\n+\n+\treturn 0;\n+}\n+\n static int files_transaction_finish(struct ref_store *ref_store,\n \t\t\t\t    struct ref_transaction *transaction,\n \t\t\t\t    struct strbuf *err)\n@@ -2860,23 +2934,20 @@ static int files_transaction_finish(struct ref_store *ref_store,\n \n \t\tif (update->flags & REF_NEEDS_COMMIT ||\n \t\t    update->flags & REF_LOG_ONLY) {\n-\t\t\tif (files_log_ref_write(refs,\n-\t\t\t\t\t\tlock->ref_name,\n-\t\t\t\t\t\t&lock->old_oid,\n-\t\t\t\t\t\t&update->new_oid,\n-\t\t\t\t\t\tupdate->msg, update->flags,\n-\t\t\t\t\t\terr)) {\n-\t\t\t\tchar *old_msg = strbuf_detach(err, NULL);\n-\n-\t\t\t\tstrbuf_addf(err, \"cannot update the ref '%s': %s\",\n-\t\t\t\t\t    lock->ref_name, old_msg);\n-\t\t\t\tfree(old_msg);\n-\t\t\t\tunlock_ref(lock);\n-\t\t\t\tupdate->backend_data = NULL;\n+\t\t\tif (parse_and_write_reflog(refs, update, lock, err)) {\n \t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n \t\t\t\tgoto cleanup;\n \t\t\t}\n \t\t}\n+\n+\t\t/*\n+\t\t * We try creating a symlink, if that succeeds we continue to the\n+\t\t * next update. If not, we try and create a regular symref.\n+\t\t */\n+\t\tif (update->new_target && prefer_symlink_refs)\n+\t\t\tif (!create_ref_symlink(lock, update->new_target))\n+\t\t\t\tcontinue;\n+\n \t\tif (update->flags & REF_NEEDS_COMMIT) {\n \t\t\tclear_loose_ref_cache(refs);\n \t\t\tif (commit_ref(lock)) {\ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 617b93a6c8..819157256e 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -754,4 +754,20 @@ struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor\n  */\n const char *ref_update_original_update_refname(struct ref_update *update);\n \n+/*\n+ * Helper function to check if the new value is null, this\n+ * takes into consideration that the update could be a regular\n+ * ref or a symbolic ref.\n+ */\n+int ref_update_has_null_new_value(struct ref_update *update);\n+\n+/*\n+ * Check whether the old_target values stored in update are consistent\n+ * with the referent, which is the symbolic reference's current value.\n+ * If everything is OK, return 0; otherwise, write an error message to\n+ * err and return -1.\n+ */\n+int ref_update_check_old_target(const char *referent, struct ref_update *update,\n+\t\t\t\tstruct strbuf *err);\n+\n #endif /* REFS_REFS_INTERNAL_H */\ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 8bba5d5096..3eab57cf95 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -846,7 +846,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t * There is no need to write the reference deletion\n \t\t\t * when the reference in question doesn't exist.\n \t\t\t */\n-\t\t\t if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) {\n+\t\t\t if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {\n \t\t\t\t ret = queue_transaction_update(refs, tx_data, u,\n \t\t\t\t\t\t\t\t&current_oid, err);\n \t\t\t\t if (ret)\n@@ -897,8 +897,10 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t\t\t * intertwined with the locking in files-backend.c.\n \t\t\t\t */\n \t\t\t\tnew_update = ref_transaction_add_update(\n-\t\t\t\t\t\ttransaction, referent.buf, new_flags,\n-\t\t\t\t\t\t&u->new_oid, &u->old_oid, NULL, NULL, u->msg);\n+\t\t\t\t\ttransaction, referent.buf, new_flags,\n+\t\t\t\t\t&u->new_oid, &u->old_oid, u->new_target,\n+\t\t\t\t\tu->old_target, u->msg);\n+\n \t\t\t\tnew_update->parent_update = u;\n \n \t\t\t\t/*\n@@ -928,7 +930,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,\n \t\t * individual refs. But the error messages match what the files\n \t\t * backend returns, which keeps our tests happy.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n+\t\tif (u->old_target) {\n+\t\t\tif (ref_update_check_old_target(referent.buf, u, err)) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto done;\n+\t\t\t}\n+\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n \t\t\tif (is_null_oid(&u->old_oid))\n \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n \t\t\t\t\t\t   \"reference already exists\"),\n@@ -1033,7 +1040,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n \t\t *   the given ref.\n \t\t */\n-\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n+\t\tif ((u->flags & REF_HAVE_NEW) &&\n+\t\t    !(u->type & REF_ISSYMREF) &&\n+\t\t    ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_log_record log = {0};\n \t\t\tstruct reftable_iterator it = {0};\n \n@@ -1074,24 +1083,52 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n \t\t\t   (u->flags & REF_FORCE_CREATE_REFLOG ||\n \t\t\t    should_write_log(&arg->refs->base, u->refname))) {\n \t\t\tstruct reftable_log_record *log;\n+\t\t\tint create_reflog = 1;\n+\n+\t\t\tif (u->new_target) {\n+\t\t\t\tif (!refs_resolve_ref_unsafe(&arg->refs->base, u->new_target,\n+\t\t\t\t\t\t\t     RESOLVE_REF_READING, &u->new_oid, NULL)) {\n+\t\t\t\t\t/*\n+\t\t\t\t\t * TODO: currently we skip creating reflogs for dangling\n+\t\t\t\t\t * symref updates. It would be nice to capture this as\n+\t\t\t\t\t * zero oid updates however.\n+\t\t\t\t\t */\n+\t\t\t\t\tcreate_reflog = 0;\n+\t\t\t\t}\n+\t\t\t}\n \n-\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n-\t\t\tlog = &logs[logs_nr++];\n-\t\t\tmemset(log, 0, sizeof(*log));\n-\n-\t\t\tfill_reftable_log_record(log);\n-\t\t\tlog->update_index = ts;\n-\t\t\tlog->refname = xstrdup(u->refname);\n-\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n-\t\t\tlog->value.update.message =\n-\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\tif (create_reflog) {\n+\t\t\t\tALLOC_GROW(logs, logs_nr + 1, logs_alloc);\n+\t\t\t\tlog = &logs[logs_nr++];\n+\t\t\t\tmemset(log, 0, sizeof(*log));\n+\n+\t\t\t\tfill_reftable_log_record(log);\n+\t\t\t\tlog->update_index = ts;\n+\t\t\t\tlog->refname = xstrdup(u->refname);\n+\t\t\t\tmemcpy(log->value.update.new_hash,\n+\t\t\t\t       u->new_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tmemcpy(log->value.update.old_hash,\n+\t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n+\t\t\t\tlog->value.update.message =\n+\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n+\t\t\t}\n \t\t}\n \n \t\tif (u->flags & REF_LOG_ONLY)\n \t\t\tcontinue;\n \n-\t\tif (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) {\n+\t\tif (u->new_target) {\n+\t\t\tstruct reftable_ref_record ref = {\n+\t\t\t\t.refname = (char *)u->refname,\n+\t\t\t\t.value_type = REFTABLE_REF_SYMREF,\n+\t\t\t\t.value.symref = (char *)u->new_target,\n+\t\t\t\t.update_index = ts,\n+\t\t\t};\n+\n+\t\t\tret = reftable_writer_add_ref(writer, &ref);\n+\t\t\tif (ret < 0)\n+\t\t\t\tgoto done;\n+\t\t} else if ((u->flags & REF_HAVE_NEW) && ref_update_has_null_new_value(u)) {\n \t\t\tstruct reftable_ref_record ref = {\n \t\t\t\t.refname = (char *)u->refname,\n \t\t\t\t.update_index = ts,\n-- \n2.43.GIT\n\n"},{"id":"494273","messageId":"20240507125859.132116-7-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 6/8] refs: use transaction in `refs_create_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:57Z","receivedAt":"2024-05-07T12:59:16Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function updates a symref to a given new\ntarget. To do this, it uses a ref-backend specific function\n`create_symref()`.\n\nIn the previous commits, we introduced symref support in transactions.\nThis means we can now use transactions to perform symref updates and\ndon't have to resort to `create_symref()`. Doing this allows us to\nremove and cleanup `create_symref()`, which we will do in the following\ncommit.\n\nModify the expected error message for a test in\n't/t0610-reftable-basics.sh', since the error is now thrown from\n'refs.c'. This is because in transactional updates, F/D conflicts are\ncaught before we're in the reference backend.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs.c                           | 24 +++++++++++++++++-------\n t/t0610-reftable-basics.sh       |  2 +-\n t/t1416-ref-transaction-hooks.sh | 23 +++++++++++++++++++++++\n 3 files changed, 41 insertions(+), 8 deletions(-)\n\ndiff --git a/refs.c b/refs.c\nindex d0ea7573d8..507f5f0525 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2289,14 +2289,24 @@ int refs_create_symref(struct ref_store *refs,\n \t\t       const char *refs_heads_master,\n \t\t       const char *logmsg)\n {\n-\tchar *msg;\n-\tint retval;\n+\tstruct ref_transaction *transaction;\n+\tstruct strbuf err = STRBUF_INIT;\n+\tint ret = 0;\n \n-\tmsg = normalize_reflog_message(logmsg);\n-\tretval = refs->be->create_symref(refs, ref_target, refs_heads_master,\n-\t\t\t\t\t msg);\n-\tfree(msg);\n-\treturn retval;\n+\ttransaction = ref_store_transaction_begin(refs, &err);\n+\tif (!transaction ||\n+\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n+\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t\t\t\t   logmsg, &err) ||\n+\t    ref_transaction_commit(transaction, &err)) {\n+\t\tret = error(\"%s\", err.buf);\n+\t}\n+\n+\tstrbuf_release(&err);\n+\tif (transaction)\n+\t\tref_transaction_free(transaction);\n+\n+\treturn ret;\n }\n \n int create_symref(const char *ref_target, const char *refs_heads_master,\ndiff --git a/t/t0610-reftable-basics.sh b/t/t0610-reftable-basics.sh\nindex 178791e086..9e8d22bcbd 100755\n--- a/t/t0610-reftable-basics.sh\n+++ b/t/t0610-reftable-basics.sh\n@@ -286,7 +286,7 @@ test_expect_success 'ref transaction: creating symbolic ref fails with F/D confl\n \tgit init repo &&\n \ttest_commit -C repo A &&\n \tcat >expect <<-EOF &&\n-\terror: unable to write symref for refs/heads: file/directory conflict\n+\terror: ${SQ}refs/heads/main${SQ} exists; cannot create ${SQ}refs/heads${SQ}\n \tEOF\n \ttest_must_fail git -C repo symbolic-ref refs/heads refs/heads/foo 2>err &&\n \ttest_cmp expect err\ndiff --git a/t/t1416-ref-transaction-hooks.sh b/t/t1416-ref-transaction-hooks.sh\nindex 2092488090..067fd57290 100755\n--- a/t/t1416-ref-transaction-hooks.sh\n+++ b/t/t1416-ref-transaction-hooks.sh\n@@ -134,4 +134,27 @@ test_expect_success 'interleaving hook calls succeed' '\n \ttest_cmp expect target-repo.git/actual\n '\n \n+test_expect_success 'hook captures git-symbolic-ref updates' '\n+\ttest_when_finished \"rm actual\" &&\n+\n+\ttest_hook reference-transaction <<-\\EOF &&\n+\t\techo \"$*\" >>actual\n+\t\twhile read -r line\n+\t\tdo\n+\t\t\tprintf \"%s\\n\" \"$line\"\n+\t\tdone >>actual\n+\tEOF\n+\n+\tgit symbolic-ref refs/heads/symref refs/heads/main &&\n+\n+\tcat >expect <<-EOF &&\n+\tprepared\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tcommitted\n+\t$ZERO_OID ref:refs/heads/main refs/heads/symref\n+\tEOF\n+\n+\ttest_cmp expect actual\n+'\n+\n test_done\n-- \n2.43.GIT\n\n"},{"id":"494274","messageId":"20240507125859.132116-8-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 7/8] refs: rename `refs_create_symref()` to `refs_update_symref()`","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:58Z","receivedAt":"2024-05-07T12:59:17Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nThe `refs_create_symref()` function is used to update/create a symref.\nBut it doesn't check the old target of the symref, if existing. It force\nupdates the symref. In this regard, the name `refs_create_symref()` is a\nbit misleading. So let's rename it to `refs_update_symref()`. This is\nakin to how 'git-update-ref(1)' also allows us to create apart from\nupdate.\n\nWhile we're here, rename the arguments in the function to clarify what\nthey actually signify and reduce confusion.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n builtin/branch.c          |  2 +-\n builtin/worktree.c        |  2 +-\n refs.c                    | 12 +++++-------\n refs.h                    |  2 +-\n t/helper/test-ref-store.c |  2 +-\n 5 files changed, 9 insertions(+), 11 deletions(-)\n\ndiff --git a/builtin/branch.c b/builtin/branch.c\nindex dd3e3a7dc0..4491f7a20c 100644\n--- a/builtin/branch.c\n+++ b/builtin/branch.c\n@@ -555,7 +555,7 @@ static int replace_each_worktree_head_symref(struct worktree **worktrees,\n \t\t\tcontinue;\n \n \t\trefs = get_worktree_ref_store(worktrees[i]);\n-\t\tif (refs_create_symref(refs, \"HEAD\", newref, logmsg))\n+\t\tif (refs_update_symref(refs, \"HEAD\", newref, logmsg))\n \t\t\tret = error(_(\"HEAD of working tree %s is not updated\"),\n \t\t\t\t    worktrees[i]->path);\n \t}\ndiff --git a/builtin/worktree.c b/builtin/worktree.c\nindex 7c6c72536b..480202c517 100644\n--- a/builtin/worktree.c\n+++ b/builtin/worktree.c\n@@ -517,7 +517,7 @@ static int add_worktree(const char *path, const char *refname,\n \t\tret = refs_update_ref(wt_refs, NULL, \"HEAD\", &commit->object.oid,\n \t\t\t\t      NULL, 0, UPDATE_REFS_MSG_ON_ERR);\n \telse\n-\t\tret = refs_create_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n+\t\tret = refs_update_symref(wt_refs, \"HEAD\", symref.buf, NULL);\n \tif (ret)\n \t\tgoto done;\n \ndiff --git a/refs.c b/refs.c\nindex 507f5f0525..fa5471d219 100644\n--- a/refs.c\n+++ b/refs.c\n@@ -2284,10 +2284,8 @@ int peel_iterated_oid(const struct object_id *base, struct object_id *peeled)\n \treturn peel_object(base, peeled) ? -1 : 0;\n }\n \n-int refs_create_symref(struct ref_store *refs,\n-\t\t       const char *ref_target,\n-\t\t       const char *refs_heads_master,\n-\t\t       const char *logmsg)\n+int refs_update_symref(struct ref_store *refs, const char *ref,\n+\t\t       const char *target, const char *logmsg)\n {\n \tstruct ref_transaction *transaction;\n \tstruct strbuf err = STRBUF_INIT;\n@@ -2295,8 +2293,8 @@ int refs_create_symref(struct ref_store *refs,\n \n \ttransaction = ref_store_transaction_begin(refs, &err);\n \tif (!transaction ||\n-\t    ref_transaction_update(transaction, ref_target, NULL, NULL,\n-\t\t\t\t   refs_heads_master, NULL, REF_NO_DEREF,\n+\t    ref_transaction_update(transaction, ref, NULL, NULL,\n+\t\t\t\t   target, NULL, REF_NO_DEREF,\n \t\t\t\t   logmsg, &err) ||\n \t    ref_transaction_commit(transaction, &err)) {\n \t\tret = error(\"%s\", err.buf);\n@@ -2312,7 +2310,7 @@ int refs_create_symref(struct ref_store *refs,\n int create_symref(const char *ref_target, const char *refs_heads_master,\n \t\t  const char *logmsg)\n {\n-\treturn refs_create_symref(get_main_ref_store(the_repository), ref_target,\n+\treturn refs_update_symref(get_main_ref_store(the_repository), ref_target,\n \t\t\t\t  refs_heads_master, logmsg);\n }\n \ndiff --git a/refs.h b/refs.h\nindex c7851bf587..71cc1c58e0 100644\n--- a/refs.h\n+++ b/refs.h\n@@ -606,7 +606,7 @@ int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,\n int copy_existing_ref(const char *oldref, const char *newref,\n \t\t\tconst char *logmsg);\n \n-int refs_create_symref(struct ref_store *refs, const char *refname,\n+int refs_update_symref(struct ref_store *refs, const char *refname,\n \t\t       const char *target, const char *logmsg);\n int create_symref(const char *refname, const char *target, const char *logmsg);\n \ndiff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c\nindex 82bbf6e2e6..4651e4ced7 100644\n--- a/t/helper/test-ref-store.c\n+++ b/t/helper/test-ref-store.c\n@@ -118,7 +118,7 @@ static int cmd_create_symref(struct ref_store *refs, const char **argv)\n \tconst char *target = notnull(*argv++, \"target\");\n \tconst char *logmsg = *argv++;\n \n-\treturn refs_create_symref(refs, refname, target, logmsg);\n+\treturn refs_update_symref(refs, refname, target, logmsg);\n }\n \n static struct flag_definition transaction_flags[] = {\n-- \n2.43.GIT\n\n"},{"id":"494275","messageId":"20240507125859.132116-9-knayak@gitlab.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"[PATCH v8 8/8] refs: remove `create_symref` and associated dead code","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-07T12:58:59Z","receivedAt":"2024-05-07T12:59:19Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"From: Karthik Nayak <karthik.188@gmail.com>\n\nIn the previous commits, we converted `refs_create_symref()` to utilize\ntransactions to perform symref updates. Earlier `refs_create_symref()`\nused `create_symref()` to do the same.\n\nWe can now remove `create_symref()` and any code associated with it\nwhich is no longer used. We remove `create_symref()` code from all the\nreference backends and also remove it entirely from the `ref_storage_be`\nstruct.\n\nSigned-off-by: Karthik Nayak <karthik.188@gmail.com>\n---\n refs/debug.c            | 13 -------\n refs/files-backend.c    | 67 --------------------------------\n refs/packed-backend.c   |  1 -\n refs/refs-internal.h    |  5 ---\n refs/reftable-backend.c | 86 -----------------------------------------\n 5 files changed, 172 deletions(-)\n\ndiff --git a/refs/debug.c b/refs/debug.c\nindex c7531b17f0..8be316bb67 100644\n--- a/refs/debug.c\n+++ b/refs/debug.c\n@@ -131,18 +131,6 @@ static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *o\n \treturn res;\n }\n \n-static int debug_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *ref_name, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;\n-\tint res = drefs->refs->be->create_symref(drefs->refs, ref_name, target,\n-\t\t\t\t\t\t logmsg);\n-\ttrace_printf_key(&trace_refs, \"create_symref: %s -> %s \\\"%s\\\": %d\\n\", ref_name,\n-\t\ttarget, logmsg, res);\n-\treturn res;\n-}\n-\n static int debug_rename_ref(struct ref_store *ref_store, const char *oldref,\n \t\t\t    const char *newref, const char *logmsg)\n {\n@@ -441,7 +429,6 @@ struct ref_storage_be refs_be_debug = {\n \t.initial_transaction_commit = debug_initial_transaction_commit,\n \n \t.pack_refs = debug_pack_refs,\n-\t.create_symref = debug_create_symref,\n \t.rename_ref = debug_rename_ref,\n \t.copy_ref = debug_copy_ref,\n \ndiff --git a/refs/files-backend.c b/refs/files-backend.c\nindex 2d1525b240..3957bfa579 100644\n--- a/refs/files-backend.c\n+++ b/refs/files-backend.c\n@@ -1903,23 +1903,6 @@ static int create_ref_symlink(struct ref_lock *lock, const char *target)\n \treturn ret;\n }\n \n-static void update_symref_reflog(struct files_ref_store *refs,\n-\t\t\t\t struct ref_lock *lock, const char *refname,\n-\t\t\t\t const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct object_id new_oid;\n-\n-\tif (logmsg &&\n-\t    refs_resolve_ref_unsafe(&refs->base, target,\n-\t\t\t\t    RESOLVE_REF_READING, &new_oid, NULL) &&\n-\t    files_log_ref_write(refs, refname, &lock->old_oid,\n-\t\t\t\t&new_oid, logmsg, 0, &err)) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t}\n-}\n-\n static int create_symref_lock(struct files_ref_store *refs,\n \t\t\t      struct ref_lock *lock, const char *refname,\n \t\t\t      const char *target, struct strbuf *err)\n@@ -1939,55 +1922,6 @@ static int create_symref_lock(struct files_ref_store *refs,\n \treturn 0;\n }\n \n-static int create_and_commit_symref(struct files_ref_store *refs,\n-\t\t\t\t    struct ref_lock *lock, const char *refname,\n-\t\t\t\t    const char *target, const char *logmsg)\n-{\n-\tstruct strbuf err = STRBUF_INIT;\n-\tint ret;\n-\n-\tif (prefer_symlink_refs && !create_ref_symlink(lock, target)) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\t\treturn 0;\n-\t}\n-\n-\tret = create_symref_lock(refs, lock, refname, target, &err);\n-\tif (!ret) {\n-\t\tupdate_symref_reflog(refs, lock, refname, target, logmsg);\n-\n-\t\tif (commit_ref(lock) < 0)\n-\t\t\treturn error(\"unable to write symref for %s: %s\", refname,\n-\t\t\t\t     strerror(errno));\n-\t} else {\n-\t\treturn error(\"%s\", err.buf);\n-\t}\n-\n-\treturn ret;\n-}\n-\n-static int files_create_symref(struct ref_store *ref_store,\n-\t\t\t       const char *refname, const char *target,\n-\t\t\t       const char *logmsg)\n-{\n-\tstruct files_ref_store *refs =\n-\t\tfiles_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct strbuf err = STRBUF_INIT;\n-\tstruct ref_lock *lock;\n-\tint ret;\n-\n-\tlock = lock_ref_oid_basic(refs, refname, &err);\n-\tif (!lock) {\n-\t\terror(\"%s\", err.buf);\n-\t\tstrbuf_release(&err);\n-\t\treturn -1;\n-\t}\n-\n-\tret = create_and_commit_symref(refs, lock, refname, target, logmsg);\n-\n-\tunlock_ref(lock);\n-\treturn ret;\n-}\n-\n static int files_reflog_exists(struct ref_store *ref_store,\n \t\t\t       const char *refname)\n {\n@@ -3374,7 +3308,6 @@ struct ref_storage_be refs_be_files = {\n \t.initial_transaction_commit = files_initial_transaction_commit,\n \n \t.pack_refs = files_pack_refs,\n-\t.create_symref = files_create_symref,\n \t.rename_ref = files_rename_ref,\n \t.copy_ref = files_copy_ref,\n \ndiff --git a/refs/packed-backend.c b/refs/packed-backend.c\nindex 4e826c05ff..a937e7dbfc 100644\n--- a/refs/packed-backend.c\n+++ b/refs/packed-backend.c\n@@ -1714,7 +1714,6 @@ struct ref_storage_be refs_be_packed = {\n \t.initial_transaction_commit = packed_initial_transaction_commit,\n \n \t.pack_refs = packed_pack_refs,\n-\t.create_symref = NULL,\n \t.rename_ref = NULL,\n \t.copy_ref = NULL,\n \ndiff --git a/refs/refs-internal.h b/refs/refs-internal.h\nindex 819157256e..53a6c5d842 100644\n--- a/refs/refs-internal.h\n+++ b/refs/refs-internal.h\n@@ -566,10 +566,6 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs,\n \n typedef int pack_refs_fn(struct ref_store *ref_store,\n \t\t\t struct pack_refs_opts *opts);\n-typedef int create_symref_fn(struct ref_store *ref_store,\n-\t\t\t     const char *ref_target,\n-\t\t\t     const char *refs_heads_master,\n-\t\t\t     const char *logmsg);\n typedef int rename_ref_fn(struct ref_store *ref_store,\n \t\t\t  const char *oldref, const char *newref,\n \t\t\t  const char *logmsg);\n@@ -690,7 +686,6 @@ struct ref_storage_be {\n \tref_transaction_commit_fn *initial_transaction_commit;\n \n \tpack_refs_fn *pack_refs;\n-\tcreate_symref_fn *create_symref;\n \trename_ref_fn *rename_ref;\n \tcopy_ref_fn *copy_ref;\n \ndiff --git a/refs/reftable-backend.c b/refs/reftable-backend.c\nindex 3eab57cf95..1b92c396b6 100644\n--- a/refs/reftable-backend.c\n+++ b/refs/reftable-backend.c\n@@ -1259,91 +1259,6 @@ struct write_create_symref_arg {\n \tconst char *logmsg;\n };\n \n-static int write_create_symref_table(struct reftable_writer *writer, void *cb_data)\n-{\n-\tstruct write_create_symref_arg *create = cb_data;\n-\tuint64_t ts = reftable_stack_next_update_index(create->stack);\n-\tstruct reftable_ref_record ref = {\n-\t\t.refname = (char *)create->refname,\n-\t\t.value_type = REFTABLE_REF_SYMREF,\n-\t\t.value.symref = (char *)create->target,\n-\t\t.update_index = ts,\n-\t};\n-\tstruct reftable_log_record log = {0};\n-\tstruct object_id new_oid;\n-\tstruct object_id old_oid;\n-\tint ret;\n-\n-\treftable_writer_set_limits(writer, ts, ts);\n-\n-\tret = reftable_writer_add_ref(writer, &ref);\n-\tif (ret)\n-\t\treturn ret;\n-\n-\t/*\n-\t * Note that it is important to try and resolve the reference before we\n-\t * write the log entry. This is because `should_write_log()` will munge\n-\t * `core.logAllRefUpdates`, which is undesirable when we create a new\n-\t * repository because it would be written into the config. As HEAD will\n-\t * not resolve for new repositories this ordering will ensure that this\n-\t * never happens.\n-\t */\n-\tif (!create->logmsg ||\n-\t    !refs_resolve_ref_unsafe(&create->refs->base, create->target,\n-\t\t\t\t     RESOLVE_REF_READING, &new_oid, NULL) ||\n-\t    !should_write_log(&create->refs->base, create->refname))\n-\t\treturn 0;\n-\n-\tfill_reftable_log_record(&log);\n-\tlog.refname = xstrdup(create->refname);\n-\tlog.update_index = ts;\n-\tlog.value.update.message = xstrndup(create->logmsg,\n-\t\t\t\t\t    create->refs->write_options.block_size / 2);\n-\tmemcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ);\n-\tif (refs_resolve_ref_unsafe(&create->refs->base, create->refname,\n-\t\t\t\t    RESOLVE_REF_READING, &old_oid, NULL))\n-\t\tmemcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ);\n-\n-\tret = reftable_writer_add_log(writer, &log);\n-\treftable_log_record_release(&log);\n-\treturn ret;\n-}\n-\n-static int reftable_be_create_symref(struct ref_store *ref_store,\n-\t\t\t\t     const char *refname,\n-\t\t\t\t     const char *target,\n-\t\t\t\t     const char *logmsg)\n-{\n-\tstruct reftable_ref_store *refs =\n-\t\treftable_be_downcast(ref_store, REF_STORE_WRITE, \"create_symref\");\n-\tstruct reftable_stack *stack = stack_for(refs, refname, &refname);\n-\tstruct write_create_symref_arg arg = {\n-\t\t.refs = refs,\n-\t\t.stack = stack,\n-\t\t.refname = refname,\n-\t\t.target = target,\n-\t\t.logmsg = logmsg,\n-\t};\n-\tint ret;\n-\n-\tret = refs->err;\n-\tif (ret < 0)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_reload(stack);\n-\tif (ret)\n-\t\tgoto done;\n-\n-\tret = reftable_stack_add(stack, &write_create_symref_table, &arg);\n-\n-done:\n-\tassert(ret != REFTABLE_API_ERROR);\n-\tif (ret)\n-\t\terror(\"unable to write symref for %s: %s\", refname,\n-\t\t      reftable_error_str(ret));\n-\treturn ret;\n-}\n-\n struct write_copy_arg {\n \tstruct reftable_ref_store *refs;\n \tstruct reftable_stack *stack;\n@@ -2251,7 +2166,6 @@ struct ref_storage_be refs_be_reftable = {\n \t.initial_transaction_commit = reftable_be_initial_transaction_commit,\n \n \t.pack_refs = reftable_be_pack_refs,\n-\t.create_symref = reftable_be_create_symref,\n \t.rename_ref = reftable_be_rename_ref,\n \t.copy_ref = reftable_be_copy_ref,\n \n-- \n2.43.GIT\n\n"},{"id":"494279","messageId":"9d4c4850-6d73-472c-9ab1-fe5f6f4657d8@gmail.com","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"Re: [PATCH v8 0/8] refs: add support for transactional symref updates","fromName":"","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-05-07T15:50:05Z","receivedAt":"2024-05-07T15:50:08Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Karthik\n\nOn 07/05/2024 13:58, Karthik Nayak wrote:\n> Changes since v6:\n> * Made the check for old/new oid & target more stricter by removing\n> the exception for null oid's.\n> * Extracted `ref_update_check_old_target` and `original_update_refname`\n> to refs.c\n> * ^This allowed us to generalize the error messages for non-matching\n> old_target values between files and reftable backend.\n> * Better line wrapping in code.\n> * Fixed some grammar in commit messages.\n\nThanks for re-rolling - these changes address all of my comments on v6 \nand the range-diff below looks good.\n\nBest Wishes\n\nPhillip\n\n> Range diff against v6:\n> \n> 1:  a354190905 ! 1:  defc1b3521 refs: accept symref values in `ref_transaction_update()`\n>      @@ refs.c: struct ref_update *ref_transaction_add_update(\n>        \tif (transaction->state != REF_TRANSACTION_OPEN)\n>        \t\tBUG(\"update called for transaction that is not open\");\n>        \n>      -+\tif (old_oid && !is_null_oid(old_oid) && old_target)\n>      ++\tif (old_oid && old_target)\n>       +\t\tBUG(\"only one of old_oid and old_target should be non NULL\");\n>      -+\tif (new_oid && !is_null_oid(new_oid) && new_target)\n>      ++\tif (new_oid && new_target)\n>       +\t\tBUG(\"only one of new_oid and new_target should be non NULL\");\n>       +\n>        \tFLEX_ALLOC_STR(update, refname, refname);\n> 2:  0d9c5b9804 = 2:  54bb78a27c files-backend: extract out `create_symref_lock()`\n> 3:  e0219ffd31 = 3:  c16b7c5da0 refs: support symrefs in 'reference-transaction' hook\n> -:  ---------- > 4:  2d268f12cc refs: move `original_update_refname` to 'refs.c'\n> 4:  b22c59c722 ! 5:  7db3a2245f refs: add support for transactional symref updates\n>      @@ Commit message\n>           updates. While this is exposed to users via 'git-update-ref' and its\n>           '--stdin' mode, it is also used internally within various commands.\n>       \n>      -    However, we never supported transactional updates of symrefs. Let's add\n>      -    support for symrefs in both the 'files' and the 'reftable' backend.\n>      +    However, we do not support transactional updates of symrefs. This commit\n>      +    adds support for symrefs in both the 'files' and the 'reftable' backend.\n>       \n>           Here, we add and use `ref_update_has_null_new_value()`, a helper\n>           function which is used to check if there is a new_value in a reference\n>           update. The new value could either be a symref target `new_target` or a\n>           OID `new_oid`.\n>       \n>      -    With this, now transactional updates (verify, create, delete, update)\n>      -    can be used for:\n>      +    We also add another common function `ref_update_check_old_target` which\n>      +    will be used to check if the update's old_target corresponds to a\n>      +    reference's current target.\n>      +\n>      +    Now transactional updates (verify, create, delete, update) can be used\n>      +    for:\n>           - regular refs\n>           - symbolic refs\n>           - conversion of regular to symbolic refs and vice versa\n>      @@ refs.c: int ref_transaction_update(struct ref_transaction *transaction,\n>        \n>        \tref_transaction_add_update(transaction, refname, flags,\n>        \t\t\t\t   new_oid, old_oid, new_target,\n>      -@@ refs.c: int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg\n>      - {\n>      - \treturn refs_copy_existing_ref(get_main_ref_store(the_repository), oldref, newref, logmsg);\n>      +@@ refs.c: const char *ref_update_original_update_refname(struct ref_update *update)\n>      + \treturn update->refname;\n>        }\n>      -+\n>      +\n>       +int ref_update_has_null_new_value(struct ref_update *update)\n>       +{\n>       +\treturn !update->new_target && is_null_oid(&update->new_oid);\n>       +}\n>      -\n>      - ## refs/files-backend.c ##\n>      -@@ refs/files-backend.c: static const char *original_update_refname(struct ref_update *update)\n>      - \treturn update->refname;\n>      - }\n>      -\n>      -+/*\n>      -+ * Check whether the old_target values stored in update are consistent\n>      -+ * with current_target, which is the symbolic reference's current value.\n>      -+ * If everything is OK, return 0; otherwise, write an error message to\n>      -+ * err and return -1.\n>      -+ */\n>      -+static int check_old_target(struct ref_update *update,\n>      -+\t\t\t    const char *current_target,\n>      -+\t\t\t    struct strbuf *err)\n>      ++\n>      ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n>      ++\t\t\t\tstruct strbuf *err)\n>       +{\n>       +\tif (!update->old_target)\n>       +\t\tBUG(\"called without old_target set\");\n>       +\n>      -+\tif (!strcmp(update->old_target, current_target))\n>      ++\tif (!strcmp(referent, update->old_target))\n>       +\t\treturn 0;\n>       +\n>      -+\tif (!strcmp(current_target, \"\"))\n>      -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>      ++\tif (!strcmp(referent, \"\"))\n>      ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>       +\t\t\t    \"reference is missing but expected %s\",\n>      -+\t\t\t    original_update_refname(update),\n>      ++\t\t\t    ref_update_original_update_refname(update),\n>       +\t\t\t    update->old_target);\n>       +\telse\n>      -+\t\tstrbuf_addf(err, \"cannot lock ref '%s': \"\n>      ++\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>       +\t\t\t    \"is at %s but expected %s\",\n>      -+\t\t\t    original_update_refname(update),\n>      -+\t\t\t    current_target, update->old_target);\n>      -+\n>      ++\t\t\t    ref_update_original_update_refname(update),\n>      ++\t\t\t    referent, update->old_target);\n>       +\treturn -1;\n>       +}\n>      -+\n>      - /*\n>      -  * Check whether the REF_HAVE_OLD and old_oid values stored in update\n>      -  * are consistent with oid, which is the reference's current value. If\n>      +\n>      + ## refs/files-backend.c ##\n>      +@@ refs/files-backend.c: static int split_symref_update(struct ref_update *update,\n>      +\n>      + \tnew_update = ref_transaction_add_update(\n>      + \t\t\ttransaction, referent, new_flags,\n>      +-\t\t\t&update->new_oid, &update->old_oid,\n>      +-\t\t\tNULL, NULL, update->msg);\n>      ++\t\t\tupdate->new_target ? NULL : &update->new_oid,\n>      ++\t\t\tupdate->old_target ? NULL : &update->old_oid,\n>      ++\t\t\tupdate->new_target, update->old_target, update->msg);\n>      +\n>      + \tnew_update->parent_update = update;\n>      +\n>       @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *refs,\n>        \n>        \tfiles_assert_main_repository(refs, \"lock_ref_for_update\");\n>      @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n>       +\t\t\t}\n>       +\n>       +\t\t\tif (update->old_target) {\n>      -+\t\t\t\tif (check_old_target(update, referent.buf, err)) {\n>      ++\t\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n>       +\t\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>       +\t\t\t\t\tgoto out;\n>       +\t\t\t\t}\n>      @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n>       +\t\t * be set for symrefs, but we're strict about its usage.\n>       +\t\t */\n>       +\t\tif (update->old_target) {\n>      -+\t\t\tif (check_old_target(update, referent.buf, err)) {\n>      ++\t\t\tif (ref_update_check_old_target(referent.buf, update, err)) {\n>       +\t\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>       +\t\t\t\tgoto out;\n>       +\t\t\t}\n>      @@ refs/files-backend.c: static int lock_ref_for_update(struct files_ref_store *ref\n>       -\t    !(update->flags & REF_DELETING) &&\n>       -\t    !(update->flags & REF_LOG_ONLY)) {\n>       +\tif (update->new_target && !(update->flags & REF_LOG_ONLY)) {\n>      -+\t\tif (create_symref_lock(refs, lock, update->refname, update->new_target, err)) {\n>      ++\t\tif (create_symref_lock(refs, lock, update->refname,\n>      ++\t\t\t\t       update->new_target, err)) {\n>       +\t\t\tret = TRANSACTION_GENERIC_ERROR;\n>       +\t\t\tgoto out;\n>       +\t\t}\n>      @@ refs/files-backend.c: static int files_transaction_finish(struct ref_store *ref_\n>        \t\t\tif (commit_ref(lock)) {\n>       \n>        ## refs/refs-internal.h ##\n>      -@@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct repository *repo,\n>      +@@ refs/refs-internal.h: struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_stor\n>         */\n>      - struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store);\n>      + const char *ref_update_original_update_refname(struct ref_update *update);\n>        \n>       +/*\n>       + * Helper function to check if the new value is null, this\n>      @@ refs/refs-internal.h: void base_ref_store_init(struct ref_store *refs, struct re\n>       + * ref or a symbolic ref.\n>       + */\n>       +int ref_update_has_null_new_value(struct ref_update *update);\n>      ++\n>      ++/*\n>      ++ * Check whether the old_target values stored in update are consistent\n>      ++ * with the referent, which is the symbolic reference's current value.\n>      ++ * If everything is OK, return 0; otherwise, write an error message to\n>      ++ * err and return -1.\n>      ++ */\n>      ++int ref_update_check_old_target(const char *referent, struct ref_update *update,\n>      ++\t\t\t\tstruct strbuf *err);\n>       +\n>        #endif /* REFS_REFS_INTERNAL_H */\n>       \n>      @@ refs/reftable-backend.c: static int reftable_be_transaction_prepare(struct ref_s\n>        \t\t */\n>       -\t\tif (u->flags & REF_HAVE_OLD && !oideq(&current_oid, &u->old_oid)) {\n>       +\t\tif (u->old_target) {\n>      -+\t\t\tif (strcmp(referent.buf, u->old_target)) {\n>      -+\t\t\t\tif (!strcmp(referent.buf, \"\"))\n>      -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>      -+\t\t\t\t\t\t    \"reference is missing but expected %s\",\n>      -+\t\t\t\t\t\t    original_update_refname(u),\n>      -+\t\t\t\t\t\t    u->old_target);\n>      -+\t\t\t\telse\n>      -+\t\t\t\t\tstrbuf_addf(err, \"verifying symref target: '%s': \"\n>      -+\t\t\t\t\t\t    \"is at %s but expected %s\",\n>      -+\t\t\t\t\t\t    original_update_refname(u),\n>      -+\t\t\t\t\t\t    referent.buf, u->old_target);\n>      ++\t\t\tif (ref_update_check_old_target(referent.buf, u, err)) {\n>       +\t\t\t\tret = -1;\n>       +\t\t\t\tgoto done;\n>       +\t\t\t}\n>       +\t\t} else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {\n>        \t\t\tif (is_null_oid(&u->old_oid))\n>        \t\t\t\tstrbuf_addf(err, _(\"cannot lock ref '%s': \"\n>      - \t\t\t\t\t    \"reference already exists\"),\n>      + \t\t\t\t\t\t   \"reference already exists\"),\n>       @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writer *writer, void *cb_data\n>        \t\t * - `core.logAllRefUpdates` tells us to create the reflog for\n>        \t\t *   the given ref.\n>        \t\t */\n>       -\t\tif (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) {\n>      -+\t\tif ((u->flags & REF_HAVE_NEW) && !(u->type & REF_ISSYMREF) && ref_update_has_null_new_value(u)) {\n>      ++\t\tif ((u->flags & REF_HAVE_NEW) &&\n>      ++\t\t    !(u->type & REF_ISSYMREF) &&\n>      ++\t\t    ref_update_has_null_new_value(u)) {\n>        \t\t\tstruct reftable_log_record log = {0};\n>        \t\t\tstruct reftable_iterator it = {0};\n>        \n>      @@ refs/reftable-backend.c: static int write_transaction_table(struct reftable_writ\n>       +\t\t\t\tfill_reftable_log_record(log);\n>       +\t\t\t\tlog->update_index = ts;\n>       +\t\t\t\tlog->refname = xstrdup(u->refname);\n>      -+\t\t\t\tmemcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ);\n>      -+\t\t\t\tmemcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n>      ++\t\t\t\tmemcpy(log->value.update.new_hash,\n>      ++\t\t\t\t       u->new_oid.hash, GIT_MAX_RAWSZ);\n>      ++\t\t\t\tmemcpy(log->value.update.old_hash,\n>      ++\t\t\t\t       tx_update->current_oid.hash, GIT_MAX_RAWSZ);\n>       +\t\t\t\tlog->value.update.message =\n>       +\t\t\t\t\txstrndup(u->msg, arg->refs->write_options.block_size / 2);\n>       +\t\t\t}\n> 5:  636bf5ce98 = 6:  354ebbe17f refs: use transaction in `refs_create_symref()`\n> 6:  07fb23374f = 7:  c8a23b3454 refs: rename `refs_create_symref()` to `refs_update_symref()`\n> 7:  5c05813bcc = 8:  fa1b8f445b refs: remove `create_symref` and associated dead code\n> \n> \n> Karthik Nayak (8):\n>    refs: accept symref values in `ref_transaction_update()`\n>    files-backend: extract out `create_symref_lock()`\n>    refs: support symrefs in 'reference-transaction' hook\n>    refs: move `original_update_refname` to 'refs.c'\n>    refs: add support for transactional symref updates\n>    refs: use transaction in `refs_create_symref()`\n>    refs: rename `refs_create_symref()` to `refs_update_symref()`\n>    refs: remove `create_symref` and associated dead code\n> \n>   Documentation/githooks.txt       |  14 ++-\n>   branch.c                         |   2 +-\n>   builtin/branch.c                 |   2 +-\n>   builtin/fast-import.c            |   5 +-\n>   builtin/fetch.c                  |   2 +-\n>   builtin/receive-pack.c           |   1 +\n>   builtin/replace.c                |   2 +-\n>   builtin/tag.c                    |   1 +\n>   builtin/update-ref.c             |   1 +\n>   builtin/worktree.c               |   2 +-\n>   refs.c                           | 119 ++++++++++++++----\n>   refs.h                           |  20 ++-\n>   refs/debug.c                     |  13 --\n>   refs/files-backend.c             | 208 +++++++++++++++++--------------\n>   refs/packed-backend.c            |   1 -\n>   refs/refs-internal.h             |  40 +++++-\n>   refs/reftable-backend.c          | 183 +++++++++------------------\n>   sequencer.c                      |   9 +-\n>   t/helper/test-ref-store.c        |   2 +-\n>   t/t0610-reftable-basics.sh       |   2 +-\n>   t/t1416-ref-transaction-hooks.sh |  23 ++++\n>   walker.c                         |   2 +-\n>   22 files changed, 374 insertions(+), 280 deletions(-)\n> \n"},{"id":"494280","messageId":"xmqqy18lpoqg.fsf@gitster.g","threadId":"61235","inReplyTo":"20240507125859.132116-1-knayak@gitlab.com","subject":"Re: [PATCH v8 0/8] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-07T16:32:07Z","receivedAt":"2024-05-07T16:32:15Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> Changes since v7:\n> * I had rebased v7 on next. I've rebased v8 on master. That's the only difference\n> between the two versions.\n\nI've applied them to the same base as used to queue the previous\nround, which I think is \"436d4e5b14 The seventeenth batch\".  It went\nwithout conflicts, and tests fine in isolation.  I'll see if it plays\nwell with other topics in 'seen' later in the day but not now.\n\nThanks.\n\n> Junio, this might cause conflicts when merging, I think you resolved them for\n> v6 and hope its the same now. Let me know if I can help otherwise somehow. \n\nThe easiest for both of us would be to do this:\n\n (1) Build on whatever base you want, and format-patch the series.\n     If you are doing \"rebase -i\" in-place to update from the\n     previous round, this will reuse the previous base so (2) and\n     (3) may become trivial.\n\n (2) Find the base of where the last round was queued, something like\n\n     $ mine='kn/ref-transaction-symref'\n     $ git checkout \"origin/seen^{/^Merge branch '$mine'}...master\"\n\n (3) Apply your format-patch result.  There are three cases\n\n     (3)-1. Things apply cleanly and tests fine.  Go to (4).\n\n     (3)-2. Things apply cleanly but does not build or test fails,\n\t    or things do not apply cleanly.\n\n     In the latter case, you have textual or semantic conflicts\n     coming from the difference between the old base and the base\n     you used to build in (1).  Identify what caused the breakages\n     (e.g., a topic or two may have merged since the base used by\n     (2) until the base used by (1)).\n\n     Check out the latest 'origin/master' (which may be newer than\n     the base used by (2)), \"merge --no-ff\" the topics you newly\n     depend on in there, and use the result of the merge(s) as the\n     base, rebuild the series and test again.  Run format-patch from\n     the last such merges to the tip of your topic.  If you did\n\n     $ git checkout origin/master\n     $ git merge --no-ff --into kn/ref-transaction-symref fo/obar\n     $ git merge --no-ff --into kn/ref-transaction-symref ba/zqux\n     ... rebuild the topic ...\n\n     Then you'd just format your topic above these \"preparing the\n     ground\" merges, e.g.\n\n     $ git format-patch \"HEAD^{/^Merge branch 'ba/zqux'}\"..HEAD\n\n     Do not forget to write in the cover letter you did this,\n     including the topics you have in your base on top of 'master'.\n     Then go to (4).\n\n (4) Make a trial merge of your topic into 'next' and 'seen', e.g.\n\n     $ git checkout --detach 'origin/seen' &&\n       git revert -m 1 <the merge of the previous iteration into seen> &&\n       git merge kn/ref-transaction-symref\n\n     The \"revert\" is needed if the previous iteration of your topic\n     is already in 'seen' (like in this case).  You could choose to\n     rebuild master..origin/seen from scratch while excluding your\n     previous iteration, which may emulate what happens on my end\n     more closely.\n\n     This trial merge may conflict.  It is primarily to see what\n     conflicts _other_ topics may have with your topic.  In other\n     words, you do not have to depend on to make your topic work on\n     'master'.  It may become the job of the other topic owners to\n     resolve conflicts if your topic goes to 'next' before theirs.\n\n     Make a note on what conflict you saw in the cover letter.  You\n     do not necessarily have to resolve them, but it would be a good\n     opportunity to learn what others are doing in an related area.\n\n     $ git checkout --detach 'origin/next' &&\n       git merge kn/ref-transaction-symref\n\n     This is to see what conflicts your topic has with other topics\n     that are already cooking.  This should not conflict if (3)-2\n     prepared a base on top of updated master plus dependent topics\n     taken from 'next'.  Unless the context is severe (one way to\n     tell is try the same trial merge with your old iteration, which\n     may conflict in a similar way), expect that it will be handled\n     on my end (if it gets unmanageable, I'll ask to rebase when I\n     receive your patches).\n\nSomething like the above should be added to the SubmittingPatches\ndocument (or its successor to cover more advanced topics, perhaps).\n\nThanks.\n"},{"id":"494560","messageId":"CAOLa=ZRERwUEbj3nc+SUuiLvwJ+JK7qswxX4P6hbstX4+5vXHw@mail.gmail.com","threadId":"61235","inReplyTo":"xmqqy18lpoqg.fsf@gitster.g","subject":"Re: [PATCH v8 0/8] refs: add support for transactional symref updates","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-05-12T17:17:14Z","receivedAt":"2024-05-12T17:17:18Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Karthik Nayak <karthik.188@gmail.com> writes:\n>\n>> Changes since v7:\n>> * I had rebased v7 on next. I've rebased v8 on master. That's the only difference\n>> between the two versions.\n>\n> I've applied them to the same base as used to queue the previous\n> round, which I think is \"436d4e5b14 The seventeenth batch\".  It went\n> without conflicts, and tests fine in isolation.  I'll see if it plays\n> well with other topics in 'seen' later in the day but not now.\n>\n> Thanks.\n>\n>> Junio, this might cause conflicts when merging, I think you resolved them for\n>> v6 and hope its the same now. Let me know if I can help otherwise somehow.\n>\n> The easiest for both of us would be to do this:\n>\n>  (1) Build on whatever base you want, and format-patch the series.\n>      If you are doing \"rebase -i\" in-place to update from the\n>      previous round, this will reuse the previous base so (2) and\n>      (3) may become trivial.\n>\n>  (2) Find the base of where the last round was queued, something like\n>\n>      $ mine='kn/ref-transaction-symref'\n>      $ git checkout \"origin/seen^{/^Merge branch '$mine'}...master\"\n>\n\nI find the '...' always so confusing, I would say suggesting to use\n'git-merge-base' would be much nicer here.\n\n>  (3) Apply your format-patch result.  There are three cases\n>\n>      (3)-1. Things apply cleanly and tests fine.  Go to (4).\n>\n>      (3)-2. Things apply cleanly but does not build or test fails,\n> \t    or things do not apply cleanly.\n>\n>      In the latter case, you have textual or semantic conflicts\n>      coming from the difference between the old base and the base\n>      you used to build in (1).  Identify what caused the breakages\n>      (e.g., a topic or two may have merged since the base used by\n>      (2) until the base used by (1)).\n>\n>      Check out the latest 'origin/master' (which may be newer than\n>      the base used by (2)), \"merge --no-ff\" the topics you newly\n\nFor my own understanding, even if we use '--ff' the end result should be\nthe same, but using '--no-ff' would ensure that the changes and\nconflicts are isolated to the merge commit, right?\n\n>      depend on in there, and use the result of the merge(s) as the\n>      base, rebuild the series and test again.  Run format-patch from\n>      the last such merges to the tip of your topic.  If you did\n>\n>      $ git checkout origin/master\n>      $ git merge --no-ff --into kn/ref-transaction-symref fo/obar\n>      $ git merge --no-ff --into kn/ref-transaction-symref ba/zqux\n>      ... rebuild the topic ...\n>\n\nI guess you mean '--into-name' here? I would skip mentioning this since\nit doesn't have any real effect and is perhaps confusing.\n\n>      Then you'd just format your topic above these \"preparing the\n>      ground\" merges, e.g.\n>\n>      $ git format-patch \"HEAD^{/^Merge branch 'ba/zqux'}\"..HEAD\n>\n>      Do not forget to write in the cover letter you did this,\n>      including the topics you have in your base on top of 'master'.\n>      Then go to (4).\n>\n>  (4) Make a trial merge of your topic into 'next' and 'seen', e.g.\n>\n>      $ git checkout --detach 'origin/seen' &&\n>        git revert -m 1 <the merge of the previous iteration into seen> &&\n>        git merge kn/ref-transaction-symref\n>\n>      The \"revert\" is needed if the previous iteration of your topic\n>      is already in 'seen' (like in this case).  You could choose to\n>      rebuild master..origin/seen from scratch while excluding your\n>      previous iteration, which may emulate what happens on my end\n>      more closely.\n>\n>      This trial merge may conflict.  It is primarily to see what\n>      conflicts _other_ topics may have with your topic.  In other\n>      words, you do not have to depend on to make your topic work on\n>      'master'.  It may become the job of the other topic owners to\n>      resolve conflicts if your topic goes to 'next' before theirs.\n>\n>      Make a note on what conflict you saw in the cover letter.  You\n>      do not necessarily have to resolve them, but it would be a good\n>      opportunity to learn what others are doing in an related area.\n>\n>      $ git checkout --detach 'origin/next' &&\n>        git merge kn/ref-transaction-symref\n>\n>      This is to see what conflicts your topic has with other topics\n>      that are already cooking.  This should not conflict if (3)-2\n>      prepared a base on top of updated master plus dependent topics\n>      taken from 'next'.  Unless the context is severe (one way to\n>      tell is try the same trial merge with your old iteration, which\n>      may conflict in a similar way), expect that it will be handled\n>      on my end (if it gets unmanageable, I'll ask to rebase when I\n>      receive your patches).\n>\n> Something like the above should be added to the SubmittingPatches\n> document (or its successor to cover more advanced topics, perhaps).\n>\n> Thanks.\n\nThe rest of this looks good, I'll cleanup add the appropriate syntax,\nmerge in your patches [1] and send something soon!\n\n[1]: https://lore.kernel.org/all/20240510165526.1412338-1-gitster@pobox.com/#t\n"},{"id":"494640","messageId":"xmqqy18dfx9w.fsf@gitster.g","threadId":"61235","inReplyTo":"CAOLa=ZRERwUEbj3nc+SUuiLvwJ+JK7qswxX4P6hbstX4+5vXHw@mail.gmail.com","subject":"Re: [PATCH v8 0/8] refs: add support for transactional symref updates","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2024-05-13T17:15:55Z","receivedAt":"2024-05-13T17:15:58Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Karthik Nayak <karthik.188@gmail.com> writes:\n\n> I find the '...' always so confusing, I would say suggesting to use\n> 'git-merge-base' would be much nicer here.\n\nThey are equivalent, except that \"...\" in\n\n\tgit checkout --detach A...B\n\n\nis internal as opposed to\n\n\tgit checkout --detach $(merge-base A B)\n\nthat uses one extra process.  I thought people loath spawning\nprocesses?\n\n>>      Check out the latest 'origin/master' (which may be newer than\n>>      the base used by (2)), \"merge --no-ff\" the topics you newly\n>\n> For my own understanding, even if we use '--ff' the end result should be\n> the same, but using '--no-ff' would ensure that the changes and\n> conflicts are isolated to the merge commit, right?\n\nIt will make it easy for you to look at \n\n\tgit log --first-parent --oneline master..\n\nby hiding the base commits you depend on behind a single merge\ncommit.\n\n>>      $ git merge --no-ff --into kn/ref-transaction-symref ba/zqux\n>>      ... rebuild the topic ...\n>\n> I guess you mean '--into-name' here? I would skip mentioning this since\n> it doesn't have any real effect and is perhaps confusing.\n\nAgain this is to help that \"one liner first-parent chain\" output.\n"}]}