Skip to content

Commit b4d34d8

Browse files
authored
Merge pull request #87 from twpayne/exact-dirs
Add support for exact directories
2 parents 67bb438 + b28a722 commit b4d34d8

File tree

10 files changed

+318
-135
lines changed

10 files changed

+318
-135
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -395,19 +395,20 @@ with a `.`. The following prefixes and suffixes are special.
395395
| -------------------- | ----------------------------------------------------------------------------------|
396396
| `private_` prefix | Remove all group and world permissions from the target file or directory. |
397397
| `empty_` prefix | Ensure the file exists, even if is empty. By default, empty files are removed. |
398+
| `exact_` prefix | Remove anything not managed by `chezmoi`. |
398399
| `executable_` prefix | Add executable permissions to the target file. |
399400
| `symlink_` prefix | Create a symlink instead of a regular file. |
400401
| `dot_` prefix | Rename to use a leading dot, e.g. `dot_foo` becomes `.foo`. |
401402
| `.tmpl` suffix | Treat the contents of the source file as a template. |
402403

403-
Order is important, the order is `private_`, `empty_`, `executable_`,
404+
Order is important, the order is `exact_`, `private_`, `empty_`, `executable_`,
404405
`symlink_`, `dot_`, `.tmpl`.
405406

406407
Different target types allow different prefixes and suffixes.
407408

408409
| Target type | Allowed prefixes and suffixes |
409410
| ------------- | ---------------------------------------------------- |
410-
| Directory | `private_`, `dot_` |
411+
| Directory | `exact_, `private_`, `dot_` |
411412
| Regular file | `private_`, `empty_`, `executable_`, `dot_`, `.tmpl` |
412413
| Symbolic link | `symlink_`, `dot_`, `.tmpl` |
413414

cmd/add.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"path/filepath"
77

88
"github.com/spf13/cobra"
9+
"github.com/twpayne/chezmoi/lib/chezmoi"
910
vfs "github.com/twpayne/go-vfs"
1011
)
1112

@@ -16,20 +17,19 @@ var addCommand = &cobra.Command{
1617
RunE: makeRunE(config.runAddCommand),
1718
}
1819

19-
// An AddCommandConfig is a configuration for the add command.
2020
type addCommandConfig struct {
21-
empty bool
2221
recursive bool
23-
template bool
22+
options chezmoi.AddOptions
2423
}
2524

2625
func init() {
2726
rootCommand.AddCommand(addCommand)
2827

2928
persistentFlags := addCommand.PersistentFlags()
30-
persistentFlags.BoolVarP(&config.add.empty, "empty", "e", false, "add empty files")
29+
persistentFlags.BoolVarP(&config.add.options.Empty, "empty", "e", false, "add empty files")
30+
persistentFlags.BoolVarP(&config.add.options.Exact, "exact", "x", false, "add directories exactly")
3131
persistentFlags.BoolVarP(&config.add.recursive, "recursive", "r", false, "recurse in to subdirectories")
32-
persistentFlags.BoolVarP(&config.add.template, "template", "T", false, "add files as templates")
32+
persistentFlags.BoolVarP(&config.add.options.Template, "template", "T", false, "add files as templates")
3333
}
3434

3535
func (c *Config) runAddCommand(fs vfs.FS, command *cobra.Command, args []string) error {
@@ -65,12 +65,12 @@ func (c *Config) runAddCommand(fs vfs.FS, command *cobra.Command, args []string)
6565
if err != nil {
6666
return err
6767
}
68-
return ts.Add(fs, path, info, c.add.empty, c.add.template, mutator)
68+
return ts.Add(fs, c.add.options, path, info, mutator)
6969
}); err != nil {
7070
return err
7171
}
7272
} else {
73-
if err := ts.Add(fs, path, nil, c.add.empty, c.add.template, mutator); err != nil {
73+
if err := ts.Add(fs, c.add.options, path, nil, mutator); err != nil {
7474
return err
7575
}
7676
}

cmd/add_test.go

Lines changed: 105 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cmd
33
import (
44
"testing"
55

6+
"github.com/twpayne/chezmoi/lib/chezmoi"
67
"github.com/twpayne/go-vfs/vfst"
78
)
89

@@ -16,142 +17,191 @@ func TestAddCommand(t *testing.T) {
1617
}{
1718
{
1819
name: "add_first_file",
19-
args: []string{"/home/jenkins/.bashrc"},
20+
args: []string{"/home/user/.bashrc"},
2021
root: map[string]interface{}{
21-
"/home/jenkins/.bashrc": "foo",
22+
"/home/user/.bashrc": "foo",
2223
},
2324
tests: []vfst.Test{
24-
vfst.TestPath("/home/jenkins/.chezmoi",
25+
vfst.TestPath("/home/user/.chezmoi",
2526
vfst.TestIsDir,
2627
vfst.TestModePerm(0700),
2728
),
28-
vfst.TestPath("/home/jenkins/.chezmoi/dot_bashrc",
29+
vfst.TestPath("/home/user/.chezmoi/dot_bashrc",
2930
vfst.TestModeIsRegular,
3031
vfst.TestContentsString("foo"),
3132
),
3233
},
3334
},
3435
{
3536
name: "add_template",
36-
args: []string{"/home/jenkins/.gitconfig"},
37+
args: []string{"/home/user/.gitconfig"},
3738
add: addCommandConfig{
38-
template: true,
39+
options: chezmoi.AddOptions{
40+
Template: true,
41+
},
3942
},
4043
root: map[string]interface{}{
41-
"/home/jenkins": &vfst.Dir{Perm: 0755},
42-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
43-
"/home/jenkins/.gitconfig": "[user]\n\tname = John Smith\n\temail = john.smith@company.com\n",
44+
"/home/user": &vfst.Dir{Perm: 0755},
45+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
46+
"/home/user/.gitconfig": "[user]\n\tname = John Smith\n\temail = john.smith@company.com\n",
4447
},
4548
tests: []vfst.Test{
46-
vfst.TestPath("/home/jenkins/.chezmoi/dot_gitconfig.tmpl",
49+
vfst.TestPath("/home/user/.chezmoi/dot_gitconfig.tmpl",
4750
vfst.TestModeIsRegular,
4851
vfst.TestContentsString("[user]\n\tname = {{ .name }}\n\temail = {{ .email }}\n"),
4952
),
5053
},
5154
},
5255
{
5356
name: "add_recursive",
54-
args: []string{"/home/jenkins/.config"},
57+
args: []string{"/home/user/.config"},
5558
add: addCommandConfig{
5659
recursive: true,
5760
},
5861
root: map[string]interface{}{
59-
"/home/jenkins": &vfst.Dir{Perm: 0755},
60-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
61-
"/home/jenkins/.config/micro/settings.json": "{}",
62+
"/home/user": &vfst.Dir{Perm: 0755},
63+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
64+
"/home/user/.config/micro/settings.json": "{}",
6265
},
6366
tests: []vfst.Test{
64-
vfst.TestPath("/home/jenkins/.chezmoi/dot_config/micro/settings.json",
67+
vfst.TestPath("/home/user/.chezmoi/dot_config/micro/settings.json",
6568
vfst.TestModeIsRegular,
6669
vfst.TestContentsString("{}"),
6770
),
6871
},
6972
},
7073
{
7174
name: "add_nested_directory",
72-
args: []string{"/home/jenkins/.config/micro/settings.json"},
75+
args: []string{"/home/user/.config/micro/settings.json"},
7376
root: map[string]interface{}{
74-
"/home/jenkins": &vfst.Dir{Perm: 0755},
75-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
76-
"/home/jenkins/.config/micro/settings.json": "{}",
77+
"/home/user": &vfst.Dir{Perm: 0755},
78+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
79+
"/home/user/.config/micro/settings.json": "{}",
7780
},
7881
tests: []vfst.Test{
79-
vfst.TestPath("/home/jenkins/.chezmoi/dot_config/micro/settings.json",
82+
vfst.TestPath("/home/user/.chezmoi/dot_config/micro/settings.json",
8083
vfst.TestModeIsRegular,
8184
vfst.TestContentsString("{}"),
8285
),
8386
},
8487
},
88+
{
89+
name: "add_exact_dir",
90+
args: []string{"/home/user/dir"},
91+
add: addCommandConfig{
92+
options: chezmoi.AddOptions{
93+
Exact: true,
94+
},
95+
},
96+
root: map[string]interface{}{
97+
"/home/user": &vfst.Dir{Perm: 0755},
98+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
99+
"/home/user/dir": &vfst.Dir{Perm: 0755},
100+
},
101+
tests: []vfst.Test{
102+
vfst.TestPath("/home/user/.chezmoi/exact_dir",
103+
vfst.TestIsDir,
104+
),
105+
},
106+
},
107+
{
108+
name: "add_exact_dir_recursive",
109+
args: []string{"/home/user/dir"},
110+
add: addCommandConfig{
111+
recursive: true,
112+
options: chezmoi.AddOptions{
113+
Exact: true,
114+
},
115+
},
116+
root: map[string]interface{}{
117+
"/home/user": &vfst.Dir{Perm: 0755},
118+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
119+
"/home/user/dir": map[string]interface{}{
120+
"foo": "bar",
121+
},
122+
},
123+
tests: []vfst.Test{
124+
vfst.TestPath("/home/user/.chezmoi/exact_dir",
125+
vfst.TestIsDir,
126+
),
127+
vfst.TestPath("/home/user/.chezmoi/exact_dir/foo",
128+
vfst.TestModeIsRegular,
129+
vfst.TestContentsString("bar"),
130+
),
131+
},
132+
},
85133
{
86134
name: "add_empty_file",
87-
args: []string{"/home/jenkins/empty"},
135+
args: []string{"/home/user/empty"},
88136
add: addCommandConfig{
89-
empty: true,
137+
options: chezmoi.AddOptions{
138+
Empty: true,
139+
},
90140
},
91141
root: map[string]interface{}{
92-
"/home/jenkins": &vfst.Dir{Perm: 0755},
93-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
94-
"/home/jenkins/empty": "",
142+
"/home/user": &vfst.Dir{Perm: 0755},
143+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
144+
"/home/user/empty": "",
95145
},
96146
tests: []vfst.Test{
97-
vfst.TestPath("/home/jenkins/.chezmoi/empty_empty",
147+
vfst.TestPath("/home/user/.chezmoi/empty_empty",
98148
vfst.TestModeIsRegular,
99149
vfst.TestContents(nil),
100150
),
101151
},
102152
},
103153
{
104154
name: "add_symlink",
105-
args: []string{"/home/jenkins/foo"},
155+
args: []string{"/home/user/foo"},
106156
root: map[string]interface{}{
107-
"/home/jenkins": &vfst.Dir{Perm: 0755},
108-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
109-
"/home/jenkins/foo": &vfst.Symlink{Target: "bar"},
157+
"/home/user": &vfst.Dir{Perm: 0755},
158+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
159+
"/home/user/foo": &vfst.Symlink{Target: "bar"},
110160
},
111161
tests: []vfst.Test{
112-
vfst.TestPath("/home/jenkins/.chezmoi/symlink_foo",
162+
vfst.TestPath("/home/user/.chezmoi/symlink_foo",
113163
vfst.TestModeIsRegular,
114164
vfst.TestContentsString("bar"),
115165
),
116166
},
117167
},
118168
{
119169
name: "add_symlink_in_dir_recursive",
120-
args: []string{"/home/jenkins/foo"},
170+
args: []string{"/home/user/foo"},
121171
add: addCommandConfig{
122172
recursive: true,
123173
},
124174
root: map[string]interface{}{
125-
"/home/jenkins": &vfst.Dir{Perm: 0755},
126-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
127-
"/home/jenkins/foo/bar": &vfst.Symlink{Target: "baz"},
175+
"/home/user": &vfst.Dir{Perm: 0755},
176+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
177+
"/home/user/foo/bar": &vfst.Symlink{Target: "baz"},
128178
},
129179
tests: []vfst.Test{
130-
vfst.TestPath("/home/jenkins/.chezmoi/foo",
180+
vfst.TestPath("/home/user/.chezmoi/foo",
131181
vfst.TestIsDir,
132182
),
133-
vfst.TestPath("/home/jenkins/.chezmoi/foo/symlink_bar",
183+
vfst.TestPath("/home/user/.chezmoi/foo/symlink_bar",
134184
vfst.TestModeIsRegular,
135185
vfst.TestContentsString("baz"),
136186
),
137187
},
138188
},
139189
{
140190
name: "add_symlink_with_parent_dir",
141-
args: []string{"/home/jenkins/foo/bar/baz"},
191+
args: []string{"/home/user/foo/bar/baz"},
142192
root: map[string]interface{}{
143-
"/home/jenkins": &vfst.Dir{Perm: 0755},
144-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
145-
"/home/jenkins/foo/bar/baz": &vfst.Symlink{Target: "qux"},
193+
"/home/user": &vfst.Dir{Perm: 0755},
194+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
195+
"/home/user/foo/bar/baz": &vfst.Symlink{Target: "qux"},
146196
},
147197
tests: []vfst.Test{
148-
vfst.TestPath("/home/jenkins/.chezmoi/foo",
198+
vfst.TestPath("/home/user/.chezmoi/foo",
149199
vfst.TestIsDir,
150200
),
151-
vfst.TestPath("/home/jenkins/.chezmoi/foo/bar",
201+
vfst.TestPath("/home/user/.chezmoi/foo/bar",
152202
vfst.TestIsDir,
153203
),
154-
vfst.TestPath("/home/jenkins/.chezmoi/foo/bar/symlink_baz",
204+
vfst.TestPath("/home/user/.chezmoi/foo/bar/symlink_baz",
155205
vfst.TestModeIsRegular,
156206
vfst.TestContentsString("qux"),
157207
),
@@ -160,8 +210,8 @@ func TestAddCommand(t *testing.T) {
160210
} {
161211
t.Run(tc.name, func(t *testing.T) {
162212
c := &Config{
163-
SourceDir: "/home/jenkins/.chezmoi",
164-
TargetDir: "/home/jenkins",
213+
SourceDir: "/home/user/.chezmoi",
214+
TargetDir: "/home/user",
165215
Umask: 022,
166216
DryRun: false,
167217
Verbose: true,
@@ -187,39 +237,39 @@ func TestAddCommand(t *testing.T) {
187237

188238
func TestAddAfterModification(t *testing.T) {
189239
c := &Config{
190-
SourceDir: "/home/jenkins/.chezmoi",
191-
TargetDir: "/home/jenkins",
240+
SourceDir: "/home/user/.chezmoi",
241+
TargetDir: "/home/user",
192242
Umask: 022,
193243
DryRun: false,
194244
Verbose: true,
195245
}
196246
fs, cleanup, err := vfst.NewTestFS(map[string]interface{}{
197-
"/home/jenkins": &vfst.Dir{Perm: 0755},
198-
"/home/jenkins/.chezmoi": &vfst.Dir{Perm: 0700},
199-
"/home/jenkins/.bashrc": "# contents of .bashrc\n",
247+
"/home/user": &vfst.Dir{Perm: 0755},
248+
"/home/user/.chezmoi": &vfst.Dir{Perm: 0700},
249+
"/home/user/.bashrc": "# contents of .bashrc\n",
200250
})
201251
defer cleanup()
202252
if err != nil {
203253
t.Fatalf("vfst.NewTestFS(_) == _, _, %v, want _, _, <nil>", err)
204254
}
205-
args := []string{"/home/jenkins/.bashrc"}
255+
args := []string{"/home/user/.bashrc"}
206256
if err := c.runAddCommand(fs, nil, args); err != nil {
207257
t.Errorf("c.runAddCommand(fs, nil, %+v) == %v, want <nil>", args, err)
208258
}
209259
vfst.RunTests(t, fs, "",
210-
vfst.TestPath("/home/jenkins/.chezmoi/dot_bashrc",
260+
vfst.TestPath("/home/user/.chezmoi/dot_bashrc",
211261
vfst.TestModeIsRegular,
212262
vfst.TestContentsString("# contents of .bashrc\n"),
213263
),
214264
)
215-
if err := fs.WriteFile("/home/jenkins/.bashrc", []byte("# new contents of .bashrc\n"), 0644); err != nil {
265+
if err := fs.WriteFile("/home/user/.bashrc", []byte("# new contents of .bashrc\n"), 0644); err != nil {
216266
t.Errorf("fs.WriteFile(...) == %v, want <nil>", err)
217267
}
218268
if err := c.runAddCommand(fs, nil, args); err != nil {
219269
t.Errorf("c.runAddCommand(fs, nil, %+v) == %v, want <nil>", args, err)
220270
}
221271
vfst.RunTests(t, fs, "",
222-
vfst.TestPath("/home/jenkins/.chezmoi/dot_bashrc",
272+
vfst.TestPath("/home/user/.chezmoi/dot_bashrc",
223273
vfst.TestModeIsRegular,
224274
vfst.TestContentsString("# new contents of .bashrc\n"),
225275
),

0 commit comments

Comments
 (0)