builtin/refs: add "rename" subcommand
Add a "rename" subcommand to git-refs(1) with the syntax: $ git refs rename <oldref> <newref> It renames <oldref> together with its reflog to <newref>; 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. Co-authored-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
Jul 6, 2026 at 15:27 UTC
002fe677caddc8162949315c73e53422d4e0f4e8
4 files changed
+200
Documentation/git-refs.adoc
+6
@@ -23,6 +23,7 @@ git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude
23
git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
24
git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
25
git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
26
+git refs rename [--message=<reason>] <old-ref> <new-ref>
27
28
DESCRIPTION
29
-----------
@@ -71,6 +72,11 @@ update::
72
`<new-value>` deletes the branch, whereas an all-zeroes `<old-value>`
73
ensures that the branch does not yet exist.
74
75
+rename::
76
+ Rename the reference `<oldref>` to `<newref>`. The old reference must
77
+ exist and the new reference must not yet exist, and both must have a
78
+ well-formed name (see linkgit:git-check-ref-format[1]).
79
+
80
OPTIONS
81
-------
82
builtin/refs.c
+49
@@ -30,6 +30,9 @@
30
#define REFS_UPDATE_USAGE \
31
N_("git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]")
32
33
+#define REFS_RENAME_USAGE \
34
+ N_("git refs rename [--message=<reason>] <old-ref> <new-ref>")
35
+
36
static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
37
struct repository *repo)
38
{
@@ -327,6 +330,50 @@ static int cmd_refs_update(int argc, const char **argv, const char *prefix,
330
return ret;
331
}
332
333
+static int cmd_refs_rename(int argc, const char **argv, const char *prefix,
334
+ struct repository *repo)
335
+{
336
+ static char const * const refs_rename_usage[] = {
337
+ REFS_RENAME_USAGE,
338
+ NULL
339
+ };
340
+ const char *message = NULL;
341
+ struct option opts[] = {
342
+ OPT_STRING(0, "message", &message, N_("reason"),
343
+ N_("reason of the update")),
344
+ OPT_END(),
345
+ };
346
+ const char *oldref, *newref;
347
+ int ret;
348
+
349
+ argc = parse_options(argc, argv, prefix, opts, refs_rename_usage, 0);
350
+ if (argc != 2)
351
+ usage(_("rename requires old and new reference name"));
352
+ if (message && !*message)
353
+ die(_("refusing to perform update with empty message"));
354
+
355
+ repo_config(repo, git_default_config, NULL);
356
+
357
+ oldref = argv[0];
358
+ newref = argv[1];
359
+
360
+ if (check_refname_format(oldref, 0))
361
+ die(_("invalid ref format: '%s'"), oldref);
362
+ if (check_refname_format(newref, 0))
363
+ die(_("invalid ref format: '%s'"), newref);
364
+
365
+ if (!refs_ref_exists(get_main_ref_store(repo), oldref))
366
+ die(_("reference does not exist: '%s'"), oldref);
367
+ if (refs_ref_exists(get_main_ref_store(repo), newref))
368
+ die(_("reference already exists: '%s'"), newref);
369
+
370
+ ret = refs_rename_ref(get_main_ref_store(repo), oldref, newref, message);
371
+
372
+ if (ret < 0)
373
+ ret = 1;
374
+ return ret;
375
+}
376
+
377
int cmd_refs(int argc,
378
const char **argv,
379
const char *prefix,
@@ -341,6 +388,7 @@ int cmd_refs(int argc,
388
REFS_CREATE_USAGE,
389
REFS_DELETE_USAGE,
390
REFS_UPDATE_USAGE,
391
+ REFS_RENAME_USAGE,
392
NULL,
393
};
394
parse_opt_subcommand_fn *fn = NULL;
@@ -353,6 +401,7 @@ int cmd_refs(int argc,
401
OPT_SUBCOMMAND("create", &fn, cmd_refs_create),
402
OPT_SUBCOMMAND("delete", &fn, cmd_refs_delete),
403
OPT_SUBCOMMAND("update", &fn, cmd_refs_update),
404
+ OPT_SUBCOMMAND("rename", &fn, cmd_refs_rename),
405
OPT_END(),
406
};
407
t/meson.build
+1
@@ -226,6 +226,7 @@ integration_tests = [
226
't1464-refs-delete.sh',
227
't1465-refs-update.sh',
228
't1466-refs-create.sh',
229
+ 't1467-refs-rename.sh',
230
't1500-rev-parse.sh',
231
't1501-work-tree.sh',
232
't1502-rev-parse-parseopt.sh',
t/t1467-refs-rename.sh
new
+144
@@ -0,0 +1,144 @@
1
+#!/bin/sh
2
+
3
+test_description='git refs rename'
4
+
5
+. ./test-lib.sh
6
+
7
+setup_repo () {
8
+ git init "$1" &&
9
+ test_commit -C "$1" A &&
10
+ test_commit -C "$1" B
11
+}
12
+
13
+test_ref_matches () {
14
+ git rev-parse "$1" >expect &&
15
+ echo "$2" >actual &&
16
+ test_cmp expect actual
17
+}
18
+
19
+test_expect_success 'rename an existing reference' '
20
+ test_when_finished "rm -rf repo" &&
21
+ setup_repo repo &&
22
+ (
23
+ cd repo &&
24
+ A=$(git rev-parse A) &&
25
+ git refs update refs/heads/foo $A &&
26
+ git refs rename refs/heads/foo refs/heads/bar &&
27
+ test_must_fail git refs exists refs/heads/foo &&
28
+ test_ref_matches refs/heads/bar $A
29
+ )
30
+'
31
+
32
+test_expect_success 'rename moves the reflog along with the reference' '
33
+ test_when_finished "rm -rf repo" &&
34
+ setup_repo repo &&
35
+ (
36
+ cd repo &&
37
+ A=$(git rev-parse A) &&
38
+ git refs update --message="rename me" refs/heads/foo $A &&
39
+ git refs rename refs/heads/foo refs/heads/bar &&
40
+ git reflog show refs/heads/bar >reflog &&
41
+ test_grep "rename me" reflog &&
42
+ test_must_fail git reflog exists refs/heads/foo
43
+ )
44
+'
45
+
46
+test_expect_success 'rename with message records reason in reflog' '
47
+ test_when_finished "rm -rf repo" &&
48
+ setup_repo repo &&
49
+ (
50
+ cd repo &&
51
+ A=$(git rev-parse A) &&
52
+ git refs update refs/heads/foo $A &&
53
+ git refs rename --message="rename reason" refs/heads/foo refs/heads/bar &&
54
+ git reflog show refs/heads/bar >actual &&
55
+ test_grep "rename reason" actual
56
+ )
57
+'
58
+
59
+test_expect_success 'rename a nonexistent reference fails' '
60
+ test_when_finished "rm -rf repo" &&
61
+ setup_repo repo &&
62
+ (
63
+ cd repo &&
64
+ test_must_fail git refs rename refs/heads/foo refs/heads/bar 2>err &&
65
+ test_grep "reference does not exist" err
66
+ )
67
+'
68
+
69
+test_expect_success 'rename to an existing reference fails' '
70
+ test_when_finished "rm -rf repo" &&
71
+ setup_repo repo &&
72
+ (
73
+ cd repo &&
74
+ A=$(git rev-parse A) &&
75
+ B=$(git rev-parse B) &&
76
+ git refs update refs/heads/foo $A &&
77
+ git refs update refs/heads/bar $B &&
78
+ test_must_fail git refs rename refs/heads/foo refs/heads/bar 2>err &&
79
+ test_grep "reference already exists" err
80
+ )
81
+'
82
+
83
+test_expect_success 'rename with symbolic ref fails' '
84
+ test_when_finished "rm -rf repo" &&
85
+ setup_repo repo &&
86
+ (
87
+ cd repo &&
88
+ A=$(git rev-parse A) &&
89
+ git refs create refs/heads/target $A &&
90
+ git symbolic-ref refs/heads/symref refs/heads/target &&
91
+ ! git refs rename refs/heads/symref refs/heads/renamed 2>err &&
92
+ test_grep "is a symbolic ref, .* not supported" err
93
+ )
94
+'
95
+
96
+test_expect_success 'rename with empty message fails' '
97
+ test_when_finished "rm -rf repo" &&
98
+ setup_repo repo &&
99
+ (
100
+ cd repo &&
101
+ A=$(git rev-parse A) &&
102
+ git refs update refs/heads/foo $A &&
103
+ test_must_fail git refs rename --message= refs/heads/foo refs/heads/bar 2>err &&
104
+ test_grep "empty message" err
105
+ )
106
+'
107
+
108
+test_expect_success 'rename with invalid old reference name fails' '
109
+ test_when_finished "rm -rf repo" &&
110
+ setup_repo repo &&
111
+ (
112
+ cd repo &&
113
+ test_must_fail git refs rename "refs/heads/foo..bar" refs/heads/bar 2>err &&
114
+ test_grep "invalid ref format" err
115
+ )
116
+'
117
+
118
+test_expect_success 'rename with invalid new reference name fails' '
119
+ test_when_finished "rm -rf repo" &&
120
+ setup_repo repo &&
121
+ (
122
+ cd repo &&
123
+ A=$(git rev-parse A) &&
124
+ git refs update refs/heads/foo $A &&
125
+ test_must_fail git refs rename refs/heads/foo "refs/heads/bar..baz" 2>err &&
126
+ test_grep "invalid ref format" err
127
+ )
128
+'
129
+
130
+test_expect_success 'rename with too few arguments fails' '
131
+ test_when_finished "rm -rf repo" &&
132
+ setup_repo repo &&
133
+ test_must_fail git -C repo refs rename refs/heads/foo 2>err &&
134
+ test_grep "requires old and new reference name" err
135
+'
136
+
137
+test_expect_success 'rename with too many arguments fails' '
138
+ test_when_finished "rm -rf repo" &&
139
+ setup_repo repo &&
140
+ test_must_fail git -C repo refs rename refs/heads/foo refs/heads/bar refs/heads/baz 2>err &&
141
+ test_grep "requires old and new reference name" err
142
+'
143
+
144
+test_done