mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-08 15:05:17 +02:00
feat: +file repository
This commit is contained in:
parent
f6dab7e054
commit
29003a9beb
3 changed files with 122 additions and 7 deletions
|
|
@ -6,38 +6,95 @@
|
|||
@File : git_repository.py
|
||||
@Desc: File repository management. RFC 135 2.2.3.2, 2.2.3.4 and 2.2.3.13.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Dict, List
|
||||
|
||||
import aiofiles
|
||||
|
||||
from metagpt.utils.git_repository import GitRepository
|
||||
from metagpt.logs import logger
|
||||
|
||||
|
||||
class FileRepository:
|
||||
def __init__(self, git_repo: GitRepository, relative_path: Path = "."):
|
||||
def __init__(self, git_repo, relative_path: Path = Path(".")):
|
||||
self._relative_path = relative_path # Relative path based on the Git repository.
|
||||
self._git_repo = git_repo
|
||||
self._dependencies: Dict[str, List[str]] = {}
|
||||
|
||||
async def save(self, filename: Path, content, dependencies: List[str] = None):
|
||||
# Initializing
|
||||
self.workdir.mkdir(parents=True, exist_ok=True)
|
||||
if self.dependency_path_name.exists():
|
||||
try:
|
||||
with open(str(self.dependency_path_name), mode="r") as reader:
|
||||
self._dependencies = json.load(reader)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to load {str(self.dependency_path_name)}, error:{e}")
|
||||
|
||||
async def save(self, filename: Path | str, content, dependencies: List[str] = None):
|
||||
path_name = self.workdir / filename
|
||||
with aiofiles.open(str(path_name), mode="w") as writer:
|
||||
path_name.parent.mkdir(parents=True, exist_ok=True)
|
||||
async with aiofiles.open(str(path_name), mode="w") as writer:
|
||||
await writer.write(content)
|
||||
if dependencies is not None:
|
||||
await self.update_dependency(filename, dependencies)
|
||||
|
||||
async def get(self, filename: Path | str):
|
||||
path_name = self.workdir / filename
|
||||
async with aiofiles.open(str(path_name), mode="r") as reader:
|
||||
return await reader.read()
|
||||
|
||||
def get_dependency(self, filename: Path | str) -> List:
|
||||
key = str(filename)
|
||||
return self._dependencies.get(key, [])
|
||||
|
||||
def get_changed_dependency(self, filename: Path | str) -> List:
|
||||
dependencies = self.get_dependency(filename=filename)
|
||||
changed_files = self.changed_files
|
||||
changed_dependent_files = []
|
||||
for df in dependencies:
|
||||
if df in changed_files.keys():
|
||||
changed_dependent_files.append(df)
|
||||
return changed_dependent_files
|
||||
|
||||
async def update_dependency(self, filename, dependencies: List[str]):
|
||||
self._dependencies[str(filename)] = dependencies
|
||||
|
||||
async def save_dependency(self):
|
||||
filename = ".dependencies.json"
|
||||
path_name = self.workdir / filename
|
||||
data = json.dumps(self._dependencies)
|
||||
with aiofiles.open(str(path_name), mode="w") as writer:
|
||||
with aiofiles.open(str(self.dependency_path_name), mode="w") as writer:
|
||||
await writer.write(data)
|
||||
|
||||
@property
|
||||
def workdir(self):
|
||||
return self._git_repo.workdir / self._relative_path
|
||||
|
||||
@property
|
||||
def dependency_path_name(self):
|
||||
filename = ".dependencies.json"
|
||||
path_name = self.workdir / filename
|
||||
return path_name
|
||||
|
||||
@property
|
||||
def changed_files(self) -> Dict[str, str]:
|
||||
files = self._git_repo.changed_files
|
||||
relative_files = {}
|
||||
for p, ct in files.items():
|
||||
try:
|
||||
rf = Path(p).relative_to(self._relative_path)
|
||||
except ValueError:
|
||||
continue
|
||||
relative_files[str(rf)] = ct
|
||||
return relative_files
|
||||
|
||||
def get_change_dir_files(self, dir: Path | str) -> List:
|
||||
changed_files = self.changed_files
|
||||
children = []
|
||||
for f in changed_files:
|
||||
try:
|
||||
Path(f).relative_to(Path(dir))
|
||||
except ValueError:
|
||||
continue
|
||||
children.append(str(f))
|
||||
return children
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from git.repo import Repo
|
|||
from git.repo.fun import is_git_dir
|
||||
|
||||
from metagpt.const import WORKSPACE_ROOT
|
||||
from metagpt.utils.file_repository import FileRepository
|
||||
|
||||
|
||||
class ChangeType(Enum):
|
||||
|
|
@ -150,6 +151,14 @@ class GitRepository:
|
|||
self.add_change(self.changed_files)
|
||||
self.commit(comments)
|
||||
|
||||
def new_file_repository(self, relative_path: Path | str) -> FileRepository:
|
||||
"""Create a new instance of FileRepository associated with this Git repository.
|
||||
|
||||
:param relative_path: The relative path to the file repository within the Git repository.
|
||||
:return: A new instance of FileRepository.
|
||||
"""
|
||||
return FileRepository(git_repo=self, relative_path=Path(relative_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
path = WORKSPACE_ROOT / "git"
|
||||
|
|
|
|||
49
tests/metagpt/utils/test_file_repository.py
Normal file
49
tests/metagpt/utils/test_file_repository.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Time : 2023/11/20
|
||||
@Author : mashenquan
|
||||
@File : test_file_repository.py
|
||||
@Desc: Unit tests for file_repository.py
|
||||
"""
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from metagpt.utils.git_repository import ChangeType, GitRepository
|
||||
from tests.metagpt.utils.test_git_repository import mock_file
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_file_repo():
|
||||
local_path = Path(__file__).parent / "file_repo_git"
|
||||
if local_path.exists():
|
||||
shutil.rmtree(local_path)
|
||||
|
||||
git_repo = GitRepository(local_path=local_path, auto_init=True)
|
||||
assert not git_repo.changed_files
|
||||
|
||||
await mock_file(local_path / "g.txt", "")
|
||||
|
||||
file_repo_path = "file_repo1"
|
||||
full_path = local_path / file_repo_path
|
||||
assert not full_path.exists()
|
||||
file_repo = git_repo.new_file_repository(file_repo_path)
|
||||
assert file_repo.workdir == full_path
|
||||
assert file_repo.workdir.exists()
|
||||
await file_repo.save("a.txt", "AAA")
|
||||
await file_repo.save("b.txt", "BBB", ["a.txt"])
|
||||
assert "AAA" == await file_repo.get("a.txt")
|
||||
assert "BBB" == await file_repo.get("b.txt")
|
||||
assert ["a.txt"] == file_repo.get_dependency("b.txt")
|
||||
assert {"a.txt": ChangeType.UNTRACTED, "b.txt": ChangeType.UNTRACTED} == file_repo.changed_files
|
||||
assert ["a.txt"] == file_repo.get_changed_dependency("b.txt")
|
||||
await file_repo.save("d/e.txt", "EEE")
|
||||
assert ["d/e.txt"] == file_repo.get_change_dir_files("d")
|
||||
|
||||
git_repo.delete_repository()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-s"])
|
||||
Loading…
Add table
Add a link
Reference in a new issue