Skip to content

Commit 265a951

Browse files
codexByron
authored andcommitted
fix(refs): address literal branch rename review feedback
Review item #2274 (comment) reports that "the returned head points to `refs/heads/topic`, which may be missing or identify a different branch" after renaming to the short branch name `refs/heads/topic`. A repository with an existing `topic` at commit A and a source branch at commit B reproduces the incorrect returned ref on both backends. Compute the destination by prepending `refs/heads/` to the literal name passed to `git branch`. Validate that same destination and retain it only after Git successfully renames the branch. Git 2.54.0's `builtin/branch.c::copy_or_rename_branch()` uses `refs.c::check_branch_ref()`, which always prepends that namespace. The general `to_full_path()` helper continues to normalize already-qualified references for its other callers. The SHA-1/SHA-256 regressions fail before this fix and pass afterwards. They check the returned path, name and commit, current `HEAD`, and that the original `topic` still points to commit A. All 50 reference tests and 24 subtests pass on each backend, including ordinary and forced renames. Ruff, ShellCheck, mypy and basedpyright pass.
1 parent 88958c9 commit 265a951

3 files changed

Lines changed: 25 additions & 2 deletions

File tree

‎git/refs/head.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,10 +252,11 @@ def rename(self, new_path: PathLike, force: bool = False) -> "Head":
252252
flag = "-M"
253253

254254
new_name = Git._check_operand(new_path, "branch")
255+
new_full_path = "%s/%s" % (self._common_path_default, new_name)
255256
self._get_validated_ref_path(self.repo, self.path)
256-
self._get_validated_ref_path(self.repo, self.to_full_path(new_name))
257+
self._get_validated_ref_path(self.repo, new_full_path)
257258
self.repo.git._call_process_safe("branch", flag, "--", self.name, new_name)
258-
self.path = str(self.to_full_path(new_name))
259+
self.path = new_full_path
259260
return self
260261

261262
def checkout(

‎test/test_refs.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,23 @@ def test_tag_without_tagger_initializes_all_metadata(object_format, message):
9292
assert tag.message == message
9393

9494

95+
@pytest.mark.parametrize("object_format", ["sha1", "sha256"])
96+
def test_head_rename_retains_literal_prefixed_branch_name(object_format):
97+
with fixture_repo("refs.sh", "rename-prefixed", object_format, write=True) as repo:
98+
original_topic = repo.heads.topic.commit
99+
source = repo.head.reference
100+
source_commit = source.commit
101+
assert source_commit != original_topic
102+
renamed = source.rename("refs/heads/topic")
103+
assert renamed is source
104+
assert renamed.path == "refs/heads/refs/heads/topic"
105+
assert renamed.name == "refs/heads/topic"
106+
assert renamed.commit == source_commit
107+
assert repo.head.reference == renamed
108+
assert repo.head.commit == source_commit
109+
assert repo.heads.topic.commit == original_topic
110+
111+
95112
class TestRefs(TestBase):
96113
def test_from_path(self):
97114
# Should be able to create any reference directly.

‎tests/fixtures/refs.sh‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ case "$1" in
3030
fi
3131
} | git hash-object -w -t tag --literally --stdin >../tag-id
3232
;;
33+
rename-prefixed)
34+
git branch topic
35+
printf 'second\n' >> file.txt
36+
git commit -am second
37+
;;
3338
outside)
3439
git rev-parse HEAD > ../outside-ref
3540
;;

0 commit comments

Comments
 (0)