Skip to content

Commit 8e9c2eb

Browse files
committed
add spark mono git supporting git url
1 parent 761bc93 commit 8e9c2eb

7 files changed

Lines changed: 296 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,16 +111,25 @@ spark git update -p ~/workspace -p ~/projects
111111
详细文档: [docs/usage/update.md](docs/usage/update.md)
112112

113113
#### `spark git mono add`
114-
将当前目录下的 Git 仓库添加为子模块,无需重新克隆
114+
将本地 Git 仓库添加为子模块,或克隆远程仓库并添加为子模块
115115

116+
**本地模式**
116117
```bash
117118
spark git mono add # 添加当前目录下的仓库
118119
spark git mono add -p /path/to/repos # 添加指定目录下的仓库
119120
```
120121

122+
**远程模式**
123+
```bash
124+
spark git mono add https://github.com/user/repo # 添加远程仓库
125+
spark git mono add https://github.com/user/repo --name my-submodule # 指定路径名
126+
spark git mono add git@github.com:user/repo.git # 使用 SSH URL
127+
```
128+
121129
| 选项 | 说明 |
122130
|------|------|
123-
| `-p, --path` | 包含 Git 仓库的目录 (默认: 当前目录) |
131+
| `-p, --path` | Mono-repo 目录 (默认: 当前目录) |
132+
| `-n, --name` | 子模块路径名称 (默认: 仓库名) |
124133

125134
#### `spark git mono sync`
126135
同步 Mono 仓库中的所有子模块到最新版本。

cmd/git/mono_add.go

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,74 @@ package git
33
import (
44
"fmt"
55
"spark/internal/mono"
6+
"strings"
67

78
"github.com/spf13/cobra"
89
)
910

1011
var monoAddPath string
12+
var monoAddURL string
13+
var monoAddName string
1114

1215
var monoAddCmd = &cobra.Command{
13-
Use: "add [flags]",
14-
Short: "Add existing git repos in current folder as submodules",
15-
Long: `Add existing git repositories in the current folder as git submodules
16-
without re-cloning them. This converts the current folder into a mono repo.
16+
Use: "add [flags] [repo-url]",
17+
Short: "Add git repos as submodules",
18+
Long: `Add git repositories as submodules to the current mono repo.
1719
18-
The command scans for git repositories in the specified directory and adds
19-
each one as a submodule, preserving the existing folder structure and content.`,
20+
Supports two modes:
21+
22+
1. Add existing local repos as submodules (default):
23+
Scans the specified directory for git repositories and adds them as submodules
24+
without re-cloning. This preserves the existing folder structure.
25+
26+
2. Add a remote repository as a submodule:
27+
Clones a remote git repository and adds it as a submodule using git submodule add.
28+
The submodule path defaults to the repository name, or can be specified with --name.
29+
30+
Examples:
31+
# Add all local git repos in current directory as submodules
32+
spark git mono add
33+
34+
# Add all local git repos from a specific directory
35+
spark git mono add -p /path/to/repos
36+
37+
# Add a remote repository as submodule
38+
spark git mono add https://github.com/user/repo
39+
40+
# Add a remote repository with custom path
41+
spark git mono add https://github.com/user/repo --name my-folder`,
2042
RunE: func(cmd *cobra.Command, args []string) error {
43+
// Check if a remote URL is provided as argument or flag
44+
var repoURL string
45+
if len(args) > 0 {
46+
repoURL = args[0]
47+
} else if monoAddURL != "" {
48+
repoURL = monoAddURL
49+
}
50+
51+
// Remote URL mode
52+
if repoURL != "" {
53+
// Validate it looks like a git URL
54+
if !isValidGitURL(repoURL) {
55+
return fmt.Errorf("invalid git URL: %s", repoURL)
56+
}
57+
58+
targetDir := monoAddPath
59+
if targetDir == "" {
60+
targetDir = "."
61+
}
62+
63+
if err := mono.AddRemoteRepoAsSubmodule(targetDir, repoURL, monoAddName); err != nil {
64+
return fmt.Errorf("failed to add remote submodule: %w", err)
65+
}
66+
67+
fmt.Println("\nSubmodule added successfully!")
68+
fmt.Println("To commit: git commit -m \"Add submodule\"")
69+
fmt.Println("To sync later: spark git mono sync .")
70+
return nil
71+
}
72+
73+
// Local directory mode (existing behavior)
2174
targetDir := monoAddPath
2275
if targetDir == "" {
2376
targetDir = "."
@@ -48,7 +101,31 @@ each one as a submodule, preserving the existing folder structure and content.`,
48101
},
49102
}
50103

104+
// isValidGitURL checks if the string looks like a valid git URL
105+
func isValidGitURL(url string) bool {
106+
// Check for common git URL patterns
107+
if strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://") {
108+
return true
109+
}
110+
if strings.HasPrefix(url, "git@") {
111+
return true
112+
}
113+
if strings.HasPrefix(url, "git://") {
114+
return true
115+
}
116+
if strings.HasPrefix(url, "ssh://") {
117+
return true
118+
}
119+
// Allow simple user/repo format for GitHub
120+
if strings.Count(url, "/") == 1 && !strings.Contains(url, " ") {
121+
return true
122+
}
123+
return false
124+
}
125+
51126
func init() {
52127
MonoCmd.AddCommand(monoAddCmd)
53128
monoAddCmd.Flags().StringVarP(&monoAddPath, "path", "p", "", "Directory containing git repos to add as submodules (default: current directory)")
129+
monoAddCmd.Flags().StringVarP(&monoAddURL, "url", "u", "", "Remote git repository URL to add as submodule")
130+
monoAddCmd.Flags().StringVarP(&monoAddName, "name", "n", "", "Custom name/path for the submodule (default: repo name from URL)")
54131
}

docs/features/git.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,13 @@ spark git update -p ~/workspace
1919
将多个独立仓库作为 Submodule 合并为一个 Mono-repo,方便统一管理和版本控制。
2020

2121
```bash
22-
# 添加现有仓库为子模块
22+
# 添加现有本地仓库为子模块
2323
spark git mono add -p /path/to/repos
2424

25+
# 添加远程仓库为子模块
26+
spark git mono add https://github.com/user/repo
27+
spark git mono add https://github.com/user/repo --name custom-folder
28+
2529
# 同步所有 Submodule 到最新
2630
spark git mono sync ./my-mono
2731
```
@@ -55,6 +59,7 @@ spark git update-org-status variableway --update-dot-github
5559
|------|------|
5660
| `-p, --path` | 指定扫描目录(支持多个),默认 `["."]` |
5761
| `-p, --path` | 包含 Git 仓库的目录,默认 `.` |
62+
| `-n, --name` | 子模块路径名称(远程模式),默认仓库名 |
5863
| `-o, --output` | 输出路径 |
5964
| `--ssh` | 使用 SSH 克隆(batch-clone) |
6065
| `--include` / `--exclude` | 包含/排除匹配模式(batch-clone) |

docs/spec/git.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ spark git update [-p <path>]
3030

3131
## spark git mono add
3232

33-
将目录下已有的 Git 仓库添加为子模块,无需重新克隆。
33+
添加 Git 仓库为子模块。支持两种模式:
34+
35+
1. 本地模式:将目录下已有的 Git 仓库添加为子模块,无需重新克隆
36+
2. 远程模式:将远程 Git 仓库克隆并添加为子模块
37+
38+
### 本地模式
3439

3540
```
3641
spark git mono add [-p <path>]
@@ -42,6 +47,21 @@ spark git mono add [-p <path>]
4247

4348
无参数。
4449

50+
### 远程模式
51+
52+
```
53+
spark git mono add <repo-url> [-n <name>] [-p <path>]
54+
```
55+
56+
| 参数 | 类型 | 必填 | 说明 |
57+
|------|------|------|------|
58+
| `repo-url` | string || 远程仓库 URL(HTTPS 或 SSH) |
59+
60+
| 标志 | 类型 | 默认值 | 必填 | 说明 |
61+
|------|------|--------|------|------|
62+
| `-n, --name` | string | 仓库名 || 子模块路径名称 |
63+
| `-p, --path` | string | `.` || Mono-repo 目录路径 |
64+
4565
---
4666

4767
## spark git mono sync

docs/usage/git.md

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
```bash
88
spark git update # 更新所有仓库
99
spark git mono add [-p <path>] # 添加现有仓库为子模块
10+
spark git mono add <repo-url> [-n <name>] # 添加远程仓库为子模块
1011
spark git mono sync <mono-path> # 同步子模块
1112
spark git gitcode # 添加 Gitcode 远程
1213
spark git config [--username --email] # 配置 Git 用户
@@ -33,7 +34,11 @@ spark git update -p ~/ws -p ~/projects # 多个目录
3334

3435
## spark git mono add
3536

36-
将当前目录下已有的 Git 仓库添加为子模块,无需重新克隆。适用于研究场景:先将多个仓库克隆到同一目录,再统一转为 Mono 仓库。
37+
将本地 Git 仓库添加为子模块,或将远程仓库克隆为子模块。
38+
39+
### 模式 1:添加本地仓库为子模块
40+
41+
扫描指定目录中的 Git 仓库,将它们添加为子模块,无需重新克隆。
3742

3843
| 标志 | 简写 | 默认值 | 说明 |
3944
|------|------|--------|------|
@@ -44,6 +49,29 @@ spark git mono add # 添加当前目录下的仓库
4449
spark git mono add -p /path/to/repos # 添加指定目录下的仓库
4550
```
4651

52+
### 模式 2:添加远程仓库为子模块
53+
54+
克隆远程 Git 仓库并将其添加为子模块。
55+
56+
| 标志 | 简写 | 默认值 | 说明 |
57+
|------|------|--------|------|
58+
| `--path` | `-p` | `.` | Mono-repo 目录(默认当前目录) |
59+
| `--name` | `-n` | 仓库名 | 子模块路径名称 |
60+
61+
```bash
62+
# 添加远程仓库(使用默认路径名)
63+
spark git mono add https://github.com/user/repo
64+
65+
# 添加远程仓库并指定路径名
66+
spark git mono add https://github.com/user/repo --name my-submodule
67+
68+
# 使用 SSH URL
69+
spark git mono add git@github.com:user/repo.git
70+
71+
# 使用简写格式(GitHub)
72+
spark git mono add user/repo
73+
```
74+
4775
---
4876

4977
## spark git mono sync
@@ -129,7 +157,7 @@ spark git batch-clone variableway -o ./repos # 指定输出目录
129157
|------|------|--------|------|
130158
| `--output` | `-o` | `.github/README.md` | 输出文件路径 |
131159
| `--dry-run` | | `false` | 预览不写入 |
132-
| `--update-dot-github` | | `false` | 直接更新 .github 仓库 |
160+
| `--update-dot-github` | | `false` | 直接更新组织的 .github 仓库 |
133161
| `--section` | | `Project List` | 更新的 section 名称 |
134162
| `--skip-push` | | `false` | 跳过 git push |
135163

internal/mono/adder.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"os/exec"
77
"path/filepath"
8+
"regexp"
89
"spark/internal/git"
910
"strings"
1011
)
@@ -154,3 +155,98 @@ func FindSubRepos(parentDir string) ([]string, error) {
154155

155156
return repos, nil
156157
}
158+
159+
// ExtractRepoName extracts repository name from a git URL
160+
func ExtractRepoName(repoURL string) string {
161+
// Remove trailing .git if present
162+
repoURL = strings.TrimSuffix(repoURL, ".git")
163+
164+
// Handle different URL formats
165+
// HTTPS: https://github.com/user/repo
166+
// SSH: git@github.com:user/repo
167+
// Simple: user/repo
168+
169+
// Try SSH format first
170+
sshPattern := regexp.MustCompile(`git@[^:]+:([^/]+/[^/]+)$`)
171+
if matches := sshPattern.FindStringSubmatch(repoURL); len(matches) > 1 {
172+
parts := strings.Split(matches[1], "/")
173+
return parts[len(parts)-1]
174+
}
175+
176+
// Try HTTPS format
177+
if strings.Contains(repoURL, "://") {
178+
parts := strings.Split(repoURL, "/")
179+
if len(parts) > 0 {
180+
return parts[len(parts)-1]
181+
}
182+
}
183+
184+
// Simple format: user/repo or just repo
185+
parts := strings.Split(repoURL, "/")
186+
return parts[len(parts)-1]
187+
}
188+
189+
// AddRemoteRepoAsSubmodule adds a remote git repository as a submodule
190+
func AddRemoteRepoAsSubmodule(parentDir string, repoURL string, submodulePath string) error {
191+
absParent, err := filepath.Abs(parentDir)
192+
if err != nil {
193+
return fmt.Errorf("failed to resolve parent dir: %w", err)
194+
}
195+
196+
// Determine submodule name/path
197+
if submodulePath == "" {
198+
submodulePath = ExtractRepoName(repoURL)
199+
}
200+
201+
// Remove trailing .git for the path name
202+
submodulePath = strings.TrimSuffix(submodulePath, ".git")
203+
204+
// Check if destination already exists
205+
targetPath := filepath.Join(absParent, submodulePath)
206+
if _, err := os.Stat(targetPath); err == nil {
207+
return fmt.Errorf("destination path already exists: %s", submodulePath)
208+
}
209+
210+
// Initialize git repo if not exists
211+
if !git.IsGitRepository(absParent) {
212+
fmt.Println("Initializing git repository...")
213+
cmd := exec.Command("git", "init")
214+
cmd.Dir = absParent
215+
cmd.Stdout = os.Stdout
216+
cmd.Stderr = os.Stderr
217+
if err := cmd.Run(); err != nil {
218+
return fmt.Errorf("failed to init git repo: %w", err)
219+
}
220+
}
221+
222+
// Check if already a submodule
223+
gitmodulesPath := filepath.Join(absParent, ".gitmodules")
224+
if _, err := os.Stat(gitmodulesPath); err == nil {
225+
// Check if submodule already exists
226+
checkCmd := exec.Command("git", "config", "--file", gitmodulesPath, fmt.Sprintf("submodule.%s.path", submodulePath))
227+
checkCmd.Dir = absParent
228+
if err := checkCmd.Run(); err == nil {
229+
return fmt.Errorf("submodule '%s' already exists", submodulePath)
230+
}
231+
}
232+
233+
fmt.Printf("Adding submodule: %s (%s)\n", submodulePath, repoURL)
234+
235+
// Add submodule using git submodule add
236+
cmd := exec.Command("git", "submodule", "add", "-f", repoURL, submodulePath)
237+
cmd.Dir = absParent
238+
cmd.Stdout = os.Stdout
239+
cmd.Stderr = os.Stderr
240+
if err := cmd.Run(); err != nil {
241+
return fmt.Errorf("failed to add submodule: %w", err)
242+
}
243+
244+
// Stage .gitmodules
245+
addModulesCmd := exec.Command("git", "add", ".gitmodules")
246+
addModulesCmd.Dir = absParent
247+
if err := addModulesCmd.Run(); err != nil {
248+
fmt.Printf("Warning: Failed to stage .gitmodules: %v\n", err)
249+
}
250+
251+
return nil
252+
}

0 commit comments

Comments
 (0)