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.
Upstream-commit: 727e7fcccadf1d3e286f5a3c8d1aa388f6b4dab8
Component: engine
This commit is contained in:
Alexander Larsson
2013-09-13 12:56:58 +02:00
committed by Tianon Gravi
parent ff5de9f609
commit ce9d889436

View File

@ -106,50 +106,85 @@ func ChangesAUFS(layers []string, rw string) ([]Change, error) {
return changes, nil
}
func ChangesDirs(newDir, oldDir string) ([]Change, error) {
var changes []Change
err := filepath.Walk(newDir, func(newPath string, f os.FileInfo, err error) error {
if err != nil {
return err
}
type FileInfo struct {
parent *FileInfo
name string
stat syscall.Stat_t
children map[string]*FileInfo
}
var newStat syscall.Stat_t
err = syscall.Lstat(newPath, &newStat)
if err != nil {
return err
}
func (root *FileInfo) LookUp(path string) *FileInfo {
parent := root
if path == "/" {
return root
}
// Rebase path
relPath, err := filepath.Rel(newDir, newPath)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
// 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
pathElements := strings.Split(path, "/")
for _, elem := range pathElements {
if elem != "" {
child := parent.children[elem]
if child == nil {
return nil
}
oldStat = nil
parent = child
}
}
return parent
}
if oldStat == nil {
change.Kind = ChangeAdd
changes = append(changes, change)
} else {
func (info *FileInfo)path() string {
if info.parent == nil {
return "/"
}
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 ||
oldStat.Mode != newStat.Mode ||
oldStat.Uid != newStat.Uid ||
@ -159,50 +194,100 @@ func ChangesDirs(newDir, oldDir string) ([]Change, error) {
oldStat.Blocks != newStat.Blocks ||
oldStat.Mtim != newStat.Mtim ||
oldStat.Ctim != newStat.Ctim {
change.Kind = ChangeModify
changes = append(changes, change)
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
return nil
})
if err != nil {
return nil, err
newChild.addChanges(oldChild, changes)
}
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 {
return err
}
// Rebase path
relPath, err := filepath.Rel(oldDir, oldPath)
relPath, err := filepath.Rel(sourceDir, path)
if err != nil {
return err
}
relPath = filepath.Join("/", relPath)
// Skip root
if relPath == "/" {
return nil
}
change := Change{
Path: relPath,
parent := root.LookUp(filepath.Dir(relPath))
if parent == nil {
return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
}
newPath := filepath.Join(newDir, relPath)
var newStat = &syscall.Stat_t{}
err = syscall.Lstat(newPath, newStat)
if err != nil && os.IsNotExist(err) {
change.Kind = ChangeDelete
changes = append(changes, change)
info := &FileInfo {
name: filepath.Base(relPath),
children: make(map[string]*FileInfo),
parent: parent,
}
if err := syscall.Lstat(path, &info.stat); err != nil {
return err
}
parent.children[info.name] = info
return nil
})
if err != nil {
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
}