1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Change how ChangesDirs() works

Rather than scan the files in the old directory twice to detect the
deletions we now scan both directories twice and then do all the
diffing on the in-memory structure.

This is more efficient, but it also lets us diff more complex things
later that are not exact on-disk trees.
This commit is contained in:
Alexander Larsson 2013-09-13 12:56:58 +02:00 committed by Victor Vieux
parent d478a4bb54
commit 02b5f1369c

View file

@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) {
return changes, nil return changes, nil
} }
func ChangesDirs(newDir, oldDir string) ([]Change, error) { type FileInfo struct {
var changes []Change parent *FileInfo
err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error { name string
if err != nil { stat syscall.Stat_t
return err children map[string]*FileInfo
} }
var newStat syscall.Stat_t func (root *FileInfo) LookUp(path string) *FileInfo {
err = syscall.Lstat(newPath, &newStat) parent := root
if err != nil { if path == "/" {
return err return root
} }
// Rebase path pathElements := strings.Split(path, "/")
relPath, err := filepath.Rel(newDir, newPath) for _, elem := range pathElements {
if err != nil { if elem != "" {
return err child := parent.children[elem]
} if child == nil {
relPath = filepath.Join("/", relPath) return nil
// Skip root
if relPath == "/" || relPath == "/.docker-id" {
return nil
}
change := Change{
Path: relPath,
}
oldPath := filepath.Join(oldDir, relPath)
var oldStat = &syscall.Stat_t{}
err = syscall.Lstat(oldPath, oldStat)
if err != nil {
if !os.IsNotExist(err) {
return err
} }
oldStat = nil parent = child
} }
}
return parent
}
if oldStat == nil { func (info *FileInfo)path() string {
change.Kind = ChangeAdd if info.parent == nil {
changes = append(changes, change) return "/"
} else { }
return filepath.Join(info.parent.path(), info.name)
}
func (info *FileInfo)unlink() {
if info.parent != nil {
delete(info.parent.children, info.name)
}
}
func (info *FileInfo)Remove(path string) bool {
child := info.LookUp(path)
if child != nil {
child.unlink()
return true
}
return false
}
func (info *FileInfo)isDir() bool {
return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}
func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) {
if oldInfo == nil {
// add
change := Change{
Path: info.path(),
Kind: ChangeAdd,
}
*changes = append(*changes, change)
}
// We make a copy so we can modify it to detect additions
// also, we only recurse on the old dir if the new info is a directory
// otherwise any previous delete/change is considered recursive
oldChildren := make(map[string]*FileInfo)
if oldInfo != nil && info.isDir() {
for k, v := range oldInfo.children {
oldChildren[k] = v
}
}
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := &oldChild.stat
newStat := &newChild.stat
if oldStat.Ino != newStat.Ino || if oldStat.Ino != newStat.Ino ||
oldStat.Mode != newStat.Mode || oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid || oldStat.Uid != newStat.Uid ||
@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldStat.Blocks != newStat.Blocks || oldStat.Blocks != newStat.Blocks ||
oldStat.Mtim != newStat.Mtim || oldStat.Mtim != newStat.Mtim ||
oldStat.Ctim != newStat.Ctim { oldStat.Ctim != newStat.Ctim {
change.Kind = ChangeModify change := Change{
changes = append(changes, change) Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
} }
// Remove from copy so we can detect deletions
delete(oldChildren, name)
} }
return nil newChild.addChanges(oldChild, changes)
})
if err != nil {
return nil, err
} }
err = filepath.Walk(oldDir, func(oldPath string, f os.FileInfo, err error) error { for _, oldChild := range oldChildren {
// delete
change := Change{
Path: oldChild.path(),
Kind: ChangeDelete,
}
*changes = append(*changes, change)
}
}
func (info *FileInfo)Changes(oldInfo *FileInfo) []Change {
var changes []Change
info.addChanges(oldInfo, &changes)
return changes
}
func collectFileInfo(sourceDir string) (*FileInfo, error) {
root := &FileInfo {
name: "/",
children: make(map[string]*FileInfo),
}
err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
} }
// Rebase path // Rebase path
relPath, err := filepath.Rel(oldDir, oldPath) relPath, err := filepath.Rel(sourceDir, path)
if err != nil { if err != nil {
return err return err
} }
relPath = filepath.Join("/", relPath) relPath = filepath.Join("/", relPath)
// Skip root
if relPath == "/" { if relPath == "/" {
return nil return nil
} }
change := Change{ parent := root.LookUp(filepath.Dir(relPath))
Path: relPath, if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
} }
newPath := filepath.Join(newDir, relPath) info := &FileInfo {
name: filepath.Base(relPath),
var newStat = &syscall.Stat_t{} children: make(map[string]*FileInfo),
err = syscall.Lstat(newPath, newStat) parent: parent,
if err != nil && os.IsNotExist(err) {
change.Kind = ChangeDelete
changes = append(changes, change)
} }
if err := syscall.Lstat(path, &info.stat); err != nil {
return err
}
parent.children[info.name] = info
return nil return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
return changes, nil return root, nil
}
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldRoot, err := collectFileInfo(oldDir)
if err != nil {
return nil, err
}
newRoot, err := collectFileInfo(newDir)
if err != nil {
return nil, err
}
// Ignore changes in .docker-id
_ = newRoot.Remove("/.docker-id")
_ = oldRoot.Remove("/.docker-id")
return newRoot.Changes(oldRoot), nil
} }