feat(client): lodge remote binds a repository to another server
This commit is contained in:
1 parent
45c81cfcaf
commit
91d83f3032
10 files changed
+639
-147
No files matched your search
+109
-38
@@ -9,6 +9,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
@@ -20,9 +21,11 @@ import (
|
||||
const usage = `lodge keeps agent files out of git and in sync across machines
|
||||
|
||||
lodge login <url> <code> connect this machine using a code from the admin page
|
||||
lodge remote <url> [code] sync the current repository with another server
|
||||
lodge remote [--default] show that server, or go back to the default one
|
||||
lodge install start the daemon, install the git hook and the agent skill
|
||||
lodge uninstall undo install
|
||||
lodge update replace this binary with the one the server ships
|
||||
lodge update replace this binary with the one the default server ships
|
||||
lodge add [dir] start syncing a repository that was cloned before install
|
||||
lodge forget [dir] stop syncing a repository
|
||||
lodge sync [dir] sync once, for machines without a daemon
|
||||
@@ -31,7 +34,7 @@ const usage = `lodge keeps agent files out of git and in sync across machines
|
||||
lodge show <rev|sha> <path> a file as of a revision or a commit of the main repository
|
||||
lodge daemon run the sync daemon in the foreground
|
||||
|
||||
status, log and sync accept --json
|
||||
status, log, sync and remote accept --json
|
||||
`
|
||||
|
||||
var hookEnvironment = []string{"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GIT_PREFIX"}
|
||||
@@ -39,12 +42,18 @@ var hookEnvironment = []string{"GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE", "GI
|
||||
type status struct {
|
||||
Root string `json:"root"`
|
||||
Key string `json:"key"`
|
||||
Server string `json:"server"`
|
||||
Branch string `json:"branch"`
|
||||
Rules []client.Rule `json:"rules"`
|
||||
Revision int64 `json:"revision"`
|
||||
Spaces map[string]protocol.Manifest `json:"spaces"`
|
||||
}
|
||||
|
||||
type remoteStatus struct {
|
||||
URL string `json:"url"`
|
||||
Default bool `json:"default"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
fmt.Print(usage)
|
||||
@@ -67,14 +76,15 @@ func hook(ctx context.Context) {
|
||||
for _, name := range hookEnvironment {
|
||||
os.Unsetenv(name)
|
||||
}
|
||||
if c, err := client.Load(); err == nil {
|
||||
_ = c.Hook(ctx, ".")
|
||||
if cfg, err := client.Load(); err == nil {
|
||||
_ = cfg.Hook(ctx, ".")
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context, name string, args []string) error {
|
||||
flags := flag.NewFlagSet(name, flag.ExitOnError)
|
||||
asJSON := flags.Bool("json", false, "machine-readable output")
|
||||
toDefault := flags.Bool("default", false, "go back to the default server")
|
||||
_ = flags.Parse(args)
|
||||
args = flags.Args()
|
||||
target := func(fallback string) string {
|
||||
@@ -84,45 +94,36 @@ func run(ctx context.Context, name string, args []string) error {
|
||||
return fallback
|
||||
}
|
||||
|
||||
if slices.Contains([]string{"help", "-h", "--help"}, name) {
|
||||
fmt.Print(usage)
|
||||
return nil
|
||||
}
|
||||
cfg, err := client.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch name {
|
||||
case "login":
|
||||
if len(args) != 2 {
|
||||
return errors.New("usage: lodge login <url> <code>")
|
||||
}
|
||||
return client.Login(ctx, args[0], args[1])
|
||||
case "help", "-h", "--help":
|
||||
fmt.Print(usage)
|
||||
return nil
|
||||
}
|
||||
|
||||
c, err := client.Load()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch name {
|
||||
return cfg.Login(ctx, args[0], args[1])
|
||||
case "remote":
|
||||
return remote(ctx, cfg, args, *toDefault, *asJSON)
|
||||
case "install":
|
||||
return c.Install()
|
||||
return cfg.Install()
|
||||
case "uninstall":
|
||||
return c.Uninstall()
|
||||
return cfg.Uninstall()
|
||||
case "daemon":
|
||||
return c.Daemon(ctx)
|
||||
return cfg.Daemon(ctx)
|
||||
case "update":
|
||||
updated, err := c.Update(ctx)
|
||||
updated, err := cfg.Update(ctx)
|
||||
if err == nil && !updated {
|
||||
fmt.Println("already up to date")
|
||||
}
|
||||
return err
|
||||
case "add", "sync":
|
||||
res, err := c.Sync(ctx, target("."), true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return output(*asJSON, res, func() {
|
||||
fmt.Printf("pushed %d, pulled %d\n", res.Pushed, res.Pulled)
|
||||
for _, path := range res.Skipped {
|
||||
fmt.Printf("skipped, over 5 MB: %s\n", path)
|
||||
}
|
||||
})
|
||||
return syncRepo(ctx, cfg, target("."), *asJSON)
|
||||
case "forget":
|
||||
root, err := filepath.Abs(target("."))
|
||||
if err != nil {
|
||||
@@ -131,16 +132,16 @@ func run(ctx context.Context, name string, args []string) error {
|
||||
if repo, err := gitrepo.Open(root); err == nil {
|
||||
root = repo.Root
|
||||
}
|
||||
return c.Forget(root)
|
||||
return cfg.Forget(root)
|
||||
case "status":
|
||||
return showStatus(ctx, c, target("."), *asJSON)
|
||||
return showStatus(ctx, cfg, target("."), *asJSON)
|
||||
case "log":
|
||||
return showLog(ctx, c, target(""), *asJSON)
|
||||
return showLog(ctx, cfg, target(""), *asJSON)
|
||||
case "show":
|
||||
if len(args) != 2 {
|
||||
return errors.New("usage: lodge show <revision|commit-sha> <path>")
|
||||
}
|
||||
return showFile(ctx, c, args[0], args[1])
|
||||
return showFile(ctx, cfg, args[0], args[1])
|
||||
}
|
||||
return fmt.Errorf("unknown command %q, see lodge help", name)
|
||||
}
|
||||
@@ -155,18 +156,80 @@ func output(asJSON bool, value any, human func()) error {
|
||||
return encoder.Encode(value)
|
||||
}
|
||||
|
||||
func showStatus(ctx context.Context, c *client.Client, dir string, asJSON bool) error {
|
||||
func syncRepo(ctx context.Context, cfg *client.Config, dir string, asJSON bool) error {
|
||||
res, err := cfg.Sync(ctx, dir, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return output(asJSON, res, func() {
|
||||
fmt.Printf("pushed %d, pulled %d\n", res.Pushed, res.Pulled)
|
||||
for _, path := range res.Skipped {
|
||||
fmt.Printf("skipped, over 5 MB: %s\n", path)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func remote(ctx context.Context, cfg *client.Config, args []string, toDefault, asJSON bool) error {
|
||||
if len(args) > 2 || toDefault && len(args) > 0 {
|
||||
return errors.New("usage: lodge remote [<url> [code]] | lodge remote --default")
|
||||
}
|
||||
if len(args) == 2 {
|
||||
if err := cfg.Connect(ctx, args[0], args[1]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
repo, err := gitrepo.Open(".")
|
||||
if err != nil && len(args) == 2 {
|
||||
fmt.Printf("connected to %s, run lodge remote %s inside a repository to sync it there\n", args[0], args[0])
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(args) == 0 && !toDefault {
|
||||
c, err := cfg.Server(repo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report := remoteStatus{URL: c.URL, Default: !cfg.Bound(repo.Key)}
|
||||
return output(asJSON, report, func() {
|
||||
if report.Default {
|
||||
fmt.Printf("%s (default)\n", report.URL)
|
||||
} else {
|
||||
fmt.Println(report.URL)
|
||||
}
|
||||
})
|
||||
}
|
||||
server := ""
|
||||
if len(args) > 0 {
|
||||
server = args[0]
|
||||
}
|
||||
if err := cfg.Bind(repo.Key, server); err != nil {
|
||||
return err
|
||||
}
|
||||
err = syncRepo(ctx, cfg, repo.Root, asJSON)
|
||||
if toDefault && errors.Is(err, client.ErrNotLoggedIn) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func showStatus(ctx context.Context, cfg *client.Config, dir string, asJSON bool) error {
|
||||
repo, err := gitrepo.Open(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := cfg.Server(repo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := c.State(ctx, repo.Key, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
report := status{Root: repo.Root, Key: repo.Key, Branch: repo.Branch(), Rules: client.LoadRules(repo.Root), Revision: state.Seq, Spaces: state.Spaces}
|
||||
report := status{Root: repo.Root, Key: repo.Key, Server: c.URL, Branch: repo.Branch(), Rules: client.LoadRules(repo.Root), Revision: state.Seq, Spaces: state.Spaces}
|
||||
return output(asJSON, report, func() {
|
||||
fmt.Printf("%s\nkey %s, branch %s, revision %d\n\nrules:\n", report.Root, report.Key, report.Branch, report.Revision)
|
||||
fmt.Printf("%s\nkey %s, branch %s, revision %d\nserver %s\n\nrules:\n", report.Root, report.Key, report.Branch, report.Revision, report.Server)
|
||||
for _, rule := range report.Rules {
|
||||
kind := "branch"
|
||||
switch {
|
||||
@@ -186,11 +249,15 @@ func showStatus(ctx context.Context, c *client.Client, dir string, asJSON bool)
|
||||
})
|
||||
}
|
||||
|
||||
func showLog(ctx context.Context, c *client.Client, path string, asJSON bool) error {
|
||||
func showLog(ctx context.Context, cfg *client.Config, path string, asJSON bool) error {
|
||||
repo, err := gitrepo.Open(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := cfg.Server(repo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
revisions, err := c.Revisions(ctx, repo.Key, path)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -212,11 +279,15 @@ func showLog(ctx context.Context, c *client.Client, path string, asJSON bool) er
|
||||
})
|
||||
}
|
||||
|
||||
func showFile(ctx context.Context, c *client.Client, at, path string) error {
|
||||
func showFile(ctx context.Context, cfg *client.Config, at, path string) error {
|
||||
repo, err := gitrepo.Open(".")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := cfg.Server(repo.Key)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state, err := c.State(ctx, repo.Key, at)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in new issue
Block a user