From: Junio C Hamano <gitster@pobox.com>
To: git@vger.kernel.org
Subject: [PATCH v2] update-ref: add --rename option
Date: Wed, 10 Jun 2026 14:28:00 -0700 [thread overview]
Message-ID: <20260610212800.2892146-1-gitster@pobox.com> (raw)
In-Reply-To: <xmqqv7brz9ba.fsf@gitster.g>
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). 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.
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>
---
* The initial draft I sent had a warning when the command is used
to rename local branches, but that is unusual for plumbing
commands that should do one thing it is designed for consistently
well without being chatty. This version only has words of warning
in the documentation.
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..0c27efaa52 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>] [--no-deref] --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. 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).
+
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 v1:
1: 79af0ef7d1 ! 1: 00cd13fda7 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>.
+ 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.
- The command warns to use "git branch --rename" instead if old or new
- refname falls inside refs/heads/. We issue this warning (but perform
- the rename anyway) because "git update-ref" acts as a low-level
- plumbing utility: unlike "git branch", it does not update active
- worktree "HEAD" symbolic references or ".git/config" branch tracking
- configuration.
-
- Note that we do not add a corresponding "rename" verb to the "--stdin"
- mode in this commit. While adding a "rename SP <oldref> SP <newref> LF"
- command to the batch stream would be useful, operations in "--stdin"
- mode are executed within an atomic "ref_transaction". Reference renaming
- is currently implemented at the backend level via standalone, non-transactional
- calls (e.g. "refs_rename_ref()"). Supporting renames in a batch transaction
- would require extending "struct ref_transaction" and the reference storage
- backend APIs to coordinate ref and reflog moves atomically, which is
- left for a future refactoring.
+ 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>
@@ Documentation/git-update-ref.adoc: the result of following the symbolic pointers
still contains <old-oid>.
+With `--rename`, it renames <old-refname> together with its reflog to
-+<new-refname>. If either <old-refname> or <new-refname> falls inside
-+`refs/heads/`, a warning will be issued to use `git branch --rename`
-+instead, because `git update-ref` does not update active worktree
-+`HEAD` symbolic references or `.git/config` tracking settings. It
-+fails if <old-refname> does not exist, or if <new-refname> already
-+exists.
++<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).
+
With `--stdin`, update-ref reads instructions from standard input and
performs all modifications together. Specify commands of the form:
@@ builtin/update-ref.c: int cmd_update_ref(int argc,
+ if (check_refname_format(newref, 0))
+ die("invalid ref format: %s", newref);
+
-+ if (starts_with(oldref, "refs/heads/") ||
-+ starts_with(newref, "refs/heads/"))
-+ warning(_("You may want 'git branch --rename' instead?"));
-+
+ if (!refs_ref_exists(get_main_ref_store(the_repository), oldref))
+ die("no ref named '%s'", oldref);
+
@@ t/t1400-update-ref.sh: test_expect_success 'dangling symref overwritten without
+ test_grep "ref .refs/tags/existing. already exists" err
+'
+
-+test_expect_success '--rename warns if old or new refname falls inside refs/heads/' '
-+ git update-ref refs/heads/old-branch HEAD &&
-+ git update-ref --rename refs/heads/old-branch refs/heads/new-branch 2>err &&
-+ test_grep "branch --rename. instead" err &&
-+ git rev-parse --verify refs/heads/new-branch &&
-+ test_must_fail git rev-parse --verify refs/heads/old-branch
-+'
-+
+test_expect_success '--rename moves old-refname and its reflog to new-refname' '
-+ git update-ref -m "initial tag" refs/tags/old-tag HEAD &&
-+ git update-ref --rename refs/tags/old-tag refs/tags/new-tag 2>err &&
++ 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 rev-parse --verify refs/tags/new-tag &&
-+ test_must_fail git rev-parse --verify refs/tags/old-tag &&
-+ git log -g refs/tags/new-tag
++ 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
--
2.54.0-615-g639a4a7340
prev parent reply other threads:[~2026-06-10 21:28 UTC|newest]
Thread overview: 2+ 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 ` 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=20260610212800.2892146-1-gitster@pobox.com \
--to=gitster@pobox.com \
--cc=git@vger.kernel.org \
/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