From: Junio C Hamano <gitster@pobox.com>
To: git@vger.kernel.org
Cc: Patrick Steinhardt <ps@pks.im>
Subject: [PATCH v3] update-ref: add --rename option
Date: Thu, 11 Jun 2026 14:37:53 -0700 [thread overview]
Message-ID: <xmqq7bo4n4ge.fsf@gitster.g> (raw)
In-Reply-To: <xmqqv7brz9ba.fsf@gitster.g> (Junio C. Hamano's message of "Tue, 09 Jun 2026 14:35:21 -0700")
Add a "--rename" option to "git update-ref" with the syntax:
$ git update-ref --rename <old-refname> <new-refname>
It renames <old-refname> together with its reflog to <new-refname>;
even when used on a local branch ref, the current value and the
reflog of the ref are the only things that are renamed. Document it
and redirect casual users to "git branch -m" if that is what they
wanted to do.
Because the "--stdin" mode wants to operate on its refs in a
reference transaction, and the API function refs_rename_ref() does
not work well as part of a transaction, it is currently not possible
to add a corresponding "rename" verb to the "--stdin" mode before
the underlying API learns to rename refs atomically inside a
transaction. It hence is left for a future refactoring.
Signed-off-by: Junio C Hamano <gitster@pobox.com>
---
* As a single patch topic, the range-diff relative to v2 is at the
end of the message.
- Simplified the proposed commit log message a bit.
- Dropped mention of --[no-]deref from the synopsis section.
- Reworded documentation with help from Patrick.
Documentation/git-update-ref.adoc | 9 +++++++++
builtin/update-ref.c | 32 +++++++++++++++++++++++++++++--
t/t1400-update-ref.sh | 24 +++++++++++++++++++++++
3 files changed, 63 insertions(+), 2 deletions(-)
diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc
index 37a5019a8b..3b4df23a86 100644
--- a/Documentation/git-update-ref.adoc
+++ b/Documentation/git-update-ref.adoc
@@ -9,6 +9,7 @@ SYNOPSIS
--------
[synopsis]
git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]
+git update-ref [-m <reason>] --rename <old-refname> <new-refname>
git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]
git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]
@@ -39,6 +40,14 @@ the result of following the symbolic pointers.
With `-d`, it deletes the named <ref> after verifying that it
still contains <old-oid>.
+With `--rename`, it renames <old-refname> together with its reflog to
+<new-refname>. The command fails if <old-refname> does not exist, or
+if <new-refname> already exists. The command does not update any
+symbolic references pointing to the renamed reference, and neither
+does it update `.git/config` tracking settings. It is thus not
+recommended to use it for renaming local branches. Use `git branch -m`
+instead.
+
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 2d68c40ecb..65ee8af08c 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -15,6 +15,7 @@
static const char * const git_update_ref_usage[] = {
N_("git update-ref [<options>] -d <refname> [<old-oid>]"),
N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"),
+ N_("git update-ref [<options>] --rename <old-refname> <new-refname>"),
N_("git update-ref [<options>] --stdin [-z] [--batch-updates]"),
NULL
};
@@ -756,13 +757,14 @@ int cmd_update_ref(int argc,
{
const char *refname, *oldval;
struct object_id oid, oldoid;
- int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
+ int delete = 0, rename = 0, no_deref = 0, read_stdin = 0, end_null = 0;
int create_reflog = 0;
unsigned int flags = 0;
struct option options[] = {
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
+ OPT_BOOL( 0 , "rename", &rename, N_("rename the reference")),
OPT_BOOL( 0 , "no-deref", &no_deref,
N_("update <refname> not the one it points to")),
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
@@ -787,7 +789,7 @@ int cmd_update_ref(int argc,
}
if (read_stdin) {
- if (delete || argc > 0)
+ if (delete || rename || argc > 0)
usage_with_options(git_update_ref_usage, options);
if (end_null)
line_termination = '\0';
@@ -800,6 +802,32 @@ int cmd_update_ref(int argc,
if (end_null)
usage_with_options(git_update_ref_usage, options);
+ if (rename) {
+ const char *oldref, *newref;
+
+ if (delete || argc != 2)
+ usage_with_options(git_update_ref_usage, options);
+
+ oldref = argv[0];
+ newref = argv[1];
+
+ if (check_refname_format(oldref, 0))
+ die("invalid ref format: %s", oldref);
+ if (check_refname_format(newref, 0))
+ die("invalid ref format: %s", newref);
+
+ if (!refs_ref_exists(get_main_ref_store(the_repository), oldref))
+ die("no ref named '%s'", oldref);
+
+ if (refs_ref_exists(get_main_ref_store(the_repository), newref))
+ die("ref '%s' already exists", newref);
+
+ if (refs_rename_ref(get_main_ref_store(the_repository),
+ oldref, newref, msg))
+ die("rename failed");
+ return 0;
+ }
+
if (delete) {
if (argc < 1 || argc > 2)
usage_with_options(git_update_ref_usage, options);
diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh
index b2858a9061..4330cad282 100755
--- a/t/t1400-update-ref.sh
+++ b/t/t1400-update-ref.sh
@@ -2455,4 +2455,28 @@ test_expect_success 'dangling symref overwritten without old oid' '
test_must_fail git rev-parse --verify refs/heads/does-not-exist
'
+test_expect_success '--rename fails if old-refname does not exist' '
+ test_must_fail git update-ref --rename refs/tags/no-such-ref refs/tags/new-ref 2>err &&
+ test_grep "no ref named .refs/tags/no-such-ref." err
+'
+
+test_expect_success '--rename fails if new-refname does exist' '
+ git update-ref refs/tags/existing HEAD &&
+ git update-ref refs/tags/old-ref HEAD &&
+ test_must_fail git update-ref --rename refs/tags/old-ref refs/tags/existing 2>err &&
+ test_grep "ref .refs/tags/existing. already exists" err
+'
+
+test_expect_success '--rename moves old-refname and its reflog to new-refname' '
+ test_config core.logallrefupdates always &&
+ git update-ref -m "old tag" refs/tags/old-tag HEAD &&
+ git update-ref -m "to new" --rename refs/tags/old-tag refs/tags/new-tag 2>err &&
+ test_must_be_empty err &&
+ git show-ref --exists refs/tags/new-tag &&
+ test_must_fail git show-ref --exists refs/tags/old-tag &&
+ git log -g refs/tags/new-tag >output &&
+ test_grep "old tag" output &&
+ test_grep "to new" output
+'
+
test_done
Range-diff against v2:
1: 00cd13fda7 ! 1: a54c2d4d68 update-ref: add --rename option
@@ Commit message
$ git update-ref --rename <old-refname> <new-refname>
- It renames <old-refname> together with its reflog to <new-refname>
- (even when used on a local branch ref, the current value and the
- reflog of the ref are the only things that are renamed). As the
- command is a low-level plumbing command, attempts to rename branches
- are not warned, but we document it to draw attention of unsuspecting
- users and protect them from burning themselves.
+ It renames <old-refname> together with its reflog to <new-refname>;
+ even when used on a local branch ref, the current value and the
+ reflog of the ref are the only things that are renamed. Document it
+ and redirect casual users to "git branch -m" if that is what they
+ wanted to do.
Because the "--stdin" mode wants to operate on its refs in a
reference transaction, and the API function refs_rename_ref() does
@@ Documentation/git-update-ref.adoc: SYNOPSIS
--------
[synopsis]
git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]
-+git update-ref [-m <reason>] [--no-deref] --rename <old-refname> <new-refname>
++git update-ref [-m <reason>] --rename <old-refname> <new-refname>
git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]
git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]
@@ Documentation/git-update-ref.adoc: the result of following the symbolic pointers
+With `--rename`, it renames <old-refname> together with its reflog to
+<new-refname>. The command fails if <old-refname> does not exist, or
-+if <new-refname> already exists. Because `git update-ref` does not
-+update active worktree `HEAD` symbolic references or `.git/config`
-+tracking settings when you rename a local branch in the `refs/heads/`
-+hierarchy, think twice before using this command to rename a local
-+branch (use `git branch -m` instead).
++if <new-refname> already exists. The command does not update any
++symbolic references pointing to the renamed reference, and neither
++does it update `.git/config` tracking settings. It is thus not
++recommended to use it for renaming local branches. Use `git branch -m`
++instead.
+
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
--
2.55.0-rc0-119-ga57a595f62
prev parent reply other threads:[~2026-06-11 21:37 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-09 21:35 [PATCH] update-ref: add --rename option Junio C Hamano
2026-06-10 21:28 ` [PATCH v2] " Junio C Hamano
2026-06-11 13:05 ` Patrick Steinhardt
2026-06-11 18:47 ` Junio C Hamano
2026-06-11 21:37 ` Junio C Hamano [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=xmqq7bo4n4ge.fsf@gitster.g \
--to=gitster@pobox.com \
--cc=git@vger.kernel.org \
--cc=ps@pks.im \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox