mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-25 21:36:52 +08:00
126 lines
4.5 KiB
Python
126 lines
4.5 KiB
Python
import json
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Callable
|
|
|
|
from openhands.core.logger import openhands_logger as logger
|
|
from openhands.runtime.utils import git_changes, git_diff
|
|
|
|
GIT_CHANGES_CMD = 'python3 /openhands/code/openhands/runtime/utils/git_changes.py'
|
|
GIT_DIFF_CMD = (
|
|
'python3 /openhands/code/openhands/runtime/utils/git_diff.py "{file_path}"'
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class CommandResult:
|
|
"""Represents the result of a shell command execution.
|
|
|
|
Attributes:
|
|
content (str): The output content of the command.
|
|
exit_code (int): The exit code of the command execution.
|
|
"""
|
|
|
|
content: str
|
|
exit_code: int
|
|
|
|
|
|
class GitHandler:
|
|
"""A handler for executing Git-related operations via shell commands."""
|
|
|
|
def __init__(
|
|
self,
|
|
execute_shell_fn: Callable[[str, str | None], CommandResult],
|
|
create_file_fn: Callable[[str, str], int],
|
|
):
|
|
self.execute = execute_shell_fn
|
|
self.create_file_fn = create_file_fn
|
|
self.cwd: str | None = None
|
|
self.git_changes_cmd = GIT_CHANGES_CMD
|
|
self.git_diff_cmd = GIT_DIFF_CMD
|
|
|
|
def set_cwd(self, cwd: str) -> None:
|
|
"""Sets the current working directory for Git operations.
|
|
|
|
Args:
|
|
cwd (str): The directory path.
|
|
"""
|
|
self.cwd = cwd
|
|
|
|
def _create_python_script_file(self, file: str):
|
|
result = self.execute('mktemp -d', self.cwd)
|
|
script_file = Path(result.content.strip(), Path(file).name)
|
|
with open(file, 'r') as f:
|
|
self.create_file_fn(str(script_file), f.read())
|
|
result = self.execute(f'chmod +x "{script_file}"', self.cwd)
|
|
return script_file
|
|
|
|
def get_git_changes(self) -> list[dict[str, str]] | None:
|
|
"""Retrieves the list of changed files in Git repositories.
|
|
Examines each direct subdirectory of the workspace directory looking for git repositories
|
|
and returns the changes for each of these directories.
|
|
Optimized to use a single git command per repository for maximum performance.
|
|
|
|
Returns:
|
|
list[dict[str, str]] | None: A list of dictionaries containing file paths and statuses. None if no git repositories found.
|
|
"""
|
|
# If cwd is not set, return None
|
|
if not self.cwd:
|
|
return None
|
|
|
|
result = self.execute(self.git_changes_cmd, self.cwd)
|
|
if result.exit_code == 0:
|
|
try:
|
|
changes = json.loads(result.content)
|
|
return changes
|
|
except Exception:
|
|
logger.exception(
|
|
'GitHandler:get_git_changes:error',
|
|
extra={'content': result.content},
|
|
)
|
|
return None
|
|
|
|
if self.git_changes_cmd != GIT_CHANGES_CMD:
|
|
# We have already tried to add a script to the workspace - it did not work
|
|
return None
|
|
|
|
# We try to add a script for getting git changes to the runtime - legacy runtimes may be missing the script
|
|
logger.info(
|
|
'GitHandler:get_git_changes: adding git_changes script to runtime...'
|
|
)
|
|
script_file = self._create_python_script_file(git_changes.__file__)
|
|
self.git_changes_cmd = f'python3 {script_file}'
|
|
|
|
# Try again with the new changes cmd
|
|
return self.get_git_changes()
|
|
|
|
def get_git_diff(self, file_path: str) -> dict[str, str]:
|
|
"""Retrieves the original and modified content of a file in the repository.
|
|
|
|
Args:
|
|
file_path (str): Path to the file.
|
|
|
|
Returns:
|
|
dict[str, str]: A dictionary containing the original and modified content.
|
|
"""
|
|
# If cwd is not set, return None
|
|
if not self.cwd:
|
|
raise ValueError('no_dir_in_git_diff')
|
|
|
|
result = self.execute(self.git_diff_cmd.format(file_path=file_path), self.cwd)
|
|
if result.exit_code == 0:
|
|
diff = json.loads(result.content, strict=False)
|
|
return diff
|
|
|
|
if self.git_diff_cmd != GIT_DIFF_CMD:
|
|
# We have already tried to add a script to the workspace - it did not work
|
|
raise ValueError('error_in_git_diff')
|
|
|
|
# We try to add a script for getting git changes to the runtime - legacy runtimes may be missing the script
|
|
logger.info('GitHandler:get_git_diff: adding git_diff script to runtime...')
|
|
script_file = self._create_python_script_file(git_diff.__file__)
|
|
self.git_diff_cmd = f'python3 {script_file} "{{file_path}}"'
|
|
|
|
# Try again with the new changes cmd
|
|
return self.get_git_diff(file_path)
|