1package cli
2
3import (
4 "fmt"
5
6 "arche/internal/gitcompat"
7 "arche/internal/repo"
8 "arche/internal/syncpkg"
9
10 "github.com/spf13/cobra"
11)
12
13var syncCmd = &cobra.Command{
14 Use: "sync [remote]",
15 Short: "Synchronise with a remote repository",
16 Long: `Synchronise this repository with a named remote (default: "origin").
17By default, performs a pull followed by a push.
18
19 arche sync - pull then push from/to "origin"
20 arche sync upstream - pull then push from/to the remote named "upstream"
21 arche sync --pull - pull only
22 arche sync --push - push only
23
24Remote URLs and tokens are stored in the repository configuration under
25[[remote]] sections, or can be specified directly with --url and --token.`,
26 Args: cobra.MaximumNArgs(1),
27 RunE: func(cmd *cobra.Command, args []string) error {
28 r := openRepo()
29 defer r.Close()
30
31 remoteName := "origin"
32 if len(args) == 1 {
33 remoteName = args[0]
34 }
35
36 pullOnly, _ := cmd.Flags().GetBool("pull")
37 pushOnly, _ := cmd.Flags().GetBool("push")
38 urlOverride, _ := cmd.Flags().GetString("url")
39 tokenOverride, _ := cmd.Flags().GetString("token")
40 force, _ := cmd.Flags().GetBool("force")
41 forcePublic, _ := cmd.Flags().GetBool("force-public")
42
43 if pullOnly && pushOnly {
44 return fmt.Errorf("--pull and --push are mutually exclusive")
45 }
46
47 url := urlOverride
48 token := tokenOverride
49 if url == "" {
50 rc := findRemote(r.Cfg.Remotes, remoteName)
51 if rc == nil {
52 return fmt.Errorf("no remote named %q; add one to .arche/config.toml:\n\n [[remote]]\n name = %q\n url = \"http://host:8765\"\n token = \"secret\"", remoteName, remoteName)
53 }
54 url = rc.URL
55 if token == "" {
56 token = rc.Token
57 }
58 }
59
60 client := syncpkg.NewClient(r, url, token)
61
62 if !pushOnly {
63 fmt.Printf("arche sync: pulling from %s …\n", url)
64 if err := client.Pull(); err != nil {
65 return fmt.Errorf("pull: %w", err)
66 }
67 fmt.Printf("arche sync: pull complete\n")
68 }
69
70 if !pullOnly {
71 fmt.Printf("arche sync: pushing to %s …\n", url)
72 if err := client.PushWith(syncpkg.PushOptions{Force: force, ForcePublic: forcePublic}); err != nil {
73 return fmt.Errorf("push: %w", err)
74 }
75 fmt.Printf("arche sync: push complete\n")
76 }
77
78 if r.Cfg.Git.Enabled {
79 gitRemote := r.Cfg.Git.Remote
80 if gitRemote == "" {
81 gitRemote = "origin"
82 }
83 if !pushOnly {
84 if err := gitcompat.SyncPull(r.Root, gitRemote); err != nil {
85 fmt.Printf("arche sync: git pull warning: %v\n", err)
86 }
87 }
88 if !pullOnly {
89 if err := gitcompat.SyncPush(r.Root, gitRemote); err != nil {
90 fmt.Printf("arche sync: git push warning: %v\n", err)
91 }
92 }
93 }
94
95 return nil
96 },
97}
98
99func findRemote(remotes []repo.RemoteConfig, name string) *repo.RemoteConfig {
100 for i := range remotes {
101 if remotes[i].Name == name {
102 return &remotes[i]
103 }
104 }
105 return nil
106}
107
108func init() {
109 syncCmd.Flags().Bool("pull", false, "pull from remote only (no push)")
110 syncCmd.Flags().Bool("push", false, "push to remote only (no pull)")
111 syncCmd.Flags().String("url", "", "remote URL (overrides config)")
112 syncCmd.Flags().String("token", "", "bearer token (overrides config)")
113 syncCmd.Flags().Bool("force", false, "force-push draft bookmarks, skipping fast-forward check")
114 syncCmd.Flags().Bool("force-public", false, "force-push even public bookmarks (writes obsolescence marker on server)")
115}