Skip to content

Commit 3969d57

Browse files
committed
update git mono add command
1 parent b32b1e5 commit 3969d57

File tree

8 files changed

+287
-90
lines changed

8 files changed

+287
-90
lines changed

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ 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|batch-clone|update-org-status]
49+
├── git [update|mono|gitcode|config|url|batch-clone|update-org-status]
50+
│ └── mono [add|sync]
5051
├── agent [list|view|edit|reset] + agent profile [list|add|edit]
5152
├── task [list|init|dispatch|sync|create|delete|impl]
5253
├── script [list|run]

cmd/git/create.go

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

cmd/git/git.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ var GitCmd = &cobra.Command{
1111
1212
This includes:
1313
- update: Update multiple git repositories
14-
- create: Create a mono repo with submodules
15-
- sync: Sync submodules in a mono repo
14+
- mono: Mono repo management (add/sync submodules)
1615
- gitcode: Add Gitcode as remote
1716
- config: Configure git user for repository
1817
- url: Get repository remote URL

cmd/git/mono.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package git
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
)
6+
7+
var MonoCmd = &cobra.Command{
8+
Use: "mono",
9+
Short: "Mono repo management commands",
10+
Long: `Commands for managing mono repositories with git submodules.
11+
12+
This includes:
13+
- add: Add existing git repos in current folder as submodules
14+
- sync: Sync all submodules to latest versions`,
15+
}
16+
17+
func init() {
18+
GitCmd.AddCommand(MonoCmd)
19+
}

cmd/git/mono_add.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package git
2+
3+
import (
4+
"fmt"
5+
"spark/internal/mono"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var monoAddPath string
11+
12+
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.
17+
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+
RunE: func(cmd *cobra.Command, args []string) error {
21+
targetDir := monoAddPath
22+
if targetDir == "" {
23+
targetDir = "."
24+
}
25+
26+
repos, err := mono.FindSubRepos(targetDir)
27+
if err != nil {
28+
return fmt.Errorf("failed to scan for repos: %w", err)
29+
}
30+
31+
if len(repos) == 0 {
32+
fmt.Println("No git repositories found in the specified directory.")
33+
return nil
34+
}
35+
36+
fmt.Printf("Found %d git repository(s)\n", len(repos))
37+
fmt.Println("Adding as submodules...")
38+
39+
if err := mono.AddExistingReposAsSubmodules(targetDir, repos); err != nil {
40+
return fmt.Errorf("failed to add submodules: %w", err)
41+
}
42+
43+
fmt.Println("\nSubmodules added successfully!")
44+
fmt.Println("To commit: git commit -m \"Add submodules\"")
45+
fmt.Println("To sync later: spark git mono sync .")
46+
47+
return nil
48+
},
49+
}
50+
51+
func init() {
52+
MonoCmd.AddCommand(monoAddCmd)
53+
monoAddCmd.Flags().StringVarP(&monoAddPath, "path", "p", "", "Directory containing git repos to add as submodules (default: current directory)")
54+
}

cmd/git/sync.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,5 @@ var syncCmd = &cobra.Command{
2727
}
2828

2929
func init() {
30-
GitCmd.AddCommand(syncCmd)
30+
MonoCmd.AddCommand(syncCmd)
3131
}

internal/mono/adder.go

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package mono
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"os/exec"
7+
"path/filepath"
8+
"spark/internal/git"
9+
"strings"
10+
)
11+
12+
func AddExistingReposAsSubmodules(parentDir string, repoPaths []string) error {
13+
absParent, err := filepath.Abs(parentDir)
14+
if err != nil {
15+
return fmt.Errorf("failed to resolve parent dir: %w", err)
16+
}
17+
18+
if !git.IsGitRepository(absParent) {
19+
fmt.Println("Initializing git repository...")
20+
cmd := exec.Command("git", "init")
21+
cmd.Dir = absParent
22+
cmd.Stdout = os.Stdout
23+
cmd.Stderr = os.Stderr
24+
if err := cmd.Run(); err != nil {
25+
return fmt.Errorf("failed to init git repo: %w", err)
26+
}
27+
}
28+
29+
modulesBase := filepath.Join(absParent, ".git", "modules")
30+
if err := os.MkdirAll(modulesBase, 0755); err != nil {
31+
return fmt.Errorf("failed to create modules dir: %w", err)
32+
}
33+
34+
gitmodulesPath := filepath.Join(absParent, ".gitmodules")
35+
36+
for _, repoPath := range repoPaths {
37+
absRepo, err := filepath.Abs(repoPath)
38+
if err != nil {
39+
fmt.Printf("Warning: Could not resolve path %s: %v\n", repoPath, err)
40+
continue
41+
}
42+
43+
repoName := filepath.Base(absRepo)
44+
remoteURL, err := git.GetRemoteURL(absRepo)
45+
if err != nil {
46+
fmt.Printf("Warning: Could not get remote URL for %s: %v\n", repoName, err)
47+
continue
48+
}
49+
50+
// Skip if already a submodule (.git is a file pointing to modules)
51+
gitEntry := filepath.Join(absRepo, ".git")
52+
if info, err := os.Lstat(gitEntry); err == nil && !info.IsDir() {
53+
fmt.Printf("Skipping %s: already a submodule\n", repoName)
54+
continue
55+
}
56+
57+
fmt.Printf("Adding submodule: %s (%s)\n", repoName, remoteURL)
58+
59+
// Move .git directory to .git/modules/<name>
60+
targetModuleDir := filepath.Join(modulesBase, repoName)
61+
if _, err := os.Stat(targetModuleDir); err == nil {
62+
fmt.Printf("Warning: Module dir already exists for %s, skipping\n", repoName)
63+
continue
64+
}
65+
66+
if err := os.Rename(gitEntry, targetModuleDir); err != nil {
67+
fmt.Printf("Warning: Failed to move .git for %s: %v\n", repoName, err)
68+
continue
69+
}
70+
71+
// Create .git file pointing to modules dir
72+
relPath := filepath.Join("..", ".git", "modules", repoName)
73+
if err := os.WriteFile(gitEntry, []byte("gitdir: "+relPath+"\n"), 0644); err != nil {
74+
fmt.Printf("Warning: Failed to create .git file for %s: %v\n", repoName, err)
75+
// Try to restore
76+
os.Rename(targetModuleDir, gitEntry)
77+
continue
78+
}
79+
80+
// Update core.worktree in the module config
81+
setWorktreeCmd := exec.Command("git", "config", "--file",
82+
filepath.Join(targetModuleDir, "config"),
83+
"core.worktree", "../../../"+repoName)
84+
setWorktreeCmd.Dir = absParent
85+
if err := setWorktreeCmd.Run(); err != nil {
86+
fmt.Printf("Warning: Failed to set worktree for %s: %v\n", repoName, err)
87+
}
88+
89+
// Add entry to .gitmodules
90+
setPathCmd := exec.Command("git", "config", "--file", gitmodulesPath,
91+
fmt.Sprintf("submodule.%s.path", repoName), repoName)
92+
setPathCmd.Dir = absParent
93+
setPathCmd.Stdout = os.Stdout
94+
setPathCmd.Stderr = os.Stderr
95+
if err := setPathCmd.Run(); err != nil {
96+
fmt.Printf("Warning: Failed to set .gitmodules path for %s: %v\n", repoName, err)
97+
continue
98+
}
99+
100+
setURLCmd := exec.Command("git", "config", "--file", gitmodulesPath,
101+
fmt.Sprintf("submodule.%s.url", repoName), remoteURL)
102+
setURLCmd.Dir = absParent
103+
setURLCmd.Stdout = os.Stdout
104+
setURLCmd.Stderr = os.Stderr
105+
if err := setURLCmd.Run(); err != nil {
106+
fmt.Printf("Warning: Failed to set .gitmodules url for %s: %v\n", repoName, err)
107+
continue
108+
}
109+
110+
// Stage the submodule
111+
addCmd := exec.Command("git", "add", repoName)
112+
addCmd.Dir = absParent
113+
if err := addCmd.Run(); err != nil {
114+
fmt.Printf("Warning: Failed to stage submodule %s: %v\n", repoName, err)
115+
}
116+
}
117+
118+
// Stage .gitmodules if it exists
119+
if _, err := os.Stat(gitmodulesPath); err == nil {
120+
addModulesCmd := exec.Command("git", "add", ".gitmodules")
121+
addModulesCmd.Dir = absParent
122+
if err := addModulesCmd.Run(); err != nil {
123+
fmt.Printf("Warning: Failed to stage .gitmodules: %v\n", err)
124+
}
125+
}
126+
127+
return nil
128+
}
129+
130+
func FindSubRepos(parentDir string) ([]string, error) {
131+
absParent, err := filepath.Abs(parentDir)
132+
if err != nil {
133+
return nil, err
134+
}
135+
136+
entries, err := os.ReadDir(absParent)
137+
if err != nil {
138+
return nil, err
139+
}
140+
141+
var repos []string
142+
for _, entry := range entries {
143+
if !entry.IsDir() {
144+
continue
145+
}
146+
if strings.HasPrefix(entry.Name(), ".") {
147+
continue
148+
}
149+
dirPath := filepath.Join(absParent, entry.Name())
150+
if git.IsGitRepository(dirPath) {
151+
repos = append(repos, dirPath)
152+
}
153+
}
154+
155+
return repos, nil
156+
}

0 commit comments

Comments
 (0)