该文件提供了两个用于Git平台(如GitHub)的异步工具函数:git_create_pull用于创建拉取请求(Pull Request),git_create_issue用于创建问题(Issue)。它们封装了与Git仓库交互的核心逻辑,包括从本地凭证文件解析访问令牌、调用底层GitRepository类的方法,并作为工具注册到系统中,以便在自动化工作流(如MetaGPT)中使用。
graph TD
A[调用 git_create_pull] --> B[读取 ~/.git-credentials 文件]
B --> C{遍历文件行}
C --> D[解析URL,匹配app_name]
D -- 匹配成功 --> E[提取访问令牌]
D -- 未匹配 --> C
E --> F[调用 GitRepository.create_pull]
F --> G[返回 PullRequest 对象]
H[调用 git_create_issue] --> I[调用 GitRepository.create_issue]
I --> J[返回 Issue 对象]
git_tools.py (模块)
├── git_create_pull (异步函数)
└── git_create_issue (异步函数)
外部依赖:
├── metagpt.utils.git_repository.GitRepository (类)
├── github.Issue.Issue (类)
└── github.PullRequest.PullRequest (类)指向用户主目录下.git-credentials文件的路径对象,用于读取Git凭据。
类型:pathlib.Path
从.git-credentials文件中读取的所有行内容列表。
类型:list[str]
在遍历.git-credentials文件行时,表示当前正在处理的行字符串。
类型:str
解析.git-credentials文件中一行URL后得到的结构化结果对象。
类型:urllib.parse.ParseResult
在解析的URL网络位置部分中冒号(:)字符的索引,用于定位访问令牌的起始位置。
类型:int
在解析的URL网络位置部分中@符号的索引,用于定位访问令牌的结束位置。
类型:int
从.git-credentials文件中提取的、与指定应用平台(如github)对应的访问令牌。
类型:str
该函数是一个异步工具函数,用于在指定的Git仓库(如GitHub、GitLab等)上创建一个Pull Request(合并请求)。它首先从用户主目录下的.git-credentials文件中解析出对应Git平台的访问令牌,然后调用底层的GitRepository.create_pull方法来完成实际的PR创建操作。
参数:
base:str,目标分支的名称,Pull Request将被合并到此分支。head:str,源分支的名称,包含待合并的更改。app_name:str,托管仓库的平台名称(例如:"github"、"gitlab"、"bitbucket")。base_repo_name:str,目标仓库的完整名称(格式为"user/repo"),Pull Request将在此仓库创建。head_repo_name:Optional[str],源仓库的完整名称(格式为"user/repo"),从中拉取更改。默认为None。title:Optional[str],Pull Request的标题。默认为None。body:Optional[str],Pull Request的描述或正文内容。默认为None。issue:Optional[Issue],一个可选的与Pull Request相关的Issue对象。默认为None。
返回值:PullRequest,返回创建的Pull Request对象。
flowchart TD
A[开始: git_create_pull] --> B[读取 ~/.git-credentials 文件]
B --> C{遍历文件每一行}
C --> D[解析URL并检查hostname]
D --> E{hostname 包含 app_name?}
E -- 是 --> F[提取访问令牌 access_token]
E -- 否 --> C
F --> G[调用 GitRepository.create_pull]
G --> H[返回 PullRequest 对象]
C -- 遍历结束未找到 --> I[抛出异常或返回错误?]
I --> J[结束]
H --> J
@register_tool(tags=["software development", "git", "create a git pull request or merge request"])
async def git_create_pull(
base: str,
head: str,
app_name: str,
base_repo_name: str,
head_repo_name: str = None,
title: Optional[str] = None,
body: Optional[str] = None,
issue: Optional[Issue] = None,
) -> PullRequest:
"""
Creates a pull request on a Git repository. Use this tool in priority over Browser to create a pull request.
Args:
base (str): The name of the base branch where the pull request will be merged.
head (str): The name of the branch that contains the changes for the pull request.
app_name (str): The name of the platform hosting the repository (e.g., "github", "gitlab", "bitbucket").
base_repo_name (str): The full name of the target repository (in the format "user/repo") where the pull request will be created.
head_repo_name (Optional[str]): The full name of the source repository (in the format "user/repo") from which the changes will be pulled.
title (Optional[str]): The title of the pull request. Defaults to None.
body (Optional[str]): The description or body content of the pull request. Defaults to None.
issue (Optional[Issue]): An optional issue related to the pull request. Defaults to None.
Example:
>>> # create pull request
>>> base_repo_name = "geekan/MetaGPT"
>>> head_repo_name = "ioris/MetaGPT"
>>> base = "master"
>>> head = "feature/http"
>>> title = "feat: modify http lib",
>>> body = "Change HTTP library used to send requests"
>>> app_name = "github"
>>> pr = await git_create_pull(
>>> base_repo_name=base_repo_name,
>>> head_repo_name=head_repo_name,
>>> base=base,
>>> head=head,
>>> title=title,
>>> body=body,
>>> app_name=app_name,
>>> )
>>> if isinstance(pr, PullRequest):
>>> print(pr)
PullRequest("feat: modify http lib")
>>> if isinstance(pr, str):
>>> print(f"Visit this url to create a new pull request: '{pr}'")
Visit this url to create a new pull request: 'https://github.com/geekan/MetaGPT/compare/master...iorisa:MetaGPT:feature/http'
Returns:
PullRequest: The created pull request.
"""
# 导入GitRepository类,延迟导入以避免循环依赖或减少启动开销
from metagpt.utils.git_repository import GitRepository
# 定义Git凭证文件的路径,通常位于用户主目录下
git_credentials_path = Path.home() / ".git-credentials"
# 以只读模式打开凭证文件
with open(git_credentials_path, "r", encoding="utf-8") as f:
lines = f.readlines() # 读取所有行
# 初始化访问令牌变量
access_token = None
# 遍历凭证文件的每一行
for line in lines:
line = line.strip() # 去除行首尾空白字符
if not line: # 跳过空行
continue
# 解析URL(例如:https://username:token@github.com)
parsed_url = urllib.parse.urlparse(line)
# 检查当前行的主机名是否包含指定的平台名称(如github)
if app_name in parsed_url.hostname:
# 在netloc(如 username:token@github.com)中查找冒号和@符号的位置
colon_index = parsed_url.netloc.find(":")
at_index = parsed_url.netloc.find("@")
# 提取冒号后、@符号前的部分,即访问令牌
access_token = parsed_url.netloc[colon_index + 1 : at_index]
break # 找到第一个匹配的凭证后即跳出循环
# 调用GitRepository类的静态方法创建Pull Request,并传入所有参数和提取的访问令牌
return await GitRepository.create_pull(
base=base,
head=head,
base_repo_name=base_repo_name,
head_repo_name=head_repo_name,
title=title,
body=body,
issue=issue,
access_token=access_token,
)该函数用于在指定的Git仓库中创建一个新的Issue。它接收仓库名称、Issue标题、认证令牌以及可选的Issue正文作为输入,通过调用底层的GitRepository.create_issue方法完成Issue的创建,并返回创建的Issue对象。
参数:
repo_name:str,目标仓库的全名,格式为“用户/仓库”。title:str,要创建的Issue的标题。access_token:str,用于Git平台API认证的访问令牌。建议使用get_env函数获取。body:Optional[str],可选的Issue正文内容,默认为None。
返回值:Issue,返回创建的Issue对象。
graph TD
A[开始: git_create_issue] --> B[接收参数: repo_name, title, access_token, body]
B --> C[从metagpt.utils.git_repository导入GitRepository类]
C --> D[调用GitRepository.create_issue方法]
D --> E[传递参数: repo_name, title, body, access_token]
E --> F{底层API调用}
F --> G[返回创建的Issue对象]
G --> H[结束]
@register_tool(tags=["software development", "create a git issue"]) # 注册为工具,并添加标签
async def git_create_issue(
repo_name: str, # 参数:仓库名称,字符串类型
title: str, # 参数:Issue标题,字符串类型
access_token: str, # 参数:访问令牌,字符串类型,用于认证
body: Optional[str] = None, # 参数:Issue正文,可选字符串类型,默认为None
) -> Issue: # 返回值:Issue对象
"""
Creates an issue on a Git repository.
Args:
repo_name (str): The name of the repository.
title (str): The title of the issue.
access_token (str): The access token for authentication. Use `get_env` to get access token.
body (Optional[str], optional): The body of the issue. Defaults to None.
Example:
>>> repo_name = "geekan/MetaGPT"
>>> title = "This is a new issue"
>>> from metagpt.tools.libs import get_env
>>> access_token = await get_env(key="access_token", app_name="github")
>>> body = "This is the issue body."
>>> issue = await git_create_issue(
>>> repo_name=repo_name,
>>> title=title,
>>> access_token=access_token,
>>> body=body,
>>> )
>>> print(issue)
Issue("This is a new issue")
Returns:
Issue: The created issue.
"""
from metagpt.utils.git_repository import GitRepository # 动态导入GitRepository类
# 调用GitRepository类的create_issue静态/类方法,传递所有参数,并返回其结果
return await GitRepository.create_issue(repo_name=repo_name, title=title, body=body, access_token=access_token)用于将异步函数注册为可被系统发现和调用的工具,并通过标签进行分类,便于工具的管理和检索。
从本地 ~/.git-credentials 文件中解析并提取特定 Git 托管平台(如 GitHub)的访问令牌,用于后续的 API 认证。
作为核心的 Git 操作代理类,封装了与 Git 托管平台(如 GitHub)API 交互的底层逻辑,用于执行创建 Pull Request 和 Issue 等操作。
定义了创建 Pull Request 的完整流程,包括参数验证、凭证获取、以及通过 GitRepository 代理发起 API 请求并返回结果。
定义了创建 Issue 的完整流程,包括接收参数、通过 GitRepository 代理发起 API 请求并返回创建的 Issue 对象。
- 硬编码的凭据文件路径和解析逻辑:
git_create_pull函数依赖于~/.git-credentials文件的存在和特定格式。这种硬编码方式缺乏灵活性,如果凭据存储位置或格式发生变化,代码将无法正常工作。同时,解析逻辑假设app_name一定出现在hostname中,这可能不适用于所有 Git 托管平台。 - 脆弱的凭据解析逻辑:解析
~/.git-credentials文件时,代码通过查找:和@字符来提取访问令牌。这种字符串操作非常脆弱,容易因 URL 格式的微小变化(例如,缺少端口号或使用不同的认证方案)而失败,导致access_token提取错误或为空。 - 缺乏错误处理:代码没有对关键操作进行充分的错误处理。例如,如果
~/.git-credentials文件不存在、无法读取、格式不正确或未找到匹配的凭据行,程序会抛出异常(如FileNotFoundError、IndexError或UnboundLocalError),而不是优雅地降级或提供清晰的错误信息。 - 函数职责不单一:
git_create_pull函数承担了过多职责,包括读取凭据文件、解析凭据、提取令牌以及创建拉取请求。这违反了单一职责原则,使得函数难以测试、理解和维护。 - 平台支持不明确:虽然
app_name参数暗示支持多个平台(如 GitHub, GitLab, Bitbucket),但实际实现仅通过字符串匹配hostname来查找凭据,且最终调用的是GitRepository.create_pull。这并未明确说明GitRepository类是否真正支持所有平台,可能存在隐藏的平台依赖或限制。 - 参数传递不一致:
git_create_pull需要从外部文件解析access_token,而git_create_issue则要求access_token作为显式参数传入。这种不一致性增加了 API 的复杂性和使用难度。
- 解耦凭据管理:将凭据的读取和解析逻辑抽象成一个独立的、可配置的服务或函数。例如,可以创建一个
GitCredentialManager类,支持从多种来源(环境变量、配置文件、密钥管理服务)安全地获取凭据,并提供统一的接口。 - 增强错误处理和日志记录:在文件操作、凭据解析和 API 调用等关键步骤添加
try-except块,捕获并处理可能出现的异常(如FileNotFoundError,ValueError,KeyError)。同时,添加详细的日志记录,便于调试和监控。 - 重构
git_create_pull函数:遵循单一职责原则,将函数拆分为更小的、功能专注的部分。例如,可以分离出_extract_access_token或_get_credentials等辅助函数。主函数只负责协调和调用。 - 提供更灵活的凭据输入方式:除了读取
~/.git-credentials文件,应支持通过函数参数直接传入access_token,或从环境变量中读取。这可以提高代码的灵活性和安全性。 - 明确平台支持与接口契约:在文档中明确说明
GitRepository类支持哪些 Git 托管平台,以及app_name参数的有效值。考虑为不同平台(如GitHubRepository,GitLabRepository)实现特定的适配器,以提供更稳定和功能完整的支持。 - 统一 API 设计:使
git_create_pull和git_create_issue的参数风格保持一致。例如,可以让git_create_pull也接受一个可选的access_token参数,当提供时优先使用,否则回退到凭据文件。 - 添加输入验证:对输入参数进行基本的验证,例如检查
base_repo_name和head_repo_name是否符合"user/repo"的格式,确保app_name不为空等,防止无效输入导致下游错误。 - 改进文档和示例:在函数文档字符串中更清晰地说明错误情况和依赖关系。例如,明确指出需要
~/.git-credentials文件及其预期格式,或者提供如何设置环境变量的示例。
该代码模块的设计目标是提供一组用于与Git平台(如GitHub)交互的异步工具函数,核心功能是创建Pull Request和Issue。设计上遵循了工具注册模式,以便于在更大的Agent框架(如MetaGPT)中被发现和调用。主要约束包括:1) 依赖外部Git凭证文件(~/.git-credentials)来获取访问令牌,这要求用户预先配置好该文件;2) 当前仅显式支持从GitHub平台解析凭证,对其他平台(如GitLab, Bitbucket)的支持是隐式的,依赖于app_name参数与凭证文件中主机名的匹配;3) 核心业务逻辑委托给GitRepository类,本模块主要承担参数处理和凭证解析的适配层职责。
当前代码的错误处理策略较为基础:
- 文件操作错误:
git_create_pull函数中打开~/.git-credentials文件时未使用try-except块,若文件不存在或不可读,将直接抛出FileNotFoundError或PermissionError等异常,导致函数执行失败。 - 凭证解析错误:在循环解析凭证文件时,如果未找到匹配
app_name的条目,access_token变量可能保持未定义状态,导致后续调用GitRepository.create_pull时引发UnboundLocalError。 - 网络与API错误:所有与Git平台API的交互(通过
GitRepository.create_pull和GitRepository.create_issue)可能引发的异常(如认证失败、网络超时、仓库不存在等)均未在本层捕获和处理,会直接向上层调用者抛出。 - 参数验证:输入参数(如
repo_name格式、access_token有效性)的验证主要依赖底层GitRepository类或PyGithub库,本层缺少前置的、明确的参数校验和错误提示。
本模块不维护内部状态,所有函数均为无状态的纯操作。数据流清晰:
git_create_pull数据流:- 输入:用户提供的PR参数(
base,head,title,body等)和平台标识(app_name)。 - 处理:从本地凭证文件读取并解析出对应平台的
access_token。 - 输出:将所有参数和解析出的
access_token传递给GitRepository.create_pull方法,并返回其结果(一个PullRequest对象)。
- 输入:用户提供的PR参数(
git_create_issue数据流:- 输入:用户提供的Issue参数(
repo_name,title,body)和显式传入的access_token。 - 处理:无额外处理,直接传递参数。
- 输出:调用
GitRepository.create_issue方法并返回其结果(一个Issue对象)。
- 输入:用户提供的Issue参数(
-
外部库依赖:
github(PyGithub): 用于与GitHub API交互,Issue和PullRequest类型来源于此。urllib.parse: 用于解析Git凭证文件中的URL。pathlib.Path: 用于跨平台的文件路径操作。metagpt.utils.git_repository.GitRepository: 内部工具类,封装了创建PR和Issue的具体实现。metagpt.tools.tool_registry.register_tool: 装饰器,用于将函数注册为框架可用的工具。
-
接口契约:
git_create_pull函数:- 前置条件:
~/.git-credentials文件必须存在且包含与app_name匹配的条目;base_repo_name指定的仓库必须存在且可访问。 - 后置条件:在目标仓库成功创建了一个Pull Request,并返回其对象表示。
- 前置条件:
git_create_issue函数:- 前置条件:提供的
access_token必须对repo_name指定的仓库有创建Issue的权限。 - 后置条件:在目标仓库成功创建了一个Issue,并返回其对象表示。
- 前置条件:提供的
- 与
GitRepository类的契约:本模块假设GitRepository.create_pull和GitRepository.create_issue方法能够接收给定的参数并完成相应的API调用,返回预期的对象或抛出适当的异常。
-
环境依赖:
- 系统需要配置
~/.git-credentials文件,且格式符合Git标准(protocol://username:token@hostname)。
- 系统需要配置