arche / internal/cli/cmd_sync.go

commit 154431fd
  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}