forked from gopasspw/gopass
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathclone.go
154 lines (133 loc) · 4.85 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
152
153
154
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/out"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/fsutil"
"github.com/gopasspw/gopass/pkg/termio"
"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"))
}
path := c.String("path")
if c.Args().Len() < 1 {
return ExitError(ExitUsage, nil, "Usage: %s clone repo [mount]", s.Name)
}
// gopass clone [--crypto=foo] [--path=/some/store] git://foo/bar team0.
repo := c.Args().Get(0)
mount := ""
if c.Args().Len() > 1 {
mount = c.Args().Get(1)
}
out.Printf(ctx, logo)
out.Printf(ctx, "🌟 Welcome to gopass!")
out.Printf(ctx, "🌟 Cloning an existing password store from %q ...", repo)
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.IsInitialized(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.
out.Noticef(ctx, "Cloning git repository %q to %q ...", repo, path)
if _, err := backend.Clone(ctx, storageBackendOrDefault(ctx), repo, path); err != nil {
return ExitError(ExitGit, err, "failed to clone repo %q to %q: %s", repo, path, err)
}
// add mount.
debug.Log("Mounting cloned repo %q at %q", 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.Notice(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.Errorf(ctx, "Failed to configure git: %s", err)
}
if mount != "" {
mount = " " + mount
}
out.Printf(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.IsInitialized(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.Printf(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) {
out.Printf(ctx, "🎩 Gathering information for the git repository ...")
// 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, "🚶 What is your name?", 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, "📧 What is your email?", email)
if err != nil {
return "", "", ExitError(ExitIO, err, "Failed to read user input: %s", err)
}
}
return username, email, nil
}