-
Notifications
You must be signed in to change notification settings - Fork 467
/
addcmd.go
208 lines (190 loc) · 6.63 KB
/
addcmd.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package cmd
import (
"fmt"
"io/fs"
"github.com/spf13/cobra"
"github.com/twpayne/chezmoi/v2/internal/chezmoi"
)
type addCmdConfig struct {
Encrypt bool `json:"encrypt" mapstructure:"encrypt" yaml:"encrypt"`
Secrets severity `json:"secrets" mapstructure:"secrets" yaml:"secrets"`
TemplateSymlinks bool `json:"templateSymlinks" mapstructure:"templateSymlinks" yaml:"templateSymlinks"`
autoTemplate bool
create bool
exact bool
filter *chezmoi.EntryTypeFilter
follow bool
prompt bool
quiet bool
recursive bool
template bool
}
func (c *Config) newAddCmd() *cobra.Command {
addCmd := &cobra.Command{
Use: "add targets...",
Aliases: []string{"manage"},
Short: "Add an existing file, directory, or symlink to the source state",
Long: mustLongHelp("add"),
Example: example("add"),
Args: cobra.MinimumNArgs(1),
RunE: c.makeRunEWithSourceState(c.runAddCmd),
Annotations: newAnnotations(
createSourceDirectoryIfNeeded,
modifiesSourceDirectory,
persistentStateModeReadWrite,
requiresWorkingTree,
),
}
addCmd.Flags().
BoolVarP(&c.Add.autoTemplate, "autotemplate", "a", c.Add.autoTemplate, "Generate the template when adding files as templates")
addCmd.Flags().BoolVar(&c.Add.create, "create", c.Add.create, "Add files that should exist, irrespective of their contents")
addCmd.Flags().BoolVar(&c.Add.Encrypt, "encrypt", c.Add.Encrypt, "Encrypt files")
addCmd.Flags().BoolVar(&c.Add.exact, "exact", c.Add.exact, "Add directories exactly")
addCmd.Flags().VarP(c.Add.filter.Exclude, "exclude", "x", "Exclude entry types")
addCmd.Flags().BoolVarP(&c.Add.follow, "follow", "f", c.Add.follow, "Add symlink targets instead of symlinks")
addCmd.Flags().VarP(c.Add.filter.Include, "include", "i", "Include entry types")
addCmd.Flags().BoolVarP(&c.Add.prompt, "prompt", "p", c.Add.prompt, "Prompt before adding each entry")
addCmd.Flags().BoolVarP(&c.Add.quiet, "quiet", "q", c.Add.quiet, "Suppress warnings")
addCmd.Flags().BoolVarP(&c.Add.recursive, "recursive", "r", c.Add.recursive, "Recurse into subdirectories")
addCmd.Flags().Var(&c.Add.Secrets, "secrets", "Scan for secrets when adding unencrypted files")
addCmd.Flags().BoolVarP(&c.Add.template, "template", "T", c.Add.template, "Add files as templates")
addCmd.Flags().
BoolVar(&c.Add.TemplateSymlinks, "template-symlinks", c.Add.TemplateSymlinks, "Add symlinks with target in source or home dirs as templates")
return addCmd
}
func (c *Config) defaultOnIgnoreFunc(targetRelPath chezmoi.RelPath) {
if !c.Add.quiet {
c.errorf("warning: ignoring %s\n", targetRelPath)
}
}
func (c *Config) defaultPreAddFunc(targetRelPath chezmoi.RelPath, fileInfo fs.FileInfo) error {
// Scan unencrypted files for secrets, if configured.
if c.Add.Secrets != severityIgnore && fileInfo.Mode().Type() == 0 && !c.Add.Encrypt {
absPath := c.DestDirAbsPath.Join(targetRelPath)
content, err := c.destSystem.ReadFile(absPath)
if err != nil {
return err
}
gitleaksDetector, err := c.getGitleaksDetector()
if err != nil {
return err
}
findings := gitleaksDetector.DetectBytes(content)
for _, finding := range findings {
c.errorf("%s:%d: %s\n", absPath, finding.StartLine+1, finding.Description)
}
if !c.force && c.Add.Secrets == severityError && len(findings) > 0 {
return chezmoi.ExitCodeError(1)
}
}
if !c.Add.prompt {
return nil
}
prompt := fmt.Sprintf("add %s", c.SourceDirAbsPath.Join(targetRelPath))
for {
switch choice, err := c.promptChoice(prompt, choicesYesNoAllQuit); {
case err != nil:
return err
case choice == "all":
c.Add.prompt = false
return nil
case choice == "no":
return fs.SkipDir
case choice == "quit":
return chezmoi.ExitCodeError(0)
case choice == "yes":
return nil
default:
panic(choice + ": unexpected choice")
}
}
}
// defaultReplaceFunc prompts the user for confirmation if the adding the entry
// would remove any of the encrypted, private, or template attributes.
func (c *Config) defaultReplaceFunc(
targetRelPath chezmoi.RelPath,
newSourceStateEntry, oldSourceStateEntry chezmoi.SourceStateEntry,
) error {
if c.force {
return nil
}
newFile, newIsFile := newSourceStateEntry.(*chezmoi.SourceStateFile)
oldFile, oldIsFile := oldSourceStateEntry.(*chezmoi.SourceStateFile)
if !newIsFile || !oldIsFile {
return nil
}
var removedAttributes []string
if !newFile.Attr.Encrypted && oldFile.Attr.Encrypted {
removedAttributes = append(removedAttributes, "encrypted")
}
if !newFile.Attr.Private && oldFile.Attr.Private {
removedAttributes = append(removedAttributes, "private")
}
if !newFile.Attr.Template && oldFile.Attr.Template {
removedAttributes = append(removedAttributes, "template")
}
if len(removedAttributes) == 0 {
return nil
}
removedAttributesStr := englishListWithNoun(removedAttributes, "attribute", "")
prompt := fmt.Sprintf("adding %s would remove %s, continue", targetRelPath, removedAttributesStr)
for {
switch choice, err := c.promptChoice(prompt, choicesYesNoAllQuit); {
case err != nil:
return err
case choice == "all":
c.force = true
return nil
case choice == "no":
return fs.SkipDir
case choice == "quit":
return chezmoi.ExitCodeError(0)
case choice == "yes":
return nil
default:
panic(choice + ": unexpected choice")
}
}
}
func (c *Config) runAddCmd(cmd *cobra.Command, args []string, sourceState *chezmoi.SourceState) error {
destAbsPathInfos, err := c.destAbsPathInfos(sourceState, args, destAbsPathInfosOptions{
follow: c.Mode == chezmoi.ModeSymlink || c.Add.follow,
onIgnoreFunc: c.defaultOnIgnoreFunc,
recursive: c.Add.recursive,
})
if err != nil {
return err
}
persistentStateFileAbsPath, err := c.persistentStateFile()
if err != nil {
return err
}
return sourceState.Add(
c.sourceSystem,
c.persistentState,
c.destSystem,
destAbsPathInfos,
&chezmoi.AddOptions{
AutoTemplate: c.Add.autoTemplate,
Create: c.Add.create,
Encrypt: c.Add.Encrypt,
EncryptedSuffix: c.encryption.EncryptedSuffix(),
Exact: c.Add.exact,
Errorf: c.errorf,
Filter: c.Add.filter,
OnIgnoreFunc: c.defaultOnIgnoreFunc,
PreAddFunc: c.defaultPreAddFunc,
ConfigFileAbsPath: c.getConfigFileAbsPath(),
ProtectedAbsPaths: []chezmoi.AbsPath{
c.CacheDirAbsPath,
c.WorkingTreeAbsPath,
c.getConfigFileAbsPath().Dir(),
persistentStateFileAbsPath,
c.sourceDirAbsPath,
},
ReplaceFunc: c.defaultReplaceFunc,
Template: c.Add.template,
TemplateSymlinks: c.Add.TemplateSymlinks,
},
)
}