-
-
Notifications
You must be signed in to change notification settings - Fork 481
/
clone.go
151 lines (129 loc) · 4.67 KB
/
clone.go
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
package action
import (
"context"
"os"
"path/filepath"
"github.com/gopasspw/gopass/internal/backend"
"github.com/gopasspw/gopass/internal/config"
"github.com/gopasspw/gopass/internal/cui"
"github.com/gopasspw/gopass/internal/debug"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/termio"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/fsutil"
"github.com/fatih/color"
"github.com/urfave/cli/v2"
)
// Clone will fetch and mount a new password store from a git repo
func (s *Action) Clone(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
if c.IsSet("crypto") {
ctx = backend.WithCryptoBackendString(ctx, c.String("crypto"))
}
if c.Args().Len() < 1 {
return ExitError(ExitUsage, nil, "Usage: %s clone repo [mount]", s.Name)
}
repo := c.Args().Get(0)
mount := ""
if c.Args().Len() > 1 {
mount = c.Args().Get(1)
}
path := c.String("path")
return s.clone(ctx, repo, mount, path)
}
// storageBackendOrDefault will return a storage backend that can be clone,
// i.e. specifically backend.FS can't be cloned.
func storageBackendOrDefault(ctx context.Context) backend.StorageBackend {
if be := backend.GetStorageBackend(ctx); be != backend.FS {
return be
}
return backend.GitFS
}
func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
if path == "" {
path = config.PwStoreDir(mount)
}
inited, err := s.Store.Initialized(ctxutil.WithGitInit(ctx, false))
if err != nil {
return ExitError(ExitUnknown, err, "Failed to initialized stores: %s", err)
}
if mount == "" && inited {
return ExitError(ExitAlreadyInitialized, nil, "Can not clone %s to the root store, as this store is already initialized. Please try cloning to a submount: `%s clone %s sub`", repo, s.Name, repo)
}
// make sure the parent directory exists
if parentPath := filepath.Dir(path); !fsutil.IsDir(parentPath) {
if err := os.MkdirAll(parentPath, 0700); err != nil {
return ExitError(ExitUnknown, err, "Failed to create parent directory for clone: %s", err)
}
}
// clone repo
debug.Log("Cloning repo '%s' to '%s'", repo, path)
if _, err := backend.Clone(ctx, storageBackendOrDefault(ctx), repo, path); err != nil {
return ExitError(ExitGit, err, "failed to clone repo '%s' to '%s': %s", repo, path, err)
}
// add mount
debug.Log("Mounting cloned repo '%s' at '%s'", path, mount)
if err := s.cloneAddMount(ctx, mount, path); err != nil {
return err
}
// save new mount in config file
if err := s.cfg.Save(); err != nil {
return ExitError(ExitIO, err, "Failed to update config: %s", err)
}
// try to init git config
out.Green(ctx, "Configuring git repository ...")
// ask for git config values
username, email, err := s.cloneGetGitConfig(ctx, mount)
if err != nil {
return err
}
// initialize git config
if err := s.Store.RCSInitConfig(ctx, mount, username, email); err != nil {
debug.Log("Stacktrace: %+v\n", err)
out.Error(ctx, "Failed to configure git: %s", err)
}
if mount != "" {
mount = " " + mount
}
out.Green(ctx, "Your password store is ready to use! Have a look around: `%s list%s`\n", s.Name, mount)
return nil
}
func (s *Action) cloneAddMount(ctx context.Context, mount, path string) error {
if mount == "" {
return nil
}
inited, err := s.Store.Initialized(ctx)
if err != nil {
return ExitError(ExitUnknown, err, "Failed to initialize store: %s", err)
}
if !inited {
return ExitError(ExitNotInitialized, nil, "Root-Store is not initialized. Clone or init root store first")
}
if err := s.Store.AddMount(ctx, mount, path); err != nil {
return ExitError(ExitMount, err, "Failed to add mount: %s", err)
}
out.Green(ctx, "Mounted password store %s at mount point `%s` ...", path, mount)
return nil
}
func (s *Action) cloneGetGitConfig(ctx context.Context, name string) (string, string, error) {
// for convenience, set defaults to user-selected values from available private keys
// NB: discarding returned error since this is merely a best-effort look-up for convenience
username, email, _ := cui.AskForGitConfigUser(ctx, s.Store.Crypto(ctx, name))
if username == "" {
username = termio.DetectName(ctx, nil)
var err error
username, err = termio.AskForString(ctx, color.CyanString("Please enter a user name for password store git config"), username)
if err != nil {
return "", "", ExitError(ExitIO, err, "Failed to read user input: %s", err)
}
}
if email == "" {
email = termio.DetectEmail(ctx, nil)
var err error
email, err = termio.AskForString(ctx, color.CyanString("Please enter an email address for password store git config"), email)
if err != nil {
return "", "", ExitError(ExitIO, err, "Failed to read user input: %s", err)
}
}
return username, email, nil
}