Преглед на файлове

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.
Alexander Larsson преди 11 години
родител
ревизия
02b5f1369c
променени са 1 файла, в които са добавени 137 реда и са изтрити 52 реда
  1. 137 52
      changes.go

+ 137 - 52
changes.go

@@ -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) {
-	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
+	pathElements := strings.Split(path, "/")
+	for _, elem := range pathElements {
+		if elem != "" {
+			child := parent.children[elem]
+			if child == nil {
+				return nil
+			}
+			parent = child
 		}
 		}
-		relPath = filepath.Join("/", relPath)
+	}
+	return parent
+}
 
 
-		// Skip root
-		if relPath == "/" || relPath == "/.docker-id" {
-			return nil
-		}
+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{
 		change := Change{
-			Path: relPath,
+			Path: info.path(),
+			Kind: ChangeAdd,
 		}
 		}
+		*changes = append(*changes, change)
+	}
 
 
-		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
+	// 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
 		}
 		}
+	}
 
 
-		if oldStat == nil {
-			change.Kind = ChangeAdd
-			changes = append(changes, change)
-		} else {
+	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
-				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)
+	}
+	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(oldDir, func(oldPath string, f os.FileInfo, err error) error {
+
+	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{
-			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)
+		info := &FileInfo {
+			name: filepath.Base(relPath),
+			children: make(map[string]*FileInfo),
+			parent: parent,
+		}
 
 
-		var newStat = &syscall.Stat_t{}
-		err = syscall.Lstat(newPath, newStat)
-		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
 }
 }