fix(git): report a detached HEAD from git_checkout - #4805
CryoThrust wants to merge 2 commits into
Conversation
git_checkout validated branch_name with rev_parse, which resolves any revision (a sha, tag, HEAD~1, or a remote-tracking ref), then always replied "Switched to branch '<name>'". For anything that is not a branch git actually detached HEAD, so the tool handed the model a success sentence over a state where new commits belong to no branch. Report the resulting state instead: say HEAD is detached, with the short sha, when it is; keep the branch wording when a branch was really checked out.
yangshuaiyufalv
left a comment
There was a problem hiding this comment.
[MCP demo] 测试评审(只读组织,预期权限错误)。
|
Reviewed as the reporter of #4804. I ran the head ( The fix also covers the three rows of the #4804 table that the new tests don't exercise. Checked each against this branch: A name that is both a branch and a tag ( One non-blocking suggestion: @pytest.mark.parametrize("revision", ["HEAD~1", "refs/heads/feature", "origin/main"])
def test_git_checkout_non_branch_revisions_report_detached_head(
test_repository, tmp_path, revision
):
test_repository.git.branch("feature")
test_repository.index.commit("second commit")
git.Repo.init(tmp_path / "remote.git", bare=True)
test_repository.create_remote("origin", str(tmp_path / "remote.git"))
test_repository.git.push("origin", "HEAD:refs/heads/main")
result = git_checkout(test_repository, revision)
assert test_repository.head.is_detached
assert result.startswith("HEAD is now detached at ")Otherwise this looks right to me — keeping non-branch checkouts and making the reply truthful is a better trade than rejecting them. |
…a branch Adds the parametrized case suggested in review: HEAD~1, refs/heads/feature and origin/main are the revisions a caller is most likely to send and none of them is a branch, so none may be reported as a branch switch. All three fail against the old unconditional return and pass with the fix.
|
Thanks for running it against the head and for checking the rows the tests didn't cover — that's exactly the gap I left, so I took the suggestion verbatim in
Your point about the branch-and-tag name resolving to the branch is a good one to have on record too — that's the case where taking the wording from |
Summary
git_checkoutvalidates its argument withrev_parse, which resolves any revision — a sha, a tag,HEAD~1, a remote-tracking ref, a fullrefs/...name. For everything that is not a branch,git checkoutdetaches HEAD, but the tool still returnedSwitched to branch '<name>'.Report the resulting state instead:
The reply is the only thing the calling model sees —
contentis a single text block,structuredContentis null andisErroris false — so a wrong success sentence is not corrected downstream. The branch wording is now taken fromactive_branchtoo, so it cannot disagree with the ref that was actually checked out.Rejecting non-branch revisions would also close the issue, but it removes a capability some callers use deliberately (checking out a tag or a remote ref), so I kept it and made the message truthful instead.
Tests
Added three cases to
src/git/tests/test_server.py:Switched to branch '<name>'and leaves HEAD attachedOn
mainthe first two fail (assert 'detached' in "Switched to branch 'v1'").Validation:
uv run pytestinsrc/git— 46 passed;pyright— 0 errors.Closes #4804.