diff --git a/dive/filetree/cache.go b/dive/filetree/cache.go index 82c1795..a7f0fc4 100644 --- a/dive/filetree/cache.go +++ b/dive/filetree/cache.go @@ -13,29 +13,36 @@ type TreeCache struct { cache map[TreeCacheKey]*FileTree } -func (cache *TreeCache) Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop int) *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 + return value, nil } - value := cache.buildTree(key) + value, err := cache.buildTree(key) + if err != nil { + return nil, err + } cache.cache[key] = value - return value + return value, nil } -func (cache *TreeCache) buildTree(key TreeCacheKey) *FileTree { - newTree := StackTreeRange(cache.refTrees, key.bottomTreeStart, key.bottomTreeStop) +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 + return newTree, nil } -func (cache *TreeCache) Build() { +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) @@ -51,7 +58,10 @@ func (cache *TreeCache) Build() { topTreeStart = selectIdx } - cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) + _, 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) @@ -66,8 +76,12 @@ func (cache *TreeCache) Build() { topTreeStart = 1 } - cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) + _, err := cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) + if err != nil { + return err + } } + return nil } func NewFileTreeCache(refTrees []*FileTree) TreeCache { diff --git a/dive/filetree/efficiency.go b/dive/filetree/efficiency.go index 7be116b..24ad3b5 100644 --- a/dive/filetree/efficiency.go +++ b/dive/filetree/efficiency.go @@ -1,7 +1,6 @@ package filetree import ( - "fmt" "sort" "github.com/sirupsen/logrus" @@ -63,14 +62,22 @@ func Efficiency(trees []*FileTree) (float64, EfficiencySlice) { sizeBytes += curNode.Data.FileInfo.Size return nil } - stackedTree := StackTreeRange(trees, 0, currentTree-1) + stackedTree, err := StackTreeRange(trees, 0, currentTree-1) + if err != nil { + logrus.Errorf("unable to stack tree range: %+v", err) + return err + } + previousTreeNode, err := stackedTree.GetNode(node.Path()) if err != nil { - logrus.Debug(fmt.Sprintf("CurrentTree: %d : %s", currentTree, err)) - } else if previousTreeNode.Data.FileInfo.IsDir { + return err + } + + if previousTreeNode.Data.FileInfo.IsDir { err = previousTreeNode.VisitDepthChildFirst(sizer, nil) if err != nil { logrus.Errorf("unable to propagate whiteout dir: %+v", err) + return err } } diff --git a/dive/filetree/file_tree.go b/dive/filetree/file_tree.go index c7c3dfc..11311fc 100644 --- a/dive/filetree/file_tree.go +++ b/dive/filetree/file_tree.go @@ -367,14 +367,15 @@ func (tree *FileTree) markRemoved(path string) error { } // StackTreeRange combines an array of trees into a single tree -func StackTreeRange(trees []*FileTree, start, stop int) *FileTree { +func StackTreeRange(trees []*FileTree, start, stop int) (*FileTree, error) { tree := trees[0].Copy() for idx := start; idx <= stop; idx++ { err := tree.Stack(trees[idx]) if err != nil { logrus.Errorf("could not stack tree range: %v", err) + return nil, err } } - return tree + return tree, nil } diff --git a/dive/filetree/file_tree_test.go b/dive/filetree/file_tree_test.go index fc57036..d9f9677 100644 --- a/dive/filetree/file_tree_test.go +++ b/dive/filetree/file_tree_test.go @@ -745,7 +745,10 @@ func TestStackRange(t *testing.T) { } } trees := []*FileTree{lowerTree, upperTree, tree} - StackTreeRange(trees, 0, 2) + _, err = StackTreeRange(trees, 0, 2) + if err != nil { + t.Fatal(err) + } } func TestRemoveOnIterate(t *testing.T) { diff --git a/runtime/run.go b/runtime/run.go index a6fca8d..b8185e8 100644 --- a/runtime/run.go +++ b/runtime/run.go @@ -2,6 +2,7 @@ package runtime import ( "fmt" + "github.com/sirupsen/logrus" "github.com/wagoodman/dive/dive" "github.com/wagoodman/dive/runtime/ci" "github.com/wagoodman/dive/runtime/export" @@ -95,7 +96,11 @@ func Run(options Options) { fmt.Println(utils.TitleFormat("Building cache...")) cache := filetree.NewFileTreeCache(result.RefTrees) - cache.Build() + err := cache.Build() + if err != nil { + logrus.Error(err) + utils.Exit(1) + } // it appears there is a race condition where termbox.Init() will // block nearly indefinitely when running as the first process in @@ -104,6 +109,12 @@ func Run(options Options) { // enough sleep will prevent this behavior (todo: remove this hack) time.Sleep(100 * time.Millisecond) - ui.Run(result, cache) + err = ui.Run(result, cache) + + if err != nil { + logrus.Error(err) + utils.Exit(1) + } + utils.Exit(0) } } diff --git a/runtime/ui/filetree_viewmodel.go b/runtime/ui/filetree_viewmodel.go index 46e9af5..30009fd 100644 --- a/runtime/ui/filetree_viewmodel.go +++ b/runtime/ui/filetree_viewmodel.go @@ -102,7 +102,11 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top if topTreeStop > len(vm.RefTrees)-1 { return fmt.Errorf("invalid layer index given: %d of %d", topTreeStop, len(vm.RefTrees)-1) } - newTree := vm.cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) + newTree, err := vm.cache.Get(bottomTreeStart, bottomTreeStop, topTreeStart, topTreeStop) + if err != nil { + logrus.Errorf("unable to fetch layer tree from cache: %+v", err) + return err + } // preserve vm state on copy visitor := func(node *filetree.FileNode) error { @@ -112,7 +116,7 @@ func (vm *FileTreeViewModel) setTreeByLayer(bottomTreeStart, bottomTreeStop, top } return nil } - err := vm.ModelTree.VisitDepthChildFirst(visitor, nil) + err = vm.ModelTree.VisitDepthChildFirst(visitor, nil) if err != nil { logrus.Errorf("unable to propagate layer tree: %+v", err) return err diff --git a/runtime/ui/filetree_viewmodel_test.go b/runtime/ui/filetree_viewmodel_test.go index a0ae5cd..ba1d86c 100644 --- a/runtime/ui/filetree_viewmodel_test.go +++ b/runtime/ui/filetree_viewmodel_test.go @@ -78,11 +78,18 @@ func initializeTestViewModel(t *testing.T) *FileTreeViewModel { t.Fatalf("%s: unable to fetch analysis: %v", t.Name(), err) } cache := filetree.NewFileTreeCache(result.RefTrees) - cache.Build() + err = cache.Build() + if err != nil { + t.Fatalf("%s: unable to build cache: %+v", t.Name(), err) + } Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc() - return NewFileTreeViewModel(filetree.StackTreeRange(result.RefTrees, 0, 0), result.RefTrees, cache) + treeStack, err := filetree.StackTreeRange(result.RefTrees, 0, 0) + if err != nil { + t.Fatalf("%s: unable to create tree ViewModel: %v", t.Name(), err) + } + return NewFileTreeViewModel(treeStack, result.RefTrees, cache) } func runTestCase(t *testing.T, vm *FileTreeViewModel, width, height int, filterRegex *regexp.Regexp) { diff --git a/runtime/ui/ui.go b/runtime/ui/ui.go index 0182607..6f4e5ed 100644 --- a/runtime/ui/ui.go +++ b/runtime/ui/ui.go @@ -251,20 +251,32 @@ func layout(g *gocui.Gui) error { view, viewErr = g.SetView(Controllers.Layer.Name, -1, -1+headerRows, splitCols, layersHeight) header, headerErr = g.SetView(Controllers.Layer.Name+"header", -1, -1, splitCols, headerRows) if isNewView(viewErr, headerErr) { - _ = Controllers.Layer.Setup(view, header) + err = Controllers.Layer.Setup(view, header) + if err != nil { + logrus.Error("unable to setup layer controller", err) + return err + } if _, err = g.SetCurrentView(Controllers.Layer.Name); err != nil { + logrus.Error("unable to set view to layer", err) return err } // since we are selecting the view, we should rerender to indicate it is selected - _ = Controllers.Layer.Render() + err = Controllers.Layer.Render() + if err != nil { + logrus.Error("unable to render layer view", err) + return err + } } // Details view, viewErr = g.SetView(Controllers.Details.Name, -1, -1+layersHeight+headerRows, splitCols, maxY-bottomRows) header, headerErr = g.SetView(Controllers.Details.Name+"header", -1, -1+layersHeight, splitCols, layersHeight+headerRows) if isNewView(viewErr, headerErr) { - _ = Controllers.Details.Setup(view, header) + err = Controllers.Details.Setup(view, header) + if err != nil { + return err + } } // Filetree @@ -275,40 +287,64 @@ func layout(g *gocui.Gui) error { view, viewErr = g.SetView(Controllers.Tree.Name, splitCols, -1+headerRows-offset, debugCols, maxY-bottomRows) header, headerErr = g.SetView(Controllers.Tree.Name+"header", splitCols, -1, debugCols, headerRows-offset) if isNewView(viewErr, headerErr) { - _ = Controllers.Tree.Setup(view, header) + err = Controllers.Tree.Setup(view, header) + if err != nil { + logrus.Error("unable to setup tree controller", err) + return err + } + } + err = Controllers.Tree.onLayoutChange(resized) + if err != nil { + logrus.Error("unable to setup layer controller onLayoutChange", err) + return err } - _ = Controllers.Tree.onLayoutChange(resized) // Status Bar view, viewErr = g.SetView(Controllers.Status.Name, -1, maxY-statusBarHeight-statusBarIndex, maxX, maxY-(statusBarIndex-1)) if isNewView(viewErr, headerErr) { - _ = Controllers.Status.Setup(view, nil) + err = Controllers.Status.Setup(view, nil) + if err != nil { + logrus.Error("unable to setup status controller", err) + return err + } } // Filter Bar view, viewErr = g.SetView(Controllers.Filter.Name, len(Controllers.Filter.headerStr)-1, maxY-filterBarHeight-filterBarIndex, maxX, maxY-(filterBarIndex-1)) header, headerErr = g.SetView(Controllers.Filter.Name+"header", -1, maxY-filterBarHeight-filterBarIndex, len(Controllers.Filter.headerStr), maxY-(filterBarIndex-1)) if isNewView(viewErr, headerErr) { - _ = Controllers.Filter.Setup(view, header) + err = Controllers.Filter.Setup(view, header) + if err != nil { + logrus.Error("unable to setup filter controller", err) + return err + } } return nil } // Update refreshes the state objects for future rendering. -func Update() { +func Update() error { for _, view := range Controllers.lookup { - _ = view.Update() + err := view.Update() + if err != nil { + return err + } } + return nil } // Render flushes the state objects to the screen. -func Render() { +func Render() error { for _, view := range Controllers.lookup { if view.IsVisible() { - _ = view.Render() + err := view.Render() + if err != nil { + return err + } } } + return nil } // renderStatusOption formats key help bindings-to-title pairs. @@ -321,7 +357,7 @@ func renderStatusOption(control, title string, selected bool) string { } // Run is the UI entrypoint. -func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { +func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) error { Formatting.Selected = color.New(color.ReverseVideo, color.Bold).SprintFunc() Formatting.Header = color.New(color.Bold).SprintFunc() @@ -335,20 +371,20 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { var err error GlobalKeybindings.quit, err = keybinding.ParseAll(viper.GetString("keybinding.quit")) if err != nil { - logrus.Error(err) + return err } GlobalKeybindings.toggleView, err = keybinding.ParseAll(viper.GetString("keybinding.toggle-view")) if err != nil { - logrus.Error(err) + return err } GlobalKeybindings.filterView, err = keybinding.ParseAll(viper.GetString("keybinding.filter-files")) if err != nil { - logrus.Error(err) + return err } g, err := gocui.NewGui(gocui.OutputNormal) if err != nil { - logrus.Error(err) + return err } utils.SetUi(g) defer g.Close() @@ -358,7 +394,11 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { Controllers.Layer = NewLayerController("side", g, analysis.Layers) Controllers.lookup[Controllers.Layer.Name] = Controllers.Layer - Controllers.Tree = NewFileTreeController("main", g, filetree.StackTreeRange(analysis.RefTrees, 0, 0), analysis.RefTrees, cache) + treeStack, err := filetree.StackTreeRange(analysis.RefTrees, 0, 0) + if err != nil { + return err + } + Controllers.Tree = NewFileTreeController("main", g, treeStack, analysis.RefTrees, cache) Controllers.lookup[Controllers.Tree.Name] = Controllers.Tree Controllers.Status = NewStatusController("status", g) @@ -381,15 +421,23 @@ func Run(analysis *image.AnalysisResult, cache filetree.TreeCache) { // } // perform the first update and render now that all resources have been loaded - Update() - Render() + err = Update() + if err != nil { + return err + } + + err = Render() + if err != nil { + return err + } if err := keyBindings(g); err != nil { - logrus.Error("keybinding error: ", err) + return err } if err := g.MainLoop(); err != nil && err != gocui.ErrQuit { logrus.Error("main loop error: ", err) + return err } - utils.Exit(0) + return nil }