馃嵃 Personal Multi-Git Remote Manager
go
git
1package cli
2
3import (
4 "errors"
5 "fmt"
6 "os"
7 "strings"
8
9 "github.com/ebisu/mugi/internal/remote"
10)
11
12type CommandType int
13
14const (
15 CommandOperation CommandType = iota
16 CommandAdd
17 CommandRemove
18 CommandList
19)
20
21type Command struct {
22 Type CommandType
23 Operation remote.Operation
24 Repo string
25 Remotes []string
26 Path string
27 ConfigPath string
28 Verbose bool
29 Force bool
30 Linear bool
31 Help bool
32 Version bool
33}
34
35var ErrUnknownCommand = errors.New("unknown command")
36
37func Parse(args []string) (Command, error) {
38 cmd := Command{
39 Remotes: []string{remote.All},
40 }
41
42 if len(args) == 0 {
43 cmd.Help = true
44
45 return cmd, nil
46 }
47
48 args, cmd.ConfigPath = extractConfigFlag(args)
49 args, cmd.Verbose = extractVerboseFlag(args)
50 args, cmd.Force = extractForceFlag(args)
51 args, cmd.Linear = extractLinearFlag(args)
52
53 for _, arg := range args {
54 if arg == "-h" || arg == "--help" || arg == "help" {
55 cmd.Help = true
56
57 return cmd, nil
58 }
59
60 if arg == "-v" || arg == "--version" || arg == "version" {
61 cmd.Version = true
62
63 return cmd, nil
64 }
65 }
66
67 if len(args) == 0 {
68 cmd.Help = true
69
70 return cmd, nil
71 }
72
73 switch args[0] {
74 case "pull":
75 cmd.Type = CommandOperation
76 cmd.Operation = remote.Pull
77 case "push":
78 cmd.Type = CommandOperation
79 cmd.Operation = remote.Push
80 case "fetch":
81 cmd.Type = CommandOperation
82 cmd.Operation = remote.Fetch
83 case "add":
84 cmd.Type = CommandAdd
85
86 if len(args) < 2 {
87 cmd.Path = "."
88 } else {
89 cmd.Path = args[1]
90 }
91
92 return cmd, nil
93 case "rm", "remove":
94 cmd.Type = CommandRemove
95
96 if len(args) < 2 {
97 return cmd, fmt.Errorf("rm requires a repository name")
98 }
99
100 cmd.Repo = args[1]
101
102 return cmd, nil
103 case "list", "ls":
104 cmd.Type = CommandList
105
106 return cmd, nil
107 default:
108 return cmd, fmt.Errorf("%w: %s", ErrUnknownCommand, args[0])
109 }
110
111 remaining := args[1:]
112
113 if len(remaining) == 0 {
114 cmd.Repo = remote.All
115
116 return cmd, nil
117 }
118
119 cmd.Repo = remaining[0]
120 remaining = remaining[1:]
121
122 if len(remaining) > 0 {
123 cmd.Remotes = remaining
124 }
125
126 return cmd, nil
127}
128
129func Usage() string {
130 return `Mugi - Personal Multi-Git Remote Manager
131
132Usage:
133 mugi [flags] <command> [repo] [remotes...]
134
135Commands:
136 pull Pull from remote(s)
137 push Push to remote(s)
138 fetch Fetch from remote(s)
139 add <path> Add repository to config
140 rm <name> Remove repository from config
141 list List tracked repositories
142 help Show this help
143 version Show version
144
145Flags:
146 -c, --config <path> Override config file path
147 -V, --verbose Show detailed output
148 -f, --force Force push (use with caution)
149 -l, --linear Run operations sequentially
150
151Examples:
152 mugi pull Pull all repositories from all remotes
153 mugi push windmark gh cb Push to GitHub and Codeberg
154 mugi add . Add current directory to config
155 mugi add ~/Developer/mugi Add repository at path
156 mugi rm mugi Remove repository from config
157 mugi list List all tracked repositories
158
159Config: ` + configPath()
160}
161
162func configPath() string {
163 xdg := os.Getenv("XDG_CONFIG_HOME")
164
165 if xdg == "" {
166 home, _ := os.UserHomeDir()
167
168 return home + "/.config/mugi/config.yaml"
169 }
170
171 return xdg + "/mugi/config.yaml"
172}
173
174func extractConfigFlag(args []string) ([]string, string) {
175 var remaining []string
176 var configPath string
177
178 for i := 0; i < len(args); i++ {
179 arg := args[i]
180
181 if arg == "-c" || arg == "--config" {
182 if i+1 < len(args) {
183 configPath = args[i+1]
184 i++
185 }
186
187 continue
188 }
189
190 if v, ok := strings.CutPrefix(arg, "--config="); ok {
191 configPath = v
192
193 continue
194 }
195
196 if v, ok := strings.CutPrefix(arg, "-c"); ok && v != "" {
197 configPath = v
198
199 continue
200 }
201
202 remaining = append(remaining, arg)
203 }
204
205 return remaining, configPath
206}
207
208func extractVerboseFlag(args []string) ([]string, bool) {
209 var remaining []string
210 var verbose bool
211
212 for _, arg := range args {
213 if arg == "-V" || arg == "--verbose" {
214 verbose = true
215
216 continue
217 }
218
219 remaining = append(remaining, arg)
220 }
221
222 return remaining, verbose
223}
224
225func extractForceFlag(args []string) ([]string, bool) {
226 var remaining []string
227 var force bool
228
229 for _, arg := range args {
230 if arg == "-f" || arg == "--force" {
231 force = true
232
233 continue
234 }
235
236 remaining = append(remaining, arg)
237 }
238
239 return remaining, force
240}
241
242func extractLinearFlag(args []string) ([]string, bool) {
243 var remaining []string
244 var linear bool
245
246 for _, arg := range args {
247 if arg == "-l" || arg == "--linear" {
248 linear = true
249
250 continue
251 }
252
253 remaining = append(remaining, arg)
254 }
255
256 return remaining, linear
257}