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
}
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
}