Merge pull request #252 from wagoodman/fix-log-and-tar-paths
Fix log and relative paths
This commit is contained in:
commit
35db1ec914
@ -121,6 +121,7 @@ func initLogging() {
|
|||||||
|
|
||||||
if viper.GetBool("log.enabled") {
|
if viper.GetBool("log.enabled") {
|
||||||
logFileObj, err = os.OpenFile(viper.GetString("log.path"), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
logFileObj, err = os.OpenFile(viper.GetString("log.path"), os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
log.SetOutput(logFileObj)
|
||||||
} else {
|
} else {
|
||||||
log.SetOutput(ioutil.Discard)
|
log.SetOutput(ioutil.Discard)
|
||||||
}
|
}
|
||||||
@ -139,7 +140,6 @@ func initLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.SetLevel(level)
|
log.SetLevel(level)
|
||||||
log.SetOutput(logFileObj)
|
|
||||||
log.Debug("Starting Dive...")
|
log.Debug("Starting Dive...")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,93 +0,0 @@
|
|||||||
package filetree
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TreeCacheKey struct {
|
|
||||||
bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
|
|
||||||
}
|
|
||||||
|
|
||||||
type TreeCache struct {
|
|
||||||
refTrees []*FileTree
|
|
||||||
cache map[TreeCacheKey]*FileTree
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *TreeCache) Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) (*FileTree, error) {
|
|
||||||
key := TreeCacheKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop}
|
|
||||||
if value, exists := cache.cache[key]; exists {
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
value, err := cache.buildTree(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cache.cache[key] = value
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *TreeCache) buildTree(key TreeCacheKey) (*FileTree, error) {
|
|
||||||
newTree, err := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
for idx := key.topTreeStart; idx <= key.topTreeStop; idx++ {
|
|
||||||
err := newTree.CompareAndMark(cache.refTrees[idx])
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("unable to build tree: %+v", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newTree, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cache *TreeCache) Build() error {
|
|
||||||
var bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
|
|
||||||
|
|
||||||
// case 1: layer compare (top tree SIZE is fixed (BUT floats forward), Bottom tree SIZE changes)
|
|
||||||
for selectIdx := 0; selectIdx < len(cache.refTrees); selectIdx++ {
|
|
||||||
bottomTreeStart = 0
|
|
||||||
topTreeStop = selectIdx
|
|
||||||
|
|
||||||
if selectIdx == 0 {
|
|
||||||
bottomTreeStop = selectIdx
|
|
||||||
topTreeStart = selectIdx
|
|
||||||
} else {
|
|
||||||
bottomTreeStop = selectIdx - 1
|
|
||||||
topTreeStart = selectIdx
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// case 2: aggregated compare (bottom tree is ENTIRELY fixed, top tree SIZE changes)
|
|
||||||
for selectIdx := 0; selectIdx < len(cache.refTrees); selectIdx++ {
|
|
||||||
bottomTreeStart = 0
|
|
||||||
topTreeStop = selectIdx
|
|
||||||
if selectIdx == 0 {
|
|
||||||
bottomTreeStop = selectIdx
|
|
||||||
topTreeStart = selectIdx
|
|
||||||
} else {
|
|
||||||
bottomTreeStop = 0
|
|
||||||
topTreeStart = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err := cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewFileTreeCache(refTrees []*FileTree) TreeCache {
|
|
||||||
|
|
||||||
return TreeCache{
|
|
||||||
refTrees: refTrees,
|
|
||||||
cache: make(map[TreeCacheKey]*FileTree),
|
|
||||||
}
|
|
||||||
}
|
|
175
dive/filetree/comparer.go
Normal file
175
dive/filetree/comparer.go
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
package filetree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TreeIndexKey struct {
|
||||||
|
bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTreeIndexKey(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) TreeIndexKey {
|
||||||
|
return TreeIndexKey{
|
||||||
|
bottomTreeStart: bottomTreeStart,
|
||||||
|
bottomTreeStop: bottomTreeStop,
|
||||||
|
topTreeStart: topTreeStart,
|
||||||
|
topTreeStop: topTreeStop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (index TreeIndexKey) String() string {
|
||||||
|
if index.bottomTreeStart == index.bottomTreeStop && index.topTreeStart == index.topTreeStop {
|
||||||
|
return fmt.Sprintf("Index(%d:%d)", index.bottomTreeStart, index.topTreeStart)
|
||||||
|
} else if index.bottomTreeStart == index.bottomTreeStop {
|
||||||
|
return fmt.Sprintf("Index(%d:%d-%d)", index.bottomTreeStart, index.topTreeStart, index.topTreeStop)
|
||||||
|
} else if index.topTreeStart == index.topTreeStop {
|
||||||
|
return fmt.Sprintf("Index(%d-%d:%d)", index.bottomTreeStart, index.bottomTreeStop, index.topTreeStart)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Index(%d-%d:%d-%d)", index.bottomTreeStart, index.bottomTreeStop, index.topTreeStart, index.topTreeStop)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comparer struct {
|
||||||
|
refTrees []*FileTree
|
||||||
|
trees map[TreeIndexKey]*FileTree
|
||||||
|
pathErrors map[TreeIndexKey][]PathError
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewComparer(refTrees []*FileTree) Comparer {
|
||||||
|
return Comparer{
|
||||||
|
refTrees: refTrees,
|
||||||
|
trees: make(map[TreeIndexKey]*FileTree),
|
||||||
|
pathErrors: make(map[TreeIndexKey][]PathError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmp *Comparer) GetPathErrors(key TreeIndexKey) ([]PathError, error) {
|
||||||
|
_, pathErrors, err := cmp.get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return pathErrors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmp *Comparer) GetTree(key TreeIndexKey) (*FileTree, error) {
|
||||||
|
//func (cmp *Comparer) GetTree(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) (*FileTree, []PathError, error) {
|
||||||
|
//key := TreeIndexKey{bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop}
|
||||||
|
|
||||||
|
if value, exists := cmp.trees[key]; exists {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value, pathErrors, err := cmp.get(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
cmp.trees[key] = value
|
||||||
|
cmp.pathErrors[key] = pathErrors
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmp *Comparer) get(key TreeIndexKey) (*FileTree, []PathError, error) {
|
||||||
|
newTree, pathErrors, err := StackTreeRange(cmp.refTrees, key.bottomTreeStart, key.bottomTreeStop)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
for idx := key.topTreeStart; idx <= key.topTreeStop; idx++ {
|
||||||
|
markPathErrors, err := newTree.CompareAndMark(cmp.refTrees[idx])
|
||||||
|
pathErrors = append(pathErrors, markPathErrors...)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error while building tree: %+v", err)
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newTree, pathErrors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 1: layer compare (top tree SIZE is fixed (BUT floats forward), Bottom tree SIZE changes)
|
||||||
|
func (cmp *Comparer) NaturalIndexes() <-chan TreeIndexKey {
|
||||||
|
indexes := make(chan TreeIndexKey)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(indexes)
|
||||||
|
|
||||||
|
var bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
|
||||||
|
|
||||||
|
for selectIdx := 0; selectIdx < len(cmp.refTrees); selectIdx++ {
|
||||||
|
bottomTreeStart = 0
|
||||||
|
topTreeStop = selectIdx
|
||||||
|
|
||||||
|
if selectIdx == 0 {
|
||||||
|
bottomTreeStop = selectIdx
|
||||||
|
topTreeStart = selectIdx
|
||||||
|
} else {
|
||||||
|
bottomTreeStop = selectIdx - 1
|
||||||
|
topTreeStart = selectIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes <- TreeIndexKey{
|
||||||
|
bottomTreeStart: bottomTreeStart,
|
||||||
|
bottomTreeStop: bottomTreeStop,
|
||||||
|
topTreeStart: topTreeStart,
|
||||||
|
topTreeStop: topTreeStop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return indexes
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// case 2: aggregated compare (bottom tree is ENTIRELY fixed, top tree SIZE changes)
|
||||||
|
func (cmp *Comparer) AggregatedIndexes() <-chan TreeIndexKey {
|
||||||
|
indexes := make(chan TreeIndexKey)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer close(indexes)
|
||||||
|
|
||||||
|
var bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int
|
||||||
|
|
||||||
|
for selectIdx := 0; selectIdx < len(cmp.refTrees); selectIdx++ {
|
||||||
|
bottomTreeStart = 0
|
||||||
|
topTreeStop = selectIdx
|
||||||
|
if selectIdx == 0 {
|
||||||
|
bottomTreeStop = selectIdx
|
||||||
|
topTreeStart = selectIdx
|
||||||
|
} else {
|
||||||
|
bottomTreeStop = 0
|
||||||
|
topTreeStart = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
indexes <- TreeIndexKey{
|
||||||
|
bottomTreeStart: bottomTreeStart,
|
||||||
|
bottomTreeStop: bottomTreeStop,
|
||||||
|
topTreeStart: topTreeStart,
|
||||||
|
topTreeStop: topTreeStop,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return indexes
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cmp *Comparer) BuildCache() (errors []error) {
|
||||||
|
for index := range cmp.NaturalIndexes() {
|
||||||
|
pathError, _ := cmp.GetPathErrors(index)
|
||||||
|
if len(pathError) > 0 {
|
||||||
|
for _, path := range pathError {
|
||||||
|
errors = append(errors, fmt.Errorf("path error at layer index %s: %s", index, path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_, err := cmp.GetTree(index)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for index := range cmp.AggregatedIndexes() {
|
||||||
|
_, err := cmp.GetTree(index)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
@ -62,7 +62,12 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) {
|
|||||||
sizeBytes += curNode.Data.FileInfo.Size
|
sizeBytes += curNode.Data.FileInfo.Size
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
stackedTree, err := StackTreeRange(trees, 0, currentTree-1)
|
stackedTree, failedPaths, err := StackTreeRange(trees, 0, currentTree-1)
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
for _, path := range failedPaths {
|
||||||
|
logrus.Errorf(path.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to stack tree range: %+v", err)
|
logrus.Errorf("unable to stack tree range: %+v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -2,6 +2,7 @@ package filetree
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"path"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -204,22 +205,23 @@ func (tree *FileTree) VisitDepthParentFirst(visitor Visitor, evaluator VisitEval
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stack takes two trees and combines them together. This is done by "stacking" the given tree on top of the owning tree.
|
// Stack takes two trees and combines them together. This is done by "stacking" the given tree on top of the owning tree.
|
||||||
func (tree *FileTree) Stack(upper *FileTree) error {
|
func (tree *FileTree) Stack(upper *FileTree) (failed []PathError, stackErr error) {
|
||||||
graft := func(node *FileNode) error {
|
graft := func(node *FileNode) error {
|
||||||
if node.IsWhiteout() {
|
if node.IsWhiteout() {
|
||||||
err := tree.RemovePath(node.Path())
|
err := tree.RemovePath(node.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot remove node %s: %v", node.Path(), err.Error())
|
failed = append(failed, NewPathError(node.Path(), ActionAdd, err))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
newNode, _, err := tree.AddPath(node.Path(), node.Data.FileInfo)
|
_, _, err := tree.AddPath(node.Path(), node.Data.FileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot add node %s: %v", newNode.Path(), err.Error())
|
failed = append(failed, NewPathError(node.Path(), ActionRemove, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return upper.VisitDepthChildFirst(graft, nil)
|
stackErr = upper.VisitDepthChildFirst(graft, nil)
|
||||||
|
return failed, stackErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNode fetches a single node when given a slash-delimited string from root ('/') to the desired node (e.g. '/a/node/path')
|
// GetNode fetches a single node when given a slash-delimited string from root ('/') to the desired node (e.g. '/a/node/path')
|
||||||
@ -239,8 +241,12 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddPath adds a new node to the tree with the given payload
|
// AddPath adds a new node to the tree with the given payload
|
||||||
func (tree *FileTree) AddPath(path string, data FileInfo) (*FileNode, []*FileNode, error) {
|
func (tree *FileTree) AddPath(filepath string, data FileInfo) (*FileNode, []*FileNode, error) {
|
||||||
nodeNames := strings.Split(strings.Trim(path, "/"), "/")
|
filepath = path.Clean(filepath)
|
||||||
|
if filepath == "." {
|
||||||
|
return nil, nil, fmt.Errorf("cannot add relative path '%s'", filepath)
|
||||||
|
}
|
||||||
|
nodeNames := strings.Split(strings.Trim(filepath, "/"), "/")
|
||||||
node := tree.Root
|
node := tree.Root
|
||||||
addedNodes := make([]*FileNode, 0)
|
addedNodes := make([]*FileNode, 0)
|
||||||
for idx, name := range nodeNames {
|
for idx, name := range nodeNames {
|
||||||
@ -263,7 +269,7 @@ func (tree *FileTree) AddPath(path string, data FileInfo) (*FileNode, []*FileNod
|
|||||||
|
|
||||||
if node == nil {
|
if node == nil {
|
||||||
// the child could not be added
|
// the child could not be added
|
||||||
return node, addedNodes, fmt.Errorf(fmt.Sprintf("could not add child node: '%s' (path:'%s')", name, path))
|
return node, addedNodes, fmt.Errorf(fmt.Sprintf("could not add child node: '%s' (path:'%s')", name, filepath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -293,17 +299,18 @@ type compareMark struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CompareAndMark marks the FileNodes in the owning (lower) tree with DiffType annotations when compared to the given (upper) tree.
|
// CompareAndMark marks the FileNodes in the owning (lower) tree with DiffType annotations when compared to the given (upper) tree.
|
||||||
func (tree *FileTree) CompareAndMark(upper *FileTree) error {
|
func (tree *FileTree) CompareAndMark(upper *FileTree) ([]PathError, error) {
|
||||||
// always compare relative to the original, unaltered tree.
|
// always compare relative to the original, unaltered tree.
|
||||||
originalTree := tree
|
originalTree := tree
|
||||||
|
|
||||||
modifications := make([]compareMark, 0)
|
modifications := make([]compareMark, 0)
|
||||||
|
failed := make([]PathError, 0)
|
||||||
|
|
||||||
graft := func(upperNode *FileNode) error {
|
graft := func(upperNode *FileNode) error {
|
||||||
if upperNode.IsWhiteout() {
|
if upperNode.IsWhiteout() {
|
||||||
err := tree.markRemoved(upperNode.Path())
|
err := tree.markRemoved(upperNode.Path())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot remove upperNode %s: %v", upperNode.Path(), err.Error())
|
failed = append(failed, NewPathError(upperNode.Path(), ActionRemove, err))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -315,7 +322,8 @@ func (tree *FileTree) CompareAndMark(upper *FileTree) error {
|
|||||||
if originalLowerNode == nil {
|
if originalLowerNode == nil {
|
||||||
_, newNodes, err := tree.AddPath(upperNode.Path(), upperNode.Data.FileInfo)
|
_, newNodes, err := tree.AddPath(upperNode.Path(), upperNode.Data.FileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot add new upperNode %s: %v", upperNode.Path(), err.Error())
|
failed = append(failed, NewPathError(upperNode.Path(), ActionAdd, err))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
for idx := len(newNodes) - 1; idx >= 0; idx-- {
|
for idx := len(newNodes) - 1; idx >= 0; idx-- {
|
||||||
newNode := newNodes[idx]
|
newNode := newNodes[idx]
|
||||||
@ -334,7 +342,7 @@ func (tree *FileTree) CompareAndMark(upper *FileTree) error {
|
|||||||
// we must visit from the leaves upwards to ensure that diff types can be derived from and assigned to children
|
// we must visit from the leaves upwards to ensure that diff types can be derived from and assigned to children
|
||||||
err := upper.VisitDepthChildFirst(graft, nil)
|
err := upper.VisitDepthChildFirst(graft, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return failed, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// take note of the comparison results on each note in the owning tree.
|
// take note of the comparison results on each note in the owning tree.
|
||||||
@ -342,19 +350,19 @@ func (tree *FileTree) CompareAndMark(upper *FileTree) error {
|
|||||||
if pair.final > 0 {
|
if pair.final > 0 {
|
||||||
err = pair.lowerNode.AssignDiffType(pair.final)
|
err = pair.lowerNode.AssignDiffType(pair.final)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return failed, err
|
||||||
}
|
}
|
||||||
} else if pair.lowerNode.Data.DiffType == Unmodified {
|
} else if pair.lowerNode.Data.DiffType == Unmodified {
|
||||||
err = pair.lowerNode.deriveDiffType(pair.tentative)
|
err = pair.lowerNode.deriveDiffType(pair.tentative)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return failed, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// persist the upper's payload on the owning tree
|
// persist the upper's payload on the owning tree
|
||||||
pair.lowerNode.Data.FileInfo = *pair.upperNode.Data.FileInfo.Copy()
|
pair.lowerNode.Data.FileInfo = *pair.upperNode.Data.FileInfo.Copy()
|
||||||
}
|
}
|
||||||
return nil
|
return failed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// markRemoved annotates the FileNode at the given path as Removed.
|
// markRemoved annotates the FileNode at the given path as Removed.
|
||||||
@ -367,15 +375,18 @@ func (tree *FileTree) markRemoved(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StackTreeRange combines an array of trees into a single tree
|
// StackTreeRange combines an array of trees into a single tree
|
||||||
func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, error) {
|
func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, []PathError, error) {
|
||||||
|
errors := make([]PathError, 0)
|
||||||
tree := trees[0].Copy()
|
tree := trees[0].Copy()
|
||||||
for idx := start; idx <= stop; idx++ {
|
for idx := start; idx <= stop; idx++ {
|
||||||
err := tree.Stack(trees[idx])
|
failedPaths, err := tree.Stack(trees[idx])
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
errors = append(errors, failedPaths...)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("could not stack tree range: %v", err)
|
logrus.Errorf("could not stack tree range: %v", err)
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tree, nil
|
return tree, errors, nil
|
||||||
}
|
}
|
||||||
|
@ -125,6 +125,40 @@ func TestStringBetween(t *testing.T) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRejectPurelyRelativePath(t *testing.T) {
|
||||||
|
tree := NewFileTree()
|
||||||
|
_, _, err := tree.AddPath("./etc/nginx/nginx.conf", FileInfo{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not setup test: %v", err)
|
||||||
|
}
|
||||||
|
_, _, err = tree.AddPath("./", FileInfo{})
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected to reject relative path, but did not")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddRelativePath(t *testing.T) {
|
||||||
|
tree := NewFileTree()
|
||||||
|
_, _, err := tree.AddPath("./etc/nginx/nginx.conf", FileInfo{})
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("could not setup test: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
expected :=
|
||||||
|
`└── etc
|
||||||
|
└── nginx
|
||||||
|
└── nginx.conf
|
||||||
|
`
|
||||||
|
actual := tree.String(false)
|
||||||
|
|
||||||
|
if expected != actual {
|
||||||
|
t.Errorf("Expected tree string:\n--->%s<---\nGot:\n--->%s<---", expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func TestAddPath(t *testing.T) {
|
func TestAddPath(t *testing.T) {
|
||||||
tree := NewFileTree()
|
tree := NewFileTree()
|
||||||
_, _, err := tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
|
_, _, err := tree.AddPath("/etc/nginx/nginx.conf", FileInfo{})
|
||||||
@ -307,12 +341,16 @@ func TestStack(t *testing.T) {
|
|||||||
t.Errorf("expected no node on whiteout file add, but got %v", node)
|
t.Errorf("expected no node on whiteout file add, but got %v", node)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tree1.Stack(tree2)
|
failedPaths, err := tree1.Stack(tree2)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Could not stack refTrees: %v", err)
|
t.Errorf("Could not stack refTrees: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
|
|
||||||
expected :=
|
expected :=
|
||||||
`├── etc
|
`├── etc
|
||||||
│ └── nginx
|
│ └── nginx
|
||||||
@ -415,10 +453,13 @@ func TestCompareWithNoChanges(t *testing.T) {
|
|||||||
t.Errorf("could not setup test: %v", err)
|
t.Errorf("could not setup test: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err := lowerTree.CompareAndMark(upperTree)
|
failedPaths, err := lowerTree.CompareAndMark(upperTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could not setup test: %v", err)
|
t.Errorf("could not setup test: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
if n.Path() == "/" {
|
if n.Path() == "/" {
|
||||||
return nil
|
return nil
|
||||||
@ -463,10 +504,13 @@ func TestCompareWithAdds(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
failedAssertions := []error{}
|
failedAssertions := []error{}
|
||||||
err := lowerTree.CompareAndMark(upperTree)
|
failedPaths, err := lowerTree.CompareAndMark(upperTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Expected tree compare to have no errors, got: %v", err)
|
t.Errorf("Expected tree compare to have no errors, got: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
|
|
||||||
p := n.Path()
|
p := n.Path()
|
||||||
@ -577,11 +621,13 @@ func TestCompareWithChanges(t *testing.T) {
|
|||||||
|
|
||||||
changedPaths = append(changedPaths, chownPath)
|
changedPaths = append(changedPaths, chownPath)
|
||||||
|
|
||||||
err = lowerTree.CompareAndMark(upperTree)
|
failedPaths, err := lowerTree.CompareAndMark(upperTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("unable to compare and mark: %+v", err)
|
t.Errorf("unable to compare and mark: %+v", err)
|
||||||
}
|
}
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
failedAssertions := []error{}
|
failedAssertions := []error{}
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
p := n.Path()
|
p := n.Path()
|
||||||
@ -642,10 +688,13 @@ func TestCompareWithRemoves(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err := lowerTree.CompareAndMark(upperTree)
|
failedPaths, err := lowerTree.CompareAndMark(upperTree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("could not setup test: %v", err)
|
t.Errorf("could not setup test: %v", err)
|
||||||
}
|
}
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
failedAssertions := []error{}
|
failedAssertions := []error{}
|
||||||
asserter := func(n *FileNode) error {
|
asserter := func(n *FileNode) error {
|
||||||
p := n.Path()
|
p := n.Path()
|
||||||
@ -745,7 +794,10 @@ func TestStackRange(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
trees := []*FileTree{lowerTree, upperTree, tree}
|
trees := []*FileTree{lowerTree, upperTree, tree}
|
||||||
_, err = StackTreeRange(trees, 0, 2)
|
_, failedPaths, err := StackTreeRange(trees, 0, 2)
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
39
dive/filetree/path_error.go
Normal file
39
dive/filetree/path_error.go
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
package filetree
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
const (
|
||||||
|
ActionAdd FileAction = iota
|
||||||
|
ActionRemove
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileAction int
|
||||||
|
|
||||||
|
func (fa FileAction) String() string {
|
||||||
|
switch fa {
|
||||||
|
case ActionAdd:
|
||||||
|
return "add"
|
||||||
|
case ActionRemove:
|
||||||
|
return "remove"
|
||||||
|
default:
|
||||||
|
return "<unknown file action>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PathError struct {
|
||||||
|
Path string
|
||||||
|
Action FileAction
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPathError(path string, action FileAction, err error) PathError {
|
||||||
|
return PathError{
|
||||||
|
Path: path,
|
||||||
|
Action: action,
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pe PathError) String() string {
|
||||||
|
return fmt.Sprintf("unable to %s '%s': %+v", pe.Action.String(), pe.Path, pe.Err)
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -119,7 +120,11 @@ func getFileList(tarReader *tar.Reader) ([]filetree.FileInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
name := header.Name
|
// always ensure relative path notations are not parsed as part of the filename
|
||||||
|
name := path.Clean(header.Name)
|
||||||
|
if name == "." {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
switch header.Typeflag {
|
switch header.Typeflag {
|
||||||
case tar.TypeXGlobalHeader:
|
case tar.TypeXGlobalHeader:
|
||||||
|
@ -87,10 +87,13 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
events.message(utils.TitleFormat("Building cache..."))
|
events.message(utils.TitleFormat("Building cache..."))
|
||||||
cache := filetree.NewFileTreeCache(analysis.RefTrees)
|
treeStack := filetree.NewComparer(analysis.RefTrees)
|
||||||
err := cache.Build()
|
errors := treeStack.BuildCache()
|
||||||
if err != nil {
|
if errors != nil {
|
||||||
events.exitWithErrorMessage("cannot build cache tree", err)
|
for _, err := range errors {
|
||||||
|
events.message(" " + err.Error())
|
||||||
|
}
|
||||||
|
events.exitWithError(fmt.Errorf("file tree has path errors"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,7 +105,7 @@ func run(enableUi bool, options Options, imageResolver image.Resolver, events ev
|
|||||||
// enough sleep will prevent this behavior (todo: remove this hack)
|
// enough sleep will prevent this behavior (todo: remove this hack)
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
|
||||||
err = ui.Run(analysis, cache)
|
err = ui.Run(analysis, treeStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
events.exitWithError(err)
|
events.exitWithError(err)
|
||||||
return
|
return
|
||||||
|
@ -24,7 +24,7 @@ var (
|
|||||||
appSingleton *app
|
appSingleton *app
|
||||||
)
|
)
|
||||||
|
|
||||||
func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeCache) (*app, error) {
|
func newApp(gui *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*app, error) {
|
||||||
var err error
|
var err error
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
var theControls *Controller
|
var theControls *Controller
|
||||||
@ -118,7 +118,7 @@ func (a *app) quit() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run is the UI entrypoint.
|
// Run is the UI entrypoint.
|
||||||
func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) error {
|
func Run(analysis *image.AnalysisResult, treeStack filetree.Comparer) error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
g, err := gocui.NewGui(gocui.OutputNormal)
|
g, err := gocui.NewGui(gocui.OutputNormal)
|
||||||
@ -127,7 +127,7 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) error {
|
|||||||
}
|
}
|
||||||
defer g.Close()
|
defer g.Close()
|
||||||
|
|
||||||
_, err = newApp(g, analysis, cache)
|
_, err = newApp(g, analysis, treeStack)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ type Controller struct {
|
|||||||
lookup map[string]view.Renderer
|
lookup map[string]view.Renderer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.TreeCache) (*Controller, error) {
|
func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.Comparer) (*Controller, error) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
controller := &Controller{
|
controller := &Controller{
|
||||||
@ -34,10 +34,11 @@ func NewCollection(g *gocui.Gui, analysis *image.AnalysisResult, cache filetree.
|
|||||||
}
|
}
|
||||||
controller.lookup[controller.Layer.Name()] = controller.Layer
|
controller.lookup[controller.Layer.Name()] = controller.Layer
|
||||||
|
|
||||||
treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0)
|
//treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0)
|
||||||
if err != nil {
|
//if err != nil {
|
||||||
return nil, err
|
// return nil, err
|
||||||
}
|
//}
|
||||||
|
treeStack := analysis.RefTrees[0]
|
||||||
controller.Tree, err = view.NewFileTreeView("filetree", g, treeStack, analysis.RefTrees, cache)
|
controller.Tree, err = view.NewFileTreeView("filetree", g, treeStack, analysis.RefTrees, cache)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -41,7 +41,7 @@ type FileTree struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFileTreeView creates a new view object attached the the global [gocui] screen object.
|
// NewFileTreeView creates a new view object attached the the global [gocui] screen object.
|
||||||
func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (controller *FileTree, err error) {
|
func NewFileTreeView(name string, gui *gocui.Gui, tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (controller *FileTree, err error) {
|
||||||
controller = new(FileTree)
|
controller = new(FileTree)
|
||||||
controller.listeners = make([]ViewOptionChangeListener, 0)
|
controller.listeners = make([]ViewOptionChangeListener, 0)
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ type FileTree struct {
|
|||||||
ModelTree *filetree.FileTree
|
ModelTree *filetree.FileTree
|
||||||
ViewTree *filetree.FileTree
|
ViewTree *filetree.FileTree
|
||||||
RefTrees []*filetree.FileTree
|
RefTrees []*filetree.FileTree
|
||||||
cache filetree.TreeCache
|
cache filetree.Comparer
|
||||||
|
|
||||||
CollapseAll bool
|
CollapseAll bool
|
||||||
ShowAttributes bool
|
ShowAttributes bool
|
||||||
@ -35,7 +35,7 @@ type FileTree struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewFileTreeViewModel creates a new view object attached the the global [gocui] screen object.
|
// NewFileTreeViewModel creates a new view object attached the the global [gocui] screen object.
|
||||||
func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.TreeCache) (treeViewModel *FileTree, err error) {
|
func NewFileTreeViewModel(tree *filetree.FileTree, refTrees []*filetree.FileTree, cache filetree.Comparer) (treeViewModel *FileTree, err error) {
|
||||||
treeViewModel = new(FileTree)
|
treeViewModel = new(FileTree)
|
||||||
|
|
||||||
// populate main fields
|
// populate main fields
|
||||||
@ -101,7 +101,7 @@ func (vm *FileTree) SetTreeByLayer(bottomTreeStart, bottomTreeStop, topTreeStart
|
|||||||
if topTreeStop > len(vm.RefTrees)-1 {
|
if topTreeStop > len(vm.RefTrees)-1 {
|
||||||
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
|
return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1)
|
||||||
}
|
}
|
||||||
newTree, err := vm.cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop)
|
newTree, err := vm.cache.GetTree(filetree.NewTreeIndexKey(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("unable to fetch layer tree from cache: %+v", err)
|
logrus.Errorf("unable to fetch layer tree from cache: %+v", err)
|
||||||
return err
|
return err
|
||||||
|
@ -76,15 +76,18 @@ func assertTestData(t *testing.T, actualBytes []byte) {
|
|||||||
func initializeTestViewModel(t *testing.T) *FileTree {
|
func initializeTestViewModel(t *testing.T) *FileTree {
|
||||||
result := docker.TestAnalysisFromArchive(t, "../../../.data/test-docker-image.tar")
|
result := docker.TestAnalysisFromArchive(t, "../../../.data/test-docker-image.tar")
|
||||||
|
|
||||||
cache := filetree.NewFileTreeCache(result.RefTrees)
|
cache := filetree.NewComparer(result.RefTrees)
|
||||||
err := cache.Build()
|
errors := cache.BuildCache()
|
||||||
if err != nil {
|
if len(errors) > 0 {
|
||||||
t.Fatalf("%s: unable to build cache: %+v", t.Name(), err)
|
t.Fatalf("%s: unable to build cache: %d errors", t.Name(), len(errors))
|
||||||
}
|
}
|
||||||
|
|
||||||
format.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc()
|
format.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc()
|
||||||
|
|
||||||
treeStack, err := filetree.StackTreeRange(result.RefTrees, 0, 0)
|
treeStack, failedPaths, err := filetree.StackTreeRange(result.RefTrees, 0, 0)
|
||||||
|
if len(failedPaths) > 0 {
|
||||||
|
t.Errorf("expected no filepath errors, got %d", len(failedPaths))
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s: unable to stack trees: %v", t.Name(), err)
|
t.Fatalf("%s: unable to stack trees: %v", t.Name(), err)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user