Pārlūkot izejas kodu

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 gadi atpakaļ
vecāks
revīzija
02b5f1369c
1 mainītis faili ar 137 papildinājumiem un 52 dzēšanām
  1. 137 52
      changes.go

+ 137 - 52
changes.go

@@ -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
+	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{
-			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 ||
 				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)
+	}
+	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 {
 			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)
+		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
 	})
 	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
 }