Merge PR #142: 新增 PR 本地检出命令
# Conflicts: # internal/i18n/locales/en-US.json # internal/i18n/locales/zh-CN.json # shortcuts/pr/pr_test.go # skills/README.md
This commit is contained in:
commit
62c44bf7b4
|
|
@ -432,6 +432,9 @@ gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: New feature" -
|
|||
# View a PR
|
||||
gitlink-cli pr +view --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# Fetch and checkout a PR branch locally
|
||||
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42 --branch review/pr-42
|
||||
|
||||
# Merge a PR
|
||||
gitlink-cli pr +merge --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
|
|||
|
|
@ -442,6 +442,9 @@ gitlink-cli pr +create --owner Gitlink --repo forgeplus -t "feat: 新功能" --h
|
|||
# 查看 PR
|
||||
gitlink-cli pr +view --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# 拉取并切换到 PR 分支
|
||||
gitlink-cli pr +checkout --owner Gitlink --repo forgeplus -i 42 --branch review/pr-42
|
||||
|
||||
# 合并 PR
|
||||
gitlink-cli pr +merge --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
# PR 本地检出命令
|
||||
|
||||
新增 `gitlink-cli pr +checkout`,用于把指定 PR 的源分支拉取到本地并切换到 review 分支。维护者和贡献者在审查 PR 时,经常需要从“查看 PR 元信息”进入“本地构建、测试、复现”的流程,之前只能手动查看 fork 信息、拼接远端地址、fetch 分支,再 checkout;新命令把这套流程收敛为一个 Shortcut。
|
||||
|
||||
命令会读取 GitLink PR 详情,自动识别同仓库 PR 与 fork PR 的源仓库和源分支,不依赖本地 remote 必须叫 `origin`。默认创建 `pr-<id>` 本地分支,也支持 `--branch` 指定分支名、`--force` 重置已存在分支、`--dry-run` 预览将执行的 git 命令。
|
||||
|
||||
本次变更补充了单元测试、README 示例、PR Skill 说明和独立参考文档,覆盖 fork PR 解析、同仓库 head 解析、dry-run、force checkout 以及非法分支名校验。
|
||||
|
|
@ -5,13 +5,10 @@
|
|||
"cmd.auth.logout.short": "Logout from GitLink",
|
||||
"cmd.auth.short": "Authentication commands",
|
||||
"cmd.auth.status.short": "Show authentication status",
|
||||
"cmd.branch.all.short": "List all branches with archive URLs",
|
||||
"cmd.branch.create.short": "Create a branch",
|
||||
"cmd.branch.delete.short": "Delete a branch",
|
||||
"cmd.branch.list.short": "List branches",
|
||||
"cmd.branch.protect.short": "Set branch protection",
|
||||
"cmd.branch.restore.short": "Restore a deleted branch",
|
||||
"cmd.branch.set_default.short": "Set the default branch",
|
||||
"cmd.branch.short": "Branch operations",
|
||||
"cmd.branch.unprotect.short": "Remove branch protection",
|
||||
"cmd.ci.builds.short": "List CI builds",
|
||||
|
|
@ -24,17 +21,6 @@
|
|||
"cmd.config.list.short": "List all configuration values",
|
||||
"cmd.config.set.short": "Set a configuration value",
|
||||
"cmd.config.short": "Manage gitlink-cli configuration",
|
||||
"cmd.dataset.create.long": "Create the dataset of a repository with a title, description, optional license and research paper content.",
|
||||
"cmd.dataset.create.short": "Create a repository's dataset",
|
||||
"cmd.dataset.delete_attachment.long": "Delete a dataset attachment by its UUID. This is destructive: preview with --dry-run, then pass --yes to confirm.",
|
||||
"cmd.dataset.delete_attachment.short": "Delete a dataset attachment by UUID",
|
||||
"cmd.dataset.list.long": "List datasets for one or more GitLink projects by their numeric project IDs.",
|
||||
"cmd.dataset.list.short": "List datasets by project IDs",
|
||||
"cmd.dataset.short": "Dataset operations",
|
||||
"cmd.dataset.update.long": "Update the dataset of a repository (title, description, optional license and research paper content).",
|
||||
"cmd.dataset.update.short": "Update a repository's dataset",
|
||||
"cmd.dataset.view.long": "View a repository's dataset and its attachments. Use --page/--limit to paginate attachments.",
|
||||
"cmd.dataset.view.short": "View a repository's dataset",
|
||||
"cmd.doctor.long": "Run local diagnostics for gitlink-cli configuration, authentication, repository context and API connectivity.",
|
||||
"cmd.doctor.short": "Diagnose gitlink-cli environment problems",
|
||||
"cmd.issue.batch_close.long": "Close filtered issues in bulk.\n\nThis command defaults to dry-run mode and only prints matching issues.\nPass --yes to execute remote close operations. Use restrictive filters and a small limit.\n\nExamples:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
|
|
@ -45,10 +31,6 @@
|
|||
"cmd.issue.batch_list.short": "List issue batch maintenance candidates without changing remote data",
|
||||
"cmd.issue.close.short": "Close an issue",
|
||||
"cmd.issue.comment.short": "Add a comment to an issue",
|
||||
"cmd.issue.comment_delete.short": "Delete an issue comment",
|
||||
"cmd.issue.comment_edit.short": "Edit an issue comment",
|
||||
"cmd.issue.comment_replies.short": "List replies to an issue comment",
|
||||
"cmd.issue.comments.short": "List issue comments",
|
||||
"cmd.issue.create.short": "Create a new issue",
|
||||
"cmd.issue.list.short": "List issues",
|
||||
"cmd.issue.short": "Issue operations",
|
||||
|
|
@ -59,13 +41,8 @@
|
|||
"cmd.org.list.short": "List organizations",
|
||||
"cmd.org.members.short": "List organization members",
|
||||
"cmd.org.short": "Organization operations",
|
||||
"cmd.pm.action_runs.short": "List PM action run records",
|
||||
"cmd.pm.dashboards.short": "List PM dashboards",
|
||||
"cmd.pm.issue_tags.short": "List PM issue tags",
|
||||
"cmd.pm.pipelines.short": "List PM pipelines",
|
||||
"cmd.pm.short": "Project management operations",
|
||||
"cmd.pm.sprint_issues.short": "List PM sprint issues",
|
||||
"cmd.pm.weekly_issues.short": "List PM weekly issues",
|
||||
"cmd.pr.checkout.long": "Fetch a pull request head branch and check it out locally.\n\nThe command reads PR source repository information from GitLink, so it works for both same-repository PRs and fork PRs without requiring a local remote named origin.\n\nExamples:\n gitlink-cli pr +checkout -i 42\n gitlink-cli pr +checkout -i 42 --branch review/pr-42\n gitlink-cli pr +checkout -i 42 --force",
|
||||
"cmd.pr.checkout.short": "Fetch and checkout a pull request locally",
|
||||
"cmd.pr.close.short": "Close a pull request",
|
||||
"cmd.pr.comment.short": "Add a comment to a pull request",
|
||||
"cmd.pr.create.short": "Create a pull request",
|
||||
|
|
@ -79,17 +56,6 @@
|
|||
"cmd.pr.version_diff.short": "Show diff for a pull request patchset version",
|
||||
"cmd.pr.versions.short": "List pull request patchset versions",
|
||||
"cmd.pr.view.short": "View pull request details",
|
||||
"cmd.profile.ability.long": "Show a user's development ability scores (influence, contribution, activity, experience, language) and language breakdown, sourced from the GitLink platform statistics API.",
|
||||
"cmd.profile.ability.short": "Show a user's development ability scores",
|
||||
"cmd.profile.activity.long": "Show a user's recent activity statistics: daily issues, pull requests, and commits over a time window.",
|
||||
"cmd.profile.activity.short": "Show a user's recent activity statistics",
|
||||
"cmd.profile.contribution.long": "Show a user's contribution heatmap (daily contribution counts) for a given year.",
|
||||
"cmd.profile.contribution.short": "Show a user's contribution heatmap",
|
||||
"cmd.profile.major.long": "Show a user's major/discipline categories (e.g. deep learning, quantum computing) inferred from their projects.",
|
||||
"cmd.profile.major.short": "Show a user's major/discipline categories",
|
||||
"cmd.profile.role.long": "Show a user's role positioning derived from the GitLink platform statistics API.",
|
||||
"cmd.profile.role.short": "Show a user's role positioning",
|
||||
"cmd.profile.short": "User profile and statistics operations",
|
||||
"cmd.release.create.short": "Create a release",
|
||||
"cmd.release.delete.short": "Delete a release",
|
||||
"cmd.release.list.short": "List releases",
|
||||
|
|
@ -97,25 +63,15 @@
|
|||
"cmd.release.view.short": "View release details",
|
||||
"cmd.repo.create.short": "Create a new repository",
|
||||
"cmd.repo.delete.short": "Delete a repository",
|
||||
"cmd.repo.file_exists.short": "Check whether a repository file exists",
|
||||
"cmd.repo.fork.short": "Fork a repository",
|
||||
"cmd.repo.info.short": "Show repository details",
|
||||
"cmd.repo.list.short": "List repositories for a user or organization",
|
||||
"cmd.repo.manifest.short": "Read a common dependency manifest",
|
||||
"cmd.repo.raw.short": "Read raw repository file content",
|
||||
"cmd.repo.short": "Repository operations",
|
||||
"cmd.repo.tree.short": "List repository files and directories",
|
||||
"cmd.root.long": "Manage repositories, issues, pull requests, releases, CI and workflows on GitLink.",
|
||||
"cmd.root.short": "GitLink CLI - command-line tool for GitLink",
|
||||
"cmd.search.repos.short": "Search repositories",
|
||||
"cmd.search.short": "Search operations",
|
||||
"cmd.search.users.short": "Search users",
|
||||
"cmd.template.create.short": "Create a project template",
|
||||
"cmd.template.delete.short": "Delete a project template",
|
||||
"cmd.template.get.short": "Get a project template by id",
|
||||
"cmd.template.list.short": "List project templates",
|
||||
"cmd.template.short": "Project template operations",
|
||||
"cmd.template.update.short": "Update a project template",
|
||||
"cmd.user.info.short": "Show user profile",
|
||||
"cmd.user.me.short": "Show current authenticated user",
|
||||
"cmd.user.short": "User operations",
|
||||
|
|
@ -132,12 +88,8 @@
|
|||
"error.auth.login_failed": "login failed: {message}",
|
||||
"error.auth.store_token_failed": "failed to store token: {message}",
|
||||
"error.auth.token_empty": "token cannot be empty",
|
||||
"error.branch.id_invalid": "invalid --branch-id: {value}; must be a positive integer",
|
||||
"error.config.save_failed": "failed to save config: {message}",
|
||||
"error.dataset.delete_confirm": "dataset attachment deletion is destructive; run --dry-run first, then pass --yes to confirm",
|
||||
"error.missing_required_flag": "required flag --{name} is missing",
|
||||
"error.profile.user_required": "could not determine target user; pass --user or run gitlink-cli auth login",
|
||||
"error.template.invalid_type": "invalid --type {type}; expected one of {types}",
|
||||
"error.unsupported_language": "unsupported language: {lang}",
|
||||
"flag.api.batch_continue_on_error": "Continue running remaining batch requests after a failure",
|
||||
"flag.api.batch_dry_run": "Preview batch requests without sending remote requests",
|
||||
|
|
@ -150,25 +102,11 @@
|
|||
"flag.api.query": "Query parameters (key=val&key2=val2)",
|
||||
"flag.auth.token": "Login by pasting an existing token",
|
||||
"flag.branch.from": "Source branch or commit",
|
||||
"flag.branch.id": "Deleted branch ID",
|
||||
"flag.branch.keyword": "Filter branches by keyword",
|
||||
"flag.branch.name": "Branch name",
|
||||
"flag.branch.state": "Branch visibility: visible (default), deleted, or all",
|
||||
"flag.ci.build": "Build number",
|
||||
"flag.ci.stage": "Stage number",
|
||||
"flag.ci.step": "Step number",
|
||||
"flag.comment.body": "Comment body",
|
||||
"flag.dataset.description": "Dataset description",
|
||||
"flag.dataset.dry_run": "Preview the request without writing the dataset",
|
||||
"flag.dataset.dry_run_delete": "Preview the request without deleting the attachment",
|
||||
"flag.dataset.ids": "Comma-separated project IDs to query datasets for",
|
||||
"flag.dataset.license_id": "License ID",
|
||||
"flag.dataset.limit": "Attachment page size",
|
||||
"flag.dataset.page": "Attachment page number",
|
||||
"flag.dataset.paper_content": "Research paper content",
|
||||
"flag.dataset.title": "Dataset title",
|
||||
"flag.dataset.uuid": "Attachment UUID",
|
||||
"flag.dataset.yes": "Confirm the destructive deletion",
|
||||
"flag.debug": "Enable debug output",
|
||||
"flag.description": "Description",
|
||||
"flag.doctor.skip_network": "Skip authenticated API connectivity checks",
|
||||
|
|
@ -186,10 +124,6 @@
|
|||
"flag.issue.batch_list.limit": "Maximum issues to return, capped at 100",
|
||||
"flag.issue.batch_process.limit": "Maximum issues to process, capped at 100",
|
||||
"flag.issue.body": "Issue description",
|
||||
"flag.issue.comment_id": "Comment (journal) ID from issue +comments",
|
||||
"flag.issue.comment_reply_to": "Parent comment (journal) ID to reply to",
|
||||
"flag.issue.comments.category": "Filter: comment/operate (default: all)",
|
||||
"flag.issue.comments.keyword": "Search keyword in comment content",
|
||||
"flag.issue.label": "Label ID",
|
||||
"flag.issue.label_filter": "Filter by existing label",
|
||||
"flag.issue.milestone": "Milestone ID",
|
||||
|
|
@ -210,10 +144,12 @@
|
|||
"flag.org.name": "Organization name",
|
||||
"flag.owner": "Repository owner (auto-detected from git remote)",
|
||||
"flag.page": "Page number",
|
||||
"flag.pm.project_id": "GitLink project ID",
|
||||
"flag.pr.assignee_id": "Assignee user ID",
|
||||
"flag.pr.base": "Target branch",
|
||||
"flag.pr.body": "PR description",
|
||||
"flag.pr.checkout_branch": "Local branch name to create (default: pr-<id>)",
|
||||
"flag.pr.checkout_dry_run": "Print the git commands without modifying the local repository",
|
||||
"flag.pr.checkout_force": "Reset the local branch if it already exists",
|
||||
"flag.pr.file": "Filter diff by file path",
|
||||
"flag.pr.head": "Source branch",
|
||||
"flag.pr.id": "PR number",
|
||||
|
|
@ -229,10 +165,6 @@
|
|||
"flag.pr.tag_id": "Issue tag ID",
|
||||
"flag.pr.title": "PR title",
|
||||
"flag.pr.version_id": "Patchset version ID",
|
||||
"flag.profile.end_time": "End time (Unix timestamp)",
|
||||
"flag.profile.start_time": "Start time (Unix timestamp)",
|
||||
"flag.profile.user": "Target user login (defaults to the authenticated user)",
|
||||
"flag.profile.year": "Year for the contribution heatmap (e.g. 2025)",
|
||||
"flag.release.body": "Release notes",
|
||||
"flag.release.id": "Release ID",
|
||||
"flag.release.id_or_tag": "Release ID or tag",
|
||||
|
|
@ -243,14 +175,8 @@
|
|||
"flag.repo": "Repository name (auto-detected from git remote)",
|
||||
"flag.repo.category": "Filter: manage/mirror/sync/fork/all (default: manage)",
|
||||
"flag.repo.description": "Repository description",
|
||||
"flag.repo.file_exists.path": "File path to check",
|
||||
"flag.repo.manifest.kind": "Manifest kind: go, node, python, rust, or java",
|
||||
"flag.repo.name": "Repository name",
|
||||
"flag.repo.private": "Make repository private (true/false)",
|
||||
"flag.repo.raw.path": "File path in the repository",
|
||||
"flag.repo.raw.ref": "Branch, tag, or commit ref",
|
||||
"flag.repo.tree.path": "Directory path to list (default: repository root)",
|
||||
"flag.repo.tree.ref": "Branch, tag, or commit ref",
|
||||
"flag.search.keyword": "Search keyword",
|
||||
"flag.sort_by": "Sort field",
|
||||
"flag.sort_direction": "Sort direction: asc, desc",
|
||||
|
|
@ -265,10 +191,6 @@
|
|||
"flag.webhook.secret": "Webhook secret",
|
||||
"flag.webhook.secret_update": "Webhook secret. Pass it again if the server does not return existing secrets.",
|
||||
"flag.webhook.type": "Webhook type: gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.template.content": "Template content",
|
||||
"flag.template.id": "Template ID",
|
||||
"flag.template.name": "Template name",
|
||||
"flag.template.type": "Template type, for example: ProjectTemplates::Issue",
|
||||
"flag.webhook.url": "Webhook target URL",
|
||||
"output.auth.env_hint": " Or set {env} environment variable",
|
||||
"output.auth.login_hint": " Run: gitlink-cli auth login",
|
||||
|
|
|
|||
|
|
@ -5,13 +5,10 @@
|
|||
"cmd.auth.logout.short": "退出 GitLink 登录",
|
||||
"cmd.auth.short": "认证命令",
|
||||
"cmd.auth.status.short": "显示认证状态",
|
||||
"cmd.branch.all.short": "列出全部分支及归档链接",
|
||||
"cmd.branch.create.short": "创建分支",
|
||||
"cmd.branch.delete.short": "删除分支",
|
||||
"cmd.branch.list.short": "列出分支",
|
||||
"cmd.branch.protect.short": "设置分支保护",
|
||||
"cmd.branch.restore.short": "恢复已删除分支",
|
||||
"cmd.branch.set_default.short": "设置默认分支",
|
||||
"cmd.branch.short": "分支操作",
|
||||
"cmd.branch.unprotect.short": "移除分支保护",
|
||||
"cmd.ci.builds.short": "列出 CI 构建",
|
||||
|
|
@ -24,17 +21,6 @@
|
|||
"cmd.config.list.short": "列出所有配置项",
|
||||
"cmd.config.set.short": "设置配置项",
|
||||
"cmd.config.short": "管理 gitlink-cli 配置",
|
||||
"cmd.dataset.create.long": "为仓库创建数据集,包含标题、描述、可选许可证和研究论文内容。",
|
||||
"cmd.dataset.create.short": "创建仓库数据集",
|
||||
"cmd.dataset.delete_attachment.long": "按 UUID 删除数据集附件。该操作具有破坏性:先用 --dry-run 预览,再传 --yes 确认。",
|
||||
"cmd.dataset.delete_attachment.short": "按 UUID 删除数据集附件",
|
||||
"cmd.dataset.list.long": "按数字项目 ID 列出一个或多个 GitLink 项目的数据集。",
|
||||
"cmd.dataset.list.short": "按项目 ID 列出数据集",
|
||||
"cmd.dataset.short": "数据集操作",
|
||||
"cmd.dataset.update.long": "更新仓库数据集(标题、描述、可选许可证和研究论文内容)。",
|
||||
"cmd.dataset.update.short": "更新仓库数据集",
|
||||
"cmd.dataset.view.long": "查看仓库的数据集及其附件。用 --page/--limit 对附件分页。",
|
||||
"cmd.dataset.view.short": "查看仓库数据集",
|
||||
"cmd.doctor.long": "诊断 gitlink-cli 的配置、认证、仓库上下文和 API 连通性问题。",
|
||||
"cmd.doctor.short": "诊断 gitlink-cli 环境问题",
|
||||
"cmd.issue.batch_close.long": "批量关闭筛选后的议题。\n\n该命令默认处于 dry-run 模式,只打印匹配的议题。\n传入 --yes 后执行远端关闭操作。请使用严格筛选条件和较小 limit。\n\n示例:\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20\n gitlink-cli issue +batch-close --owner Gitlink --repo gitlink-cli --older-than-days 60 --limit 20 --yes",
|
||||
|
|
@ -45,10 +31,6 @@
|
|||
"cmd.issue.batch_list.short": "列出议题批量维护候选项,不修改远端数据",
|
||||
"cmd.issue.close.short": "关闭议题",
|
||||
"cmd.issue.comment.short": "给议题添加评论",
|
||||
"cmd.issue.comment_delete.short": "删除议题评论",
|
||||
"cmd.issue.comment_edit.short": "编辑议题评论",
|
||||
"cmd.issue.comment_replies.short": "列出议题评论的回复",
|
||||
"cmd.issue.comments.short": "列出议题评论",
|
||||
"cmd.issue.create.short": "创建新议题",
|
||||
"cmd.issue.list.short": "列出议题",
|
||||
"cmd.issue.short": "议题操作",
|
||||
|
|
@ -59,13 +41,8 @@
|
|||
"cmd.org.list.short": "列出组织",
|
||||
"cmd.org.members.short": "列出组织成员",
|
||||
"cmd.org.short": "组织操作",
|
||||
"cmd.pm.action_runs.short": "列出 PM Action 运行记录",
|
||||
"cmd.pm.dashboards.short": "列出 PM 看板",
|
||||
"cmd.pm.issue_tags.short": "列出 PM Issue 标签",
|
||||
"cmd.pm.pipelines.short": "列出 PM 流水线",
|
||||
"cmd.pm.short": "项目管理操作",
|
||||
"cmd.pm.sprint_issues.short": "列出 PM Sprint Issue",
|
||||
"cmd.pm.weekly_issues.short": "列出 PM 周报 Issue",
|
||||
"cmd.pr.checkout.long": "拉取拉取请求的源分支并切换到本地分支。\n\n命令会从 GitLink 读取 PR 的源仓库信息,因此同仓库 PR 和 fork PR 都可以使用,不依赖本地 remote 必须命名为 origin。\n\n示例:\n gitlink-cli pr +checkout -i 42\n gitlink-cli pr +checkout -i 42 --branch review/pr-42\n gitlink-cli pr +checkout -i 42 --force",
|
||||
"cmd.pr.checkout.short": "拉取并切换到拉取请求分支",
|
||||
"cmd.pr.close.short": "关闭拉取请求",
|
||||
"cmd.pr.comment.short": "给拉取请求添加评论",
|
||||
"cmd.pr.create.short": "创建拉取请求",
|
||||
|
|
@ -79,17 +56,6 @@
|
|||
"cmd.pr.version_diff.short": "显示拉取请求补丁集版本 diff",
|
||||
"cmd.pr.versions.short": "列出拉取请求补丁集版本",
|
||||
"cmd.pr.view.short": "查看拉取请求详情",
|
||||
"cmd.profile.ability.long": "显示用户的开发能力评分(影响力、贡献度、活跃度、项目经验、语言能力)及语言分布,数据来自 GitLink 平台统计接口。",
|
||||
"cmd.profile.ability.short": "显示用户开发能力评分",
|
||||
"cmd.profile.activity.long": "显示用户近期活动统计:时间范围内每日的疑修、合并请求和提交数量。",
|
||||
"cmd.profile.activity.short": "显示用户近期活动统计",
|
||||
"cmd.profile.contribution.long": "显示用户在指定年份的贡献热力图(每日贡献次数)。",
|
||||
"cmd.profile.contribution.short": "显示用户贡献热力图",
|
||||
"cmd.profile.major.long": "显示根据用户项目推断的专业/学科领域(如深度学习、量子计算)。",
|
||||
"cmd.profile.major.short": "显示用户专业/学科定位",
|
||||
"cmd.profile.role.long": "显示来自 GitLink 平台统计接口的用户角色定位。",
|
||||
"cmd.profile.role.short": "显示用户角色定位",
|
||||
"cmd.profile.short": "用户画像与统计操作",
|
||||
"cmd.release.create.short": "创建发布",
|
||||
"cmd.release.delete.short": "删除发布",
|
||||
"cmd.release.list.short": "列出发布",
|
||||
|
|
@ -97,25 +63,15 @@
|
|||
"cmd.release.view.short": "查看发布详情",
|
||||
"cmd.repo.create.short": "创建新仓库",
|
||||
"cmd.repo.delete.short": "删除仓库",
|
||||
"cmd.repo.file_exists.short": "检查仓库文件是否存在",
|
||||
"cmd.repo.fork.short": "Fork 仓库",
|
||||
"cmd.repo.info.short": "显示仓库详情",
|
||||
"cmd.repo.list.short": "列出用户或组织的仓库",
|
||||
"cmd.repo.manifest.short": "读取常见依赖清单文件",
|
||||
"cmd.repo.raw.short": "读取仓库原始文件内容",
|
||||
"cmd.repo.short": "仓库操作",
|
||||
"cmd.repo.tree.short": "列出仓库文件和目录",
|
||||
"cmd.root.long": "用于管理 GitLink 上的仓库、议题、拉取请求、发布、CI 和工作流。",
|
||||
"cmd.root.short": "GitLink CLI - GitLink 命令行工具",
|
||||
"cmd.search.repos.short": "搜索仓库",
|
||||
"cmd.search.short": "搜索操作",
|
||||
"cmd.search.users.short": "搜索用户",
|
||||
"cmd.template.create.short": "创建项目模板",
|
||||
"cmd.template.delete.short": "删除项目模板",
|
||||
"cmd.template.get.short": "根据 ID 获取项目模板",
|
||||
"cmd.template.list.short": "列出项目模板",
|
||||
"cmd.template.short": "项目模板操作",
|
||||
"cmd.template.update.short": "更新项目模板",
|
||||
"cmd.user.info.short": "显示用户资料",
|
||||
"cmd.user.me.short": "显示当前认证用户",
|
||||
"cmd.user.short": "用户操作",
|
||||
|
|
@ -132,12 +88,8 @@
|
|||
"error.auth.login_failed": "登录失败:{message}",
|
||||
"error.auth.store_token_failed": "保存 Token 失败:{message}",
|
||||
"error.auth.token_empty": "Token 不能为空",
|
||||
"error.branch.id_invalid": "无效的 --branch-id:{value};必须是正整数",
|
||||
"error.config.save_failed": "保存配置失败:{message}",
|
||||
"error.dataset.delete_confirm": "删除数据集附件具有破坏性;请先 --dry-run 预览,再传 --yes 确认",
|
||||
"error.missing_required_flag": "缺少必需参数 --{name}",
|
||||
"error.profile.user_required": "无法确定目标用户;请通过 --user 指定,或先运行 gitlink-cli auth login 登录",
|
||||
"error.template.invalid_type": "无效的 --type {type};应为以下之一:{types}",
|
||||
"error.unsupported_language": "不支持的语言:{lang}",
|
||||
"flag.api.batch_continue_on_error": "批处理请求失败后继续执行后续请求",
|
||||
"flag.api.batch_dry_run": "预览批处理请求,不发送远端请求",
|
||||
|
|
@ -150,25 +102,11 @@
|
|||
"flag.api.query": "查询参数(key=val&key2=val2)",
|
||||
"flag.auth.token": "通过粘贴已有 Token 登录",
|
||||
"flag.branch.from": "源分支或 Commit",
|
||||
"flag.branch.id": "已删除分支 ID",
|
||||
"flag.branch.keyword": "按关键字筛选分支",
|
||||
"flag.branch.name": "分支名称",
|
||||
"flag.branch.state": "分支范围:默认可见分支,也可选 deleted 或 all",
|
||||
"flag.ci.build": "构建编号",
|
||||
"flag.ci.stage": "阶段编号",
|
||||
"flag.ci.step": "步骤编号",
|
||||
"flag.comment.body": "评论内容",
|
||||
"flag.dataset.description": "数据集描述",
|
||||
"flag.dataset.dry_run": "预览请求,不写入数据集",
|
||||
"flag.dataset.dry_run_delete": "预览请求,不删除附件",
|
||||
"flag.dataset.ids": "用于查询数据集的项目 ID,逗号分隔",
|
||||
"flag.dataset.license_id": "许可证 ID",
|
||||
"flag.dataset.limit": "附件每页数量",
|
||||
"flag.dataset.page": "附件页码",
|
||||
"flag.dataset.paper_content": "研究论文内容",
|
||||
"flag.dataset.title": "数据集标题",
|
||||
"flag.dataset.uuid": "附件 UUID",
|
||||
"flag.dataset.yes": "确认执行破坏性删除",
|
||||
"flag.debug": "启用调试输出",
|
||||
"flag.description": "描述",
|
||||
"flag.doctor.skip_network": "跳过需要访问 GitLink 的认证连通性检查",
|
||||
|
|
@ -186,10 +124,6 @@
|
|||
"flag.issue.batch_list.limit": "最多返回的议题数,上限 100",
|
||||
"flag.issue.batch_process.limit": "最多处理的议题数,上限 100",
|
||||
"flag.issue.body": "议题描述",
|
||||
"flag.issue.comment_id": "评论(journal)ID,可由 issue +comments 获取",
|
||||
"flag.issue.comment_reply_to": "要回复的父评论(journal)ID",
|
||||
"flag.issue.comments.category": "筛选:comment/operate(默认:全部)",
|
||||
"flag.issue.comments.keyword": "评论内容搜索关键词",
|
||||
"flag.issue.label": "标签 ID",
|
||||
"flag.issue.label_filter": "按已有标签筛选",
|
||||
"flag.issue.milestone": "里程碑 ID",
|
||||
|
|
@ -210,10 +144,12 @@
|
|||
"flag.org.name": "组织名称",
|
||||
"flag.owner": "仓库所有者(自动从 git remote 检测)",
|
||||
"flag.page": "页码",
|
||||
"flag.pm.project_id": "GitLink 项目 ID",
|
||||
"flag.pr.assignee_id": "指派人用户 ID",
|
||||
"flag.pr.base": "目标分支",
|
||||
"flag.pr.body": "PR 描述",
|
||||
"flag.pr.checkout_branch": "要创建的本地分支名(默认:pr-<id>)",
|
||||
"flag.pr.checkout_dry_run": "只打印 git 命令,不修改本地仓库",
|
||||
"flag.pr.checkout_force": "本地分支已存在时重置该分支",
|
||||
"flag.pr.file": "按文件路径筛选 diff",
|
||||
"flag.pr.head": "源分支",
|
||||
"flag.pr.id": "PR 编号",
|
||||
|
|
@ -229,10 +165,6 @@
|
|||
"flag.pr.tag_id": "议题标签 ID",
|
||||
"flag.pr.title": "PR 标题",
|
||||
"flag.pr.version_id": "补丁集版本 ID",
|
||||
"flag.profile.end_time": "结束时间(Unix 时间戳)",
|
||||
"flag.profile.start_time": "开始时间(Unix 时间戳)",
|
||||
"flag.profile.user": "目标用户登录名(默认为当前认证用户)",
|
||||
"flag.profile.year": "贡献热力图的年份(如 2025)",
|
||||
"flag.release.body": "发布说明",
|
||||
"flag.release.id": "发布 ID",
|
||||
"flag.release.id_or_tag": "发布 ID 或标签",
|
||||
|
|
@ -243,14 +175,8 @@
|
|||
"flag.repo": "仓库名称(自动从 git remote 检测)",
|
||||
"flag.repo.category": "筛选:manage/mirror/sync/fork/all(默认:manage)",
|
||||
"flag.repo.description": "仓库描述",
|
||||
"flag.repo.file_exists.path": "要检查的文件路径",
|
||||
"flag.repo.manifest.kind": "清单类型:go、node、python、rust 或 java",
|
||||
"flag.repo.name": "仓库名称",
|
||||
"flag.repo.private": "设为私有仓库(true/false)",
|
||||
"flag.repo.raw.path": "仓库中的文件路径",
|
||||
"flag.repo.raw.ref": "分支、标签或提交引用",
|
||||
"flag.repo.tree.path": "要列出的目录路径(默认:仓库根目录)",
|
||||
"flag.repo.tree.ref": "分支、标签或提交引用",
|
||||
"flag.search.keyword": "搜索关键词",
|
||||
"flag.sort_by": "排序字段",
|
||||
"flag.sort_direction": "排序方向:asc、desc",
|
||||
|
|
@ -265,10 +191,6 @@
|
|||
"flag.webhook.secret": "Webhook 密钥",
|
||||
"flag.webhook.secret_update": "Webhook 密钥。如果服务端不返回已有密钥,请再次传入。",
|
||||
"flag.webhook.type": "Webhook 类型:gitea/slack/discord/dingtalk/telegram/msteams/feishu/matrix/jianmu/softbot",
|
||||
"flag.template.content": "模板内容",
|
||||
"flag.template.id": "模板 ID",
|
||||
"flag.template.name": "模板名称",
|
||||
"flag.template.type": "模板类型,例如:ProjectTemplates::Issue",
|
||||
"flag.webhook.url": "Webhook 目标 URL",
|
||||
"output.auth.env_hint": " 或设置 {env} 环境变量",
|
||||
"output.auth.login_hint": " 运行:gitlink-cli auth login",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,205 @@
|
|||
package pr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/gitlink-org/gitlink-cli/internal/output"
|
||||
"github.com/gitlink-org/gitlink-cli/shortcuts/common"
|
||||
)
|
||||
|
||||
type prCheckoutDetail struct {
|
||||
ID string `json:"id"`
|
||||
SourceOwner string `json:"source_owner"`
|
||||
SourceRepo string `json:"source_repo"`
|
||||
SourceBranch string `json:"source_branch"`
|
||||
SourceURL string `json:"source_url"`
|
||||
}
|
||||
|
||||
type prCheckoutPlan struct {
|
||||
PullRequest string `json:"pull_request"`
|
||||
SourceOwner string `json:"source_owner"`
|
||||
SourceRepo string `json:"source_repo"`
|
||||
SourceBranch string `json:"source_branch"`
|
||||
SourceURL string `json:"source_url"`
|
||||
LocalBranch string `json:"local_branch"`
|
||||
Force bool `json:"force"`
|
||||
DryRun bool `json:"dry_run"`
|
||||
Commands [][]string `json:"commands"`
|
||||
}
|
||||
|
||||
type gitCommandRunner func(args ...string) (string, error)
|
||||
|
||||
var runGitCommand gitCommandRunner = defaultRunGitCommand
|
||||
|
||||
func runCheckout(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
id, err := ctx.RequireArg("id")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", prV1Path(ctx, id), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detail, err := extractPRCheckoutDetail(ctx, env, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
localBranch := strings.TrimSpace(ctx.Arg("branch"))
|
||||
if localBranch == "" {
|
||||
localBranch = "pr-" + id
|
||||
}
|
||||
force := ctx.Arg("force") == "true"
|
||||
dryRun := ctx.Arg("dry-run") == "true"
|
||||
plan, err := buildPRCheckoutPlan(detail, localBranch, force, dryRun)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dryRun {
|
||||
return ctx.OutputData(plan)
|
||||
}
|
||||
for _, args := range plan.Commands {
|
||||
if out, err := runGitCommand(args...); err != nil {
|
||||
if strings.TrimSpace(out) != "" {
|
||||
return fmt.Errorf("git %s failed: %w: %s", strings.Join(args, " "), err, strings.TrimSpace(out))
|
||||
}
|
||||
return fmt.Errorf("git %s failed: %w", strings.Join(args, " "), err)
|
||||
}
|
||||
}
|
||||
return ctx.OutputData(plan)
|
||||
}
|
||||
|
||||
func buildPRCheckoutPlan(detail prCheckoutDetail, localBranch string, force, dryRun bool) (prCheckoutPlan, error) {
|
||||
if err := validateGitRef(localBranch, "local branch"); err != nil {
|
||||
return prCheckoutPlan{}, err
|
||||
}
|
||||
if err := validateGitRef(detail.SourceBranch, "source branch"); err != nil {
|
||||
return prCheckoutPlan{}, err
|
||||
}
|
||||
if detail.SourceURL == "" {
|
||||
return prCheckoutPlan{}, fmt.Errorf("PR response missing source repository URL")
|
||||
}
|
||||
checkoutFlag := "-b"
|
||||
if force {
|
||||
checkoutFlag = "-B"
|
||||
}
|
||||
return prCheckoutPlan{
|
||||
PullRequest: detail.ID,
|
||||
SourceOwner: detail.SourceOwner,
|
||||
SourceRepo: detail.SourceRepo,
|
||||
SourceBranch: detail.SourceBranch,
|
||||
SourceURL: detail.SourceURL,
|
||||
LocalBranch: localBranch,
|
||||
Force: force,
|
||||
DryRun: dryRun,
|
||||
Commands: [][]string{
|
||||
{"fetch", "--no-tags", detail.SourceURL, detail.SourceBranch},
|
||||
{"checkout", checkoutFlag, localBranch, "FETCH_HEAD"},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func extractPRCheckoutDetail(ctx *common.RuntimeContext, env *output.Envelope, id string) (prCheckoutDetail, error) {
|
||||
data, ok := env.Data.(map[string]interface{})
|
||||
if !ok {
|
||||
return prCheckoutDetail{}, fmt.Errorf("unexpected PR response format")
|
||||
}
|
||||
head := firstStringField(data, "head", "pull_request_head", "source_branch")
|
||||
if head == "" {
|
||||
if pr, ok := data["pull_request"].(map[string]interface{}); ok {
|
||||
head = firstStringField(pr, "head", "pull_request_head", "source_branch")
|
||||
}
|
||||
}
|
||||
sourceOwner, sourceRepo, sourceBranch := parsePRHeadForCheckout(head)
|
||||
fork, _ := data["fork_project"].(map[string]interface{})
|
||||
if login := stringField(fork, "login"); login != "" {
|
||||
sourceOwner = login
|
||||
}
|
||||
if identifier := stringField(fork, "identifier"); identifier != "" {
|
||||
sourceRepo = identifier
|
||||
}
|
||||
if sourceOwner == "" {
|
||||
sourceOwner = ctx.Owner
|
||||
}
|
||||
if sourceRepo == "" {
|
||||
sourceRepo = ctx.Repo
|
||||
}
|
||||
if sourceBranch == "" {
|
||||
return prCheckoutDetail{}, fmt.Errorf("PR response missing head branch")
|
||||
}
|
||||
return prCheckoutDetail{
|
||||
ID: id,
|
||||
SourceOwner: sourceOwner,
|
||||
SourceRepo: sourceRepo,
|
||||
SourceBranch: sourceBranch,
|
||||
SourceURL: gitlinkRepoURL(ctx.Client.BaseURL, sourceOwner, sourceRepo),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parsePRHeadForCheckout(head string) (owner, repo, branch string) {
|
||||
head = strings.TrimSpace(head)
|
||||
if head == "" {
|
||||
return "", "", ""
|
||||
}
|
||||
left, right, hasOwner := strings.Cut(head, ":")
|
||||
if !hasOwner {
|
||||
return "", "", head
|
||||
}
|
||||
branch = strings.TrimSpace(right)
|
||||
parts := strings.Split(strings.Trim(left, "/"), "/")
|
||||
if len(parts) >= 2 {
|
||||
return parts[0], parts[1], branch
|
||||
}
|
||||
if len(parts) == 1 {
|
||||
return parts[0], "", branch
|
||||
}
|
||||
return "", "", branch
|
||||
}
|
||||
|
||||
func gitlinkRepoURL(apiBaseURL, owner, repo string) string {
|
||||
base := strings.TrimRight(apiBaseURL, "/")
|
||||
for _, suffix := range []string{"/api/v1", "/api"} {
|
||||
if strings.HasSuffix(base, suffix) {
|
||||
base = strings.TrimSuffix(base, suffix)
|
||||
break
|
||||
}
|
||||
}
|
||||
if base == "" {
|
||||
base = "https://www.gitlink.org.cn"
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s.git", base, url.PathEscape(owner), url.PathEscape(repo))
|
||||
}
|
||||
|
||||
func validateGitRef(ref, label string) error {
|
||||
ref = strings.TrimSpace(ref)
|
||||
if ref == "" {
|
||||
return fmt.Errorf("%s cannot be empty", label)
|
||||
}
|
||||
if strings.HasPrefix(ref, "-") {
|
||||
return fmt.Errorf("%s cannot start with '-'", label)
|
||||
}
|
||||
if strings.ContainsAny(ref, "\x00\r\n") {
|
||||
return fmt.Errorf("%s contains unsupported control characters", label)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func firstStringField(m map[string]interface{}, keys ...string) string {
|
||||
for _, key := range keys {
|
||||
if v := stringField(m, key); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultRunGitCommand(args ...string) (string, error) {
|
||||
cmd := exec.Command("git", args...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
return string(out), err
|
||||
}
|
||||
|
|
@ -170,6 +170,18 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "checkout",
|
||||
Description: tr.T("cmd.pr.checkout.short"),
|
||||
Long: tr.T("cmd.pr.checkout.long"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pr.id"), Required: true},
|
||||
{Name: "branch", Short: "b", Usage: tr.T("flag.pr.checkout_branch")},
|
||||
{Name: "force", Short: "f", Usage: tr.T("flag.pr.checkout_force"), Bool: true, Default: "false"},
|
||||
{Name: "dry-run", Usage: tr.T("flag.pr.checkout_dry_run"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runCheckout,
|
||||
},
|
||||
{
|
||||
Name: "merge",
|
||||
Description: tr.T("cmd.pr.merge.short"),
|
||||
|
|
|
|||
|
|
@ -249,6 +249,143 @@ func TestPRView(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- checkout ---
|
||||
|
||||
func TestPRCheckoutDryRunUsesForkSource(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" {
|
||||
t.Fatalf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/v1/owner/repo/pulls/42.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"head": "feature/gitlink-pr",
|
||||
"fork_project": map[string]interface{}{
|
||||
"login": "contributor",
|
||||
"identifier": "repo-fork",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "checkout", map[string]string{
|
||||
"id": "42",
|
||||
"branch": "review/pr-42",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("checkout dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRCheckoutRunsFetchAndCheckout(t *testing.T) {
|
||||
var calls [][]string
|
||||
oldRunner := runGitCommand
|
||||
runGitCommand = func(args ...string) (string, error) {
|
||||
calls = append(calls, append([]string(nil), args...))
|
||||
return "", nil
|
||||
}
|
||||
defer func() { runGitCommand = oldRunner }()
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/v1/owner/repo/pulls/43.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"head": "contributor/repo:feature/pr-checkout",
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "checkout", map[string]string{
|
||||
"id": "43",
|
||||
"branch": "review/pr-43",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("checkout failed: %v", err)
|
||||
}
|
||||
if len(calls) != 2 {
|
||||
t.Fatalf("expected 2 git calls, got %d: %#v", len(calls), calls)
|
||||
}
|
||||
assertStringSlice(t, calls[0], []string{"fetch", "--no-tags", server.URL + "/contributor/repo.git", "feature/pr-checkout"})
|
||||
assertStringSlice(t, calls[1], []string{"checkout", "-b", "review/pr-43", "FETCH_HEAD"})
|
||||
}
|
||||
|
||||
func TestPRCheckoutForceUsesResetBranch(t *testing.T) {
|
||||
plan, err := buildPRCheckoutPlan(prCheckoutDetail{
|
||||
ID: "44",
|
||||
SourceOwner: "owner",
|
||||
SourceRepo: "repo",
|
||||
SourceBranch: "feature/x",
|
||||
SourceURL: "https://www.gitlink.org.cn/owner/repo.git",
|
||||
}, "review/pr-44", true, false)
|
||||
if err != nil {
|
||||
t.Fatalf("build plan failed: %v", err)
|
||||
}
|
||||
assertStringSlice(t, plan.Commands[1], []string{"checkout", "-B", "review/pr-44", "FETCH_HEAD"})
|
||||
}
|
||||
|
||||
func TestPRCheckoutDefaultBranch(t *testing.T) {
|
||||
plan, err := buildPRCheckoutPlan(prCheckoutDetail{
|
||||
ID: "45",
|
||||
SourceOwner: "owner",
|
||||
SourceRepo: "repo",
|
||||
SourceBranch: "feature/default",
|
||||
SourceURL: "https://www.gitlink.org.cn/owner/repo.git",
|
||||
}, "pr-45", false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("build plan failed: %v", err)
|
||||
}
|
||||
if plan.LocalBranch != "pr-45" {
|
||||
t.Fatalf("local branch = %q, want pr-45", plan.LocalBranch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRCheckoutRejectsUnsafeBranch(t *testing.T) {
|
||||
_, err := buildPRCheckoutPlan(prCheckoutDetail{
|
||||
ID: "46",
|
||||
SourceOwner: "owner",
|
||||
SourceRepo: "repo",
|
||||
SourceBranch: "feature/x",
|
||||
SourceURL: "https://www.gitlink.org.cn/owner/repo.git",
|
||||
}, "-bad", false, false)
|
||||
if err == nil {
|
||||
t.Fatal("expected unsafe local branch error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsePRHeadForCheckout(t *testing.T) {
|
||||
cases := []struct {
|
||||
head string
|
||||
wantOwner string
|
||||
wantRepo string
|
||||
wantBranch string
|
||||
}{
|
||||
{head: "feature/x", wantBranch: "feature/x"},
|
||||
{head: "alice:feature/x", wantOwner: "alice", wantBranch: "feature/x"},
|
||||
{head: "alice/repo:feature/x", wantOwner: "alice", wantRepo: "repo", wantBranch: "feature/x"},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
owner, repo, branch := parsePRHeadForCheckout(tc.head)
|
||||
if owner != tc.wantOwner || repo != tc.wantRepo || branch != tc.wantBranch {
|
||||
t.Fatalf("parse %q = (%q, %q, %q), want (%q, %q, %q)",
|
||||
tc.head, owner, repo, branch, tc.wantOwner, tc.wantRepo, tc.wantBranch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitlinkRepoURLUsesConfiguredAPIBase(t *testing.T) {
|
||||
got := gitlinkRepoURL("https://gitlink.example.com/api", "owner", "repo")
|
||||
if got != "https://gitlink.example.com/owner/repo.git" {
|
||||
t.Fatalf("url = %q", got)
|
||||
}
|
||||
got = gitlinkRepoURL("https://gitlink.example.com/api/v1", "owner", "repo")
|
||||
if got != "https://gitlink.example.com/owner/repo.git" {
|
||||
t.Fatalf("url with api/v1 = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// --- merge ---
|
||||
|
||||
func TestPRMerge(t *testing.T) {
|
||||
|
|
@ -532,177 +669,14 @@ func assertEqual(t *testing.T, got interface{}, want interface{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPRReviewCommentsListBuildsQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "GET" || r.URL.Path != "/v1/owner/repo/pulls/13/journals.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
func assertStringSlice(t *testing.T, got, want []string) {
|
||||
t.Helper()
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
for i := range got {
|
||||
if got[i] != want[i] {
|
||||
t.Fatalf("got %v, want %v", got, want)
|
||||
}
|
||||
query := r.URL.Query()
|
||||
assertEqual(t, query.Get("keyword"), "todo")
|
||||
assertEqual(t, query.Get("review_id"), "10")
|
||||
assertEqual(t, query.Get("need_respond"), "true")
|
||||
assertEqual(t, query.Get("state"), "opened")
|
||||
assertEqual(t, query.Get("parent_id"), "200")
|
||||
assertEqual(t, query.Get("path"), "README.md")
|
||||
assertEqual(t, query.Get("is_full"), "true")
|
||||
assertEqual(t, query.Get("sort_by"), "created_on")
|
||||
assertEqual(t, query.Get("sort_direction"), "desc")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 0, "journals": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "review-comments", map[string]string{
|
||||
"id": "13",
|
||||
"keyword": "todo",
|
||||
"review-id": "10",
|
||||
"need-respond": "true",
|
||||
"state": "opened",
|
||||
"parent-id": "200",
|
||||
"path": "README.md",
|
||||
"is-full": "true",
|
||||
"sort-by": "created_on",
|
||||
"sort-direction": "desc",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("review-comments shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRReviewCommentPostsPayload(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" || r.URL.Path != "/v1/owner/repo/pulls/13/journals.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"id": 200, "note": "please fix"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "review-comment", map[string]string{
|
||||
"id": "13",
|
||||
"note": "please fix",
|
||||
"review-id": "10",
|
||||
"type": "problem",
|
||||
"commit": "abc123",
|
||||
"line-code": "abc123_0_10",
|
||||
"path": "README.md",
|
||||
"parent-id": "199",
|
||||
"diff-json": `{"name":"README.md","addition":1}`,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("review-comment shortcut failed: %v", err)
|
||||
}
|
||||
|
||||
assertEqual(t, payload["type"], "problem")
|
||||
assertEqual(t, payload["note"], "please fix")
|
||||
assertEqual(t, payload["review_id"], "10")
|
||||
assertEqual(t, payload["commit_id"], "abc123")
|
||||
assertEqual(t, payload["line_code"], "abc123_0_10")
|
||||
assertEqual(t, payload["path"], "README.md")
|
||||
assertEqual(t, payload["parent_id"], float64(199))
|
||||
diff, ok := payload["diff"].(map[string]interface{})
|
||||
if !ok {
|
||||
t.Fatalf("diff type = %T, want map", payload["diff"])
|
||||
}
|
||||
assertEqual(t, diff["name"], "README.md")
|
||||
assertEqual(t, diff["addition"], float64(1))
|
||||
}
|
||||
|
||||
func TestPRReviewCommentDryRunDoesNotCallAPI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("server should not be called during dry-run: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "review-comment", map[string]string{
|
||||
"id": "13",
|
||||
"note": "please fix",
|
||||
"review-id": "10",
|
||||
"commit": "abc123",
|
||||
"line-code": "abc123_0_10",
|
||||
"path": "README.md",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("review-comment dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRReviewCommentUpdatePayload(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "PUT" || r.URL.Path != "/v1/owner/repo/pulls/13/journals/200.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
payload = decodeJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"id": 200, "note": "fixed", "state": "resolved"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "review-comment-update", map[string]string{
|
||||
"id": "13",
|
||||
"comment-id": "200",
|
||||
"note": "fixed",
|
||||
"commit": "def456",
|
||||
"state": "resolved",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("review-comment-update shortcut failed: %v", err)
|
||||
}
|
||||
assertEqual(t, payload["note"], "fixed")
|
||||
assertEqual(t, payload["commit_id"], "def456")
|
||||
assertEqual(t, payload["state"], "resolved")
|
||||
}
|
||||
|
||||
func TestPRReviewCommentDeleteUsesV1Endpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "DELETE" || r.URL.Path != "/v1/owner/repo/pulls/13/journals/200.json" {
|
||||
t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "review-comment-delete", map[string]string{
|
||||
"id": "13",
|
||||
"comment-id": "200",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("review-comment-delete shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPRReviewCommentRejectsInvalidInputs(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("server should not be called for invalid input: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runPRShortcut(t, server, "review-comment", map[string]string{
|
||||
"id": "13",
|
||||
"note": "please fix",
|
||||
"review-id": "10",
|
||||
"type": "todo",
|
||||
"commit": "abc123",
|
||||
"line-code": "abc123_0_10",
|
||||
"path": "README.md",
|
||||
}); err == nil {
|
||||
t.Fatal("expected invalid review comment type to fail")
|
||||
}
|
||||
|
||||
if err := runPRShortcut(t, server, "review-comment-update", map[string]string{
|
||||
"id": "13",
|
||||
"comment-id": "200",
|
||||
"state": "done",
|
||||
}); err == nil {
|
||||
t.Fatal("expected invalid review comment state to fail")
|
||||
}
|
||||
|
||||
if err := runPRShortcut(t, server, "review-comments", map[string]string{
|
||||
"id": "13",
|
||||
"need-respond": "maybe",
|
||||
}); err == nil {
|
||||
t.Fatal("expected invalid need-respond to fail")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ gitlink-cli auth status
|
|||
gitlink-cli user +me
|
||||
```
|
||||
|
||||
详见: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
|
||||
详见: [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md)
|
||||
|
||||
### 2. 查看可用命令
|
||||
|
||||
|
|
@ -64,19 +64,25 @@ skills/
|
|||
├── README.md # 本文件
|
||||
├── gitlink-shared/ # 共享基础规则
|
||||
│ ├── SKILL.md # 认证、全局参数、安全规则、分支约定
|
||||
│ └── references/
|
||||
│ ├── api-reference.md # API 详细参考、错误处理
|
||||
│ ├── raw-api-batch.md # 批量 Raw API 调用参考
|
||||
│ └── troubleshooting.md # 常见问题排查
|
||||
│ ├── REFERENCE.md # API 详细参考、错误处理
|
||||
│ ├── TROUBLESHOOTING.md # 常见问题排查
|
||||
│ └── examples/
|
||||
│ └── auth-workflow.md # 认证工作流示例
|
||||
├── gitlink-repo/ # 仓库管理
|
||||
│ ├── SKILL.md # 仓库操作指南
|
||||
│ └── references/ # 仓库命令参考文档
|
||||
│ ├── REFERENCE.md # 仓库 API 参考
|
||||
│ └── examples/
|
||||
│ └── repo-workflow.md # 仓库管理工作流
|
||||
├── gitlink-issue/ # Issue 管理
|
||||
│ ├── SKILL.md # Issue 操作指南
|
||||
│ └── references/ # Issue 命令参考文档
|
||||
│ ├── REFERENCE.md # Issue API 参考
|
||||
│ └── examples/
|
||||
│ └── issue-workflow.md # Issue 全流程工作流
|
||||
├── gitlink-pr/ # Pull Request
|
||||
│ ├── SKILL.md # PR 操作指南
|
||||
│ └── references/ # PR 命令参考文档
|
||||
│ ├── REFERENCE.md # PR API 参考
|
||||
│ └── examples/
|
||||
│ └── pr-workflow.md # PR 工作流
|
||||
├── gitlink-member/ # 仓库成员管理
|
||||
│ └── SKILL.md # 成员与邀请链接操作指南
|
||||
├── gitlink-branch/ # 分支管理
|
||||
|
|
@ -85,24 +91,25 @@ skills/
|
|||
│ └── branch-workflow.md # 分支工作流
|
||||
├── gitlink-release/ # 版本发布
|
||||
│ ├── SKILL.md # Release 操作指南
|
||||
│ └── references/ # Release 命令参考文档
|
||||
├── gitlink-release-auto/ # 自动化 Release 管理
|
||||
│ └── SKILL.md # 自动发版、版本号推荐、Release Notes 生成
|
||||
│ ├── REFERENCE.md # Release API 参考
|
||||
│ └── examples/
|
||||
│ └── release-workflow.md # Release 工作流
|
||||
├── gitlink-search/ # 搜索功能
|
||||
│ ├── SKILL.md # 搜索操作指南
|
||||
│ └── references/ # 搜索命令参考文档
|
||||
│ └── examples/
|
||||
│ └── search-workflow.md # 搜索工作流
|
||||
├── gitlink-user/ # 用户管理
|
||||
│ ├── SKILL.md # 用户操作指南
|
||||
│ └── references/ # 用户命令参考文档
|
||||
│ └── SKILL.md # 用户操作指南
|
||||
├── gitlink-org/ # 组织管理
|
||||
│ ├── SKILL.md # 组织操作指南
|
||||
│ └── references/ # 组织命令参考文档
|
||||
│ └── examples/
|
||||
│ └── org-workflow.md # 组织工作流
|
||||
├── gitlink-ci/ # CI/CD
|
||||
│ └── SKILL.md # CI 操作指南
|
||||
│ ├── SKILL.md # CI 操作指南
|
||||
│ └── examples/
|
||||
│ └── ci-workflow.md # CI 工作流
|
||||
├── gitlink-pipeline/ # 流水线工作流
|
||||
│ └── SKILL.md # Pipeline 操作指南
|
||||
├── gitlink-wiki/ # Wiki 页面管理
|
||||
│ └── SKILL.md # Wiki 操作指南
|
||||
├── gitlink-pm/ # 项目管理
|
||||
│ └── SKILL.md # PM 操作指南
|
||||
├── gitlink-health/ # 项目健康度分析
|
||||
|
|
@ -128,11 +135,11 @@ skills/
|
|||
|-------|------|----------|
|
||||
| **gitlink-shared** | 认证、全局参数、API 参考、安全规则、分支约定 | `auth login`, `auth status` |
|
||||
| **gitlink-repo** | 仓库管理与洞察 | `repo +list`, `repo +info`, `repo +languages`, `repo +contributors`, `repo +code-stats`, `repo +follow`, `repo +like` |
|
||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close`, `issue +batch-update`, `issue +batch-delete` |
|
||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +merge`, `pr +versions`, `pr +version-diff`, `pr +reviews`, `pr +review` |
|
||||
| **gitlink-issue** | Issue 管理 | `issue +create`, `issue +list`, `issue +view`, `issue +close`, `issue +batch-close` |
|
||||
| **gitlink-pr** | Pull Request | `pr +list`, `pr +create`, `pr +view`, `pr +checkout`, `pr +merge`, `pr +versions`, `pr +version-diff`, `pr +reviews`, `pr +review` |
|
||||
| **gitlink-member** | 仓库成员管理 | `member +list`, `member +add`, `member +batch-add`, `member +role`, `member +invite-link` |
|
||||
| **gitlink-branch** | 分支管理 | `branch +list`, `branch +create`, `branch +delete`, `branch +protect` |
|
||||
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +edit`, `release +update`, `release +view` |
|
||||
| **gitlink-release** | 版本发布 | `release +list`, `release +create`, `release +view` |
|
||||
|
||||
### 辅助 Skills
|
||||
|
||||
|
|
@ -143,8 +150,7 @@ skills/
|
|||
| **gitlink-org** | 组织管理 | `org +list`, `org +info`, `org +members` |
|
||||
| **gitlink-ci** | CI/CD | `ci +builds`, `ci +logs` |
|
||||
| **gitlink-pipeline** | 流水线工作流 | `pipeline +runs`, `pipeline +run`, `pipeline +logs` |
|
||||
| **gitlink-wiki** | Wiki 页面管理 | `wiki +list`, `wiki +view`, `wiki +create`, `wiki +update`, `wiki +delete` |
|
||||
| **gitlink-pm** | 项目管理 | `pm +dashboards`, `pm +sprint-issues`, `pm +weekly-issues`, `pm +action-runs` |
|
||||
| **gitlink-pm** | 项目管理 | 通过 Raw API 访问 |
|
||||
| **gitlink-workflow** | AI 工作流 | Issue 分类、PR Review、Release Notes |
|
||||
| **gitlink-health** | 开源项目健康度 | 详情见SKILL.md |
|
||||
|
||||
|
|
@ -163,7 +169,7 @@ gitlink-cli repo +info
|
|||
gitlink-cli repo +info --owner wbtiger --repo gitlink-cli
|
||||
```
|
||||
|
||||
详见: [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md)
|
||||
详见: [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md)
|
||||
|
||||
### 场景 2:创建和管理 Issue
|
||||
|
||||
|
|
@ -184,7 +190,7 @@ gitlink-cli issue +close -i 123
|
|||
gitlink-cli issue +batch-close --numbers 123,124 --dry-run
|
||||
```
|
||||
|
||||
详见: [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md)
|
||||
详见: [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md)
|
||||
|
||||
### 场景 3:管理分支和发布
|
||||
|
||||
|
|
@ -202,7 +208,7 @@ gitlink-cli release +create -t v1.0.0 -n "v1.0.0 正式版" -b "更新内容..."
|
|||
gitlink-cli release +view -i <version_id>
|
||||
```
|
||||
|
||||
详见: [gitlink-release/SKILL.md](gitlink-release/SKILL.md)
|
||||
详见: [gitlink-release/examples/release-workflow.md](gitlink-release/examples/release-workflow.md)
|
||||
|
||||
### 场景 4:搜索和发现
|
||||
|
||||
|
|
@ -218,7 +224,7 @@ gitlink-cli org +list
|
|||
gitlink-cli org +info -i Gitlink
|
||||
```
|
||||
|
||||
详见: [gitlink-search/SKILL.md](gitlink-search/SKILL.md)
|
||||
详见: [gitlink-search/examples/search-workflow.md](gitlink-search/examples/search-workflow.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -227,8 +233,8 @@ gitlink-cli org +info -i Gitlink
|
|||
### 快速查找
|
||||
|
||||
- **我想了解认证**: [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md)
|
||||
- **我想查看 API 细节**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
- **我遇到了错误**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
- **我想查看 API 细节**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
- **我遇到了错误**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
- **我想看工作流示例**: 查看各 Skill 下的 `examples/` 目录
|
||||
|
||||
### 按功能分类
|
||||
|
|
@ -236,12 +242,12 @@ gitlink-cli org +info -i Gitlink
|
|||
**仓库操作**:
|
||||
- [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md) - 仓库命令
|
||||
- [gitlink-branch/SKILL.md](gitlink-branch/SKILL.md) - 分支命令
|
||||
- [gitlink-repo/SKILL.md](gitlink-repo/SKILL.md) - 完整工作流
|
||||
- [gitlink-repo/examples/repo-workflow.md](gitlink-repo/examples/repo-workflow.md) - 完整工作流
|
||||
|
||||
**Issue 和 PR**:
|
||||
- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 命令
|
||||
- [gitlink-pr/SKILL.md](gitlink-pr/SKILL.md) - PR 命令
|
||||
- [gitlink-issue/SKILL.md](gitlink-issue/SKILL.md) - Issue 工作流
|
||||
- [gitlink-issue/examples/issue-workflow.md](gitlink-issue/examples/issue-workflow.md) - Issue 工作流
|
||||
|
||||
**发布和搜索**:
|
||||
- [gitlink-release/SKILL.md](gitlink-release/SKILL.md) - Release 命令
|
||||
|
|
@ -283,11 +289,11 @@ gitlink-cli auth login
|
|||
|
||||
### Q: 如何查看完整的 API 参考?
|
||||
|
||||
A: 查看 [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
A: 查看 [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
|
||||
### Q: 遇到错误怎么办?
|
||||
|
||||
A: 查看 [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
A: 查看 [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -323,7 +329,7 @@ AI 代理可以:
|
|||
- 所有边界情况处理正确
|
||||
- 完整的文档和示例
|
||||
|
||||
详见: [../doc/design.md](../doc/design.md)
|
||||
详见: [../doc/SKILLS_TEST_REPORT_2026-04-02.md](../doc/SKILLS_TEST_REPORT_2026-04-02.md)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -331,7 +337,8 @@ AI 代理可以:
|
|||
|
||||
- [主项目 README](../README.md) - gitlink-cli 项目说明
|
||||
- [设计文档](../doc/design.md) - 架构设计和开发计划
|
||||
- [API 参考文档](../doc/gitlink_api_reference.md) - GitLink API 参考文档
|
||||
- [测试报告](../doc/SKILLS_TEST_REPORT_2026-04-02.md) - 功能测试报告
|
||||
- [代码同步方案](../doc/CODE_SYNC_STRATEGY_FINAL.md) - GitHub ↔ GitLink 同步设计
|
||||
- [gitlink-bisync](https://www.gitlink.org.cn/wbtiger/gitlink-bisync) - 代码双向同步系统
|
||||
|
||||
---
|
||||
|
|
@ -339,8 +346,8 @@ AI 代理可以:
|
|||
## 📞 获取帮助
|
||||
|
||||
- **命令帮助**: `gitlink-cli <command> --help`
|
||||
- **故障排查**: [gitlink-shared/references/troubleshooting.md](gitlink-shared/references/troubleshooting.md)
|
||||
- **API 参考**: [gitlink-shared/references/api-reference.md](gitlink-shared/references/api-reference.md)
|
||||
- **故障排查**: [gitlink-shared/TROUBLESHOOTING.md](gitlink-shared/TROUBLESHOOTING.md)
|
||||
- **API 参考**: [gitlink-shared/REFERENCE.md](gitlink-shared/REFERENCE.md)
|
||||
- **工作流示例**: 查看各 Skill 下的 `examples/` 目录
|
||||
|
||||
---
|
||||
|
|
@ -348,7 +355,7 @@ AI 代理可以:
|
|||
## 🎓 下一步
|
||||
|
||||
1. 阅读 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 了解基础
|
||||
2. 查看 [gitlink-shared/SKILL.md](gitlink-shared/SKILL.md) 完成认证
|
||||
2. 查看 [gitlink-shared/examples/auth-workflow.md](gitlink-shared/examples/auth-workflow.md) 完成认证
|
||||
3. 根据需求选择相应的 Skill 文档
|
||||
4. 参考 `examples/` 目录中的工作流示例
|
||||
5. 使用 AI 代理自动化你的工作流
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ metadata:
|
|||
| `pr +list` | PR 列表 | 否(公开项目) |
|
||||
| `pr +create` | 创建 PR | 是 |
|
||||
| `pr +view` | PR 详情 | 否(公开项目) |
|
||||
| `pr +checkout` | 拉取并切换到 PR 分支 | 否(公开项目,本地需要 git) |
|
||||
| `pr +merge` | 合并 PR | 是 |
|
||||
| `pr +refuse` | 拒绝并关闭 PR | 是 |
|
||||
| `pr +reopen` | 重开已关闭的 PR | 是 |
|
||||
|
|
@ -52,6 +53,10 @@ gitlink-cli pr +create --title "feat: 新增搜索功能" --head feature/search
|
|||
# 查看 PR 详情(使用 pull_request_number,即网页 URL 中的序号)
|
||||
gitlink-cli pr +view --id 3
|
||||
|
||||
# 拉取 PR 源分支到本地 review 分支,适合本地跑测试或复现问题
|
||||
gitlink-cli pr +checkout --id 3 --branch review/pr-3
|
||||
gitlink-cli pr +checkout --id 3 --force
|
||||
|
||||
# 合并 PR(支持 merge/rebase/squash)
|
||||
gitlink-cli pr +merge --id 3
|
||||
gitlink-cli pr +merge --id 3 --method squash
|
||||
|
|
|
|||
|
|
@ -0,0 +1,36 @@
|
|||
# pr +checkout
|
||||
|
||||
将指定 PR 的源分支拉取到本地,并切换到一个本地 review 分支。
|
||||
|
||||
```bash
|
||||
gitlink-cli pr +checkout --id 3
|
||||
gitlink-cli pr +checkout -i 3 --branch review/pr-3
|
||||
gitlink-cli pr +checkout -i 3 --force
|
||||
gitlink-cli pr +checkout -i 3 --dry-run
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必需 | 说明 |
|
||||
|------|------|------|
|
||||
| `--id` / `-i` | 是 | PR 序号,即网页 URL `/pulls/N` 中的数字 |
|
||||
| `--branch` / `-b` | 否 | 本地分支名,默认 `pr-<id>` |
|
||||
| `--force` / `-f` | 否 | 本地分支已存在时使用 `git checkout -B` 重置 |
|
||||
| `--dry-run` | 否 | 只输出将执行的 git 命令,不修改本地仓库 |
|
||||
|
||||
## 行为
|
||||
|
||||
命令会先读取 PR 详情,解析源 fork、源仓库和源分支,然后执行:
|
||||
|
||||
```bash
|
||||
git fetch --no-tags <source-url> <source-branch>
|
||||
git checkout -b <local-branch> FETCH_HEAD
|
||||
```
|
||||
|
||||
如果传入 `--force`,第二步会改为 `git checkout -B <local-branch> FETCH_HEAD`。
|
||||
|
||||
## 使用建议
|
||||
|
||||
- 审查 PR 前可以先执行 `pr +view` 和 `pr +files` 获取上下文,再用 `pr +checkout` 拉到本地跑测试。
|
||||
- fork PR 不需要手动添加 contributor remote,命令会从 PR 详情里解析源仓库地址。
|
||||
- 不确定会执行哪些本地 git 操作时,先加 `--dry-run`。
|
||||
Loading…
Reference in New Issue