Skip to content

Commit 3afa499

Browse files
committed
update git clone-org command to batch-clone command
1 parent 13c3861 commit 3afa499

17 files changed

Lines changed: 345 additions & 182 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ spark git sync # 同步子模块
9696
spark git gitcode # 添加 Gitcode 远程
9797
spark git config # 配置 Git 用户
9898
spark git url # 获取仓库 URL
99-
spark git clone-org # 克隆组织所有仓库
99+
spark git batch-clone # 克隆用户/组织所有仓库
100100
```
101101

102102
#### `spark git update`

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ Spark is a Go CLI tool (`module spark`, binary `spark`) for managing multiple Gi
4646

4747
```
4848
spark
49-
├── git [update|create|sync|gitcode|config|url|clone-org|update-org-status]
49+
├── git [update|create|sync|gitcode|config|url|batch-clone|update-org-status]
5050
├── agent [list|view|edit|reset] + agent profile [list|add|edit]
5151
├── task [list|init|dispatch|sync|create|delete|impl]
5252
├── script [list|run]

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ go test ./internal/git/... -v -run TestFunctionName
7979
| `spark git gitcode [-p <path>]` | Add Gitcode remote to repos |
8080
| `spark git config [--username --email]` | Configure git user for repo |
8181
| `spark git url [repo-path]` | Get remote URL of repository |
82-
| `spark git clone-org <org> [--ssh] [--include] [--exclude] [-o <dir>]` | Clone all repos from GitHub org |
82+
| `spark git batch-clone <account> [--ssh] [--include] [--exclude] [-o <dir>]` | Clone all repos from GitHub org/user |
8383
| `spark git update-org-status <org> [--dry-run] [--update-dot-github] [--section <name>]` | Update org README with repo list |
8484

8585
---

cmd/git/batch_clone.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package git
2+
3+
import (
4+
"fmt"
5+
"spark/internal/github"
6+
"os"
7+
"os/exec"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var (
14+
batchCloneUseSSH bool
15+
batchCloneInclude string
16+
batchCloneExclude string
17+
batchCloneIncludeFork bool
18+
batchCloneOutput string
19+
)
20+
21+
var batchCloneCmd = &cobra.Command{
22+
Use: "batch-clone <account-name-or-url>",
23+
Short: "Clone all repositories from a GitHub organization or user",
24+
Long: `Clone all repositories from a GitHub organization or user account.
25+
26+
This command will:
27+
1. Detect whether the account is an organization or a user
28+
2. Fetch all public repositories from the specified account
29+
3. Clone each repository to the current directory (or specified output directory)
30+
31+
You can provide either the account name (e.g., "jackwener" or "variableway")
32+
or the full GitHub URL (e.g., "https://github.com/jackwener").`,
33+
Args: cobra.ExactArgs(1),
34+
RunE: func(cmd *cobra.Command, args []string) error {
35+
input := args[0]
36+
37+
accountName, err := github.ParseAccountFromURL(input)
38+
if err != nil {
39+
return err
40+
}
41+
42+
outputDir := batchCloneOutput
43+
if outputDir == "" {
44+
outputDir = "."
45+
}
46+
47+
fmt.Printf("Detecting account type for: %s\n", accountName)
48+
49+
repos, accountType, err := github.GetReposForAccount(accountName)
50+
if err != nil {
51+
return err
52+
}
53+
54+
accountTypeLabel := "organization"
55+
if accountType == github.AccountTypeUser {
56+
accountTypeLabel = "user"
57+
}
58+
fmt.Printf("Found %d repositories for %s: %s\n\n", len(repos), accountTypeLabel, accountName)
59+
60+
var reposToClone []github.Repository
61+
for _, repo := range repos {
62+
if !batchCloneIncludeFork && repo.Fork {
63+
continue
64+
}
65+
66+
if batchCloneExclude != "" && matchesPattern(repo.Name, batchCloneExclude) {
67+
continue
68+
}
69+
70+
if batchCloneInclude != "" && !matchesPattern(repo.Name, batchCloneInclude) {
71+
continue
72+
}
73+
74+
reposToClone = append(reposToClone, repo)
75+
}
76+
77+
fmt.Printf("Cloning %d repositories...\n\n", len(reposToClone))
78+
79+
successCount := 0
80+
skipCount := 0
81+
failCount := 0
82+
83+
for i, repo := range reposToClone {
84+
fmt.Printf("[%d/%d] ", i+1, len(reposToClone))
85+
86+
repoPath := fmt.Sprintf("%s/%s", outputDir, repo.Name)
87+
if _, err := os.Stat(repoPath); !os.IsNotExist(err) {
88+
fmt.Printf("Skipping %s (already exists)\n", repo.Name)
89+
skipCount++
90+
continue
91+
}
92+
93+
var cloneURL string
94+
if batchCloneUseSSH {
95+
cloneURL = repo.SSHURL
96+
} else {
97+
cloneURL = repo.CloneURL
98+
}
99+
100+
fmt.Printf("Cloning %s...\n", repo.Name)
101+
102+
cloneCmd := exec.Command("git", "clone", cloneURL, repoPath)
103+
cloneCmd.Stdout = os.Stdout
104+
cloneCmd.Stderr = os.Stderr
105+
106+
if err := cloneCmd.Run(); err != nil {
107+
fmt.Printf(" Error: failed to clone %s: %v\n", repo.Name, err)
108+
failCount++
109+
} else {
110+
fmt.Printf(" Successfully cloned %s\n", repo.Name)
111+
successCount++
112+
}
113+
}
114+
115+
fmt.Printf("\n--- Summary ---\n")
116+
fmt.Printf("Cloned: %d\n", successCount)
117+
fmt.Printf("Skipped: %d\n", skipCount)
118+
fmt.Printf("Failed: %d\n", failCount)
119+
120+
return nil
121+
},
122+
}
123+
124+
func matchesPattern(name, pattern string) bool {
125+
if pattern == "" {
126+
return true
127+
}
128+
129+
patterns := strings.Split(pattern, ",")
130+
for _, p := range patterns {
131+
p = strings.TrimSpace(p)
132+
if p != "" && strings.Contains(name, p) {
133+
return true
134+
}
135+
}
136+
return false
137+
}
138+
139+
func init() {
140+
GitCmd.AddCommand(batchCloneCmd)
141+
142+
batchCloneCmd.Flags().BoolVar(&batchCloneUseSSH, "ssh", false, "Use SSH URLs instead of HTTPS")
143+
batchCloneCmd.Flags().StringVar(&batchCloneInclude, "include", "", "Include only repos matching pattern (comma-separated)")
144+
batchCloneCmd.Flags().StringVar(&batchCloneExclude, "exclude", "", "Exclude repos matching pattern (comma-separated)")
145+
batchCloneCmd.Flags().BoolVar(&batchCloneIncludeFork, "include-forks", false, "Include forked repositories")
146+
batchCloneCmd.Flags().StringVarP(&batchCloneOutput, "output", "o", ".", "Output directory for cloned repositories")
147+
}

cmd/git/clone_org.go

Lines changed: 0 additions & 142 deletions
This file was deleted.

cmd/git/git.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ This includes:
1616
- gitcode: Add Gitcode as remote
1717
- config: Configure git user for repository
1818
- url: Get repository remote URL
19-
- clone-org: Clone all repos from a GitHub organization`,
19+
- batch-clone: Clone all repos from a GitHub organization or user`,
2020
}
2121

2222
func init() {

cmd/git/update_org_status.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ This command will:
4040
RunE: func(cmd *cobra.Command, args []string) error {
4141
input := args[0]
4242

43-
orgName, err := github.ParseOrgFromURL(input)
43+
orgName, err := github.ParseAccountFromURL(input)
4444
if err != nil {
4545
return err
4646
}

docs/Agents.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ spark git sync # 同步子模块
9696
spark git gitcode # 添加 Gitcode 远程
9797
spark git config # 配置 Git 用户
9898
spark git url # 获取仓库 URL
99-
spark git clone-org # 克隆组织所有仓库
99+
spark git batch-clone # 克隆用户/组织所有仓库
100100
```
101101

102102
#### `spark git update`

docs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ go test ./internal/git/... -v -run TestFunctionName
7979
| `spark git gitcode [-p <path>]` | Add Gitcode remote to repos |
8080
| `spark git config [--username --email]` | Configure git user for repo |
8181
| `spark git url [repo-path]` | Get remote URL of repository |
82-
| `spark git clone-org <org> [--ssh] [--include] [--exclude] [-o <dir>]` | Clone all repos from GitHub org |
82+
| `spark git batch-clone <account> [--ssh] [--include] [--exclude] [-o <dir>]` | Clone all repos from GitHub org/user |
8383
| `spark git update-org-status <org> [--dry-run] [--update-dot-github] [--section <name>]` | Update org README with repo list |
8484

8585
---

docs/features/git.md

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,16 @@ spark git sync ./my-mono
3434
spark git gitcode -p ~/workspace
3535
```
3636

37-
### 组织仓库管理
37+
### 批量克隆
3838

39-
克隆 GitHub 组织下所有仓库,或更新组织 README 中的仓库状态列表。
39+
克隆 GitHub 组织或用户下所有仓库,或更新 README 中的仓库状态列表。
4040

4141
```bash
4242
# 克隆组织仓库
43-
spark git clone-org variableway -o ./repos
43+
spark git batch-clone variableway -o ./repos
44+
45+
# 克隆用户仓库
46+
spark git batch-clone jackwener -o ./repos
4447

4548
# 更新组织状态
4649
spark git update-org-status variableway --update-dot-github
@@ -53,13 +56,13 @@ spark git update-org-status variableway --update-dot-github
5356
| `-p, --path` | 指定扫描目录(支持多个),默认 `["."]` |
5457
| `-n, --name` | Mono-repo 名称,默认 `mono-repo` |
5558
| `-o, --output` | 输出路径 |
56-
| `--ssh` | 使用 SSH 克隆(clone-org|
57-
| `--include` / `--exclude` | 包含/排除匹配模式(clone-org|
59+
| `--ssh` | 使用 SSH 克隆(batch-clone|
60+
| `--include` / `--exclude` | 包含/排除匹配模式(batch-clone|
5861

5962
## 依赖
6063

6164
- `git` 命令行工具
62-
- `gh` CLI(clone-org、update-org-status 需要 GitHub API 访问)
65+
- `gh` CLI(batch-clone、update-org-status 需要 GitHub API 访问)
6366

6467
## 相关文档
6568

0 commit comments

Comments
 (0)