Skip to content

Commit 021a95c

Browse files
committed
Allow to check out branches
The git server currently lacks branch switching capabilities, limiting both LLMs and developers. This adds branch checkout so LLMs can help developers add new functionality in a new feature branch.
1 parent 2ecb382 commit 021a95c

File tree

4 files changed

+139
-3
lines changed

4 files changed

+139
-3
lines changed

src/git/pyproject.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,10 @@ requires = ["hatchling"]
3030
build-backend = "hatchling.build"
3131

3232
[tool.uv]
33-
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3"]
33+
dev-dependencies = ["pyright>=1.1.389", "ruff>=0.7.3", "pytest>=8.0.0"]
34+
35+
[tool.pytest.ini_options]
36+
testpaths = ["tests"]
37+
python_files = "test_*.py"
38+
python_classes = "Test*"
39+
python_functions = "test_*"

src/git/src/mcp_server_git/server.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ class GitCreateBranch(BaseModel):
4444
branch_name: str
4545
base_branch: str | None = None
4646

47+
class GitCheckout(BaseModel):
48+
repo_path: str
49+
branch_name: str
50+
4751
class GitTools(str, Enum):
4852
STATUS = "git_status"
4953
DIFF_UNSTAGED = "git_diff_unstaged"
@@ -53,6 +57,7 @@ class GitTools(str, Enum):
5357
RESET = "git_reset"
5458
LOG = "git_log"
5559
CREATE_BRANCH = "git_create_branch"
60+
CHECKOUT = "git_checkout"
5661

5762
def git_status(repo: git.Repo) -> str:
5863
return repo.git.status()
@@ -96,6 +101,10 @@ def git_create_branch(repo: git.Repo, branch_name: str, base_branch: str | None
96101
repo.create_head(branch_name, base)
97102
return f"Created branch '{branch_name}' from '{base.name}'"
98103

104+
def git_checkout(repo: git.Repo, branch_name: str) -> str:
105+
repo.git.checkout(branch_name)
106+
return f"Switched to branch '{branch_name}'"
107+
99108
async def serve(repository: Path | None) -> None:
100109
logger = logging.getLogger(__name__)
101110

@@ -152,6 +161,11 @@ async def list_tools() -> list[Tool]:
152161
description="Creates a new branch from an optional base branch",
153162
inputSchema=GitCreateBranch.schema(),
154163
),
164+
Tool(
165+
name=GitTools.CHECKOUT,
166+
description="Switches branches",
167+
inputSchema=GitCheckout.schema(),
168+
),
155169
]
156170

157171
async def list_repos() -> Sequence[str]:
@@ -249,6 +263,13 @@ async def call_tool(name: str, arguments: dict) -> list[TextContent]:
249263
text=result
250264
)]
251265

266+
case GitTools.CHECKOUT:
267+
result = git_checkout(repo, arguments["branch_name"])
268+
return [TextContent(
269+
type="text",
270+
text=result
271+
)]
272+
252273
case _:
253274
raise ValueError(f"Unknown tool: {name}")
254275

src/git/tests/test_server.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pytest
2+
from pathlib import Path
3+
import git
4+
from mcp_server_git.server import git_checkout
5+
6+
def test_git_checkout_existing_branch(tmp_path: Path):
7+
# Setup test repo
8+
repo = git.Repo.init(tmp_path)
9+
Path(tmp_path / "test.txt").write_text("test")
10+
repo.index.add(["test.txt"])
11+
repo.index.commit("initial commit")
12+
13+
# Create and test branch
14+
repo.git.branch("test-branch")
15+
result = git_checkout(repo, "test-branch")
16+
17+
assert "Switched to branch 'test-branch'" in result
18+
assert repo.active_branch.name == "test-branch"
19+
20+
def test_git_checkout_nonexistent_branch(tmp_path: Path):
21+
repo = git.Repo.init(tmp_path)
22+
23+
with pytest.raises(git.GitCommandError):
24+
git_checkout(repo, "nonexistent-branch")

src/git/uv.lock

Lines changed: 87 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)