From 2e033a7751aca91e1846540ae8a1f95b4905090c Mon Sep 17 00:00:00 2001 From: Booyaka101 Date: Sat, 2 May 2026 12:59:41 +0800 Subject: [PATCH] fix(filetree): use forward-slash path separator for git diff paths (#121) git diff always emits forward-slash paths regardless of host OS. The file tree builder used os.PathSeparator and filepath.Dir/Join, which on Windows split paths on '\'. Since git paths contain only '/', no split ever happened and every file became a flat root child with no directory hierarchy. Reported by @abdmoh123 in #121: tree shows files as a list, not a tree, on native Windows. Linux and WSL were unaffected because their os.PathSeparator is already '/'. Switch to the path package (forward-slash, OS-agnostic) for all git-path manipulation in buildFullFileTree and collapseTree. Six call sites updated; imports trimmed (os and path/filepath dropped, path added). Verification: - Pre-fix Windows (Go 1.26.2 native): TestBuildFullFileTree fails with "expected 5 nodes, but got 3" (the bug abdmoh123 reported). - Post-fix Windows: full test suite passes. - Pre-fix Linux (golang:1.26 container): all tests pass; Linux is not affected by the bug because os.PathSeparator is already '/'. - Post-fix Linux: all tests still pass; behavior is byte-for-byte identical because path.{Dir,Join} return the same results as filepath.{Dir,Join} when inputs are forward-slash. The existing TestBuildFullFileTree, TestCollapseTree, TestUncollapsableTree, and TestCloseDirsBelow* serve as the regression tests; the fix flips them from failing to passing on Windows without disturbing Linux behavior. --- pkg/ui/panes/filetree/filetree.go | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pkg/ui/panes/filetree/filetree.go b/pkg/ui/panes/filetree/filetree.go index 8b5f45c..ce6e2f9 100644 --- a/pkg/ui/panes/filetree/filetree.go +++ b/pkg/ui/panes/filetree/filetree.go @@ -1,8 +1,7 @@ package filetree import ( - "os" - "path/filepath" + "path" "strings" "charm.land/bubbles/v2/key" @@ -241,8 +240,13 @@ func buildFullFileTree(files []*gitdiff.File, cfg config.Config) *tree.Node { subTree := t name := filenode.GetFileName(file) - dir := filepath.Dir(name) - parts := strings.Split(dir, string(os.PathSeparator)) + // git diff always emits forward-slash paths regardless of host OS, so + // use the path package (forward-slash) rather than filepath / + // os.PathSeparator. On Windows the latter would split on `\\` and + // never find the `/` separators, leaving every file as a flat root + // child with no directory hierarchy. See #121. + dir := path.Dir(name) + parts := strings.Split(dir, "/") existingPath := "" // walk the tree to find existing path @@ -252,7 +256,7 @@ func buildFullFileTree(files []*gitdiff.File, cfg config.Config) *tree.Node { for _, child := range children { if dir, ok := child.GivenValue().(*dirnode.DirNode); ok && dir.Name == part { subTree = child - existingPath = existingPath + part + string(os.PathSeparator) + existingPath = existingPath + part + "/" found = true // found a part of the path, continue to the subtree break @@ -265,7 +269,7 @@ func buildFullFileTree(files []*gitdiff.File, cfg config.Config) *tree.Node { // path does not exist from this point, need to create it leftover := strings.TrimPrefix(name, existingPath) - parts = strings.Split(leftover, string(os.PathSeparator)) + parts = strings.Split(leftover, "/") for i, part := range parts { var c *tree.Node if i == len(parts)-1 { @@ -277,7 +281,7 @@ func buildFullFileTree(files []*gitdiff.File, cfg config.Config) *tree.Node { } else { dirNode := dirnode.DirNode{ Name: part, - FullPath: filepath.Join(existingPath, filepath.Join(parts[:i]...), part), + FullPath: path.Join(existingPath, path.Join(parts[:i]...), part), } c = tree.Root(&dirNode) subTree.Child(c) @@ -341,8 +345,8 @@ func collapseTree(t *tree.Node) *tree.Node { } newDir := dirnode.DirNode{ - FullPath: filepath.Join(rootDir.FullPath, dir.Name), - Name: filepath.Join(rootDir.Name, dir.Name), + FullPath: path.Join(rootDir.FullPath, dir.Name), + Name: path.Join(rootDir.Name, dir.Name), } children := make([]any, 0) for _, c := range child.ChildNodes() {