Skip to content

Commit 002fe67

Browse files
pks-tgitster
andcommitted
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>
1 parent fa4eefe commit 002fe67

4 files changed

Lines changed: 200 additions & 0 deletions

File tree

Documentation/git-refs.adoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ git refs optimize [--all] [--no-prune] [--auto] [--include <pattern>] [--exclude
2323
git refs create [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value>
2424
git refs delete [--message=<reason>] [--no-deref] <ref> [<old-value>]
2525
git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]
26+
git refs rename [--message=<reason>] <old-ref> <new-ref>
2627

2728
DESCRIPTION
2829
-----------
@@ -71,6 +72,11 @@ update::
7172
`<new-value>` deletes the branch, whereas an all-zeroes `<old-value>`
7273
ensures that the branch does not yet exist.
7374

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+
7480
OPTIONS
7581
-------
7682

builtin/refs.c

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
#define REFS_UPDATE_USAGE \
3131
N_("git refs update [--message=<reason>] [--no-deref] [--create-reflog] <ref> <new-value> [<old-value>]")
3232

33+
#define REFS_RENAME_USAGE \
34+
N_("git refs rename [--message=<reason>] <old-ref> <new-ref>")
35+
3336
static int cmd_refs_migrate(int argc, const char **argv, const char *prefix,
3437
struct repository *repo)
3538
{
@@ -327,6 +330,50 @@ static int cmd_refs_update(int argc, const char **argv, const char *prefix,
327330
return ret;
328331
}
329332

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+
330377
int cmd_refs(int argc,
331378
const char **argv,
332379
const char *prefix,
@@ -341,6 +388,7 @@ int cmd_refs(int argc,
341388
REFS_CREATE_USAGE,
342389
REFS_DELETE_USAGE,
343390
REFS_UPDATE_USAGE,
391+
REFS_RENAME_USAGE,
344392
NULL,
345393
};
346394
parse_opt_subcommand_fn *fn = NULL;
@@ -353,6 +401,7 @@ int cmd_refs(int argc,
353401
OPT_SUBCOMMAND("create", &fn, cmd_refs_create),
354402
OPT_SUBCOMMAND("delete", &fn, cmd_refs_delete),
355403
OPT_SUBCOMMAND("update", &fn, cmd_refs_update),
404+
OPT_SUBCOMMAND("rename", &fn, cmd_refs_rename),
356405
OPT_END(),
357406
};
358407

t/meson.build

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ integration_tests = [
226226
't1464-refs-delete.sh',
227227
't1465-refs-update.sh',
228228
't1466-refs-create.sh',
229+
't1467-refs-rename.sh',
229230
't1500-rev-parse.sh',
230231
't1501-work-tree.sh',
231232
't1502-rev-parse-parseopt.sh',

t/t1467-refs-rename.sh

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)