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>.
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.
Signed-off-by: Junio C Hamano <gitster@pobox.com> ---
Documentation/git-update-ref.adoc | 9 ++++++++ builtin/update-ref.c | 36 +++++++++++++++++++++++++++++-- t/t1400-update-ref.sh | 29 +++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-)
Show changes to 3 files +72 −2
Documentation/git-update-ref.adoc, builtin/update-ref.c, t/t1400-update-ref.sh
diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc index 37a5019a8b..a622f2512b 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>. 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. + 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..1ae2dac6c5 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,36 @@ 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 (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); + + 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..fcb986d485 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -2455,4 +2455,33 @@ 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 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_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 +' + test_done
-- 2.54.0-618-gdfae347457