Skip to content

Commit

Permalink
Merge pull request #99 from twpayne/ignore
Browse files Browse the repository at this point in the history
Add facility to ignore files
  • Loading branch information
twpayne authored Jan 12, 2019
2 parents de45990 + d66bf04 commit c68d36a
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 58 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ will be removed. This can be used to ensure that files are only present on
certain machines. If you want an empty file to be created anyway, you will need
to give it an `empty_` prefix. See "Under the hood" below.

For coarser-grained control of files and entire directories are managed on
different machines, or to exclude certain files completely, you can create
`.chezmoiignore` files in the source directory. These specify a list of
patterns that `chezmoi` should ignore, and are interpreted as templates. An
example `.chezmoiignore` file might look like:

README.md
{{- if ne .chezmoi.hostname "work-laptop" }}
.work # only manage .work on work-laptop
{{- end }}


## Keeping data private

Expand Down
2 changes: 1 addition & 1 deletion cmd/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (c *Config) applyArgs(fs vfs.FS, args []string, mutator chezmoi.Mutator) er
return err
}
for _, entry := range entries {
if err := entry.Apply(fs, ts.TargetDir, ts.Umask, mutator); err != nil {
if err := entry.Apply(fs, ts.TargetDir, ts.TargetIgnore.Match, ts.Umask, mutator); err != nil {
return err
}
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (c *Config) runEditCommand(fs vfs.FS, command *cobra.Command, args []string
if c.edit.diff {
mutator = chezmoi.NewLoggingMutator(os.Stdout, mutator)
}
if err := entry.Apply(readOnlyFS, ts.TargetDir, ts.Umask, mutator); err != nil {
if err := entry.Apply(readOnlyFS, ts.TargetDir, ts.TargetIgnore.Match, ts.Umask, mutator); err != nil {
return err
}
if c.edit.apply && anyMutator.Mutated() {
Expand All @@ -81,7 +81,7 @@ func (c *Config) runEditCommand(fs vfs.FS, command *cobra.Command, args []string
c.edit.prompt = false
}
}
if err := entry.Apply(readOnlyFS, ts.TargetDir, ts.Umask, applyMutator); err != nil {
if err := entry.Apply(readOnlyFS, ts.TargetDir, ts.TargetIgnore.Match, ts.Umask, applyMutator); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ require (
github.com/spf13/viper v1.3.1
github.com/stretchr/objx v0.1.1 // indirect
github.com/twpayne/go-shell v0.0.1
github.com/twpayne/go-vfs v0.1.6
github.com/twpayne/go-vfs v1.0.2
github.com/twpayne/go-xdg v1.0.0
github.com/zalando/go-keyring v0.0.0-20180221093347-6d81c293b3fb
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/twpayne/go-shell v0.0.1 h1:Ako3cUeuULhWadYk37jM3FlJ8lkSSW4INBjYj9K60Gw=
github.com/twpayne/go-shell v0.0.1/go.mod h1:QCjEvdZndTuPObd+11NYAI1UeNLSuGZVxJ+67Wl+IU4=
github.com/twpayne/go-vfs v0.1.5/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8=
github.com/twpayne/go-vfs v0.1.6 h1:HeCoBKvyfgEt1FA04hUC/9T1Md5B0M7OZyqjh7il75c=
github.com/twpayne/go-vfs v0.1.6/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8=
github.com/twpayne/go-vfs v1.0.2 h1:6auHbiujxMUlqIAEVmJPXYC8/+27P9hBaUKnZujg7nk=
github.com/twpayne/go-vfs v1.0.2/go.mod h1:OIXA6zWkcn7Jk46XT7ceYqBMeIkfzJ8WOBhGJM0W4y8=
github.com/twpayne/go-xdg v1.0.0 h1:k+QM2LL00/zx/gvxKsCMRBJ8nxCBWDBe2LU9y3Xo7x0=
github.com/twpayne/go-xdg v1.0.0/go.mod h1:SHOoqWXTQT0rcQM4isbONZ6bH6uwIBgXuymEVgTc+Ao=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
Expand Down
2 changes: 1 addition & 1 deletion lib/chezmoi/chezmoi.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type templateFuncError struct {

// An Entry is either a Dir, a File, or a Symlink.
type Entry interface {
Apply(fs vfs.FS, targetDir string, umask os.FileMode, mutator Mutator) error
Apply(fs vfs.FS, targetDir string, ignore func(string) bool, umask os.FileMode, mutator Mutator) error
ConcreteValue(targetDir, sourceDir string, recursive bool) (interface{}, error)
Evaluate() error
SourceName() string
Expand Down
7 changes: 5 additions & 2 deletions lib/chezmoi/dir.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ func newDir(sourceName string, targetName string, exact bool, perm os.FileMode)
}

// Apply ensures that targetDir in fs matches d.
func (d *Dir) Apply(fs vfs.FS, targetDir string, umask os.FileMode, mutator Mutator) error {
func (d *Dir) Apply(fs vfs.FS, targetDir string, ignore func(string) bool, umask os.FileMode, mutator Mutator) error {
if ignore(d.targetName) {
return nil
}
targetPath := filepath.Join(targetDir, d.targetName)
info, err := fs.Lstat(targetPath)
switch {
Expand All @@ -109,7 +112,7 @@ func (d *Dir) Apply(fs vfs.FS, targetDir string, umask os.FileMode, mutator Muta
return err
}
for _, entryName := range sortedEntryNames(d.Entries) {
if err := d.Entries[entryName].Apply(fs, targetDir, umask, mutator); err != nil {
if err := d.Entries[entryName].Apply(fs, targetDir, ignore, umask, mutator); err != nil {
return err
}
}
Expand Down
5 changes: 4 additions & 1 deletion lib/chezmoi/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ func (fa FileAttributes) SourceName() string {
}

// Apply ensures that the state of targetPath in fs matches f.
func (f *File) Apply(fs vfs.FS, targetDir string, umask os.FileMode, mutator Mutator) error {
func (f *File) Apply(fs vfs.FS, targetDir string, ignore func(string) bool, umask os.FileMode, mutator Mutator) error {
if ignore(f.targetName) {
return nil
}
contents, err := f.Contents()
if err != nil {
return err
Expand Down
30 changes: 30 additions & 0 deletions lib/chezmoi/pattern_set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package chezmoi

import "path/filepath"

// An PatternSet is a set of patterns.
type PatternSet map[string]struct{}

// NewPatternSet returns a new PatternSet.
func NewPatternSet() PatternSet {
return PatternSet(make(map[string]struct{}))
}

// Add adds pattern to ps.
func (ps PatternSet) Add(pattern string) error {
if _, err := filepath.Match(pattern, ""); err != nil {
return nil
}
ps[pattern] = struct{}{}
return nil
}

// Match returns if name matches any pattern in ps.
func (ps PatternSet) Match(name string) bool {
for pattern := range ps {
if ok, _ := filepath.Match(pattern, name); ok {
return true
}
}
return false
}
5 changes: 4 additions & 1 deletion lib/chezmoi/symlink.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ type symlinkConcreteValue struct {
}

// Apply ensures that the state of s's target in fs matches s.
func (s *Symlink) Apply(fs vfs.FS, targetDir string, umask os.FileMode, mutator Mutator) error {
func (s *Symlink) Apply(fs vfs.FS, targetDir string, ignore func(string) bool, umask os.FileMode, mutator Mutator) error {
if ignore(s.targetName) {
return nil
}
target, err := s.Linkname()
if err != nil {
return err
Expand Down
62 changes: 48 additions & 14 deletions lib/chezmoi/target_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package chezmoi

import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
Expand Down Expand Up @@ -33,23 +34,25 @@ type ImportTAROptions struct {

// A TargetState represents the root target state.
type TargetState struct {
TargetDir string
Umask os.FileMode
SourceDir string
Data map[string]interface{}
Funcs template.FuncMap
Entries map[string]Entry
TargetDir string
TargetIgnore PatternSet
Umask os.FileMode
SourceDir string
Data map[string]interface{}
Funcs template.FuncMap
Entries map[string]Entry
}

// NewTargetState creates a new TargetState.
func NewTargetState(targetDir string, umask os.FileMode, sourceDir string, data map[string]interface{}, funcs template.FuncMap) *TargetState {
return &TargetState{
TargetDir: targetDir,
Umask: umask,
SourceDir: sourceDir,
Data: data,
Funcs: funcs,
Entries: make(map[string]Entry),
TargetDir: targetDir,
TargetIgnore: NewPatternSet(),
Umask: umask,
SourceDir: sourceDir,
Data: data,
Funcs: funcs,
Entries: make(map[string]Entry),
}
}

Expand Down Expand Up @@ -168,7 +171,7 @@ func (ts *TargetState) Archive(w *tar.Writer, umask os.FileMode) error {
// Apply ensures that ts.TargetDir in fs matches ts.
func (ts *TargetState) Apply(fs vfs.FS, mutator Mutator) error {
for _, entryName := range sortedEntryNames(ts.Entries) {
if err := ts.Entries[entryName].Apply(fs, ts.TargetDir, ts.Umask, mutator); err != nil {
if err := ts.Entries[entryName].Apply(fs, ts.TargetDir, ts.TargetIgnore.Match, ts.Umask, mutator); err != nil {
return err
}
}
Expand Down Expand Up @@ -242,8 +245,12 @@ func (ts *TargetState) Populate(fs vfs.FS) error {
if relPath == "." {
return nil
}
// Ignore all files and directories beginning with "."
// Treat all files and directories beginning with "." specially.
if _, name := filepath.Split(relPath); strings.HasPrefix(name, ".") {
if info.Name() == ".chezmoiignore" {
return ts.addSourceIgnore(fs, path, relPath)
}
// Ignore all other files and directories.
if info.IsDir() {
return filepath.SkipDir
}
Expand Down Expand Up @@ -546,3 +553,30 @@ func (ts *TargetState) importHeader(r io.Reader, importTAROptions ImportTAROptio
return fmt.Errorf("%s: unspported typeflag '%c'", header.Name, header.Typeflag)
}
}

func (ts *TargetState) addSourceIgnore(fs vfs.FS, path, relPath string) error {
data, err := ts.executeTemplate(fs, path)
if err != nil {
return err
}
dir := filepath.Dir(relPath)
s := bufio.NewScanner(bytes.NewReader(data))
for s.Scan() {
text := s.Text()
if index := strings.IndexRune(text, '#'); index != -1 {
text = text[:index]
}
text = strings.TrimSpace(text)
if text == "" {
continue
}
pattern := filepath.Join(dir, text)
if err := ts.TargetIgnore.Add(pattern); err != nil {
return fmt.Errorf("%s: %v", path, err)
}
}
if err := s.Err(); err != nil {
return fmt.Errorf("%s: %v", path, err)
}
return nil
}
Loading

0 comments on commit c68d36a

Please sign in to comment.