arche / internal/cli/cmd_clone.go

commit 154431fd
 1package cli
 2
 3import (
 4	"fmt"
 5	"os"
 6	"path/filepath"
 7	"strings"
 8
 9	"arche/internal/repo"
10	"arche/internal/syncpkg"
11
12	"github.com/spf13/cobra"
13)
14
15var cloneCmd = &cobra.Command{
16	Use:   "clone <url> [directory]",
17	Short: "Clone a remote Arche repository",
18	Long: `Clone a remote Arche repository into a new directory.
19
20The URL may use any scheme supported by arche sync:
21  arche clone http://host:8765         # bearer token over HTTP
22  arche clone arche+ssh://host/repo    # SSH with key auth
23
24The directory defaults to the last path component of the URL.
25
26Examples:
27  arche clone http://forge.example.com/myrepo
28  arche clone arche+ssh://forge.example.com/simao/project local-dir
29  arche clone http://forge.example.com/myrepo --token mysecret`,
30	Args: cobra.RangeArgs(1, 2),
31	RunE: func(cmd *cobra.Command, args []string) error {
32		remoteURL := args[0]
33		token, _ := cmd.Flags().GetString("token")
34
35		dir := ""
36		if len(args) == 2 {
37			dir = args[1]
38		} else {
39			dir = guessLocalDir(remoteURL)
40		}
41
42		if _, err := os.Stat(dir); err == nil {
43			return fmt.Errorf("destination %q already exists", dir)
44		}
45
46		fmt.Printf("arche clone: cloning %s into %s …\n", remoteURL, dir)
47
48		r, err := repo.Init(dir)
49		if err != nil {
50			return fmt.Errorf("init: %w", err)
51		}
52		defer r.Close()
53
54		client := syncpkg.NewClient(r, remoteURL, token)
55		if err := client.Pull(); err != nil {
56			os.RemoveAll(dir) //nolint:errcheck
57			return fmt.Errorf("pull: %w", err)
58		}
59
60		r.Cfg.Remotes = append(r.Cfg.Remotes, repo.RemoteConfig{
61			Name:  "origin",
62			URL:   remoteURL,
63			Token: token,
64		})
65		if err := r.SaveConfig(); err != nil {
66			fmt.Fprintf(os.Stderr, "arche clone: warning: could not save remote config: %v\n", err)
67		}
68
69		headCID, _ := r.Head()
70		fmt.Printf("arche clone: done. Working copy at %s\n", headCID)
71		fmt.Printf("cd %s && arche log\n", dir)
72		return nil
73	},
74}
75
76func guessLocalDir(rawURL string) string {
77	s := rawURL
78	for _, prefix := range []string{"arche+ssh://", "arche://", "https://", "http://"} {
79		s = strings.TrimPrefix(s, prefix)
80	}
81
82	s = strings.TrimRight(s, "/")
83	if idx := strings.LastIndex(s, "/"); idx >= 0 {
84		s = s[idx+1:]
85	}
86
87	s = strings.TrimSuffix(s, ".git")
88	if s == "" {
89		s = "repo"
90	}
91	return filepath.Clean(s)
92}
93
94func init() {
95	cloneCmd.Flags().String("token", "", "bearer token for HTTP auth")
96}