Refactor changes and test functions

Remove change type in favor of explicit change function.
Using change function makes it more difficult to unnecessarily
add to the change interface.

Update test apply functions to use an interface rather
than a function type.

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2017-02-02 17:30:11 -08:00
parent 65e8c07847
commit d96e6e3952
5 changed files with 172 additions and 193 deletions

View file

@ -16,14 +16,14 @@ import (
// setxattr fstest.SetXAttr("/home", "trusted.overlay.opaque", "y"),
func TestCopyDirectory(t *testing.T) {
apply := fstest.MultiApply(
fstest.CreateDirectory("/etc/", 0755),
fstest.NewTestFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
apply := fstest.Apply(
fstest.CreateDir("/etc/", 0755),
fstest.CreateFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644),
fstest.Link("/etc/hosts", "/etc/hosts.allow"),
fstest.CreateDirectory("/usr/local/lib", 0755),
fstest.NewTestFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
fstest.CreateDir("/usr/local/lib", 0755),
fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755),
fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"),
fstest.CreateDirectory("/home", 0755),
fstest.CreateDir("/home", 0755),
)
if err := testCopy(apply); err != nil {
@ -41,7 +41,7 @@ func testCopy(apply fstest.Applier) error {
return errors.Wrap(err, "failed to create temporary directory")
}
if err := apply(t1); err != nil {
if err := apply.Apply(t1); err != nil {
return errors.Wrap(err, "failed to apply changes")
}

View file

@ -48,12 +48,17 @@ type Change struct {
Path string
}
// Changes computes changes between lower and upper calling the
// given change function for each computed change. Callbacks
// will be done serialially and order by path name.
// ChangeFunc is the type of function called for each change
// computed during a directory changes calculation.
type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error
// Changes computes changes between two directories calling the
// given change function for each computed change. The first
// directory is intended to the base directory and second
// directory the changed directory.
//
// Changes are ordered by name and should be appliable in the
// order in which they received.
// The change callback is called by the order of path names and
// should be appliable in that order.
// Due to this apply ordering, the following is true
// - Removed directory trees only create a single change for the root
// directory removed. Remaining changes are implied.
@ -62,7 +67,7 @@ type Change struct {
// by the removal of the parent directory.
//
// Opaque directories will not be treated specially and each file
// removed from the lower will show up as a removal
// removed from the base directory will show up as a removal.
//
// File content comparisons will be done on files which have timestamps
// which may have been truncated. If either of the files being compared
@ -71,22 +76,20 @@ type Change struct {
// nanosecond values where one of those values is zero, the files will
// be considered unchanged if the content is the same. This behavior
// is to account for timestamp truncation during archiving.
func Changes(ctx context.Context, upper, lower string, ch func(Change, os.FileInfo) error) error {
if lower == "" {
logrus.Debugf("Using single walk diff for %s", upper)
return addDirChanges(ctx, ch, upper)
} else if diffOptions := detectDirDiff(upper, lower); diffOptions != nil {
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, lower)
return diffDirChanges(ctx, ch, lower, diffOptions)
func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error {
if a == "" {
logrus.Debugf("Using single walk diff for %s", b)
return addDirChanges(ctx, changeFn, b)
} else if diffOptions := detectDirDiff(b, a); diffOptions != nil {
logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a)
return diffDirChanges(ctx, changeFn, a, diffOptions)
}
logrus.Debugf("Using double walk diff for %s from %s", upper, lower)
return doubleWalkDiff(ctx, ch, upper, lower)
logrus.Debugf("Using double walk diff for %s from %s", b, a)
return doubleWalkDiff(ctx, changeFn, a, b)
}
type changeFn func(Change, os.FileInfo) error
func addDirChanges(ctx context.Context, changes changeFn, root string) error {
func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error {
return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
@ -105,25 +108,20 @@ func addDirChanges(ctx context.Context, changes changeFn, root string) error {
return nil
}
change := Change{
Path: path,
Kind: ChangeKindAdd,
}
return changes(change, f)
return changeFn(ChangeKindAdd, path, f, nil)
})
}
// diffDirOptions is used when the diff can be directly calculated from
// a diff directory to its lower, without walking both trees.
// a diff directory to its base, without walking both trees.
type diffDirOptions struct {
diffDir string
skipChange func(string) (bool, error)
deleteChange func(string, string, os.FileInfo) (string, error)
}
// diffDirChanges walks the diff directory and compares changes against the lower.
func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diffDirOptions) error {
// diffDirChanges walks the diff directory and compares changes against the base.
func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error {
changedDirs := make(map[string]struct{})
return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error {
if err != nil {
@ -152,9 +150,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
}
}
change := Change{
Path: path,
}
var kind ChangeKind
deletedFile, err := o.deleteChange(o.diffDir, path, f)
if err != nil {
@ -163,20 +159,20 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
// Find out what kind of modification happened
if deletedFile != "" {
change.Path = deletedFile
change.Kind = ChangeKindDelete
path = deletedFile
kind = ChangeKindDelete
f = nil
} else {
// Otherwise, the file was added
change.Kind = ChangeKindAdd
kind = ChangeKindAdd
// ...Unless it already existed in a lower, in which case, it's a modification
stat, err := os.Stat(filepath.Join(lower, path))
// ...Unless it already existed in a base, in which case, it's a modification
stat, err := os.Stat(filepath.Join(base, path))
if err != nil && !os.IsNotExist(err) {
return err
}
if err == nil {
// The file existed in the lower, so that's a modification
// The file existed in the base, so that's a modification
// However, if it's a directory, maybe it wasn't actually modified.
// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
@ -186,7 +182,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
return nil
}
}
change.Kind = ChangeKindModify
kind = ChangeKindModify
}
}
@ -197,30 +193,23 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff
if f.IsDir() {
changedDirs[path] = struct{}{}
}
if change.Kind == ChangeKindAdd || change.Kind == ChangeKindDelete {
if kind == ChangeKindAdd || kind == ChangeKindDelete {
parent := filepath.Dir(path)
if _, ok := changedDirs[parent]; !ok && parent != "/" {
pi, err := os.Stat(filepath.Join(o.diffDir, parent))
if err != nil {
return err
}
dirChange := Change{
Path: parent,
Kind: ChangeKindModify,
}
if err := changes(dirChange, pi); err != nil {
if err := changeFn(ChangeKindModify, parent, pi, err); err != nil {
return err
}
changedDirs[parent] = struct{}{}
}
}
return changes(change, f)
return changeFn(kind, path, f, nil)
})
}
// doubleWalkDiff walks both directories to create a diff
func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) (err error) {
func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) {
g, ctx := errgroup.WithContext(ctx)
var (
@ -232,11 +221,11 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
)
g.Go(func() error {
defer close(c1)
return pathWalk(ctx, lower, c1)
return pathWalk(ctx, a, c1)
})
g.Go(func() error {
defer close(c2)
return pathWalk(ctx, upper, c2)
return pathWalk(ctx, b, c2)
})
g.Go(func() error {
for c1 != nil || c2 != nil {
@ -264,8 +253,8 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
}
var f os.FileInfo
c := pathChange(f1, f2)
switch c.Kind {
k, p := pathChange(f1, f2)
switch k {
case ChangeKindAdd:
if rmdir != "" {
rmdir = ""
@ -301,7 +290,7 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string)
continue
}
}
if err := changes(c, f); err != nil {
if err := changeFn(k, p, f, nil); err != nil {
return err
}
}

View file

@ -21,21 +21,21 @@ import (
// - hardlink test
func TestSimpleDiff(t *testing.T) {
l1 := fstest.MultiApply(
fstest.CreateDirectory("/etc", 0755),
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
fstest.NewTestFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644),
fstest.NewTestFile("/etc/unexpected", []byte("#!/bin/sh"), 0644),
l1 := fstest.Apply(
fstest.CreateDir("/etc", 0755),
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
fstest.CreateFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644),
fstest.CreateFile("/etc/unexpected", []byte("#!/bin/sh"), 0644),
)
l2 := fstest.MultiApply(
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644),
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0666),
fstest.CreateDirectory("/root", 0700),
fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
l2 := fstest.Apply(
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644),
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0666),
fstest.CreateDir("/root", 0700),
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
fstest.RemoveFile("/etc/unexpected"),
)
diff := []Change{
diff := []testChange{
Modify("/etc/hosts"),
Modify("/etc/profile"),
Delete("/etc/unexpected"),
@ -49,18 +49,18 @@ func TestSimpleDiff(t *testing.T) {
}
func TestDirectoryReplace(t *testing.T) {
l1 := fstest.MultiApply(
fstest.CreateDirectory("/dir1", 0755),
fstest.NewTestFile("/dir1/f1", []byte("#####"), 0644),
fstest.CreateDirectory("/dir1/f2", 0755),
fstest.NewTestFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644),
l1 := fstest.Apply(
fstest.CreateDir("/dir1", 0755),
fstest.CreateFile("/dir1/f1", []byte("#####"), 0644),
fstest.CreateDir("/dir1/f2", 0755),
fstest.CreateFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644),
)
l2 := fstest.MultiApply(
fstest.NewTestFile("/dir1/f11", []byte("#New file here"), 0644),
l2 := fstest.Apply(
fstest.CreateFile("/dir1/f11", []byte("#New file here"), 0644),
fstest.RemoveFile("/dir1/f2"),
fstest.NewTestFile("/dir1/f2", []byte("Now file"), 0666),
fstest.CreateFile("/dir1/f2", []byte("Now file"), 0666),
)
diff := []Change{
diff := []testChange{
Add("/dir1/f11"),
Modify("/dir1/f2"),
}
@ -71,15 +71,15 @@ func TestDirectoryReplace(t *testing.T) {
}
func TestRemoveDirectoryTree(t *testing.T) {
l1 := fstest.MultiApply(
fstest.CreateDirectory("/dir1/dir2/dir3", 0755),
fstest.NewTestFile("/dir1/f1", []byte("f1"), 0644),
fstest.NewTestFile("/dir1/dir2/f2", []byte("f2"), 0644),
l1 := fstest.Apply(
fstest.CreateDir("/dir1/dir2/dir3", 0755),
fstest.CreateFile("/dir1/f1", []byte("f1"), 0644),
fstest.CreateFile("/dir1/dir2/f2", []byte("f2"), 0644),
)
l2 := fstest.MultiApply(
l2 := fstest.Apply(
fstest.RemoveFile("/dir1"),
)
diff := []Change{
diff := []testChange{
Delete("/dir1"),
}
@ -89,15 +89,15 @@ func TestRemoveDirectoryTree(t *testing.T) {
}
func TestFileReplace(t *testing.T) {
l1 := fstest.MultiApply(
fstest.NewTestFile("/dir1", []byte("a file, not a directory"), 0644),
l1 := fstest.Apply(
fstest.CreateFile("/dir1", []byte("a file, not a directory"), 0644),
)
l2 := fstest.MultiApply(
l2 := fstest.Apply(
fstest.RemoveFile("/dir1"),
fstest.CreateDirectory("/dir1/dir2", 0755),
fstest.NewTestFile("/dir1/dir2/f1", []byte("also a file"), 0644),
fstest.CreateDir("/dir1/dir2", 0755),
fstest.CreateFile("/dir1/dir2/f1", []byte("also a file"), 0644),
)
diff := []Change{
diff := []testChange{
Modify("/dir1"),
Add("/dir1/dir2"),
Add("/dir1/dir2/f1"),
@ -112,31 +112,31 @@ func TestUpdateWithSameTime(t *testing.T) {
tt := time.Now().Truncate(time.Second)
t1 := tt.Add(5 * time.Nanosecond)
t2 := tt.Add(6 * time.Nanosecond)
l1 := fstest.MultiApply(
fstest.NewTestFile("/file-modified-time", []byte("1"), 0644),
l1 := fstest.Apply(
fstest.CreateFile("/file-modified-time", []byte("1"), 0644),
fstest.Chtime("/file-modified-time", t1),
fstest.NewTestFile("/file-no-change", []byte("1"), 0644),
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
fstest.Chtime("/file-no-change", t1),
fstest.NewTestFile("/file-same-time", []byte("1"), 0644),
fstest.CreateFile("/file-same-time", []byte("1"), 0644),
fstest.Chtime("/file-same-time", t1),
fstest.NewTestFile("/file-truncated-time-1", []byte("1"), 0644),
fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644),
fstest.Chtime("/file-truncated-time-1", t1),
fstest.NewTestFile("/file-truncated-time-2", []byte("1"), 0644),
fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644),
fstest.Chtime("/file-truncated-time-2", tt),
)
l2 := fstest.MultiApply(
fstest.NewTestFile("/file-modified-time", []byte("2"), 0644),
l2 := fstest.Apply(
fstest.CreateFile("/file-modified-time", []byte("2"), 0644),
fstest.Chtime("/file-modified-time", t2),
fstest.NewTestFile("/file-no-change", []byte("1"), 0644),
fstest.CreateFile("/file-no-change", []byte("1"), 0644),
fstest.Chtime("/file-no-change", tt), // use truncated time, should be regarded as no change
fstest.NewTestFile("/file-same-time", []byte("2"), 0644),
fstest.CreateFile("/file-same-time", []byte("2"), 0644),
fstest.Chtime("/file-same-time", t1),
fstest.NewTestFile("/file-truncated-time-1", []byte("2"), 0644),
fstest.CreateFile("/file-truncated-time-1", []byte("2"), 0644),
fstest.Chtime("/file-truncated-time-1", tt),
fstest.NewTestFile("/file-truncated-time-2", []byte("2"), 0644),
fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644),
fstest.Chtime("/file-truncated-time-2", tt),
)
diff := []Change{
diff := []testChange{
// "/file-same-time" excluded because matching non-zero nanosecond values
Modify("/file-modified-time"),
Modify("/file-truncated-time-1"),
@ -148,7 +148,7 @@ func TestUpdateWithSameTime(t *testing.T) {
}
}
func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
func testDiffWithBase(base, diff fstest.Applier, expected []testChange) error {
t1, err := ioutil.TempDir("", "diff-with-base-lower-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
@ -160,7 +160,7 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
}
defer os.RemoveAll(t2)
if err := base(t1); err != nil {
if err := base.Apply(t1); err != nil {
return errors.Wrap(err, "failed to apply base filesytem")
}
@ -168,11 +168,11 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
return errors.Wrap(err, "failed to copy base directory")
}
if err := diff(t2); err != nil {
if err := diff.Apply(t2); err != nil {
return errors.Wrap(err, "failed to apply diff filesystem")
}
changes, err := collectChanges(t2, t1)
changes, err := collectChanges(t1, t2)
if err != nil {
return errors.Wrap(err, "failed to collect changes")
}
@ -181,14 +181,14 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error {
}
func TestBaseDirectoryChanges(t *testing.T) {
apply := fstest.MultiApply(
fstest.CreateDirectory("/etc", 0755),
fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
fstest.CreateDirectory("/root", 0700),
fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
apply := fstest.Apply(
fstest.CreateDir("/etc", 0755),
fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644),
fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644),
fstest.CreateDir("/root", 0700),
fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644),
)
changes := []Change{
changes := []testChange{
Add("/etc"),
Add("/etc/hosts"),
Add("/etc/profile"),
@ -201,18 +201,18 @@ func TestBaseDirectoryChanges(t *testing.T) {
}
}
func testDiffWithoutBase(apply fstest.Applier, expected []Change) error {
func testDiffWithoutBase(apply fstest.Applier, expected []testChange) error {
tmp, err := ioutil.TempDir("", "diff-without-base-")
if err != nil {
return errors.Wrap(err, "failed to create temp dir")
}
defer os.RemoveAll(tmp)
if err := apply(tmp); err != nil {
if err := apply.Apply(tmp); err != nil {
return errors.Wrap(err, "failed to apply filesytem changes")
}
changes, err := collectChanges(tmp, "")
changes, err := collectChanges("", tmp)
if err != nil {
return errors.Wrap(err, "failed to collect changes")
}
@ -220,13 +220,13 @@ func testDiffWithoutBase(apply fstest.Applier, expected []Change) error {
return checkChanges(tmp, changes, expected)
}
func checkChanges(root string, changes []testChange, expected []Change) error {
func checkChanges(root string, changes, expected []testChange) error {
if len(changes) != len(expected) {
return errors.Errorf("Unexpected number of changes:\n%s", diffString(convertTestChanges(changes), expected))
return errors.Errorf("Unexpected number of changes:\n%s", diffString(changes, expected))
}
for i := range changes {
if changes[i].Path != expected[i].Path || changes[i].Kind != expected[i].Kind {
return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(convertTestChanges(changes), expected))
return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(changes, expected))
}
if changes[i].Kind != ChangeKindDelete {
filename := filepath.Join(root, changes[i].Path)
@ -254,18 +254,23 @@ func checkChanges(root string, changes []testChange, expected []Change) error {
}
type testChange struct {
Change
Kind ChangeKind
Path string
FileInfo os.FileInfo
Source string
}
func collectChanges(upper, lower string) ([]testChange, error) {
func collectChanges(a, b string) ([]testChange, error) {
changes := []testChange{}
err := Changes(context.Background(), upper, lower, func(c Change, f os.FileInfo) error {
err := Changes(context.Background(), a, b, func(k ChangeKind, p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
changes = append(changes, testChange{
Change: c,
Kind: k,
Path: p,
FileInfo: f,
Source: filepath.Join(upper, c.Path),
Source: filepath.Join(b, p),
})
return nil
})
@ -276,20 +281,12 @@ func collectChanges(upper, lower string) ([]testChange, error) {
return changes, nil
}
func convertTestChanges(c []testChange) []Change {
nc := make([]Change, len(c))
for i := range c {
nc[i] = c[i].Change
}
return nc
}
func diffString(c1, c2 []Change) string {
func diffString(c1, c2 []testChange) string {
return fmt.Sprintf("got(%d):\n%s\nexpected(%d):\n%s", len(c1), changesString(c1), len(c2), changesString(c2))
}
func changesString(c []Change) string {
func changesString(c []testChange) string {
strs := make([]string, len(c))
for i := range c {
strs[i] = fmt.Sprintf("\t%s\t%s", c[i].Kind, c[i].Path)
@ -297,22 +294,22 @@ func changesString(c []Change) string {
return strings.Join(strs, "\n")
}
func Add(p string) Change {
return Change{
func Add(p string) testChange {
return testChange{
Kind: ChangeKindAdd,
Path: p,
}
}
func Delete(p string) Change {
return Change{
func Delete(p string) testChange {
return testChange{
Kind: ChangeKindDelete,
Path: p,
}
}
func Modify(p string) Change {
return Change{
func Modify(p string) testChange {
return testChange{
Kind: ChangeKindModify,
Path: p,
}

View file

@ -10,12 +10,20 @@ import (
)
// Applier applies single file changes
type Applier func(root string) error
type Applier interface {
Apply(root string) error
}
// NewTestFile returns a file applier which creates a file as the
type applyFn func(root string) error
func (a applyFn) Apply(root string) error {
return a(root)
}
// CreateFile returns a file applier which creates a file as the
// provided name with the given content and permission.
func NewTestFile(name string, content []byte, perm os.FileMode) Applier {
return func(root string) error {
func CreateFile(name string, content []byte, perm os.FileMode) Applier {
return applyFn(func(root string) error {
fullPath := filepath.Join(root, name)
if err := ioutil.WriteFile(fullPath, content, perm); err != nil {
return err
@ -26,67 +34,67 @@ func NewTestFile(name string, content []byte, perm os.FileMode) Applier {
}
return nil
}
})
}
// RemoveFile returns a file applier which removes the provided file name
func RemoveFile(name string) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.RemoveAll(filepath.Join(root, name))
}
})
}
// CreateDirectory returns a file applier to create the directory with
// CreateDir returns a file applier to create the directory with
// the provided name and permission
func CreateDirectory(name string, perm os.FileMode) Applier {
return func(root string) error {
func CreateDir(name string, perm os.FileMode) Applier {
return applyFn(func(root string) error {
fullPath := filepath.Join(root, name)
if err := os.MkdirAll(fullPath, perm); err != nil {
return err
}
return nil
}
})
}
// Rename returns a file applier which renames a file
func Rename(old, new string) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.Rename(filepath.Join(root, old), filepath.Join(root, new))
}
})
}
// Chown returns a file applier which changes the ownership of a file
func Chown(name string, uid, gid int) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.Chown(filepath.Join(root, name), uid, gid)
}
})
}
// Chtime changes access and mod time of file
func Chtime(name string, t time.Time) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.Chtimes(filepath.Join(root, name), t, t)
}
})
}
// Symlink returns a file applier which creates a symbolic link
func Symlink(oldname, newname string) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.Symlink(oldname, filepath.Join(root, newname))
}
})
}
// Link returns a file applier which creates a hard link
func Link(oldname, newname string) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return os.Link(filepath.Join(root, oldname), filepath.Join(root, newname))
}
})
}
func SetXAttr(name, key, value string) Applier {
return func(root string) error {
return applyFn(func(root string) error {
return sysx.LSetxattr(name, key, []byte(value), 0)
}
})
}
// TODO: Make platform specific, windows applier is always no-op
@ -96,14 +104,14 @@ func SetXAttr(name, key, value string) Applier {
// }
//}
// MultiApply returns a new applier from the given appliers
func MultiApply(appliers ...Applier) Applier {
return func(root string) error {
// Apply returns a new applier from the given appliers
func Apply(appliers ...Applier) Applier {
return applyFn(func(root string) error {
for _, a := range appliers {
if err := a(root); err != nil {
if err := a.Apply(root); err != nil {
return err
}
}
return nil
}
})
}

View file

@ -15,42 +15,27 @@ type currentPath struct {
fullPath string
}
func pathChange(lower, upper *currentPath) Change {
func pathChange(lower, upper *currentPath) (ChangeKind, string) {
if lower == nil {
if upper == nil {
panic("cannot compare nil paths")
}
return Change{
Kind: ChangeKindAdd,
Path: upper.path,
}
return ChangeKindAdd, upper.path
}
if upper == nil {
return Change{
Kind: ChangeKindDelete,
Path: lower.path,
}
return ChangeKindDelete, lower.path
}
// TODO: compare by directory
switch i := strings.Compare(lower.path, upper.path); {
case i < 0:
// File in lower that is not in upper
return Change{
Kind: ChangeKindDelete,
Path: lower.path,
}
return ChangeKindDelete, lower.path
case i > 0:
// File in upper that is not in lower
return Change{
Kind: ChangeKindAdd,
Path: upper.path,
}
return ChangeKindAdd, upper.path
default:
return Change{
Kind: ChangeKindModify,
Path: upper.path,
}
return ChangeKindModify, upper.path
}
}