Add fs package
Add diff comparison with support for double walking two trees for comparison or single walking a diff tree. Single walking requires further implementation for specific mount types. Add directory copy function which is intended to provide fastest possible local copy of file system directories without hardlinking. Add test package to make creating filesystems for test easy and comparisons deep and informative. Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
parent
5f08e609c0
commit
574862fd89
16 changed files with 1672 additions and 0 deletions
189
fs/fstest/continuity_util.go
Normal file
189
fs/fstest/continuity_util.go
Normal file
|
@ -0,0 +1,189 @@
|
|||
package fstest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/stevvooe/continuity"
|
||||
)
|
||||
|
||||
type resourceUpdate struct {
|
||||
Original continuity.Resource
|
||||
Updated continuity.Resource
|
||||
}
|
||||
|
||||
func (u resourceUpdate) String() string {
|
||||
return fmt.Sprintf("%s(mode: %o, uid: %s, gid: %s) -> %s(mode: %o, uid: %s, gid: %s)",
|
||||
u.Original.Path(), u.Original.Mode(), u.Original.UID(), u.Original.GID(),
|
||||
u.Updated.Path(), u.Updated.Mode(), u.Updated.UID(), u.Updated.GID(),
|
||||
)
|
||||
}
|
||||
|
||||
type resourceListDifference struct {
|
||||
Additions []continuity.Resource
|
||||
Deletions []continuity.Resource
|
||||
Updates []resourceUpdate
|
||||
}
|
||||
|
||||
func (l resourceListDifference) HasDiff() bool {
|
||||
return len(l.Additions) > 0 || len(l.Deletions) > 0 || len(l.Updates) > 0
|
||||
}
|
||||
|
||||
func (l resourceListDifference) String() string {
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for _, add := range l.Additions {
|
||||
fmt.Fprintf(buf, "+ %s\n", add.Path())
|
||||
}
|
||||
for _, del := range l.Deletions {
|
||||
fmt.Fprintf(buf, "- %s\n", del.Path())
|
||||
}
|
||||
for _, upt := range l.Updates {
|
||||
fmt.Fprintf(buf, "~ %s\n", upt.String())
|
||||
}
|
||||
return string(buf.Bytes())
|
||||
}
|
||||
|
||||
// diffManifest compares two resource lists and returns the list
|
||||
// of adds updates and deletes, resource lists are not reordered
|
||||
// before doing difference.
|
||||
func diffResourceList(r1, r2 []continuity.Resource) resourceListDifference {
|
||||
i1 := 0
|
||||
i2 := 0
|
||||
var d resourceListDifference
|
||||
|
||||
for i1 < len(r1) && i2 < len(r2) {
|
||||
p1 := r1[i1].Path()
|
||||
p2 := r2[i2].Path()
|
||||
switch {
|
||||
case p1 < p2:
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
case p1 == p2:
|
||||
if !compareResource(r1[i1], r2[i2]) {
|
||||
d.Updates = append(d.Updates, resourceUpdate{
|
||||
Original: r1[i1],
|
||||
Updated: r2[i2],
|
||||
})
|
||||
}
|
||||
i1++
|
||||
i2++
|
||||
case p1 > p2:
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
}
|
||||
|
||||
for i1 < len(r1) {
|
||||
d.Deletions = append(d.Deletions, r1[i1])
|
||||
i1++
|
||||
|
||||
}
|
||||
for i2 < len(r2) {
|
||||
d.Additions = append(d.Additions, r2[i2])
|
||||
i2++
|
||||
}
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func compareResource(r1, r2 continuity.Resource) bool {
|
||||
if r1.Path() != r2.Path() {
|
||||
return false
|
||||
}
|
||||
if r1.Mode() != r2.Mode() {
|
||||
return false
|
||||
}
|
||||
if r1.UID() != r2.UID() {
|
||||
return false
|
||||
}
|
||||
if r1.GID() != r2.GID() {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO(dmcgowan): Check if is XAttrer
|
||||
|
||||
return compareResourceTypes(r1, r2)
|
||||
|
||||
}
|
||||
|
||||
func compareResourceTypes(r1, r2 continuity.Resource) bool {
|
||||
switch t1 := r1.(type) {
|
||||
case continuity.RegularFile:
|
||||
t2, ok := r2.(continuity.RegularFile)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareRegularFile(t1, t2)
|
||||
case continuity.Directory:
|
||||
t2, ok := r2.(continuity.Directory)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDirectory(t1, t2)
|
||||
case continuity.SymLink:
|
||||
t2, ok := r2.(continuity.SymLink)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareSymLink(t1, t2)
|
||||
case continuity.NamedPipe:
|
||||
t2, ok := r2.(continuity.NamedPipe)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareNamedPipe(t1, t2)
|
||||
case continuity.Device:
|
||||
t2, ok := r2.(continuity.Device)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return compareDevice(t1, t2)
|
||||
default:
|
||||
// TODO(dmcgowan): Should this panic?
|
||||
return r1 == r2
|
||||
}
|
||||
}
|
||||
|
||||
func compareRegularFile(r1, r2 continuity.RegularFile) bool {
|
||||
if r1.Size() != r2.Size() {
|
||||
return false
|
||||
}
|
||||
p1 := r1.Paths()
|
||||
p2 := r2.Paths()
|
||||
if len(p1) != len(p2) {
|
||||
return false
|
||||
}
|
||||
for i := range p1 {
|
||||
if p1[i] != p2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
d1 := r1.Digests()
|
||||
d2 := r2.Digests()
|
||||
if len(d1) != len(d2) {
|
||||
return false
|
||||
}
|
||||
for i := range d1 {
|
||||
if d1[i] != d2[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func compareSymLink(r1, r2 continuity.SymLink) bool {
|
||||
return r1.Target() == r2.Target()
|
||||
}
|
||||
|
||||
func compareDirectory(r1, r2 continuity.Directory) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareNamedPipe(r1, r2 continuity.NamedPipe) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func compareDevice(r1, r2 continuity.Device) bool {
|
||||
return r1.Major() == r2.Major() && r1.Minor() == r2.Minor()
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue