[PATCH v4 0/5] builtin/refs: add ability to write references
From: Patrick Steinhardt <hidden>
Date: 2026-07-06 13:27:13
Hi,
Reference-related functionality in Git is currently spread across many
different commands: git-update-ref(1), git-for-each-ref(1),
git-show-ref(1), git-pack-refs(1) and git-symbolic-ref(1). This makes it
hard for users to discover what functionality we have available to work
with references.
We have thus started to consolidate this functionality into git-refs(1),
which is a toolbox of everything related to references. Until now, the
command doesn't handle functionality of git-update-ref(1).
This patch series backfills most of the functionality by introducing
three new commands:
- `git refs delete` to delete references. This is the equivalent of
`git update-ref -d`.
- `git refs update` to update references. This is the equivalent of
`git update-ref <refname> <oldvalue> <newvalue>`.
- `git refs rename` to rename a reference, including its reflog. This
does not have an equivalent in git-update-ref(1), but is inspired by
and supersedes [1].
Changes in v4:
- Add a couple more tests around symrefs.
- Use a subshell in one of the tests for consistency.
- Link to v3: https://patch.msgid.link/20260630-pks-refs-writing-subcommands-v3-0-deb04de1ecef@pks.im
Changes in v3:
- Fix confused error message.
- Link to v2: https://patch.msgid.link/20260617-pks-refs-writing-subcommands-v2-0-07f3d18336f9@pks.im
Changes in v2:
- Add a new "create" subcommand.
- Consistently quote in error messages.
- Consistently use `<old-value>` in the synopsis.
- Don't return negative exit codes.
- Improve documentation of "update" subcommand to mention that you can
create and delete branches.
- Add tests to verify that we can use "update" to do this, both in
racy and raceless ways.
- Add missing calls to `repo_config()`.
- Drop useless `GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME` variable.
- Link to v1: https://patch.msgid.link/20260616-pks-refs-writing-subcommands-v1-0-9f5219b6109d@pks.im
Thanks!
Patrick
[1]: <xmqqv7brz9ba.fsf@gitster.g>
---
Patrick Steinhardt (5):
builtin/refs: drop `the_repository`
builtin/refs: add "delete" subcommand
builtin/refs: add "update" subcommand
builtin/refs: add "create" subcommand
builtin/refs: add "rename" subcommand
Documentation/git-refs.adoc | 40 +++++++
builtin/refs.c | 222 ++++++++++++++++++++++++++++++++++--
t/meson.build | 4 +
t/t1464-refs-delete.sh | 152 +++++++++++++++++++++++++
t/t1465-refs-update.sh | 268 ++++++++++++++++++++++++++++++++++++++++++++
t/t1466-refs-create.sh | 151 +++++++++++++++++++++++++
t/t1467-refs-rename.sh | 144 ++++++++++++++++++++++++
7 files changed, 973 insertions(+), 8 deletions(-)
Range-diff versus v3:
1: dc87ed0ebc = 1: 3b40441317 builtin/refs: drop `the_repository`
2: cec7d978f1 ! 2: 8089847912 builtin/refs: add "delete" subcommand
@@ t/t1464-refs-delete.sh (new)
+test_expect_success 'delete without oldvalue verification' '
+ test_when_finished "rm -rf repo" &&
+ setup_repo repo &&
-+ A=$(git -C repo rev-parse A) &&
-+ git -C repo update-ref refs/heads/foo $A &&
-+ git -C repo refs delete refs/heads/foo &&
-+ test_must_fail git -C repo show-ref --verify -q refs/heads/foo
++ (
++ cd repo &&
++ A=$(git rev-parse A) &&
++ git update-ref refs/heads/foo $A &&
++ git refs delete refs/heads/foo &&
++ test_must_fail git refs exists refs/heads/foo
++ )
+'
+
+test_expect_success 'delete with matching oldvalue' '
@@ t/t1464-refs-delete.sh (new)
+ )
+'
+
++test_expect_success 'delete symref with --no-deref verifies target OID' '
++ test_when_finished "rm -rf repo" &&
++ setup_repo repo &&
++ (
++ cd repo &&
++ A=$(git rev-parse A) &&
++ B=$(git rev-parse B) &&
++ git update-ref refs/heads/foo $A &&
++ git symbolic-ref refs/heads/symref refs/heads/foo &&
++
++ test_must_fail git refs delete --no-deref refs/heads/symref $B &&
++ git refs exists refs/heads/symref &&
++
++ git refs delete --no-deref refs/heads/symref $A &&
++ test_must_fail git refs exists refs/heads/symref &&
++ git refs exists refs/heads/foo
++ )
++'
++
+test_expect_success 'delete with message records reason in reflog' '
+ test_when_finished "rm -rf repo" &&
+ setup_repo repo &&
3: 8e73b0f711 = 3: fb830f8f9e builtin/refs: add "update" subcommand
4: f5ad0c9b18 = 4: f3c17471c1 builtin/refs: add "create" subcommand
5: 5c01f2e828 ! 5: 82e4efb2a9 builtin/refs: add "rename" subcommand
@@ t/t1467-refs-rename.sh (new)
+ )
+'
+
++test_expect_success 'rename with symbolic ref fails' '
++ test_when_finished "rm -rf repo" &&
++ setup_repo repo &&
++ (
++ cd repo &&
++ A=$(git rev-parse A) &&
++ git refs create refs/heads/target $A &&
++ git symbolic-ref refs/heads/symref refs/heads/target &&
++ ! git refs rename refs/heads/symref refs/heads/renamed 2>err &&
++ test_grep "is a symbolic ref, .* not supported" err
++ )
++'
++
+test_expect_success 'rename with empty message fails' '
+ test_when_finished "rm -rf repo" &&
+ setup_repo repo &&
---
base-commit: 700432b2ba22603a0bcb71475c9c333d17c9b0d1
change-id: 20260616-pks-refs-writing-subcommands-7a77be5bda9b