Skip to content

Commit 97be465

Browse files
committed
Refactor Git server with improved branch and checkout handling
- Enhance branch creation with more robust error handling - Improve checkout method with better error management - Rename GitCheckoutModel and update related references - Simplify path validation logic in PathValidator - Add more comprehensive error handling for Git operations
1 parent b311c75 commit 97be465

1 file changed

Lines changed: 60 additions & 37 deletions

File tree

src/git/src/mcp_server_git/server.py

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -39,31 +39,21 @@ def __init__(self, base_path: Path | str | None = None):
3939
self.base_path = Path(base_path).resolve() if base_path else None
4040

4141
def is_safe_path(self, path: Path) -> bool:
42-
"""Check if a path is safe to access.
43-
44-
A path is considered safe if:
45-
1. It's absolute
46-
2. It doesn't contain symlinks that could bypass restrictions
47-
3. If base_path is set, it must be within that directory
48-
4. It's not a system directory (/etc, /usr, /bin, etc.)
49-
"""
42+
"""Check if a path is safe to access."""
5043
try:
51-
# Resolve to absolute path, following symlinks
5244
resolved = path.resolve()
5345

5446
# Check if it's a system directory
5547
system_dirs = ["/etc", "/usr", "/bin", "/sbin", "/lib", "/sys", "/dev", "/proc"]
5648
if any(str(resolved).startswith(sys_dir) for sys_dir in system_dirs):
5749
return False
5850

59-
# If base_path is set, ensure path is within it
60-
if self.base_path:
61-
return resolved.is_relative_to(self.base_path)
51+
# If no base path is configured, allow absolute paths that aren't system dirs
52+
if not self.base_path:
53+
return True
6254

63-
# In unconfigured mode, apply additional restrictions
64-
# Only allow paths under /home or custom project directories
65-
safe_roots = ["/home", "/projects", "/repo"]
66-
return any(str(resolved).startswith(root) for root in safe_roots)
55+
# If base path is configured, ensure path is within base directory
56+
return resolved.is_relative_to(self.base_path)
6757

6858
except (RuntimeError, OSError):
6959
# RuntimeError can occur with circular symlinks
@@ -126,9 +116,12 @@ class git_create_branch(GitPathModel):
126116
branch_name: str
127117
base_branch: str | None = None
128118

129-
class git_checkout(GitPathModel):
119+
class GitCheckoutModel(GitPathModel):
130120
branch_name: str
131121

122+
# Alias for backward compatibility with tool registration
123+
git_checkout_model = GitCheckoutModel
124+
132125
class git_show(GitPathModel):
133126
revision: str
134127

@@ -145,7 +138,7 @@ class GitTools(str, Enum):
145138
RESET = "git_reset"
146139
LOG = "git_log"
147140
CREATE_BRANCH = "git_create_branch"
148-
CHECKOUT = "git_checkout"
141+
CHECKOUT = "git_checkout_model" # Updated to use new model name
149142
SHOW = "git_show"
150143
INIT = "git_init"
151144

@@ -174,6 +167,15 @@ def resolve_repo_path(self, repo_path: str | Path) -> Path:
174167
"""Resolve repository path, handling both absolute and relative paths.
175168
176169
In Docker environment, relative paths are resolved against base_path.
170+
171+
Args:
172+
repo_path: Path to resolve (absolute or relative)
173+
174+
Returns:
175+
Path: Resolved absolute path
176+
177+
Raises:
178+
ValueError: If base path is not configured for relative paths
177179
"""
178180
path = Path(repo_path)
179181
if not path.is_absolute():
@@ -194,9 +196,9 @@ def get_repo(self, repo_path: str) -> git.Repo:
194196
Raises:
195197
ValueError: If the path is invalid or not a Git repository
196198
"""
197-
resolved_path = self.resolve_repo_path(repo_path)
198-
validated_path = self.path_validator.validate_path(resolved_path)
199199
try:
200+
resolved_path = self.resolve_repo_path(repo_path)
201+
validated_path = self.path_validator.validate_path(resolved_path)
200202
repo = git.Repo(validated_path)
201203
# Additional check to ensure the repo root is within allowed scope
202204
repo_root = Path(repo.git_dir).parent.resolve()
@@ -207,6 +209,9 @@ def get_repo(self, repo_path: str) -> git.Repo:
207209
raise ValueError(f"{validated_path} is not a Git repository")
208210
except git.NoSuchPathError:
209211
raise ValueError(f"Path {validated_path} does not exist")
212+
except ValueError as e:
213+
# Re-raise ValueError with the original message
214+
raise ValueError(str(e))
210215

211216
def git_status(self, repo: git.Repo) -> str:
212217
return repo.git.status()
@@ -249,21 +254,37 @@ def git_log(self, repo: git.Repo, max_count: int = 10) -> list[str]:
249254
return log
250255

251256
def git_create_branch(self, repo: git.Repo, branch_name: str, base_branch: str | None = None) -> str:
252-
if base_branch:
253-
try:
254-
# Try to get the ref (branch)
255-
base = repo.refs[base_branch]
256-
except KeyError:
257-
# If it's not a ref, try to get the commit
257+
"""Create a new branch in the repository.
258+
259+
Args:
260+
repo: The Git repository
261+
branch_name: Name of the new branch
262+
base_branch: Base branch or commit to create from (optional)
263+
264+
Returns:
265+
A confirmation message
266+
267+
Raises:
268+
ValueError: If the base branch or commit is invalid
269+
"""
270+
try:
271+
if base_branch:
258272
try:
259-
base = repo.commit(base_branch)
260-
except git.BadName:
261-
raise ValueError(f"Invalid base branch or commit: {base_branch}")
262-
else:
263-
base = repo.active_branch
264-
265-
repo.create_head(branch_name, base)
266-
return f"Created branch '{branch_name}' from '{base.name if hasattr(base, 'name') else base.hexsha}'"
273+
# Try to get the ref (branch)
274+
base = repo.refs[base_branch]
275+
except (IndexError, AttributeError):
276+
# If it's not a ref, try to get the commit
277+
try:
278+
base = repo.commit(base_branch)
279+
except (git.BadName, ValueError):
280+
raise ValueError(f"Invalid base branch or commit: {base_branch}")
281+
else:
282+
base = repo.active_branch
283+
284+
new_branch = repo.create_head(branch_name, base)
285+
return f"Created branch '{branch_name}' from '{base.name if hasattr(base, 'name') else base.hexsha}'"
286+
except git.GitCommandError as e:
287+
raise ValueError(f"Failed to create branch: {str(e)}")
267288

268289
def git_checkout(self, repo: git.Repo, branch_name: str) -> str:
269290
"""
@@ -277,10 +298,12 @@ def git_checkout(self, repo: git.Repo, branch_name: str) -> str:
277298
A confirmation message
278299
279300
Raises:
280-
git.GitCommandError: If the branch doesn't exist or other Git errors occur
301+
ValueError: If the branch doesn't exist or other Git errors occur
281302
"""
282-
repo.git.checkout(branch_name)
283-
return f"Switched to branch '{branch_name}'"
303+
try:
304+
return git_checkout(repo, branch_name)
305+
except git.GitCommandError as e:
306+
raise ValueError(f"Failed to checkout branch: {str(e)}")
284307

285308
def git_init(self, repo_path: str) -> str:
286309
try:

0 commit comments

Comments
 (0)