fix: unquote git quoted filenames

Ayman Bagabas created

Git core.quotePath is enabled by default and causes unicode in filenames
to be quoted. Unquote filenames and requote them to escape control
characters BUT not unicode characters.

Fixes: https://github.com/charmbracelet/soft-serve/issues/457

Change summary

pkg/ssh/cmd/tree.go                 |  3 ++-
pkg/ui/common/format.go             | 15 +++++++++++++++
pkg/ui/pages/repo/filesitem.go      |  9 ++-------
testscript/testdata/repo-tree.txtar | 16 +++++++++++++++-
4 files changed, 34 insertions(+), 9 deletions(-)

Detailed changes

pkg/ssh/cmd/tree.go 🔗

@@ -6,6 +6,7 @@ import (
 	"github.com/charmbracelet/soft-serve/git"
 	"github.com/charmbracelet/soft-serve/pkg/backend"
 	"github.com/charmbracelet/soft-serve/pkg/proto"
+	"github.com/charmbracelet/soft-serve/pkg/ui/common"
 	"github.com/dustin/go-humanize"
 	"github.com/spf13/cobra"
 )
@@ -93,7 +94,7 @@ func treeCommand() *cobra.Command {
 				} else {
 					ssize = humanize.Bytes(uint64(size))
 				}
-				cmd.Printf("%s\t%s\t %s\n", ent.Mode(), ssize, ent.Name())
+				cmd.Printf("%s\t%s\t %s\n", ent.Mode(), ssize, common.UnquoteFilename(ent.Name()))
 			}
 			return nil
 		},

pkg/ui/common/format.go 🔗

@@ -2,6 +2,7 @@ package common
 
 import (
 	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/alecthomas/chroma/v2/lexers"
@@ -54,3 +55,17 @@ func FormatHighlight(p, c string) (string, error) {
 	}
 	return r.String(), nil
 }
+
+// UnquoteFilename unquotes a filename.
+// When Git is with "core.quotePath" set to "true" (default), it will quote
+// the filename with double quotes if it contains control characters or unicode.
+// this function will unquote the filename.
+func UnquoteFilename(s string) string {
+	name := s
+	if n, err := strconv.Unquote(`"` + s + `"`); err == nil {
+		name = n
+	}
+
+	name = strconv.Quote(name)
+	return strings.Trim(name, `"`)
+}

pkg/ui/pages/repo/filesitem.go 🔗

@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
-	"strconv"
 	"strings"
 
 	"github.com/charmbracelet/bubbles/key"
@@ -23,16 +22,12 @@ type FileItem struct {
 
 // ID returns the ID of the file item.
 func (i FileItem) ID() string {
-	name := i.entry.Name()
-	if n, err := strconv.Unquote(name); err == nil {
-		name = n
-	}
-	return name
+	return i.entry.Name()
 }
 
 // Title returns the title of the file item.
 func (i FileItem) Title() string {
-	return i.entry.Name()
+	return common.UnquoteFilename(i.entry.Name())
 }
 
 // Description returns the description of the file item.

testscript/testdata/repo-tree.txtar 🔗

@@ -1,7 +1,7 @@
 # vi: set ft=conf
 
 # convert crlf to lf on windows
-[windows] dos2unix tree1.txt tree2.txt tree3.txt
+[windows] dos2unix tree1.txt tree2.txt tree3.txt tree4.txt
 
 # start soft serve
 exec soft serve &
@@ -47,6 +47,18 @@ stderr 'file not found'
 ! stdout .
 stderr 'revision does not exist'
 
+# test unicode file name issue #457
+soft repo create repo4
+git clone ssh://localhost:$SSH_PORT/repo4 repo4
+mkfile ./repo4/🍕.md '🍕'
+git -C repo4 add -A
+git -C repo4 commit -m 'unicode'
+git -C repo4 push origin HEAD
+
+# print root tree
+soft repo tree repo4
+cmp stdout tree4.txt
+
 # stop the server
 [windows] stopserver
 
@@ -59,3 +71,5 @@ drwxrwxrwx	-	 folder
 -rw-r--r--	5 B	 aa.md
 -- tree3.txt --
 -rw-r--r--	2 B	 b.md
+-- tree4.txt --
+-rw-r--r--	4 B	 🍕.md