feat(repo,pr): add code history file shortcuts
This commit is contained in:
parent
71ca2bb683
commit
e20f80e26c
24
README.md
24
README.md
|
|
@ -227,6 +227,17 @@ gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
|||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
||||
|
||||
# Search repository files and inspect commit history
|
||||
gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref master
|
||||
gitlink-cli repo +commits --owner Gitlink --repo forgeplus --ref master --limit 20
|
||||
gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
gitlink-cli repo +commit-diff --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
|
||||
# Inspect repository tags
|
||||
gitlink-cli repo +tags --owner Gitlink --repo forgeplus --name v1 --only-name true
|
||||
gitlink-cli repo +tag --owner Gitlink --repo forgeplus --name v1.0.0
|
||||
gitlink-cli repo +delete-tag --owner Gitlink --repo forgeplus --name v1.0.0 --dry-run
|
||||
|
||||
# Show language breakdown
|
||||
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
|
||||
|
||||
|
|
@ -250,6 +261,16 @@ gitlink-cli repo +unfollow --owner Gitlink --repo forgeplus --project-id 123
|
|||
gitlink-cli repo +like --owner Gitlink --repo forgeplus
|
||||
gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
|
||||
|
||||
# Preview and apply a multi-file commit
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
|
||||
--dry-run
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;delete:old.md' \
|
||||
--yes
|
||||
|
||||
# Create a repository
|
||||
gitlink-cli repo +create -n my-project -d "Project description"
|
||||
|
||||
|
|
@ -420,6 +441,9 @@ gitlink-cli pr +reopen --owner Gitlink --repo forgeplus -i 42
|
|||
# View changed files
|
||||
gitlink-cli pr +files --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# View commits included in a PR
|
||||
gitlink-cli pr +commits --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# List PR patchset versions
|
||||
gitlink-cli pr +versions --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
|
|||
|
|
@ -238,6 +238,17 @@ gitlink-cli repo +readme --owner Gitlink --repo forgeplus --ref master
|
|||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --ref master
|
||||
gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
||||
|
||||
# 搜索仓库文件并查看提交历史
|
||||
gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref master
|
||||
gitlink-cli repo +commits --owner Gitlink --repo forgeplus --ref master --limit 20
|
||||
gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
gitlink-cli repo +commit-diff --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
|
||||
# 查看仓库标签
|
||||
gitlink-cli repo +tags --owner Gitlink --repo forgeplus --name v1 --only-name true
|
||||
gitlink-cli repo +tag --owner Gitlink --repo forgeplus --name v1.0.0
|
||||
gitlink-cli repo +delete-tag --owner Gitlink --repo forgeplus --name v1.0.0 --dry-run
|
||||
|
||||
# 查看语言占比
|
||||
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
|
||||
|
||||
|
|
@ -261,6 +272,16 @@ gitlink-cli repo +unfollow --owner Gitlink --repo forgeplus --project-id 123
|
|||
gitlink-cli repo +like --owner Gitlink --repo forgeplus
|
||||
gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
|
||||
|
||||
# 预览并执行多文件提交
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
|
||||
--dry-run
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;delete:old.md' \
|
||||
--yes
|
||||
|
||||
# 创建仓库
|
||||
gitlink-cli repo +create -n my-project -d "项目描述"
|
||||
|
||||
|
|
@ -430,6 +451,9 @@ gitlink-cli pr +reopen --owner Gitlink --repo forgeplus -i 42
|
|||
# 查看 PR 变更文件
|
||||
gitlink-cli pr +files --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# 查看 PR 包含的提交
|
||||
gitlink-cli pr +commits --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
# 查看 PR patchset/version 列表
|
||||
gitlink-cli pr +versions --owner Gitlink --repo forgeplus -i 42
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# Code history and batch file shortcuts
|
||||
|
||||
## Background
|
||||
|
||||
Agent workflows such as PR review, commit quality checks, repository health
|
||||
reports, and research reproducibility audits need commit timelines, changed
|
||||
files, commit diffs, and sometimes controlled multi-file updates. Before this
|
||||
change, several of these operations required Raw API calls.
|
||||
|
||||
This change adds a larger Subtask 1 feature set around repository code history
|
||||
and file operations.
|
||||
|
||||
## New shortcuts
|
||||
|
||||
Repository shortcuts:
|
||||
|
||||
- `repo +files` searches repository files with optional `--search` and `--ref`.
|
||||
- `repo +commits` lists commits for a branch, tag, or commit ref with pagination.
|
||||
- `repo +commit-files` lists files changed by a commit, with optional file-path filtering.
|
||||
- `repo +commit-diff` returns a commit diff.
|
||||
- `repo +tags` lists repository tags with pagination and optional name filtering.
|
||||
- `repo +tag` returns one tag's metadata and target commit.
|
||||
- `repo +delete-tag` deletes a repository tag after explicit confirmation.
|
||||
- `repo +batch-commit` creates, updates, or deletes multiple files in one commit.
|
||||
|
||||
Pull request shortcuts:
|
||||
|
||||
- `pr +commits` lists commits included in a pull request.
|
||||
|
||||
## Safety model
|
||||
|
||||
All history and file inspection commands are read-only.
|
||||
|
||||
`repo +delete-tag` and `repo +batch-commit` can modify repository content, so
|
||||
they require an explicit confirmation flag for remote writes:
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +delete-tag --owner me --repo proj --name v0.1.0 --dry-run
|
||||
gitlink-cli repo +delete-tag --owner me --repo proj --name v0.1.0 --yes
|
||||
```
|
||||
|
||||
```bash
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update" \
|
||||
--files 'update:README.md:# Updated' \
|
||||
--dry-run
|
||||
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update" \
|
||||
--files 'update:README.md:# Updated' \
|
||||
--yes
|
||||
```
|
||||
|
||||
The `--files` format is:
|
||||
|
||||
```text
|
||||
action:path[:content][;action:path[:content]...]
|
||||
```
|
||||
|
||||
Supported actions are `create`, `update`, and `delete`. `create` and `update`
|
||||
require content; `delete` does not.
|
||||
|
||||
## Tests
|
||||
|
||||
Unit tests cover:
|
||||
|
||||
- repository file search query mapping;
|
||||
- commit list pagination and ref mapping;
|
||||
- commit changed-file and diff endpoints;
|
||||
- repository tag list/detail/delete endpoint mapping;
|
||||
- PR commit list endpoint;
|
||||
- `repo +batch-commit` dry-run behavior;
|
||||
- `repo +batch-commit` remote write protection without `--yes`;
|
||||
- batch file operation payload construction and validation.
|
||||
|
||||
Suggested verification:
|
||||
|
||||
```bash
|
||||
go test ./shortcuts/repo ./shortcuts/pr
|
||||
```
|
||||
|
||||
Full project verification:
|
||||
|
||||
```bash
|
||||
make test
|
||||
```
|
||||
|
|
@ -54,6 +54,7 @@
|
|||
"cmd.org.short": "Organization operations",
|
||||
"cmd.pr.close.short": "Close a pull request",
|
||||
"cmd.pr.comment.short": "Add a comment to a pull request",
|
||||
"cmd.pr.commits.short": "List commits in a pull request",
|
||||
"cmd.pr.create.short": "Create a pull request",
|
||||
"cmd.pr.diff.short": "Show diff for a pull request",
|
||||
"cmd.pr.files.short": "List changed files in a pull request",
|
||||
|
|
@ -82,11 +83,19 @@
|
|||
"cmd.release.short": "Release operations",
|
||||
"cmd.release.view.short": "View release details",
|
||||
"cmd.repo.create.short": "Create a new repository",
|
||||
"cmd.repo.batch_commit.short": "Create, update, or delete multiple files in one commit",
|
||||
"cmd.repo.commit_diff.short": "Show diff for one commit",
|
||||
"cmd.repo.commit_files.short": "List files changed by one commit",
|
||||
"cmd.repo.commits.short": "List repository commits",
|
||||
"cmd.repo.delete_tag.short": "Delete a repository tag",
|
||||
"cmd.repo.delete.short": "Delete a repository",
|
||||
"cmd.repo.files.short": "Search repository files",
|
||||
"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.short": "Repository operations",
|
||||
"cmd.repo.tag.short": "View repository tag details",
|
||||
"cmd.repo.tags.short": "List repository tags",
|
||||
"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",
|
||||
|
|
@ -209,9 +218,29 @@
|
|||
"flag.release.target": "Target branch",
|
||||
"flag.repo": "Repository name (auto-detected from git remote)",
|
||||
"flag.repo.category": "Filter: manage/mirror/sync/fork/all (default: manage)",
|
||||
"flag.repo.author_email": "Commit author email",
|
||||
"flag.repo.author_name": "Commit author name",
|
||||
"flag.repo.batch_branch": "Target branch",
|
||||
"flag.repo.batch_dry_run": "Preview the batch commit request without changing files",
|
||||
"flag.repo.batch_encoding": "Content encoding for inline file content: text or base64",
|
||||
"flag.repo.batch_files": "File operations: action:path:content; repeat with semicolons",
|
||||
"flag.repo.batch_message": "Commit message",
|
||||
"flag.repo.batch_new_branch": "Create and commit to this new branch",
|
||||
"flag.repo.batch_yes": "Confirm remote file changes",
|
||||
"flag.repo.commit_sha": "Commit SHA",
|
||||
"flag.repo.committer_email": "Commit committer email",
|
||||
"flag.repo.committer_name": "Commit committer name",
|
||||
"flag.repo.delete_tag_dry_run": "Preview the tag deletion without changing repository state",
|
||||
"flag.repo.delete_tag_yes": "Confirm the destructive tag deletion",
|
||||
"flag.repo.description": "Repository description",
|
||||
"flag.repo.file": "Filter by file path",
|
||||
"flag.repo.files.search": "File name/path keyword",
|
||||
"flag.repo.name": "Repository name",
|
||||
"flag.repo.only_name": "Return only names (true/false)",
|
||||
"flag.repo.private": "Make repository private (true/false)",
|
||||
"flag.repo.ref": "Branch, tag, or commit SHA",
|
||||
"flag.repo.tag_name": "Tag name",
|
||||
"flag.repo.tag_name_filter": "Tag search keyword",
|
||||
"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",
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@
|
|||
"cmd.org.short": "组织操作",
|
||||
"cmd.pr.close.short": "关闭拉取请求",
|
||||
"cmd.pr.comment.short": "给拉取请求添加评论",
|
||||
"cmd.pr.commits.short": "列出拉取请求中的提交",
|
||||
"cmd.pr.create.short": "创建拉取请求",
|
||||
"cmd.pr.diff.short": "显示拉取请求 diff",
|
||||
"cmd.pr.files.short": "列出拉取请求中的变更文件",
|
||||
|
|
@ -82,11 +83,19 @@
|
|||
"cmd.release.short": "发布操作",
|
||||
"cmd.release.view.short": "查看发布详情",
|
||||
"cmd.repo.create.short": "创建新仓库",
|
||||
"cmd.repo.batch_commit.short": "在一次提交中创建、更新或删除多个文件",
|
||||
"cmd.repo.commit_diff.short": "显示单个提交的 diff",
|
||||
"cmd.repo.commit_files.short": "列出单个提交变更的文件",
|
||||
"cmd.repo.commits.short": "列出仓库提交",
|
||||
"cmd.repo.delete_tag.short": "删除仓库标签",
|
||||
"cmd.repo.delete.short": "删除仓库",
|
||||
"cmd.repo.files.short": "搜索仓库文件",
|
||||
"cmd.repo.fork.short": "Fork 仓库",
|
||||
"cmd.repo.info.short": "显示仓库详情",
|
||||
"cmd.repo.list.short": "列出用户或组织的仓库",
|
||||
"cmd.repo.short": "仓库操作",
|
||||
"cmd.repo.tag.short": "查看仓库标签详情",
|
||||
"cmd.repo.tags.short": "列出仓库标签",
|
||||
"cmd.repo.tree.short": "列出仓库文件和目录",
|
||||
"cmd.root.long": "用于管理 GitLink 上的仓库、议题、拉取请求、发布、CI 和工作流。",
|
||||
"cmd.root.short": "GitLink CLI - GitLink 命令行工具",
|
||||
|
|
@ -209,9 +218,29 @@
|
|||
"flag.release.target": "目标分支",
|
||||
"flag.repo": "仓库名称(自动从 git remote 检测)",
|
||||
"flag.repo.category": "筛选:manage/mirror/sync/fork/all(默认:manage)",
|
||||
"flag.repo.author_email": "提交作者邮箱",
|
||||
"flag.repo.author_name": "提交作者名称",
|
||||
"flag.repo.batch_branch": "目标分支",
|
||||
"flag.repo.batch_dry_run": "预览批量提交请求,不修改文件",
|
||||
"flag.repo.batch_encoding": "内联文件内容编码:text 或 base64",
|
||||
"flag.repo.batch_files": "文件操作:action:path:content;多个操作用英文分号分隔",
|
||||
"flag.repo.batch_message": "提交信息",
|
||||
"flag.repo.batch_new_branch": "创建并提交到这个新分支",
|
||||
"flag.repo.batch_yes": "确认修改远端文件",
|
||||
"flag.repo.commit_sha": "Commit SHA",
|
||||
"flag.repo.committer_email": "提交者邮箱",
|
||||
"flag.repo.committer_name": "提交者名称",
|
||||
"flag.repo.delete_tag_dry_run": "预览标签删除操作,不修改仓库状态",
|
||||
"flag.repo.delete_tag_yes": "确认执行破坏性标签删除",
|
||||
"flag.repo.description": "仓库描述",
|
||||
"flag.repo.file": "按文件路径筛选",
|
||||
"flag.repo.files.search": "文件名或路径关键词",
|
||||
"flag.repo.name": "仓库名称",
|
||||
"flag.repo.only_name": "只返回名称(true/false)",
|
||||
"flag.repo.private": "设为私有仓库(true/false)",
|
||||
"flag.repo.ref": "分支、标签或 Commit SHA",
|
||||
"flag.repo.tag_name": "标签名称",
|
||||
"flag.repo.tag_name_filter": "标签搜索关键词",
|
||||
"flag.repo.tree.path": "要列出的目录路径(默认:仓库根目录)",
|
||||
"flag.repo.tree.ref": "分支、标签或提交引用",
|
||||
"flag.search.keyword": "搜索关键词",
|
||||
|
|
|
|||
|
|
@ -245,6 +245,27 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "commits",
|
||||
Description: tr.T("cmd.pr.commits.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "id", Short: "i", Usage: tr.T("flag.pr.id"), Required: true},
|
||||
},
|
||||
Run: func(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", fmt.Sprintf("%s/pulls/%s/commits", ctx.RepoPath(), url.PathEscape(id)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "versions",
|
||||
Description: tr.T("cmd.pr.versions.short"),
|
||||
|
|
|
|||
|
|
@ -351,6 +351,31 @@ func TestPRDiff(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// --- commits ---
|
||||
|
||||
func TestPRCommits(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 != "/owner/repo/pulls/42/commits.json" {
|
||||
t.Fatalf("unexpected path: %s", r.URL.Path)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{
|
||||
"commits_count": float64(1),
|
||||
"commits": []interface{}{
|
||||
map[string]interface{}{"sha": "abc123", "message": "feat: add shortcut"},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runPRShortcut(t, server, "commits", map[string]string{"id": "42"})
|
||||
if err != nil {
|
||||
t.Fatalf("commits failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// --- extractIssueID ---
|
||||
|
||||
func TestExtractIssueID(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -108,6 +108,91 @@ func Shortcuts(translators ...*i18n.Translator) []*common.Shortcut {
|
|||
return ctx.Output(env)
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "files",
|
||||
Description: tr.T("cmd.repo.files.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "search", Short: "s", Usage: tr.T("flag.repo.files.search")},
|
||||
{Name: "ref", Short: "r", Usage: tr.T("flag.repo.ref")},
|
||||
},
|
||||
Run: runFiles,
|
||||
},
|
||||
{
|
||||
Name: "commits",
|
||||
Description: tr.T("cmd.repo.commits.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "ref", Short: "r", Usage: tr.T("flag.repo.ref")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: runCommits,
|
||||
},
|
||||
{
|
||||
Name: "commit-files",
|
||||
Description: tr.T("cmd.repo.commit_files.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: tr.T("flag.repo.commit_sha"), Required: true},
|
||||
{Name: "file", Short: "f", Usage: tr.T("flag.repo.file")},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: runCommitFiles,
|
||||
},
|
||||
{
|
||||
Name: "commit-diff",
|
||||
Description: tr.T("cmd.repo.commit_diff.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "sha", Short: "s", Usage: tr.T("flag.repo.commit_sha"), Required: true},
|
||||
},
|
||||
Run: runCommitDiff,
|
||||
},
|
||||
{
|
||||
Name: "tags",
|
||||
Description: tr.T("cmd.repo.tags.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.repo.tag_name_filter")},
|
||||
{Name: "only-name", Usage: tr.T("flag.repo.only_name"), Default: "false"},
|
||||
{Name: "page", Short: "p", Usage: tr.T("flag.page"), Default: "1"},
|
||||
{Name: "limit", Short: "l", Usage: tr.T("flag.limit"), Default: "20"},
|
||||
},
|
||||
Run: runTags,
|
||||
},
|
||||
{
|
||||
Name: "tag",
|
||||
Description: tr.T("cmd.repo.tag.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.repo.tag_name"), Required: true},
|
||||
},
|
||||
Run: runTag,
|
||||
},
|
||||
{
|
||||
Name: "delete-tag",
|
||||
Description: tr.T("cmd.repo.delete_tag.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "name", Short: "n", Usage: tr.T("flag.repo.tag_name"), Required: true},
|
||||
{Name: "dry-run", Usage: tr.T("flag.repo.delete_tag_dry_run"), Bool: true, Default: "false"},
|
||||
{Name: "yes", Usage: tr.T("flag.repo.delete_tag_yes"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runDeleteTag,
|
||||
},
|
||||
{
|
||||
Name: "batch-commit",
|
||||
Description: tr.T("cmd.repo.batch_commit.short"),
|
||||
Flags: []common.Flag{
|
||||
{Name: "branch", Short: "b", Usage: tr.T("flag.repo.batch_branch"), Required: true},
|
||||
{Name: "message", Short: "m", Usage: tr.T("flag.repo.batch_message"), Required: true},
|
||||
{Name: "files", Short: "f", Usage: tr.T("flag.repo.batch_files"), Required: true},
|
||||
{Name: "new-branch", Usage: tr.T("flag.repo.batch_new_branch")},
|
||||
{Name: "encoding", Usage: tr.T("flag.repo.batch_encoding"), Default: "text"},
|
||||
{Name: "author-name", Usage: tr.T("flag.repo.author_name")},
|
||||
{Name: "author-email", Usage: tr.T("flag.repo.author_email")},
|
||||
{Name: "committer-name", Usage: tr.T("flag.repo.committer_name")},
|
||||
{Name: "committer-email", Usage: tr.T("flag.repo.committer_email")},
|
||||
{Name: "dry-run", Usage: tr.T("flag.repo.batch_dry_run"), Bool: true, Default: "false"},
|
||||
{Name: "yes", Usage: tr.T("flag.repo.batch_yes"), Bool: true, Default: "false"},
|
||||
},
|
||||
Run: runBatchCommit,
|
||||
},
|
||||
{
|
||||
Name: "languages",
|
||||
Description: "Show repository language statistics",
|
||||
|
|
@ -263,6 +348,173 @@ func shortcutTranslator(translators ...*i18n.Translator) *i18n.Translator {
|
|||
return i18n.Default()
|
||||
}
|
||||
|
||||
func runFiles(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
setRepoQueryIfPresent(q, "search", ctx.Arg("search"))
|
||||
setRepoQueryIfPresent(q, "ref", ctx.Arg("ref"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/files", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runCommits(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("page", firstRepoValue(ctx.Arg("page"), "1"))
|
||||
q.Set("limit", firstRepoValue(ctx.Arg("limit"), "20"))
|
||||
setRepoQueryIfPresent(q, "sha", ctx.Arg("ref"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/v1"+ctx.RepoPath()+"/commits", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runCommitFiles(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
sha, err := ctx.RequireArg("sha")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
if file := strings.TrimSpace(ctx.Arg("file")); file != "" {
|
||||
q.Set("filepath", file)
|
||||
} else {
|
||||
q.Set("page", firstRepoValue(ctx.Arg("page"), "1"))
|
||||
q.Set("limit", firstRepoValue(ctx.Arg("limit"), "20"))
|
||||
}
|
||||
env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1%s/commits/%s/files", ctx.RepoPath(), url.PathEscape(sha)), q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runCommitDiff(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
sha, err := ctx.RequireArg("sha")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("/v1%s/commits/%s/diff", ctx.RepoPath(), url.PathEscape(sha)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runTags(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
q := url.Values{}
|
||||
if name := strings.TrimSpace(ctx.Arg("name")); name != "" {
|
||||
q.Set("name", name)
|
||||
}
|
||||
onlyName := strings.TrimSpace(ctx.Arg("only-name"))
|
||||
if onlyName != "" && onlyName != "false" {
|
||||
q.Set("only_name", onlyName)
|
||||
}
|
||||
if len(q) > 0 {
|
||||
env, err := ctx.CallAPIWithQuery("GET", ctx.RepoPath()+"/tags", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
q.Set("page", firstRepoValue(ctx.Arg("page"), "1"))
|
||||
q.Set("limit", firstRepoValue(ctx.Arg("limit"), "20"))
|
||||
env, err := ctx.CallAPIWithQuery("GET", "/v1"+ctx.RepoPath()+"/tags", q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runTag(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
env, err := ctx.CallAPI("GET", fmt.Sprintf("/v1%s/tags/%s", ctx.RepoPath(), url.PathEscape(name)), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runDeleteTag(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := ctx.RequireArg("name")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := fmt.Sprintf("/v1%s/tags/%s", ctx.RepoPath(), url.PathEscape(name))
|
||||
if ctx.Arg("dry-run") == "true" {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"dry_run": true,
|
||||
"action": "delete_tag",
|
||||
"method": "DELETE",
|
||||
"path": path,
|
||||
"repository": fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
||||
"tag": name,
|
||||
})
|
||||
}
|
||||
if ctx.Arg("yes") != "true" {
|
||||
return fmt.Errorf("tag deletion is destructive; run --dry-run first, then pass --yes to execute")
|
||||
}
|
||||
env, err := ctx.CallAPI("DELETE", path, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runBatchCommit(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
}
|
||||
payload, err := batchCommitPayload(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
path := "/v1" + ctx.RepoPath() + "/contents/batch"
|
||||
if ctx.Arg("dry-run") == "true" {
|
||||
return ctx.OutputData(map[string]interface{}{
|
||||
"dry_run": true,
|
||||
"action": "batch_commit",
|
||||
"method": "POST",
|
||||
"path": path,
|
||||
"repository": fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
|
||||
"payload": payload,
|
||||
})
|
||||
}
|
||||
if ctx.Arg("yes") != "true" {
|
||||
return fmt.Errorf("batch file commit changes repository content; run --dry-run first, then pass --yes to execute")
|
||||
}
|
||||
env, err := ctx.CallAPI("POST", path, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ctx.Output(env)
|
||||
}
|
||||
|
||||
func runLanguages(ctx *common.RuntimeContext) error {
|
||||
if err := ctx.ResolveOwnerRepo(); err != nil {
|
||||
return err
|
||||
|
|
@ -482,6 +734,103 @@ func setRepoQueryIfPresent(q url.Values, key, value string) {
|
|||
}
|
||||
}
|
||||
|
||||
func firstRepoValue(value, fallback string) string {
|
||||
if value := strings.TrimSpace(value); value != "" {
|
||||
return value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
func batchCommitPayload(ctx *common.RuntimeContext) (map[string]interface{}, error) {
|
||||
branch, err := ctx.RequireArg("branch")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
message, err := ctx.RequireArg("message")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawFiles, err := ctx.RequireArg("files")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
encoding := firstRepoValue(ctx.Arg("encoding"), "text")
|
||||
if encoding != "text" && encoding != "base64" {
|
||||
return nil, fmt.Errorf("invalid --encoding %q: use text or base64", encoding)
|
||||
}
|
||||
files, err := parseBatchFileSpecs(rawFiles, encoding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload := map[string]interface{}{
|
||||
"branch": strings.TrimSpace(branch),
|
||||
"message": message,
|
||||
"files": files,
|
||||
}
|
||||
if newBranch := strings.TrimSpace(ctx.Arg("new-branch")); newBranch != "" {
|
||||
payload["new_branch"] = newBranch
|
||||
}
|
||||
setRepoPayloadIfPresent(payload, "author_name", ctx.Arg("author-name"))
|
||||
setRepoPayloadIfPresent(payload, "author_email", ctx.Arg("author-email"))
|
||||
setRepoPayloadIfPresent(payload, "committer_name", ctx.Arg("committer-name"))
|
||||
setRepoPayloadIfPresent(payload, "committer_email", ctx.Arg("committer-email"))
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func parseBatchFileSpecs(raw, encoding string) ([]map[string]interface{}, error) {
|
||||
specs := strings.Split(raw, ";")
|
||||
files := make([]map[string]interface{}, 0, len(specs))
|
||||
for _, spec := range specs {
|
||||
spec = strings.TrimSpace(spec)
|
||||
if spec == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(spec, ":", 3)
|
||||
if len(parts) < 2 {
|
||||
return nil, fmt.Errorf("invalid --files item %q: use action:path[:content]", spec)
|
||||
}
|
||||
action := strings.TrimSpace(parts[0])
|
||||
path := strings.TrimSpace(parts[1])
|
||||
if !isBatchFileAction(action) {
|
||||
return nil, fmt.Errorf("invalid file action %q: use create, update, or delete", action)
|
||||
}
|
||||
if path == "" {
|
||||
return nil, fmt.Errorf("invalid --files item %q: file path is required", spec)
|
||||
}
|
||||
item := map[string]interface{}{
|
||||
"action_type": action,
|
||||
"file_path": path,
|
||||
}
|
||||
if action != "delete" {
|
||||
if len(parts) != 3 {
|
||||
return nil, fmt.Errorf("file action %q for %q requires content", action, path)
|
||||
}
|
||||
item["content"] = parts[2]
|
||||
item["encoding"] = encoding
|
||||
}
|
||||
files = append(files, item)
|
||||
}
|
||||
if len(files) == 0 {
|
||||
return nil, fmt.Errorf("--files must include at least one file operation")
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func isBatchFileAction(action string) bool {
|
||||
switch action {
|
||||
case "create", "update", "delete":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func setRepoPayloadIfPresent(payload map[string]interface{}, key, value string) {
|
||||
if value := strings.TrimSpace(value); value != "" {
|
||||
payload[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
func parseOptionalRepoNonNegativeInt(value, name string) (int, bool, error) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return 0, false, nil
|
||||
|
|
|
|||
|
|
@ -57,6 +57,15 @@ func writeText(t *testing.T, w http.ResponseWriter, code int, text string) {
|
|||
}
|
||||
}
|
||||
|
||||
func decodeRepoJSON(t *testing.T, r *http.Request) map[string]interface{} {
|
||||
t.Helper()
|
||||
var payload map[string]interface{}
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
t.Fatalf("decode request body: %v", err)
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
// --- list ---
|
||||
|
||||
func TestRepoListDefault(t *testing.T) {
|
||||
|
|
@ -273,6 +282,250 @@ func TestRepoCodeStatsUsesRefQuery(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoFilesBuildsSearchAndRefQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/files.json")
|
||||
assertEqual(t, r.URL.Query().Get("search"), "README")
|
||||
assertEqual(t, r.URL.Query().Get("ref"), "main")
|
||||
writeJSON(t, w, []interface{}{map[string]interface{}{"path": "README.md"}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "files", map[string]string{"search": "README", "ref": "main"}); err != nil {
|
||||
t.Fatalf("files shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCommitsBuildsRefPaginationQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits.json")
|
||||
assertEqual(t, r.URL.Query().Get("sha"), "main")
|
||||
assertEqual(t, r.URL.Query().Get("page"), "2")
|
||||
assertEqual(t, r.URL.Query().Get("limit"), "50")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 1, "commits": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "commits", map[string]string{"ref": "main", "page": "2", "limit": "50"})
|
||||
if err != nil {
|
||||
t.Fatalf("commits shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCommitFilesUsesPaginationWhenNoFileFilter(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits/abc123/files.json")
|
||||
assertEqual(t, r.URL.Query().Get("page"), "3")
|
||||
assertEqual(t, r.URL.Query().Get("limit"), "10")
|
||||
if got := r.URL.Query().Get("filepath"); got != "" {
|
||||
t.Fatalf("did not expect filepath query, got %q", got)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"files": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "commit-files", map[string]string{"sha": "abc123", "page": "3", "limit": "10"})
|
||||
if err != nil {
|
||||
t.Fatalf("commit-files shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCommitFilesUsesFileFilterWithoutPagination(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits/abc123/files.json")
|
||||
assertEqual(t, r.URL.Query().Get("filepath"), "src/main.go")
|
||||
if got := r.URL.Query().Get("page"); got != "" {
|
||||
t.Fatalf("did not expect page query with filepath filter, got %q", got)
|
||||
}
|
||||
writeJSON(t, w, map[string]interface{}{"files": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "commit-files", map[string]string{"sha": "abc123", "file": "src/main.go"})
|
||||
if err != nil {
|
||||
t.Fatalf("commit-files shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoCommitDiffUsesCommitDiffEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/commits/abc123/diff.json")
|
||||
writeJSON(t, w, map[string]interface{}{"diff": "@@ -1 +1 @@"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "commit-diff", map[string]string{"sha": "abc123"}); err != nil {
|
||||
t.Fatalf("commit-diff shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTagsBuildsPaginationQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/tags.json")
|
||||
assertEqual(t, r.URL.Query().Get("page"), "2")
|
||||
assertEqual(t, r.URL.Query().Get("limit"), "30")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 1, "tags": []interface{}{}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "tags", map[string]string{"page": "2", "limit": "30"})
|
||||
if err != nil {
|
||||
t.Fatalf("tags shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTagsUsesSearchEndpointForNameFilter(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/tags.json")
|
||||
assertEqual(t, r.URL.Query().Get("name"), "v1")
|
||||
assertEqual(t, r.URL.Query().Get("only_name"), "true")
|
||||
writeJSON(t, w, map[string]interface{}{"total_count": 1, "tags": []interface{}{map[string]interface{}{"name": "v1.0.0"}}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "tags", map[string]string{"name": "v1", "only-name": "true"})
|
||||
if err != nil {
|
||||
t.Fatalf("tags shortcut with name filter failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoTagUsesTagDetailEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/v1/owner/repo/tags/v1.0.0.json")
|
||||
writeJSON(t, w, map[string]interface{}{"name": "v1.0.0"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
if err := runShortcut(t, server, "tag", map[string]string{"name": "v1.0.0"}); err != nil {
|
||||
t.Fatalf("tag shortcut failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoDeleteTagDryRunDoesNotCallAPI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("delete-tag dry-run should not call remote API: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "delete-tag", map[string]string{"name": "v1.0.0", "dry-run": "true"})
|
||||
if err != nil {
|
||||
t.Fatalf("delete-tag dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoDeleteTagRequiresYesForRemoteDelete(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("delete-tag without --yes should not call remote API: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "delete-tag", map[string]string{"name": "v1.0.0"})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when delete-tag is missing --yes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoDeleteTagWithYesCallsDeleteEndpoint(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "DELETE", "/v1/owner/repo/tags/v1.0.0.json")
|
||||
writeJSON(t, w, map[string]interface{}{"status": 0, "message": "success"})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "delete-tag", map[string]string{"name": "v1.0.0", "yes": "true"})
|
||||
if err != nil {
|
||||
t.Fatalf("delete-tag failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoBatchCommitDryRunDoesNotCallAPI(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("dry-run should not call remote API: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "batch-commit", map[string]string{
|
||||
"branch": "master",
|
||||
"message": "docs: update",
|
||||
"files": "update:README.md:# hello;delete:old.txt",
|
||||
"new-branch": "docs/update-readme",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("batch-commit dry-run failed: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoBatchCommitRequiresYesForRemoteWrite(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatalf("batch-commit without --yes should not call remote API: %s %s", r.Method, r.URL.Path)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "batch-commit", map[string]string{
|
||||
"branch": "master",
|
||||
"message": "docs: update",
|
||||
"files": "update:README.md:# hello",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected error when batch-commit is missing --yes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoBatchCommitPostsPayloadWithYes(t *testing.T) {
|
||||
var payload map[string]interface{}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "POST", "/v1/owner/repo/contents/batch.json")
|
||||
payload = decodeRepoJSON(t, r)
|
||||
writeJSON(t, w, map[string]interface{}{"commit": map[string]interface{}{"sha": "abc123"}})
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "batch-commit", map[string]string{
|
||||
"branch": "master",
|
||||
"message": "docs: update",
|
||||
"files": "create:docs/new.md:# new;update:README.md:# hello;delete:old.txt",
|
||||
"encoding": "text",
|
||||
"author-name": "Alice",
|
||||
"author-email": "alice@example.com",
|
||||
"committer-name": "Bot",
|
||||
"committer-email": "bot@example.com",
|
||||
"yes": "true",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("batch-commit failed: %v", err)
|
||||
}
|
||||
assertEqual(t, payload["branch"], "master")
|
||||
assertEqual(t, payload["message"], "docs: update")
|
||||
assertEqual(t, payload["author_name"], "Alice")
|
||||
files, ok := payload["files"].([]interface{})
|
||||
if !ok || len(files) != 3 {
|
||||
t.Fatalf("expected three file operations, got %#v", payload["files"])
|
||||
}
|
||||
first, _ := files[0].(map[string]interface{})
|
||||
assertEqual(t, first["action_type"], "create")
|
||||
assertEqual(t, first["file_path"], "docs/new.md")
|
||||
assertEqual(t, first["content"], "# new")
|
||||
assertEqual(t, first["encoding"], "text")
|
||||
}
|
||||
|
||||
func TestRepoBatchCommitRejectsInvalidFileSpec(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {
|
||||
t.Fatal("invalid file spec should fail before calling remote API")
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
err := runShortcut(t, server, "batch-commit", map[string]string{
|
||||
"branch": "master",
|
||||
"message": "docs: update",
|
||||
"files": "move:README.md:# hello",
|
||||
"dry-run": "true",
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("expected invalid file action error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoWatchersBuildsTimeRangeQuery(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
assertRequest(t, r, "GET", "/owner/repo/watchers.json")
|
||||
|
|
|
|||
|
|
@ -59,10 +59,7 @@ metadata:
|
|||
|
||||
```bash
|
||||
# 获取 PR 关联的提交列表
|
||||
gitlink-cli pr +diff --id <pr_id> --owner <owner> --repo <repo> --format json
|
||||
|
||||
# 或通过 Raw API 获取提交详情
|
||||
gitlink-cli api GET /:owner/:repo/pulls/:pr_id/commits --format json
|
||||
gitlink-cli pr +commits --id <pr_id> --owner <owner> --repo <repo> --format json
|
||||
```
|
||||
|
||||
### Commit Message 质量检查清单
|
||||
|
|
|
|||
|
|
@ -223,7 +223,7 @@ def decide(total, hard_gate_failed, thresholds):
|
|||
| PR 元信息 | 标题/描述/作者/关联 | `gitlink-cli pr +view -i <id> --format json` | `+view`,`-i`/`--id` |
|
||||
| 变更文件 | 文件路径列表 | `gitlink-cli pr +files -i <id> --format json` | `+files` |
|
||||
| Diff | 变更内容供 AI 审查 | `gitlink-cli pr +diff -i <id> --format json` | `+diff`;当前实现与 `+files` 命中同一 `/pulls/:id/files` 端点 |
|
||||
| commits | commit 列表 | `gitlink-cli api GET /:owner/:repo/pulls/:id/commits --format json` | Raw API(无对应 shortcut) |
|
||||
| commits | commit 列表 | `gitlink-cli pr +commits -i <id> --format json` | `+commits` |
|
||||
| CI 状态 | 构建结果 | `gitlink-cli ci +builds --format json` | `+builds`(`-p`/`-l` 分页) |
|
||||
| 回写评论 | 评分卡 | `gitlink-cli pr +comment -i <id> -b "<scorecard>"` | `+comment` 底层走 issue journals(评论流);评审记录形式用 `pr +review -i <id> -s common -c "..."`(走 reviews 端点,payload 字段是 `content`/`status`,status 取 `common`/`approved`/`rejected`)。评分卡作为建议性回写,二者均用 `common` |
|
||||
| 创建标签 | 裁决标签 | `gitlink-cli label +create -n "<name>" -c "#RRGGBB"` | `+create`(本作品子题一新增;`label +list/+update/+delete` 同组) |
|
||||
|
|
|
|||
|
|
@ -99,17 +99,17 @@ behavior:
|
|||
| PR 元信息 | 标题/描述/作者/关联 issue | `gitlink-cli pr +view -i <id> --format json` |
|
||||
| 变更文件 | 文件路径列表 | `gitlink-cli pr +files -i <id> --format json` |
|
||||
| Diff | 变更内容供 AI 审查 | `gitlink-cli pr +diff -i <id> --format json` |
|
||||
| commits | commit 列表(消息供 commit_quality) | `gitlink-cli api GET /:owner/:repo/pulls/:id/commits --format json` |
|
||||
| commits | commit 列表(消息供 commit_quality) | `gitlink-cli pr +commits -i <id> --format json` |
|
||||
| CI 状态 | 构建结果 | `gitlink-cli ci +builds --format json` |
|
||||
|
||||
> 实测注意:`pr +files` 与 `pr +diff` 底层都打 `/pulls/:id/files`——`+files` 取路径列表,`+diff` 取含 patch 的同一份数据,按需取用即可。无 `pr +commits` 快捷命令,commit 列表只能走 Raw API。CI 通过/失败需从 builds 返回的 `status` 字段判断;**无 build 记录时按「CI 未知」处理**(见 §3.5)。
|
||||
> 实测注意:`pr +files` 与 `pr +diff` 底层都打 `/pulls/:id/files`——`+files` 取路径列表,`+diff` 取含 patch 的同一份数据,按需取用即可。commit 列表使用 `pr +commits`。CI 通过/失败需从 builds 返回的 `status` 字段判断;**无 build 记录时按「CI 未知」处理**(见 §3.5)。
|
||||
|
||||
```bash
|
||||
PR=42
|
||||
gitlink-cli pr +view -i "$PR" --format json # title / body / 关联 issue
|
||||
gitlink-cli pr +files -i "$PR" --format json # changed files
|
||||
gitlink-cli pr +diff -i "$PR" --format json # diff(供 AI 审查)
|
||||
gitlink-cli api GET /:owner/:repo/pulls/$PR/commits --format json
|
||||
gitlink-cli pr +commits -i "$PR" --format json
|
||||
gitlink-cli ci +builds --format json
|
||||
```
|
||||
|
||||
|
|
@ -295,7 +295,7 @@ PR=42
|
|||
gitlink-cli pr +view -i "$PR" --format json
|
||||
gitlink-cli pr +files -i "$PR" --format json
|
||||
gitlink-cli pr +diff -i "$PR" --format json
|
||||
gitlink-cli api GET /:owner/:repo/pulls/$PR/commits --format json
|
||||
gitlink-cli pr +commits -i "$PR" --format json
|
||||
gitlink-cli ci +builds --format json
|
||||
# → AI 产出发现 → 按 §4 评分 → §5 硬门禁 → §6 裁决 → §7 渲染评分卡
|
||||
# → dry-run:仅把评分卡打印给用户,结尾提示「如需回写到 PR,请加 --apply」
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
gitlink-cli pr +view -i 277 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli pr +files -i 277 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli pr +diff -i 277 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli api GET /Gitlink/forgeplus/pulls/277/commits --format json
|
||||
gitlink-cli pr +commits -i 277 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli ci +builds --owner Gitlink --repo forgeplus --format json
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ gitlink-cli pr +files -i 214 --owner Gitlink --repo forgeplus --format json
|
|||
gitlink-cli pr +diff -i 214 --owner Gitlink --repo forgeplus --format json
|
||||
|
||||
# commits
|
||||
gitlink-cli api GET /Gitlink/forgeplus/pulls/214/commits --format json
|
||||
gitlink-cli pr +commits -i 214 --owner Gitlink --repo forgeplus --format json
|
||||
|
||||
# CI 状态
|
||||
gitlink-cli ci +builds --owner Gitlink --repo forgeplus --format json
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
gitlink-cli pr +view -i 305 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli pr +files -i 305 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli pr +diff -i 305 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli api GET /Gitlink/forgeplus/pulls/305/commits --format json
|
||||
gitlink-cli pr +commits -i 305 --owner Gitlink --repo forgeplus --format json
|
||||
gitlink-cli ci +builds --owner Gitlink --repo forgeplus --format json
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ metadata:
|
|||
| `pr +reopen` | 重开已关闭的 PR | 是 |
|
||||
| `pr +files` | 变更文件列表 | 否 |
|
||||
| `pr +diff` | 查看变更文件和 diff 内容 | 否 |
|
||||
| `pr +commits` | 查看 PR 包含的提交列表 | 否 |
|
||||
| `pr +versions` | 查看 PR patchset/version 列表 | 否 |
|
||||
| `pr +version-diff` | 查看指定 patchset/version diff | 否 |
|
||||
| `pr +reviews` | 查看 PR 审查记录,支持状态过滤 | 否 |
|
||||
|
|
@ -61,6 +62,9 @@ gitlink-cli pr +reopen --id 3
|
|||
# 查看变更文件(含 diff 内容)
|
||||
gitlink-cli pr +files --id 3
|
||||
|
||||
# 查看 PR 包含的提交列表
|
||||
gitlink-cli pr +commits --id 3
|
||||
|
||||
# 查看 PR patchset/version 列表
|
||||
gitlink-cli pr +versions --id 3
|
||||
|
||||
|
|
@ -172,3 +176,10 @@ gitlink-cli api GET /v1/:owner/:repo/pulls/:id/versions/:version_id/diff
|
|||
- `pr +list` 的 `--state` 参数会映射为 GitLink API 的 `status` 筛选;需要查看所有 PR 时传 `--state all`
|
||||
- PR 状态值:`pull_request_status` 0=open, 1=merged, 2=closed
|
||||
- 关联已有 Issue 时,把 Issue 编号或 URL 写入 PR `--body`,或使用 `issue +comment` 留痕;不要用 Raw API 对 Issue 做不完整更新,否则可能清空 Issue 描述
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [`references/gitlink-pr-commits.md`](references/gitlink-pr-commits.md)
|
||||
- [`references/gitlink-pr-files.md`](references/gitlink-pr-files.md)
|
||||
- [`references/gitlink-pr-review.md`](references/gitlink-pr-review.md)
|
||||
- [`references/gitlink-pr-version-diff.md`](references/gitlink-pr-version-diff.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
# pr +commits
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
查看 Pull Request 包含的提交列表。该命令适合代码审查、commit message 质量检查、PR 门禁和 Release Notes 生成。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 查看 PR 提交列表
|
||||
gitlink-cli pr +commits --id 3
|
||||
|
||||
# 简写
|
||||
gitlink-cli pr +commits -i 3
|
||||
|
||||
# JSON 格式
|
||||
gitlink-cli pr +commits -i 3 --format json
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--id, -i` | 是 | PR 序号(`pull_request_number`) |
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
GET /{owner}/{repo}/pulls/{number}/commits
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- 返回提交 SHA、提交消息、作者、提交者和时间信息。
|
||||
- 在 `gitlink-gatekeeper`、`gitlink-commit-quality` 等 Skill 中优先使用此 Shortcut,不再需要 Raw API。
|
||||
|
||||
## 参考
|
||||
|
||||
- [gitlink-pr](../SKILL.md)
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md)
|
||||
|
|
@ -24,6 +24,12 @@ metadata:
|
|||
| `repo +info` | 仓库详情 | 否(公开项目) |
|
||||
| `repo +readme` | README 内容 | 否(公开项目) |
|
||||
| `repo +tree` | 仓库文件树 | 否(公开项目) |
|
||||
| `repo +files` | 搜索仓库文件 | 否(公开项目) |
|
||||
| `repo +commits` | 提交历史列表 | 否(公开项目) |
|
||||
| `repo +commit-files` | 单个提交的变更文件 | 否(公开项目) |
|
||||
| `repo +commit-diff` | 单个提交的 diff | 否(公开项目) |
|
||||
| `repo +tags` | 仓库标签列表 | 否(公开项目) |
|
||||
| `repo +tag` | 仓库标签详情 | 否(公开项目) |
|
||||
| `repo +languages` | 仓库语言统计 | 否(公开项目) |
|
||||
| `repo +contributors` | 仓库贡献者列表 | 否(公开项目) |
|
||||
| `repo +contributor-stats` | 贡献者代码行统计 | 否(公开项目) |
|
||||
|
|
@ -34,6 +40,8 @@ metadata:
|
|||
| `repo +unfollow` | 取消关注仓库 | 是 |
|
||||
| `repo +like` | 点赞仓库 | 是 |
|
||||
| `repo +unlike` | 取消点赞仓库 | 是 |
|
||||
| `repo +delete-tag` | 删除仓库标签,默认建议先 dry-run | 是 |
|
||||
| `repo +batch-commit` | 多文件批量提交,默认建议先 dry-run | 是 |
|
||||
| `repo +create` | 创建仓库 | 是 |
|
||||
| `repo +fork` | Fork 仓库 | 是 |
|
||||
| `repo +delete` | 删除仓库 | 是 |
|
||||
|
|
@ -57,6 +65,18 @@ gitlink-cli repo +tree --owner Gitlink --repo forgeplus --path src --ref main
|
|||
gitlink-cli repo +languages --owner Gitlink --repo forgeplus
|
||||
gitlink-cli repo +contributors --owner Gitlink --repo forgeplus
|
||||
|
||||
# 搜索文件和分析提交历史
|
||||
gitlink-cli repo +files --owner Gitlink --repo forgeplus --search README --ref master
|
||||
gitlink-cli repo +commits --owner Gitlink --repo forgeplus --ref master --limit 20
|
||||
gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
gitlink-cli repo +commit-files --owner Gitlink --repo forgeplus --sha <commit_sha> --file src/main.go
|
||||
gitlink-cli repo +commit-diff --owner Gitlink --repo forgeplus --sha <commit_sha>
|
||||
|
||||
# 查看标签和标签详情
|
||||
gitlink-cli repo +tags --owner Gitlink --repo forgeplus --name v1 --only-name true
|
||||
gitlink-cli repo +tag --owner Gitlink --repo forgeplus --name v1.0.0
|
||||
gitlink-cli repo +delete-tag --owner Gitlink --repo forgeplus --name v1.0.0 --dry-run
|
||||
|
||||
# 查看代码统计
|
||||
gitlink-cli repo +contributor-stats --owner Gitlink --repo forgeplus --ref master --pass-year 1
|
||||
gitlink-cli repo +code-stats --owner Gitlink --repo forgeplus --ref master
|
||||
|
|
@ -72,6 +92,16 @@ gitlink-cli repo +unfollow --owner Gitlink --repo forgeplus --project-id 123
|
|||
gitlink-cli repo +like --owner Gitlink --repo forgeplus
|
||||
gitlink-cli repo +unlike --owner Gitlink --repo forgeplus --project-id 123
|
||||
|
||||
# 预览多文件提交;真实写入前必须确认用户意图
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
|
||||
--dry-run
|
||||
gitlink-cli repo +batch-commit --owner me --repo proj \
|
||||
--branch master --message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;delete:old.md' \
|
||||
--yes
|
||||
|
||||
# 创建仓库
|
||||
gitlink-cli repo +create --name my-project --description "项目描述"
|
||||
|
||||
|
|
@ -87,9 +117,6 @@ gitlink-cli repo +delete --owner myuser --repo old-project
|
|||
Shortcuts 未覆盖的仓库操作可用 Raw API:
|
||||
|
||||
```bash
|
||||
# 获取提交列表
|
||||
gitlink-cli api GET /:owner/:repo/commits --query 'page=1&limit=20'
|
||||
|
||||
# 获取标签列表
|
||||
gitlink-cli api GET /:owner/:repo/tags
|
||||
|
||||
|
|
@ -101,3 +128,13 @@ gitlink-cli api GET /:owner/:repo/raw/main/README.md
|
|||
|
||||
- `repo +delete` 是不可逆操作,执行前必须确认用户意图
|
||||
- 创建仓库默认为公开,使用 `--private true` 创建私有仓库
|
||||
- `repo +delete-tag` 会删除远端标签,Agent 必须先运行 `--dry-run` 并获得用户明确确认,再加 `--yes`
|
||||
- `repo +batch-commit` 会修改仓库内容,Agent 必须先运行 `--dry-run` 并向用户展示 payload,再在用户明确确认后加 `--yes`
|
||||
- `repo +batch-commit --files` 使用 `action:path[:content]`,多个操作用英文分号分隔;`delete` 不需要 content,`create/update` 需要 content
|
||||
|
||||
## 参考文档
|
||||
|
||||
- [`references/gitlink-repo-tree.md`](references/gitlink-repo-tree.md)
|
||||
- [`references/gitlink-repo-code-history.md`](references/gitlink-repo-code-history.md)
|
||||
- [`references/gitlink-repo-tags.md`](references/gitlink-repo-tags.md)
|
||||
- [`references/gitlink-repo-batch-commit.md`](references/gitlink-repo-batch-commit.md)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
# repo +batch-commit
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
在一次提交中创建、更新或删除多个文件。该命令封装 GitLink 的 batch contents API,适合 Agent 生成文档、配置或示例文件后集中提交到分支。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 预览多文件提交
|
||||
gitlink-cli repo +batch-commit --owner someone --repo myrepo \
|
||||
--branch master \
|
||||
--message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;create:docs/demo.md:# Demo' \
|
||||
--dry-run
|
||||
|
||||
# 确认执行多文件提交
|
||||
gitlink-cli repo +batch-commit --owner someone --repo myrepo \
|
||||
--branch master \
|
||||
--message "docs: update guide" \
|
||||
--files 'update:README.md:# Updated;delete:old.md' \
|
||||
--yes
|
||||
|
||||
# 创建新分支并提交
|
||||
gitlink-cli repo +batch-commit --owner someone --repo myrepo \
|
||||
--branch master \
|
||||
--new-branch docs/update-guide \
|
||||
--message "docs: update guide" \
|
||||
--files 'create:docs/guide.md:# Guide' \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| `--branch, -b` | 是 | 目标分支 |
|
||||
| `--message, -m` | 是 | 提交信息 |
|
||||
| `--files, -f` | 是 | 文件操作列表,格式见下文 |
|
||||
| `--new-branch` | 否 | 创建并提交到新分支 |
|
||||
| `--encoding` | 否 | 内容编码,`text` 或 `base64`,默认 `text` |
|
||||
| `--author-name` | 否 | 作者名称 |
|
||||
| `--author-email` | 否 | 作者邮箱 |
|
||||
| `--committer-name` | 否 | 提交者名称 |
|
||||
| `--committer-email` | 否 | 提交者邮箱 |
|
||||
| `--dry-run` | 否 | 预览请求,不修改远端 |
|
||||
| `--yes` | 否 | 确认执行远端写入 |
|
||||
|
||||
## `--files` 格式
|
||||
|
||||
```text
|
||||
action:path[:content][;action:path[:content]...]
|
||||
```
|
||||
|
||||
支持的 `action`:
|
||||
|
||||
- `create`:创建文件,必须提供 content。
|
||||
- `update`:更新文件,必须提供 content。
|
||||
- `delete`:删除文件,不需要 content。
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
POST /v1/{owner}/{repo}/contents/batch
|
||||
```
|
||||
|
||||
## 安全规则
|
||||
|
||||
- 真实写入前必须先运行 `--dry-run`。
|
||||
- 未传 `--yes` 时命令不会调用远端写入 API。
|
||||
- Agent 必须向用户展示 dry-run 中的 `payload`,确认文件路径、操作类型、分支和提交信息无误后再执行。
|
||||
|
||||
## 参考
|
||||
|
||||
- [gitlink-repo](../SKILL.md)
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md)
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# repo code history shortcuts
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
用于搜索仓库文件、查看提交历史、查看单个提交的变更文件和 diff。适合代码审查、科研复现性分析、Release Notes 生成和项目健康度报告。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 搜索仓库文件
|
||||
gitlink-cli repo +files --owner someone --repo myrepo --search README --ref master
|
||||
|
||||
# 查看提交历史
|
||||
gitlink-cli repo +commits --owner someone --repo myrepo --ref master --page 1 --limit 20
|
||||
|
||||
# 查看单个提交变更文件
|
||||
gitlink-cli repo +commit-files --owner someone --repo myrepo --sha <commit_sha>
|
||||
|
||||
# 按文件路径筛选单个提交变更
|
||||
gitlink-cli repo +commit-files --owner someone --repo myrepo --sha <commit_sha> --file src/main.go
|
||||
|
||||
# 查看单个提交 diff
|
||||
gitlink-cli repo +commit-diff --owner someone --repo myrepo --sha <commit_sha>
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 命令 | 参数 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `repo +files` | `--search, -s` | 否 | 文件名或路径关键词 |
|
||||
| `repo +files` | `--ref, -r` | 否 | 分支、标签或 Commit SHA |
|
||||
| `repo +commits` | `--ref, -r` | 否 | 分支、标签或 Commit SHA,映射到 API `sha` |
|
||||
| `repo +commits` | `--page, -p` | 否 | 页码,默认 `1` |
|
||||
| `repo +commits` | `--limit, -l` | 否 | 每页数量,默认 `20` |
|
||||
| `repo +commit-files` | `--sha, -s` | 是 | Commit SHA |
|
||||
| `repo +commit-files` | `--file, -f` | 否 | 只查看指定文件路径的变更 |
|
||||
| `repo +commit-diff` | `--sha, -s` | 是 | Commit SHA |
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
GET /{owner}/{repo}/files
|
||||
GET /v1/{owner}/{repo}/commits
|
||||
GET /v1/{owner}/{repo}/commits/{sha}/files
|
||||
GET /v1/{owner}/{repo}/commits/{sha}/diff
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- AI Agent 场景建议始终使用 `--format json`。
|
||||
- `repo +commit-files --file` 会使用 `filepath` 查询参数;不传 `--file` 时使用分页参数。
|
||||
- `repo +commits --ref` 支持分支、标签或 Commit SHA。
|
||||
|
||||
## 参考
|
||||
|
||||
- [gitlink-repo](../SKILL.md)
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md)
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
# repo tag shortcuts
|
||||
|
||||
> **前置条件:** 先阅读 [`../../gitlink-shared/SKILL.md`](../../gitlink-shared/SKILL.md) 了解认证、全局参数和安全规则。
|
||||
|
||||
用于查看仓库标签列表、标签详情,以及在明确确认后删除远端标签。
|
||||
|
||||
## 命令
|
||||
|
||||
```bash
|
||||
# 分页列出标签
|
||||
gitlink-cli repo +tags --owner someone --repo myrepo --page 1 --limit 20
|
||||
|
||||
# 按名称搜索标签,仅返回名称
|
||||
gitlink-cli repo +tags --owner someone --repo myrepo --name v1 --only-name true
|
||||
|
||||
# 查看标签详情
|
||||
gitlink-cli repo +tag --owner someone --repo myrepo --name v1.0.0
|
||||
|
||||
# 预览删除标签
|
||||
gitlink-cli repo +delete-tag --owner someone --repo myrepo --name v1.0.0 --dry-run
|
||||
|
||||
# 确认删除标签
|
||||
gitlink-cli repo +delete-tag --owner someone --repo myrepo --name v1.0.0 --yes
|
||||
```
|
||||
|
||||
## 参数
|
||||
|
||||
| 命令 | 参数 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| `repo +tags` | `--page, -p` | 否 | 页码,默认 `1` |
|
||||
| `repo +tags` | `--limit, -l` | 否 | 每页数量,默认 `20` |
|
||||
| `repo +tags` | `--name, -n` | 否 | 标签搜索关键词 |
|
||||
| `repo +tags` | `--only-name` | 否 | 只返回标签名称,通常与 `--name` 搭配 |
|
||||
| `repo +tag` | `--name, -n` | 是 | 标签名称 |
|
||||
| `repo +delete-tag` | `--name, -n` | 是 | 标签名称 |
|
||||
| `repo +delete-tag` | `--dry-run` | 否 | 预览删除请求,不修改远端 |
|
||||
| `repo +delete-tag` | `--yes` | 否 | 确认执行删除 |
|
||||
|
||||
## API
|
||||
|
||||
```text
|
||||
GET /v1/{owner}/{repo}/tags
|
||||
GET /{owner}/{repo}/tags
|
||||
GET /v1/{owner}/{repo}/tags/{name}
|
||||
DELETE /v1/{owner}/{repo}/tags/{tag}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `repo +delete-tag` 是破坏性操作,Agent 必须先执行 `--dry-run`,展示将删除的标签,再等待用户明确确认。
|
||||
- 不传 `--name`/`--only-name` 时,`repo +tags` 使用 v1 分页标签接口;传搜索条件时使用支持名称筛选的标签接口。
|
||||
|
||||
## 参考
|
||||
|
||||
- [gitlink-repo](../SKILL.md)
|
||||
- [gitlink-shared](../../gitlink-shared/SKILL.md)
|
||||
Loading…
Reference in New Issue