-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
205 lines (181 loc) · 4.22 KB
/
main.go
File metadata and controls
205 lines (181 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
package main
import (
"fmt"
"os"
"path/filepath"
"time"
"gee/cmd"
"gee/pkg/tui"
"gee/pkg/util"
tea "github.com/charmbracelet/bubbletea"
"github.com/pelletier/go-toml"
"github.com/urfave/cli/v2"
)
var (
app *cli.App
version = "dev"
)
func main() {
app.Flags = []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Usage: "Enable verbose logging",
},
&cli.BoolFlag{
Name: "init",
Usage: "Print shell integration function to stdout",
},
}
app.Before = func(c *cli.Context) error {
verbose := c.Bool("verbose")
util.SetVerbose(verbose)
if verbose {
util.VerboseLog("Verbose logging enabled")
}
return nil
}
app.Commands = []*cli.Command{
cmd.AddCmd(),
cmd.PullCmd(),
cmd.StatusCmd(),
cmd.RemoveCmd(),
cmd.ExecCmd(),
}
// No subcommand → launch interactive TUI (or handle --init)
app.Action = func(c *cli.Context) error {
if c.Bool("init") {
fmt.Print(shellInitScript())
return nil
}
cache := util.NewRepoCache()
if _, err := cache.Load(); err != nil {
return err
}
// One-time migration: import repos from gee.toml if cache is empty.
if len(cache.All()) == 0 {
migrateFromGeeToml(cache)
}
model := tui.NewAppModel(cache)
p := tea.NewProgram(model, tea.WithAltScreen(), tea.WithOutput(os.Stderr))
finalModel, err := p.Run()
if err != nil {
return err
}
// Teleport: if the user pressed Enter on a repo, print the path to stdout.
// The shell wrapper function (from gee --init) will cd into it.
if m, ok := finalModel.(tui.AppModel); ok && m.SelectedPath != "" {
fmt.Fprintln(os.Stdout, m.SelectedPath)
if os.Getenv("GEE_TELEPORT") != "1" {
fmt.Fprintln(os.Stderr, "")
fmt.Fprintln(os.Stderr, "Tip: add this to your shell config to enable teleport (cd on Enter):")
fmt.Fprintln(os.Stderr, " eval \"$(gee --init)\"")
}
}
return nil
}
// Run the CLI app
err := app.Run(os.Args)
if err != nil {
switch err.(type) {
case *util.InfoError:
util.Info("%s", err.Error())
case *util.WarningError:
util.Warning("%s", err.Error())
default:
util.CheckIfError(err)
}
}
}
func init() {
// Initialise a CLI app
app = cli.NewApp()
app.Name = "gee"
app.Usage = "Manage git repos from one place"
app.Version = version
}
// shellInitScript returns the shell function wrapper for the user's shell.
// Users add `eval "$(gee --init)"` to their .zshrc/.bashrc.
func shellInitScript() string {
shell := filepath.Base(os.Getenv("SHELL"))
switch shell {
case "fish":
return fishInit()
default:
return bashZshInit()
}
}
func bashZshInit() string {
return `gee() {
if [ $# -eq 0 ]; then
local result
result="$(GEE_TELEPORT=1 command gee)"
if [ -n "$result" ] && [ -d "$result" ]; then
cd "$result" || return
fi
else
command gee "$@"
fi
}
`
}
func fishInit() string {
return `function gee
if test (count $argv) -eq 0
set -l result (env GEE_TELEPORT=1 command gee)
if test -n "$result" -a -d "$result"
cd $result
end
else
command gee $argv
end
end
`
}
// migrateFromGeeToml attempts a one-time import of repos from a gee.toml file.
// It searches from cwd upward. On success, each repo is added as pinned.
func migrateFromGeeToml(cache *util.RepoCache) {
cwd, err := os.Getwd()
if err != nil {
return
}
configHelper := util.NewConfigHelper()
geeTomlPath, err := configHelper.FindConfig(cwd, "gee.toml")
if err != nil {
return
}
tree, err := toml.LoadFile(geeTomlPath)
if err != nil {
return
}
type tomlConfig struct {
Repos []struct {
Name string `toml:"name"`
Path string `toml:"path"`
Remote string `toml:"remote"`
} `toml:"repos"`
}
var cfg tomlConfig
if err := tree.Unmarshal(&cfg); err != nil {
return
}
imported := 0
for _, r := range cfg.Repos {
fullPath := filepath.Join(r.Path, r.Name)
// Verify the repo still exists on disk.
if info, err := os.Stat(filepath.Join(fullPath, ".git")); err != nil || !info.IsDir() {
continue
}
cache.Add(util.CachedRepo{
Name: r.Name,
Path: fullPath,
Remote: r.Remote,
Pinned: true,
DiscoveredAt: time.Now(),
})
imported++
}
if imported > 0 {
cache.Save()
fmt.Fprintf(os.Stderr, "Migrated %d repos from gee.toml → cache.json\n", imported)
}
}