1package action
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"os"
8
9	"github.com/gopasspw/gopass/internal/backend"
10	"github.com/gopasspw/gopass/internal/cui"
11	"github.com/gopasspw/gopass/internal/out"
12	si "github.com/gopasspw/gopass/internal/store"
13	"github.com/gopasspw/gopass/pkg/ctxutil"
14	"github.com/gopasspw/gopass/pkg/debug"
15	"github.com/gopasspw/gopass/pkg/termio"
16
17	"github.com/fatih/color"
18	"github.com/urfave/cli/v2"
19)
20
21// RCSInit initializes a git repo including basic configuration
22func (s *Action) RCSInit(c *cli.Context) error {
23	ctx := ctxutil.WithGlobalFlags(c)
24	store := c.String("store")
25	un := termio.DetectName(c.Context, c)
26	ue := termio.DetectEmail(c.Context, c)
27	ctx = backend.WithStorageBackendString(ctx, c.String("storage"))
28
29	// default to git
30	if !backend.HasStorageBackend(ctx) {
31		ctx = backend.WithStorageBackend(ctx, backend.GitFS)
32	}
33
34	if err := s.rcsInit(ctx, store, un, ue); err != nil {
35		return ExitError(ExitGit, err, "failed to initialize git: %s", err)
36	}
37	return nil
38}
39
40func (s *Action) rcsInit(ctx context.Context, store, un, ue string) error {
41	bn := backend.StorageBackendName(backend.GetStorageBackend(ctx))
42	userName, userEmail := s.getUserData(ctx, store, un, ue)
43	if err := s.Store.RCSInit(ctx, store, userName, userEmail); err != nil {
44		if errors.Is(err, backend.ErrNotSupported) {
45			debug.Log("RCSInit not supported for backend %s", bn)
46			return nil
47		}
48		if gtv := os.Getenv("GPG_TTY"); gtv == "" {
49			out.Printf(ctx, "Git initialization failed. You may want to try to 'export GPG_TTY=$(tty)' and start over.")
50		}
51		return fmt.Errorf("failed to run git init: %w", err)
52	}
53
54	out.Printf(ctx, "Initialized git repository (%s) for %s / %s...", bn, un, ue)
55	return nil
56}
57
58func (s *Action) getUserData(ctx context.Context, store, name, email string) (string, string) {
59	if name != "" && email != "" {
60		debug.Log("Username: %s, Email: %s (provided)", name, email)
61		return name, email
62	}
63
64	// for convenience, set defaults to user-selected values from available private keys
65	// NB: discarding returned error since this is merely a best-effort look-up for convenience
66	userName, userEmail, _ := cui.AskForGitConfigUser(ctx, s.Store.Crypto(ctx, store))
67
68	if name == "" {
69		if userName == "" {
70			userName = termio.DetectName(ctx, nil)
71		}
72		var err error
73		name, err = termio.AskForString(ctx, color.CyanString("Please enter a user name for password store git config"), userName)
74		if err != nil {
75			out.Errorf(ctx, "Failed to ask for user input: %s", err)
76		}
77	}
78	if email == "" {
79		if userEmail == "" {
80			userEmail = termio.DetectEmail(ctx, nil)
81		}
82		var err error
83		email, err = termio.AskForString(ctx, color.CyanString("Please enter an email address for password store git config"), userEmail)
84		if err != nil {
85			out.Errorf(ctx, "Failed to ask for user input: %s", err)
86		}
87	}
88
89	debug.Log("Username: %s, Email: %s (detected)", name, email)
90	return name, email
91}
92
93// RCSAddRemote adds a new git remote
94func (s *Action) RCSAddRemote(c *cli.Context) error {
95	ctx := ctxutil.WithGlobalFlags(c)
96	store := c.String("store")
97	remote := c.Args().Get(0)
98	url := c.Args().Get(1)
99
100	if remote == "" || url == "" {
101		return ExitError(ExitUsage, nil, "Usage: %s git remote add <REMOTE> <URL>", s.Name)
102	}
103
104	return s.Store.RCSAddRemote(ctx, store, remote, url)
105}
106
107// RCSRemoveRemote removes a git remote
108func (s *Action) RCSRemoveRemote(c *cli.Context) error {
109	ctx := ctxutil.WithGlobalFlags(c)
110	store := c.String("store")
111	remote := c.Args().Get(0)
112
113	if remote == "" {
114		return ExitError(ExitUsage, nil, "Usage: %s git remote rm <REMOTE>", s.Name)
115	}
116
117	return s.Store.RCSRemoveRemote(ctx, store, remote)
118}
119
120// RCSPull pulls from a git remote
121func (s *Action) RCSPull(c *cli.Context) error {
122	ctx := ctxutil.WithGlobalFlags(c)
123	store := c.String("store")
124	origin := c.Args().Get(0)
125	branch := c.Args().Get(1)
126
127	return s.Store.RCSPull(ctx, store, origin, branch)
128}
129
130// RCSPush pushes to a git remote
131func (s *Action) RCSPush(c *cli.Context) error {
132	ctx := ctxutil.WithGlobalFlags(c)
133	store := c.String("store")
134	origin := c.Args().Get(0)
135	branch := c.Args().Get(1)
136
137	if err := s.Store.RCSPush(ctx, store, origin, branch); err != nil {
138		if errors.Is(err, si.ErrGitNoRemote) {
139			out.Noticef(ctx, "No Git remote. Not pushing")
140			return nil
141		}
142		return ExitError(ExitGit, err, "Failed to push to remote")
143	}
144	out.OKf(ctx, "Pushed to git remote")
145	return nil
146}
147
148// RCSStatus prints the rcs status
149func (s *Action) RCSStatus(c *cli.Context) error {
150	ctx := ctxutil.WithGlobalFlags(c)
151	store := c.String("store")
152
153	return s.Store.RCSStatus(ctx, store)
154}
155