{"thread":{"id":"65782","subject":"[PATCH] update-ref: add --rename option","startedAt":"2026-06-09T21:35:24Z","lastAt":"2026-06-16T07:42:06Z","messageCount":12,"participants":["Junio C Hamano","Patrick Steinhardt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"545094","messageId":"xmqqv7brz9ba.fsf@gitster.g","threadId":"65782","inReplyTo":null,"subject":"[PATCH] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-09T21:35:21Z","receivedAt":"2026-06-09T21:35:24Z","isPatch":true,"body":"Add a \"--rename\" option to \"git update-ref\" with the syntax:\n\n $ git update-ref --rename <old-refname> <new-refname>\n\nIt renames <old-refname> together with its reflog to <new-refname>.\n\nThe command warns to use \"git branch --rename\" instead if old or new\nrefname falls inside refs/heads/. We issue this warning (but perform\nthe rename anyway) because \"git update-ref\" acts as a low-level\nplumbing utility: unlike \"git branch\", it does not update active\nworktree \"HEAD\" symbolic references or \".git/config\" branch tracking\nconfiguration.\n\nNote that we do not add a corresponding \"rename\" verb to the \"--stdin\"\nmode in this commit. While adding a \"rename SP <oldref> SP <newref> LF\"\ncommand to the batch stream would be useful, operations in \"--stdin\"\nmode are executed within an atomic \"ref_transaction\". Reference renaming\nis currently implemented at the backend level via standalone, non-transactional\ncalls (e.g. \"refs_rename_ref()\"). Supporting renames in a batch transaction\nwould require extending \"struct ref_transaction\" and the reference storage\nbackend APIs to coordinate ref and reflog moves atomically, which is\nleft for a future refactoring.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n Documentation/git-update-ref.adoc |  9 ++++++++\n builtin/update-ref.c              | 36 +++++++++++++++++++++++++++++--\n t/t1400-update-ref.sh             | 29 +++++++++++++++++++++++++\n 3 files changed, 72 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\nindex 37a5019a8b..a622f2512b 100644\n--- a/Documentation/git-update-ref.adoc\n+++ b/Documentation/git-update-ref.adoc\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [synopsis]\n git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]\n+git update-ref [-m <reason>] [--no-deref] --rename <old-refname> <new-refname>\n git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]\n git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]\n \n@@ -39,6 +40,14 @@ the result of following the symbolic pointers.\n With `-d`, it deletes the named <ref> after verifying that it\n still contains <old-oid>.\n \n+With `--rename`, it renames <old-refname> together with its reflog to\n+<new-refname>. If either <old-refname> or <new-refname> falls inside\n+`refs/heads/`, a warning will be issued to use `git branch --rename`\n+instead, because `git update-ref` does not update active worktree\n+`HEAD` symbolic references or `.git/config` tracking settings. It\n+fails if <old-refname> does not exist, or if <new-refname> already\n+exists.\n+\n With `--stdin`, update-ref reads instructions from standard input and\n performs all modifications together.  Specify commands of the form:\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..1ae2dac6c5 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -15,6 +15,7 @@\n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [<options>] -d <refname> [<old-oid>]\"),\n \tN_(\"git update-ref [<options>]    <refname> <new-oid> [<old-oid>]\"),\n+\tN_(\"git update-ref [<options>] --rename <old-refname> <new-refname>\"),\n \tN_(\"git update-ref [<options>] --stdin [-z] [--batch-updates]\"),\n \tNULL\n };\n@@ -756,13 +757,14 @@ int cmd_update_ref(int argc,\n {\n \tconst char *refname, *oldval;\n \tstruct object_id oid, oldoid;\n-\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n+\tint delete = 0, rename = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n \tint create_reflog = 0;\n \tunsigned int flags = 0;\n \n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOL('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOL( 0 , \"rename\", &rename, N_(\"rename the reference\")),\n \t\tOPT_BOOL( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n \t\tOPT_BOOL('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n@@ -787,7 +789,7 @@ int cmd_update_ref(int argc,\n \t}\n \n \tif (read_stdin) {\n-\t\tif (delete || argc > 0)\n+\t\tif (delete || rename || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n@@ -800,6 +802,36 @@ int cmd_update_ref(int argc,\n \tif (end_null)\n \t\tusage_with_options(git_update_ref_usage, options);\n \n+\tif (rename) {\n+\t\tconst char *oldref, *newref;\n+\n+\t\tif (delete || argc != 2)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\n+\t\toldref = argv[0];\n+\t\tnewref = argv[1];\n+\n+\t\tif (check_refname_format(oldref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", oldref);\n+\t\tif (check_refname_format(newref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", newref);\n+\n+\t\tif (starts_with(oldref, \"refs/heads/\") ||\n+\t\t    starts_with(newref, \"refs/heads/\"))\n+\t\t\twarning(_(\"You may want 'git branch --rename' instead?\"));\n+\n+\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n+\t\t\tdie(\"no ref named '%s'\", oldref);\n+\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), newref))\n+\t\t\tdie(\"ref '%s' already exists\", newref);\n+\n+\t\tif (refs_rename_ref(get_main_ref_store(the_repository),\n+\t\t\t\t    oldref, newref, msg))\n+\t\t\tdie(\"rename failed\");\n+\t\treturn 0;\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex b2858a9061..fcb986d485 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -2455,4 +2455,33 @@ test_expect_success 'dangling symref overwritten without old oid' '\n \ttest_must_fail git rev-parse --verify refs/heads/does-not-exist\n '\n \n+test_expect_success '--rename fails if old-refname does not exist' '\n+\ttest_must_fail git update-ref --rename refs/tags/no-such-ref refs/tags/new-ref 2>err &&\n+\ttest_grep \"no ref named .refs/tags/no-such-ref.\" err\n+'\n+\n+test_expect_success '--rename fails if new-refname does exist' '\n+\tgit update-ref refs/tags/existing HEAD &&\n+\tgit update-ref refs/tags/old-ref HEAD &&\n+\ttest_must_fail git update-ref --rename refs/tags/old-ref refs/tags/existing 2>err &&\n+\ttest_grep \"ref .refs/tags/existing. already exists\" err\n+'\n+\n+test_expect_success '--rename warns if old or new refname falls inside refs/heads/' '\n+\tgit update-ref refs/heads/old-branch HEAD &&\n+\tgit update-ref --rename refs/heads/old-branch refs/heads/new-branch 2>err &&\n+\ttest_grep \"branch --rename. instead\" err &&\n+\tgit rev-parse --verify refs/heads/new-branch &&\n+\ttest_must_fail git rev-parse --verify refs/heads/old-branch\n+'\n+\n+test_expect_success '--rename moves old-refname and its reflog to new-refname' '\n+\tgit update-ref -m \"initial tag\" refs/tags/old-tag HEAD &&\n+\tgit update-ref --rename refs/tags/old-tag refs/tags/new-tag 2>err &&\n+\ttest_must_be_empty err &&\n+\tgit rev-parse --verify refs/tags/new-tag &&\n+\ttest_must_fail git rev-parse --verify refs/tags/old-tag &&\n+\tgit log -g refs/tags/new-tag\n+'\n+\n test_done\n-- \n2.54.0-618-gdfae347457\n\n"},{"id":"545201","messageId":"20260610212800.2892146-1-gitster@pobox.com","threadId":"65782","inReplyTo":"xmqqv7brz9ba.fsf@gitster.g","subject":"[PATCH v2] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-10T21:28:00Z","receivedAt":"2026-06-10T21:28:03Z","isPatch":true,"body":"Add a \"--rename\" option to \"git update-ref\" with the syntax:\n\n $ git update-ref --rename <old-refname> <new-refname>\n\nIt renames <old-refname> together with its reflog to <new-refname>\n(even when used on a local branch ref, the current value and the\nreflog of the ref are the only things that are renamed).  As the\ncommand is a low-level plumbing command, attempts to rename branches\nare not warned, but we document it to draw attention of unsuspecting\nusers and protect them from burning themselves.\n\nBecause the \"--stdin\" mode wants to operate on its refs in a\nreference transaction, and the API function refs_rename_ref() does\nnot work well as part of a transaction, it is currently not possible\nto add a corresponding \"rename\" verb to the \"--stdin\" mode before\nthe underlying API learns to rename refs atomically inside a\ntransaction.  It hence is left for a future refactoring.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * The initial draft I sent had a warning when the command is used\n   to rename local branches, but that is unusual for plumbing\n   commands that should do one thing it is designed for consistently\n   well without being chatty.  This version only has words of warning\n   in the documentation.\n\n Documentation/git-update-ref.adoc |  9 +++++++++\n builtin/update-ref.c              | 32 +++++++++++++++++++++++++++++--\n t/t1400-update-ref.sh             | 24 +++++++++++++++++++++++\n 3 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\nindex 37a5019a8b..0c27efaa52 100644\n--- a/Documentation/git-update-ref.adoc\n+++ b/Documentation/git-update-ref.adoc\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [synopsis]\n git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]\n+git update-ref [-m <reason>] [--no-deref] --rename <old-refname> <new-refname>\n git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]\n git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]\n \n@@ -39,6 +40,14 @@ the result of following the symbolic pointers.\n With `-d`, it deletes the named <ref> after verifying that it\n still contains <old-oid>.\n \n+With `--rename`, it renames <old-refname> together with its reflog to\n+<new-refname>.  The command fails if <old-refname> does not exist, or\n+if <new-refname> already exists.  Because `git update-ref` does not\n+update active worktree `HEAD` symbolic references or `.git/config`\n+tracking settings when you rename a local branch in the `refs/heads/`\n+hierarchy, think twice before using this command to rename a local\n+branch (use `git branch -m` instead).\n+\n With `--stdin`, update-ref reads instructions from standard input and\n performs all modifications together.  Specify commands of the form:\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..65ee8af08c 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -15,6 +15,7 @@\n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [<options>] -d <refname> [<old-oid>]\"),\n \tN_(\"git update-ref [<options>]    <refname> <new-oid> [<old-oid>]\"),\n+\tN_(\"git update-ref [<options>] --rename <old-refname> <new-refname>\"),\n \tN_(\"git update-ref [<options>] --stdin [-z] [--batch-updates]\"),\n \tNULL\n };\n@@ -756,13 +757,14 @@ int cmd_update_ref(int argc,\n {\n \tconst char *refname, *oldval;\n \tstruct object_id oid, oldoid;\n-\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n+\tint delete = 0, rename = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n \tint create_reflog = 0;\n \tunsigned int flags = 0;\n \n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOL('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOL( 0 , \"rename\", &rename, N_(\"rename the reference\")),\n \t\tOPT_BOOL( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n \t\tOPT_BOOL('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n@@ -787,7 +789,7 @@ int cmd_update_ref(int argc,\n \t}\n \n \tif (read_stdin) {\n-\t\tif (delete || argc > 0)\n+\t\tif (delete || rename || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n@@ -800,6 +802,32 @@ int cmd_update_ref(int argc,\n \tif (end_null)\n \t\tusage_with_options(git_update_ref_usage, options);\n \n+\tif (rename) {\n+\t\tconst char *oldref, *newref;\n+\n+\t\tif (delete || argc != 2)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\n+\t\toldref = argv[0];\n+\t\tnewref = argv[1];\n+\n+\t\tif (check_refname_format(oldref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", oldref);\n+\t\tif (check_refname_format(newref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", newref);\n+\n+\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n+\t\t\tdie(\"no ref named '%s'\", oldref);\n+\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), newref))\n+\t\t\tdie(\"ref '%s' already exists\", newref);\n+\n+\t\tif (refs_rename_ref(get_main_ref_store(the_repository),\n+\t\t\t\t    oldref, newref, msg))\n+\t\t\tdie(\"rename failed\");\n+\t\treturn 0;\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex b2858a9061..4330cad282 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -2455,4 +2455,28 @@ test_expect_success 'dangling symref overwritten without old oid' '\n \ttest_must_fail git rev-parse --verify refs/heads/does-not-exist\n '\n \n+test_expect_success '--rename fails if old-refname does not exist' '\n+\ttest_must_fail git update-ref --rename refs/tags/no-such-ref refs/tags/new-ref 2>err &&\n+\ttest_grep \"no ref named .refs/tags/no-such-ref.\" err\n+'\n+\n+test_expect_success '--rename fails if new-refname does exist' '\n+\tgit update-ref refs/tags/existing HEAD &&\n+\tgit update-ref refs/tags/old-ref HEAD &&\n+\ttest_must_fail git update-ref --rename refs/tags/old-ref refs/tags/existing 2>err &&\n+\ttest_grep \"ref .refs/tags/existing. already exists\" err\n+'\n+\n+test_expect_success '--rename moves old-refname and its reflog to new-refname' '\n+\ttest_config core.logallrefupdates always &&\n+\tgit update-ref -m \"old tag\" refs/tags/old-tag HEAD &&\n+\tgit update-ref -m \"to new\" --rename refs/tags/old-tag refs/tags/new-tag 2>err &&\n+\ttest_must_be_empty err &&\n+\tgit show-ref --exists refs/tags/new-tag &&\n+\ttest_must_fail git show-ref --exists refs/tags/old-tag &&\n+\tgit log -g refs/tags/new-tag >output &&\n+\ttest_grep \"old tag\" output &&\n+\ttest_grep \"to new\" output\n+'\n+\n test_done\n\nRange-diff against v1:\n1:  79af0ef7d1 ! 1:  00cd13fda7 update-ref: add --rename option\n    @@ Commit message\n     \n          $ git update-ref --rename <old-refname> <new-refname>\n     \n    -    It renames <old-refname> together with its reflog to <new-refname>.\n    +    It renames <old-refname> together with its reflog to <new-refname>\n    +    (even when used on a local branch ref, the current value and the\n    +    reflog of the ref are the only things that are renamed).  As the\n    +    command is a low-level plumbing command, attempts to rename branches\n    +    are not warned, but we document it to draw attention of unsuspecting\n    +    users and protect them from burning themselves.\n     \n    -    The command warns to use \"git branch --rename\" instead if old or new\n    -    refname falls inside refs/heads/. We issue this warning (but perform\n    -    the rename anyway) because \"git update-ref\" acts as a low-level\n    -    plumbing utility: unlike \"git branch\", it does not update active\n    -    worktree \"HEAD\" symbolic references or \".git/config\" branch tracking\n    -    configuration.\n    -\n    -    Note that we do not add a corresponding \"rename\" verb to the \"--stdin\"\n    -    mode in this commit. While adding a \"rename SP <oldref> SP <newref> LF\"\n    -    command to the batch stream would be useful, operations in \"--stdin\"\n    -    mode are executed within an atomic \"ref_transaction\". Reference renaming\n    -    is currently implemented at the backend level via standalone, non-transactional\n    -    calls (e.g. \"refs_rename_ref()\"). Supporting renames in a batch transaction\n    -    would require extending \"struct ref_transaction\" and the reference storage\n    -    backend APIs to coordinate ref and reflog moves atomically, which is\n    -    left for a future refactoring.\n    +    Because the \"--stdin\" mode wants to operate on its refs in a\n    +    reference transaction, and the API function refs_rename_ref() does\n    +    not work well as part of a transaction, it is currently not possible\n    +    to add a corresponding \"rename\" verb to the \"--stdin\" mode before\n    +    the underlying API learns to rename refs atomically inside a\n    +    transaction.  It hence is left for a future refactoring.\n     \n         Signed-off-by: Junio C Hamano <gitster@pobox.com>\n     \n    @@ Documentation/git-update-ref.adoc: the result of following the symbolic pointers\n      still contains <old-oid>.\n      \n     +With `--rename`, it renames <old-refname> together with its reflog to\n    -+<new-refname>. If either <old-refname> or <new-refname> falls inside\n    -+`refs/heads/`, a warning will be issued to use `git branch --rename`\n    -+instead, because `git update-ref` does not update active worktree\n    -+`HEAD` symbolic references or `.git/config` tracking settings. It\n    -+fails if <old-refname> does not exist, or if <new-refname> already\n    -+exists.\n    ++<new-refname>.  The command fails if <old-refname> does not exist, or\n    ++if <new-refname> already exists.  Because `git update-ref` does not\n    ++update active worktree `HEAD` symbolic references or `.git/config`\n    ++tracking settings when you rename a local branch in the `refs/heads/`\n    ++hierarchy, think twice before using this command to rename a local\n    ++branch (use `git branch -m` instead).\n     +\n      With `--stdin`, update-ref reads instructions from standard input and\n      performs all modifications together.  Specify commands of the form:\n    @@ builtin/update-ref.c: int cmd_update_ref(int argc,\n     +\t\tif (check_refname_format(newref, 0))\n     +\t\t\tdie(\"invalid ref format: %s\", newref);\n     +\n    -+\t\tif (starts_with(oldref, \"refs/heads/\") ||\n    -+\t\t    starts_with(newref, \"refs/heads/\"))\n    -+\t\t\twarning(_(\"You may want 'git branch --rename' instead?\"));\n    -+\n     +\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n     +\t\t\tdie(\"no ref named '%s'\", oldref);\n     +\n    @@ t/t1400-update-ref.sh: test_expect_success 'dangling symref overwritten without\n     +\ttest_grep \"ref .refs/tags/existing. already exists\" err\n     +'\n     +\n    -+test_expect_success '--rename warns if old or new refname falls inside refs/heads/' '\n    -+\tgit update-ref refs/heads/old-branch HEAD &&\n    -+\tgit update-ref --rename refs/heads/old-branch refs/heads/new-branch 2>err &&\n    -+\ttest_grep \"branch --rename. instead\" err &&\n    -+\tgit rev-parse --verify refs/heads/new-branch &&\n    -+\ttest_must_fail git rev-parse --verify refs/heads/old-branch\n    -+'\n    -+\n     +test_expect_success '--rename moves old-refname and its reflog to new-refname' '\n    -+\tgit update-ref -m \"initial tag\" refs/tags/old-tag HEAD &&\n    -+\tgit update-ref --rename refs/tags/old-tag refs/tags/new-tag 2>err &&\n    ++\ttest_config core.logallrefupdates always &&\n    ++\tgit update-ref -m \"old tag\" refs/tags/old-tag HEAD &&\n    ++\tgit update-ref -m \"to new\" --rename refs/tags/old-tag refs/tags/new-tag 2>err &&\n     +\ttest_must_be_empty err &&\n    -+\tgit rev-parse --verify refs/tags/new-tag &&\n    -+\ttest_must_fail git rev-parse --verify refs/tags/old-tag &&\n    -+\tgit log -g refs/tags/new-tag\n    ++\tgit show-ref --exists refs/tags/new-tag &&\n    ++\ttest_must_fail git show-ref --exists refs/tags/old-tag &&\n    ++\tgit log -g refs/tags/new-tag >output &&\n    ++\ttest_grep \"old tag\" output &&\n    ++\ttest_grep \"to new\" output\n     +'\n     +\n      test_done\n-- \n2.54.0-615-g639a4a7340\n\n"},{"id":"545268","messageId":"aiqytJD-rcEirhgE@pks.im","threadId":"65782","inReplyTo":"20260610212800.2892146-1-gitster@pobox.com","subject":"Re: [PATCH v2] update-ref: add --rename option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-11T13:05:56Z","receivedAt":"2026-06-11T13:06:03Z","isPatch":true,"body":"On Wed, Jun 10, 2026 at 02:28:00PM -0700, Junio C Hamano wrote:\n> Add a \"--rename\" option to \"git update-ref\" with the syntax:\n> \n>  $ git update-ref --rename <old-refname> <new-refname>\n> \n> It renames <old-refname> together with its reflog to <new-refname>\n> (even when used on a local branch ref, the current value and the\n> reflog of the ref are the only things that are renamed).  As the\n> command is a low-level plumbing command, attempts to rename branches\n> are not warned, but we document it to draw attention of unsuspecting\n> users and protect them from burning themselves.\n\nThis sentence reads a tiny bit awkward now and is probably an artifact\nof the change between v1 and v2.\n\n> Because the \"--stdin\" mode wants to operate on its refs in a\n> reference transaction, and the API function refs_rename_ref() does\n> not work well as part of a transaction, it is currently not possible\n> to add a corresponding \"rename\" verb to the \"--stdin\" mode before\n> the underlying API learns to rename refs atomically inside a\n> transaction.  It hence is left for a future refactoring.\n\nFair. I thought at times about extending reference transactions to also\nfully support renames, but I never had a good use case where it would\nreally matter.\n\n>  * The initial draft I sent had a warning when the command is used\n>    to rename local branches, but that is unusual for plumbing\n>    commands that should do one thing it is designed for consistently\n>    well without being chatty.  This version only has words of warning\n>    in the documentation.\n\nGood, I just wanted to complain about that warning. I think it's good to\njust accept this as-is. I really doubt that folks would go out of their\nway to use git-update-ref(1) if all they want was to rename their\nbranch.\n\nOne thing that I'm missing from the commit message: what's the\nmotivation for this new mode?\n\n> diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\n> index 37a5019a8b..0c27efaa52 100644\n> --- a/Documentation/git-update-ref.adoc\n> +++ b/Documentation/git-update-ref.adoc\n> @@ -39,6 +40,14 @@ the result of following the symbolic pointers.\n>  With `-d`, it deletes the named <ref> after verifying that it\n>  still contains <old-oid>.\n>  \n> +With `--rename`, it renames <old-refname> together with its reflog to\n> +<new-refname>.  The command fails if <old-refname> does not exist, or\n> +if <new-refname> already exists.  Because `git update-ref` does not\n> +update active worktree `HEAD` symbolic references or `.git/config`\n> +tracking settings when you rename a local branch in the `refs/heads/`\n> +hierarchy, think twice before using this command to rename a local\n> +branch (use `git branch -m` instead).\n\nI'd rephrase this slightly to first document behaviour and then draw the\nconclusion that it shouldn't be used in many cases separately. For\nexample:\n\n    This command does not update any symbolic references pointing to\n    the renamed reference, and neither does it update `.git/config`\n    tracking settings. It is thus not recommended to use it for renaming\n    local branches. Use `git branch -m` instead.\n\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 2d68c40ecb..65ee8af08c 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -787,7 +789,7 @@ int cmd_update_ref(int argc,\n>  \t}\n>  \n>  \tif (read_stdin) {\n> -\t\tif (delete || argc > 0)\n> +\t\tif (delete || rename || argc > 0)\n>  \t\t\tusage_with_options(git_update_ref_usage, options);\n>  \t\tif (end_null)\n>  \t\t\tline_termination = '\\0';\n\nOkay, so here we make it mutually exclusive with \"--delete\".\n\n> @@ -800,6 +802,32 @@ int cmd_update_ref(int argc,\n>  \tif (end_null)\n>  \t\tusage_with_options(git_update_ref_usage, options);\n>  \n> +\tif (rename) {\n> +\t\tconst char *oldref, *newref;\n> +\n> +\t\tif (delete || argc != 2)\n> +\t\t\tusage_with_options(git_update_ref_usage, options);\n\nAnd here with \"-d\". Good.\n\n> +\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n> +\t\t\tdie(\"no ref named '%s'\", oldref);\n> +\n> +\t\tif (refs_ref_exists(get_main_ref_store(the_repository), newref))\n> +\t\t\tdie(\"ref '%s' already exists\", newref);\n> +\n> +\t\tif (refs_rename_ref(get_main_ref_store(the_repository),\n> +\t\t\t\t    oldref, newref, msg))\n> +\t\t\tdie(\"rename failed\");\n> +\t\treturn 0;\n> +\t}\n\nHm. I think we're not using \"--deref\" / \"--no-deref\" at all, but we\ndocument this flag as accepted in the synopsis.\n\nThanks!\n\nPatrick\n"},{"id":"545310","messageId":"xmqqwlw4nccr.fsf@gitster.g","threadId":"65782","inReplyTo":"aiqytJD-rcEirhgE@pks.im","subject":"Re: [PATCH v2] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-11T18:47:16Z","receivedAt":"2026-06-11T18:47:19Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> One thing that I'm missing from the commit message: what's the\n> motivation for this new mode?\n\nMaintenance of merge-fix database, a kludgy way to manage evil\nmerges that are needed to deal with inter-topic semantic crashes.\n\nIf you are really interested, see the appendix.\n\n>> diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\n>> index 37a5019a8b..0c27efaa52 100644\n>> --- a/Documentation/git-update-ref.adoc\n>> +++ b/Documentation/git-update-ref.adoc\n>> @@ -39,6 +40,14 @@ the result of following the symbolic pointers.\n>>  With `-d`, it deletes the named <ref> after verifying that it\n>>  still contains <old-oid>.\n>>  \n>> +With `--rename`, it renames <old-refname> together with its reflog to\n>> +<new-refname>.  The command fails if <old-refname> does not exist, or\n>> +if <new-refname> already exists.  Because `git update-ref` does not\n>> +update active worktree `HEAD` symbolic references or `.git/config`\n>> +tracking settings when you rename a local branch in the `refs/heads/`\n>> +hierarchy, think twice before using this command to rename a local\n>> +branch (use `git branch -m` instead).\n>\n> I'd rephrase this slightly to first document behaviour and then draw the\n> conclusion that it shouldn't be used in many cases separately. For\n> example:\n>\n>     This command does not update any symbolic references pointing to\n>     the renamed reference, and neither does it update `.git/config`\n>     tracking settings. It is thus not recommended to use it for renaming\n>     local branches. Use `git branch -m` instead.\n\nThanks, that is much better.\n\n>> +\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n>> +\t\t\tdie(\"no ref named '%s'\", oldref);\n>> +\n>> +\t\tif (refs_ref_exists(get_main_ref_store(the_repository), newref))\n>> +\t\t\tdie(\"ref '%s' already exists\", newref);\n>> +\n>> +\t\tif (refs_rename_ref(get_main_ref_store(the_repository),\n>> +\t\t\t\t    oldref, newref, msg))\n>> +\t\t\tdie(\"rename failed\");\n>> +\t\treturn 0;\n>> +\t}\n>\n> Hm. I think we're not using \"--deref\" / \"--no-deref\" at all, but we\n> document this flag as accepted in the synopsis.\n\nGood point.  refs_rename_ref() never derefs, right?  We should drop\nthese two from the synopsis section.\n\n\n\n[Appendix]\n\nOften there are two topics, A and B, in flight that merging A into B\n(or vice versa) requires changes more than the mechanical merge\nneeds.  If this is a one-shot merge of A into B (or B into A), then\nwe can just record the evil merge and be done with it, but the same\nissue arises if you are merging A into 'seen' first and then later\n(possibly after merging other topics on top) B into 'seen'.  The\nmerge of 'B' needs the same evil merge to resolve semantic\nconflicts.\n\nAs those familiar with how 'seen' works in my tree, reapplying such\nevil merges MUST BE automated, or the project will not work at all,\nas 'seen' is rebuilt at least twice during the day, or even more\noften.\n\nSo, what I do is, when I merge 'B' into 'seen' after merging 'A' and\npossibly some other topics, I let the rerere database to record the\nresolution of textual conflicts and make a commit.  The tree\nrecorded in this commit will not work, due to semantic conflicts.  I\ncreate another commit on top of this merge to resolve the semantic\nconflict to make the tree work.\n\nLet's take ps/history-drop (A) and ps/setup-drop-global-state (B) as\nan easy-to-understand example.  \n\n\t$ git checkout --detach ps/history-drop\n\t$ git merge ps/setup-drop-global-state\n\nThis textually merges cleanly, but the result would not compile.\nThe history-drop added a new call to \"is_bare_repository()\", while\nsetup-drop-global-state added an extra parameter to the function.\n\nSo a merge-fix prepared on top of this \"textually clean but does\nnot work\" merge is created and looks something like this:\n\ndiff --git a/builtin/history.c b/builtin/history.c\nindex 65845e7359..eece221e63 100644\n--- a/builtin/history.c\n+++ b/builtin/history.c\n@@ -1150,7 +1150,7 @@ static int cmd_history_drop(int argc,\n \t * inconsistent repository state. So we first perform a dry-run merge\n \t * here before updating refs.\n \t */\n-\tif (!is_bare_repository()) {\n+\tif (!is_bare_repository(repo)) {\n \t\tret = find_head_tree_change(repo, &result, &old_head,\n \t\t\t\t\t    &new_head, &head_moves);\n \t\tif (ret < 0)\n\nAnd this commit (i.e. a commit on top of the mechanical/textual\nmerge result that adjusts the non-working merge result into workable\nform) is pointed at by refs/merge-fix/ps/setup-drop-global-state.\n\nRebuilding 'seen' is driven by a script that takes a moral\nequivalent of 'git log --first-parent --oneline --reverse\nmaster..seen' and replays each merge on top of what is checked out\n(to bootstrap, you would \"git checkout -B seen master\" and start\nthere).  For each topic branch found in the input, the script\n\n (1) skips if the topic has been merged and move on to the next\n     topic.\n\n (2) runs \"git merge\" of the topic, taking resolution by the rerere\n     database.  If this step leaves mechanical/textual conflicts,\n     the script stops and I'll hand resolve to update my rerere\n     database, and rerun the script (which will succeed the next\n     time).\n\n (3) runs \"git cherry-pick --no-commit merge-fix/$topic\" if such a\n     ref exists, and if successfull, runs \"git commit --amend\".\n\nThat is how merging ps/setup-drop-global-state into 'seen' that has\nalready merged ps/history-drop would automatically get the right\nevil merge to resolve semantic conflicts.\n\nThe renaming of update-ref becomes needed when the order of merging\ntopics into 'seen' changes.  Ideally, these cherry-pickable commits\nthat are stored under refs/merge-fix hierarchies SHOULD be indexable\nby a pair of topic (i.e. \"when topic A and topic B first meets, apply\nthis evil merge\"), but this computation is cumbersome to write, so\nthe above scheme has baked-in assumption that we know which topic\ncomes later.  Once we start merging ps/setup-drop-global-state first\nand then ps/history-drop next, we would need\n\n    $ git update-ref --rename \\\n\trefs/merge-fix/ps/setup-drop-global-state \\\n\trefs/merge-fix/ps/history-drop\n\n"},{"id":"545317","messageId":"xmqq7bo4n4ge.fsf@gitster.g","threadId":"65782","inReplyTo":"xmqqv7brz9ba.fsf@gitster.g","subject":"[PATCH v3] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-11T21:37:53Z","receivedAt":"2026-06-11T21:37:55Z","isPatch":true,"body":"Add a \"--rename\" option to \"git update-ref\" with the syntax:\n\n $ git update-ref --rename <old-refname> <new-refname>\n\nIt renames <old-refname> together with its reflog to <new-refname>;\neven when used on a local branch ref, the current value and the\nreflog of the ref are the only things that are renamed.  Document it\nand redirect casual users to \"git branch -m\" if that is what they\nwanted to do.\n\nBecause the \"--stdin\" mode wants to operate on its refs in a\nreference transaction, and the API function refs_rename_ref() does\nnot work well as part of a transaction, it is currently not possible\nto add a corresponding \"rename\" verb to the \"--stdin\" mode before\nthe underlying API learns to rename refs atomically inside a\ntransaction.  It hence is left for a future refactoring.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * As a single patch topic, the range-diff relative to v2 is at the\n   end of the message.\n\n   - Simplified the proposed commit log message a bit.\n   - Dropped mention of --[no-]deref from the synopsis section.\n   - Reworded documentation with help from Patrick.\n\n Documentation/git-update-ref.adoc |  9 +++++++++\n builtin/update-ref.c              | 32 +++++++++++++++++++++++++++++--\n t/t1400-update-ref.sh             | 24 +++++++++++++++++++++++\n 3 files changed, 63 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\nindex 37a5019a8b..3b4df23a86 100644\n--- a/Documentation/git-update-ref.adoc\n+++ b/Documentation/git-update-ref.adoc\n@@ -9,6 +9,7 @@ SYNOPSIS\n --------\n [synopsis]\n git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]\n+git update-ref [-m <reason>] --rename <old-refname> <new-refname>\n git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]\n git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]\n \n@@ -39,6 +40,14 @@ the result of following the symbolic pointers.\n With `-d`, it deletes the named <ref> after verifying that it\n still contains <old-oid>.\n \n+With `--rename`, it renames <old-refname> together with its reflog to\n+<new-refname>.  The command fails if <old-refname> does not exist, or\n+if <new-refname> already exists.  The command does not update any\n+symbolic references pointing to the renamed reference, and neither\n+does it update `.git/config` tracking settings. It is thus not\n+recommended to use it for renaming local branches. Use `git branch -m`\n+instead.\n+\n With `--stdin`, update-ref reads instructions from standard input and\n performs all modifications together.  Specify commands of the form:\n \ndiff --git a/builtin/update-ref.c b/builtin/update-ref.c\nindex 2d68c40ecb..65ee8af08c 100644\n--- a/builtin/update-ref.c\n+++ b/builtin/update-ref.c\n@@ -15,6 +15,7 @@\n static const char * const git_update_ref_usage[] = {\n \tN_(\"git update-ref [<options>] -d <refname> [<old-oid>]\"),\n \tN_(\"git update-ref [<options>]    <refname> <new-oid> [<old-oid>]\"),\n+\tN_(\"git update-ref [<options>] --rename <old-refname> <new-refname>\"),\n \tN_(\"git update-ref [<options>] --stdin [-z] [--batch-updates]\"),\n \tNULL\n };\n@@ -756,13 +757,14 @@ int cmd_update_ref(int argc,\n {\n \tconst char *refname, *oldval;\n \tstruct object_id oid, oldoid;\n-\tint delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n+\tint delete = 0, rename = 0, no_deref = 0, read_stdin = 0, end_null = 0;\n \tint create_reflog = 0;\n \tunsigned int flags = 0;\n \n \tstruct option options[] = {\n \t\tOPT_STRING( 'm', NULL, &msg, N_(\"reason\"), N_(\"reason of the update\")),\n \t\tOPT_BOOL('d', NULL, &delete, N_(\"delete the reference\")),\n+\t\tOPT_BOOL( 0 , \"rename\", &rename, N_(\"rename the reference\")),\n \t\tOPT_BOOL( 0 , \"no-deref\", &no_deref,\n \t\t\t\t\tN_(\"update <refname> not the one it points to\")),\n \t\tOPT_BOOL('z', NULL, &end_null, N_(\"stdin has NUL-terminated arguments\")),\n@@ -787,7 +789,7 @@ int cmd_update_ref(int argc,\n \t}\n \n \tif (read_stdin) {\n-\t\tif (delete || argc > 0)\n+\t\tif (delete || rename || argc > 0)\n \t\t\tusage_with_options(git_update_ref_usage, options);\n \t\tif (end_null)\n \t\t\tline_termination = '\\0';\n@@ -800,6 +802,32 @@ int cmd_update_ref(int argc,\n \tif (end_null)\n \t\tusage_with_options(git_update_ref_usage, options);\n \n+\tif (rename) {\n+\t\tconst char *oldref, *newref;\n+\n+\t\tif (delete || argc != 2)\n+\t\t\tusage_with_options(git_update_ref_usage, options);\n+\n+\t\toldref = argv[0];\n+\t\tnewref = argv[1];\n+\n+\t\tif (check_refname_format(oldref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", oldref);\n+\t\tif (check_refname_format(newref, 0))\n+\t\t\tdie(\"invalid ref format: %s\", newref);\n+\n+\t\tif (!refs_ref_exists(get_main_ref_store(the_repository), oldref))\n+\t\t\tdie(\"no ref named '%s'\", oldref);\n+\n+\t\tif (refs_ref_exists(get_main_ref_store(the_repository), newref))\n+\t\t\tdie(\"ref '%s' already exists\", newref);\n+\n+\t\tif (refs_rename_ref(get_main_ref_store(the_repository),\n+\t\t\t\t    oldref, newref, msg))\n+\t\t\tdie(\"rename failed\");\n+\t\treturn 0;\n+\t}\n+\n \tif (delete) {\n \t\tif (argc < 1 || argc > 2)\n \t\t\tusage_with_options(git_update_ref_usage, options);\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex b2858a9061..4330cad282 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -2455,4 +2455,28 @@ test_expect_success 'dangling symref overwritten without old oid' '\n \ttest_must_fail git rev-parse --verify refs/heads/does-not-exist\n '\n \n+test_expect_success '--rename fails if old-refname does not exist' '\n+\ttest_must_fail git update-ref --rename refs/tags/no-such-ref refs/tags/new-ref 2>err &&\n+\ttest_grep \"no ref named .refs/tags/no-such-ref.\" err\n+'\n+\n+test_expect_success '--rename fails if new-refname does exist' '\n+\tgit update-ref refs/tags/existing HEAD &&\n+\tgit update-ref refs/tags/old-ref HEAD &&\n+\ttest_must_fail git update-ref --rename refs/tags/old-ref refs/tags/existing 2>err &&\n+\ttest_grep \"ref .refs/tags/existing. already exists\" err\n+'\n+\n+test_expect_success '--rename moves old-refname and its reflog to new-refname' '\n+\ttest_config core.logallrefupdates always &&\n+\tgit update-ref -m \"old tag\" refs/tags/old-tag HEAD &&\n+\tgit update-ref -m \"to new\" --rename refs/tags/old-tag refs/tags/new-tag 2>err &&\n+\ttest_must_be_empty err &&\n+\tgit show-ref --exists refs/tags/new-tag &&\n+\ttest_must_fail git show-ref --exists refs/tags/old-tag &&\n+\tgit log -g refs/tags/new-tag >output &&\n+\ttest_grep \"old tag\" output &&\n+\ttest_grep \"to new\" output\n+'\n+\n test_done\n\nRange-diff against v2:\n1:  00cd13fda7 ! 1:  a54c2d4d68 update-ref: add --rename option\n    @@ Commit message\n     \n          $ git update-ref --rename <old-refname> <new-refname>\n     \n    -    It renames <old-refname> together with its reflog to <new-refname>\n    -    (even when used on a local branch ref, the current value and the\n    -    reflog of the ref are the only things that are renamed).  As the\n    -    command is a low-level plumbing command, attempts to rename branches\n    -    are not warned, but we document it to draw attention of unsuspecting\n    -    users and protect them from burning themselves.\n    +    It renames <old-refname> together with its reflog to <new-refname>;\n    +    even when used on a local branch ref, the current value and the\n    +    reflog of the ref are the only things that are renamed.  Document it\n    +    and redirect casual users to \"git branch -m\" if that is what they\n    +    wanted to do.\n     \n         Because the \"--stdin\" mode wants to operate on its refs in a\n         reference transaction, and the API function refs_rename_ref() does\n    @@ Documentation/git-update-ref.adoc: SYNOPSIS\n      --------\n      [synopsis]\n      git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]\n    -+git update-ref [-m <reason>] [--no-deref] --rename <old-refname> <new-refname>\n    ++git update-ref [-m <reason>] --rename <old-refname> <new-refname>\n      git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]\n      git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]\n      \n    @@ Documentation/git-update-ref.adoc: the result of following the symbolic pointers\n      \n     +With `--rename`, it renames <old-refname> together with its reflog to\n     +<new-refname>.  The command fails if <old-refname> does not exist, or\n    -+if <new-refname> already exists.  Because `git update-ref` does not\n    -+update active worktree `HEAD` symbolic references or `.git/config`\n    -+tracking settings when you rename a local branch in the `refs/heads/`\n    -+hierarchy, think twice before using this command to rename a local\n    -+branch (use `git branch -m` instead).\n    ++if <new-refname> already exists.  The command does not update any\n    ++symbolic references pointing to the renamed reference, and neither\n    ++does it update `.git/config` tracking settings. It is thus not\n    ++recommended to use it for renaming local branches. Use `git branch -m`\n    ++instead.\n     +\n      With `--stdin`, update-ref reads instructions from standard input and\n      performs all modifications together.  Specify commands of the form:\n-- \n2.55.0-rc0-119-ga57a595f62\n\n"},{"id":"545338","messageId":"aiugat0gvprSX5yr@pks.im","threadId":"65782","inReplyTo":"xmqq7bo4n4ge.fsf@gitster.g","subject":"Re: [PATCH v3] update-ref: add --rename option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-12T06:00:10Z","receivedAt":"2026-06-12T06:00:18Z","isPatch":true,"body":"On Thu, Jun 11, 2026 at 02:37:53PM -0700, Junio C Hamano wrote:\n> Add a \"--rename\" option to \"git update-ref\" with the syntax:\n> \n>  $ git update-ref --rename <old-refname> <new-refname>\n> \n> It renames <old-refname> together with its reflog to <new-refname>;\n> even when used on a local branch ref, the current value and the\n> reflog of the ref are the only things that are renamed.  Document it\n> and redirect casual users to \"git branch -m\" if that is what they\n> wanted to do.\n\nThis reads much better, thanks.\n\n> diff --git a/Documentation/git-update-ref.adoc b/Documentation/git-update-ref.adoc\n> index 37a5019a8b..3b4df23a86 100644\n> --- a/Documentation/git-update-ref.adoc\n> +++ b/Documentation/git-update-ref.adoc\n> @@ -9,6 +9,7 @@ SYNOPSIS\n>  --------\n>  [synopsis]\n>  git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]\n> +git update-ref [-m <reason>] --rename <old-refname> <new-refname>\n>  git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]\n>  git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]\n\nThis slightly triggers my OCD, but oh, well. No need to change this.\n\n> diff --git a/builtin/update-ref.c b/builtin/update-ref.c\n> index 2d68c40ecb..65ee8af08c 100644\n> --- a/builtin/update-ref.c\n> +++ b/builtin/update-ref.c\n> @@ -800,6 +802,32 @@ int cmd_update_ref(int argc,\n>  \tif (end_null)\n>  \t\tusage_with_options(git_update_ref_usage, options);\n>  \n> +\tif (rename) {\n> +\t\tconst char *oldref, *newref;\n> +\n> +\t\tif (delete || argc != 2)\n> +\t\t\tusage_with_options(git_update_ref_usage, options);\n\nArguably, we should also complain when either \"--no-deref\" or \"--deref\"\nwere given, as they don't have any effect.\n\nA slight tangent: this is part of why I really don't like commands that\ndetermine their mode via flags: you now have to worry about every\ncombination of flags and whether they even make sense. With subcommands\nwe at least only have to worry about the set of flags that directly\napply to that given subcommand.\n\nMakes me wonder whether I should have a look at extending git-refs(1)\nfurther:\n\n    git refs delete <ref> [<oldvalue>]\n    git refs update <ref> <newvalue> [<oldvalue>]\n    git refs rename <ref> <oldname> <newname>\n\nI always wanted to do this eventually so that we have one top-level\ncommand that knows how to do \"everything refs\".\n\nAnyway, except for this nit the patch looks good to me, thanks!\n\nPatrick\n"},{"id":"545339","messageId":"aiuhWIUlOVuuHSNJ@pks.im","threadId":"65782","inReplyTo":"xmqqwlw4nccr.fsf@gitster.g","subject":"Re: [PATCH v2] update-ref: add --rename option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-12T06:04:08Z","receivedAt":"2026-06-12T06:04:13Z","isPatch":true,"body":"On Thu, Jun 11, 2026 at 11:47:16AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > One thing that I'm missing from the commit message: what's the\n> > motivation for this new mode?\n> \n> Maintenance of merge-fix database, a kludgy way to manage evil\n> merges that are needed to deal with inter-topic semantic crashes.\n> \n> If you are really interested, see the appendix.\n\nThanks for the explanation!\n\nPatrick\n"},{"id":"545394","messageId":"xmqqqzmbhikj.fsf@gitster.g","threadId":"65782","inReplyTo":"aiugat0gvprSX5yr@pks.im","subject":"Re: [PATCH v3] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-12T15:41:48Z","receivedAt":"2026-06-12T15:41:51Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> A slight tangent: this is part of why I really don't like commands that\n> determine their mode via flags: you now have to worry about every\n> combination of flags and whether they even make sense. With subcommands\n> we at least only have to worry about the set of flags that directly\n> apply to that given subcommand.\n>\n> Makes me wonder whether I should have a look at extending git-refs(1)\n> further:\n>\n>     git refs delete <ref> [<oldvalue>]\n>     git refs update <ref> <newvalue> [<oldvalue>]\n>     git refs rename <ref> <oldname> <newname>\n>\n> I always wanted to do this eventually so that we have one top-level\n> command that knows how to do \"everything refs\".\n\nThat may indeed be a better direction to go, but isn't update-ref\nthe \"everything refs\" command already?\n\n"},{"id":"545403","messageId":"xmqqmrwzhg97.fsf@gitster.g","threadId":"65782","inReplyTo":"xmqqwlw4nccr.fsf@gitster.g","subject":"Re: [PATCH v2] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-12T16:31:48Z","receivedAt":"2026-06-12T16:31:52Z","isPatch":true,"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> [Appendix]\n> ...  Ideally, these cherry-pickable commits\n> that are stored under refs/merge-fix hierarchies SHOULD be indexable\n> by a pair of topic (i.e. \"when topic A and topic B first meets, apply\n> this evil merge\"), but this computation is cumbersome to write.\n\nOne scheme might be to use \"refs/merge-fix/$A--$B\" to store the\ninteraction between topic $A and topic $B, with the convention that\nno topic is named with double-dash in its name.\n\nWe sequencially merge these in-flight topic into the integration\nbranch (e.g., 'seen').  When merging topic X, we roughly would need\nto do the following.\n\n (0) Skip if X is already in the integration branch.\n\n (1) See what topic Y that would also be merged for the first time\n     to the integration branch.  This is because a complex topic X\n     often is done by merging in-flight Y into then-current master\n     and applyng patches on top, and depending on the state of the\n     integration branch, such topic Y may or may not have already\n     been mergeed there.  Enumerate all these topic Ys that would\n     be pulled into the integration branch as a side effect of\n     merging X.\n\n (2) Enumerate all merge-fix refs that has any of the topic Ys or X.\n     For each such refs/merge-fix/$A--$B (where either $A or $B is X\n     or one of Ys), call the other side of \"--\" Z.  If Z is already\n     in the integration branch, then we found the merge-fix we need\n     to apply.\n\nThe \"topic X might pull other topics that haven't been merged\ntogether with it when it gets merged\" is what makes it cumbersome to\nwrite.  If we do not have to worry about it, it would be fairly\nstraight forward, but then the bulk-merge driver probably needs to\nlearn a safety check to make sure at the point of merging each topic\nthat the merge is not pulling another topic (base) into as a side\neffect.  It would mean that you prepare a new topic X on top of a\nmerge of Y into 'master', and X can never be merged into 'seen'\nbefore Y is.  Which may or may not be what we really want, and I\nneed to think about it a bit.\n\n"},{"id":"545531","messageId":"ai--jEk23E7RJPnc@pks.im","threadId":"65782","inReplyTo":"xmqqqzmbhikj.fsf@gitster.g","subject":"Re: [PATCH v3] update-ref: add --rename option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-15T08:57:48Z","receivedAt":"2026-06-15T08:57:53Z","isPatch":true,"body":"On Fri, Jun 12, 2026 at 08:41:48AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > A slight tangent: this is part of why I really don't like commands that\n> > determine their mode via flags: you now have to worry about every\n> > combination of flags and whether they even make sense. With subcommands\n> > we at least only have to worry about the set of flags that directly\n> > apply to that given subcommand.\n> >\n> > Makes me wonder whether I should have a look at extending git-refs(1)\n> > further:\n> >\n> >     git refs delete <ref> [<oldvalue>]\n> >     git refs update <ref> <newvalue> [<oldvalue>]\n> >     git refs rename <ref> <oldname> <newname>\n> >\n> > I always wanted to do this eventually so that we have one top-level\n> > command that knows how to do \"everything refs\".\n> \n> That may indeed be a better direction to go, but isn't update-ref\n> the \"everything refs\" command already?\n\nWell, it doesn't handle reading references, which is something that\ngit-refs(1) already knows to do.\n\nPatrick\n"},{"id":"545601","messageId":"xmqqv7bjbusb.fsf@gitster.g","threadId":"65782","inReplyTo":"ai--jEk23E7RJPnc@pks.im","subject":"Re: [PATCH v3] update-ref: add --rename option","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-15T17:03:32Z","receivedAt":"2026-06-15T17:03:35Z","isPatch":true,"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> On Fri, Jun 12, 2026 at 08:41:48AM -0700, Junio C Hamano wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>> \n>> > A slight tangent: this is part of why I really don't like commands that\n>> > determine their mode via flags: you now have to worry about every\n>> > combination of flags and whether they even make sense. With subcommands\n>> > we at least only have to worry about the set of flags that directly\n>> > apply to that given subcommand.\n>> >\n>> > Makes me wonder whether I should have a look at extending git-refs(1)\n>> > further:\n>> >\n>> >     git refs delete <ref> [<oldvalue>]\n>> >     git refs update <ref> <newvalue> [<oldvalue>]\n>> >     git refs rename <ref> <oldname> <newname>\n>> >\n>> > I always wanted to do this eventually so that we have one top-level\n>> > command that knows how to do \"everything refs\".\n>> \n>> That may indeed be a better direction to go, but isn't update-ref\n>> the \"everything refs\" command already?\n>\n> Well, it doesn't handle reading references, which is something that\n> git-refs(1) already knows to do.\n\nOK, fair enough.  \"git refs\" should become superset of \"git\nshow-ref\" and \"git update-ref\", I guess.\n\nSo do you want to take the topic over and add it to \"git refs\"?\n\n"},{"id":"545629","messageId":"ajD-Rt3ow4wpTNkq@pks.im","threadId":"65782","inReplyTo":"xmqqv7bjbusb.fsf@gitster.g","subject":"Re: [PATCH v3] update-ref: add --rename option","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-16T07:41:58Z","receivedAt":"2026-06-16T07:42:06Z","isPatch":true,"body":"On Mon, Jun 15, 2026 at 10:03:32AM -0700, Junio C Hamano wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> > On Fri, Jun 12, 2026 at 08:41:48AM -0700, Junio C Hamano wrote:\n> >> Patrick Steinhardt <ps@pks.im> writes:\n> >> \n> >> > A slight tangent: this is part of why I really don't like commands that\n> >> > determine their mode via flags: you now have to worry about every\n> >> > combination of flags and whether they even make sense. With subcommands\n> >> > we at least only have to worry about the set of flags that directly\n> >> > apply to that given subcommand.\n> >> >\n> >> > Makes me wonder whether I should have a look at extending git-refs(1)\n> >> > further:\n> >> >\n> >> >     git refs delete <ref> [<oldvalue>]\n> >> >     git refs update <ref> <newvalue> [<oldvalue>]\n> >> >     git refs rename <ref> <oldname> <newname>\n> >> >\n> >> > I always wanted to do this eventually so that we have one top-level\n> >> > command that knows how to do \"everything refs\".\n> >> \n> >> That may indeed be a better direction to go, but isn't update-ref\n> >> the \"everything refs\" command already?\n> >\n> > Well, it doesn't handle reading references, which is something that\n> > git-refs(1) already knows to do.\n> \n> OK, fair enough.  \"git refs\" should become superset of \"git\n> show-ref\" and \"git update-ref\", I guess.\n> \n> So do you want to take the topic over and add it to \"git refs\"?\n\nSure, I can do that.\n\nPatrick\n"}]}