From c1e046f4287412dea6c39781cb09c294c35f4c33 Mon Sep 17 00:00:00 2001 From: Thomas Miceli <27960254+thomiceli@users.noreply.github.com> Date: Sun, 17 Nov 2024 18:09:44 +0100 Subject: [PATCH] Convert octal notation file names in Git (#380) --- internal/git/commands.go | 52 +++++++++++++++++++++++++++++++++-- internal/git/commands_test.go | 11 ++++++-- internal/git/output_parser.go | 12 ++++---- 3 files changed, 64 insertions(+), 11 deletions(-) diff --git a/internal/git/commands.go b/internal/git/commands.go index 2bd06451..e1b81730 100644 --- a/internal/git/commands.go +++ b/internal/git/commands.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "io" + "net/url" "os" "os/exec" "path" @@ -115,6 +116,9 @@ func GetFilesOfRepository(user string, gist string, revision string) ([]string, } slice := strings.Split(string(stdout), "\n") + for i, s := range slice { + slice[i] = convertOctalToUTF8(s) + } return slice[:len(slice)-1], nil } @@ -153,7 +157,7 @@ func CatFileBatch(user string, gist string, revision string, truncate bool) ([]* fileMap = append(fileMap, &catFileBatch{ Hash: hash, Size: size, - Name: name, + Name: convertOctalToUTF8(name), }) } @@ -249,7 +253,7 @@ func GetFileContent(user string, gist string, revision string, filename string, "git", "--no-pager", "show", - revision+":"+filename, + revision+":"+convertURLToOctal(filename), ) cmd.Dir = repositoryPath @@ -273,7 +277,7 @@ func GetFileSize(user string, gist string, revision string, filename string) (ui "git", "cat-file", "-s", - revision+":"+filename, + revision+":"+convertURLToOctal(filename), ) cmd.Dir = repositoryPath @@ -565,6 +569,48 @@ func removeFilesExceptGit(dir string) error { }) } +func convertOctalToUTF8(name string) string { + name = strings.Trim(name, `"`) + utf8Name, err := strconv.Unquote(name) + if err != nil { + utf8Name, err = strconv.Unquote(`"` + name + `"`) + if err != nil { + return name + } + } + return utf8Name +} + +func convertUTF8ToOctal(name string) string { + if strings.Contains(name, "\\") { + return name + } + + needsQuoting := false + for _, r := range name { + if r > 127 { + needsQuoting = true + break + } + } + + if !needsQuoting { + return name + } + + quoted := fmt.Sprintf("%q", name) + return strings.Trim(quoted, `"`) +} + +func convertURLToOctal(name string) string { + decoded, err := url.QueryUnescape(name) + if err != nil { + return name + } + + return convertUTF8ToOctal(decoded) +} + const hookTemplate = `#!/bin/sh "$OG_OPENGIST_HOME_INTERNAL/symlinks/opengist" --config=$OG_OPENGIST_HOME_INTERNAL/symlinks/config.yml hook %s ` diff --git a/internal/git/commands_test.go b/internal/git/commands_test.go index d17a98ba..f31e3c76 100644 --- a/internal/git/commands_test.go +++ b/internal/git/commands_test.go @@ -61,11 +61,12 @@ func TestContent(t *testing.T) { "my_other_file.txt": `I really hate Opengist`, "rip.txt": "byebye", + "中文名.txt": "中文内容", }) files, err := GetFilesOfRepository("thomas", "gist1", "HEAD") require.NoError(t, err, "Could not get files of repository") - require.Subset(t, []string{"my_file.txt", "my_other_file.txt", "rip.txt"}, files, "Files are not correct") + require.Subset(t, []string{"my_file.txt", "my_other_file.txt", "rip.txt", "中文名.txt"}, files, "Files are not correct") content, truncated, err := GetFileContent("thomas", "gist1", "HEAD", "my_file.txt", false) require.NoError(t, err, "Could not get content") @@ -77,16 +78,22 @@ hate Opengist`, require.False(t, truncated, "Content should not be truncated") require.Equal(t, "I really\nhate Opengist", content, "Content is not correct") + content, truncated, err = GetFileContent("thomas", "gist1", "HEAD", "中文名.txt", false) + require.NoError(t, err, "Could not get content") + require.False(t, truncated, "Content should not be truncated") + require.Equal(t, "中文内容", content, "Content is not correct") + CommitToBare(t, "thomas", "gist1", map[string]string{ "my_renamed_file.txt": "I love Opengist\n", "my_other_file.txt": `I really like Opengist actually`, "new_file.txt": "Wait now there is a new file", + "中文名.txt": "中文内容", }) files, err = GetFilesOfRepository("thomas", "gist1", "HEAD") require.NoError(t, err, "Could not get files of repository") - require.Subset(t, []string{"my_renamed_file.txt", "my_other_file.txt", "new_file.txt"}, files, "Files are not correct") + require.Subset(t, []string{"my_renamed_file.txt", "my_other_file.txt", "new_file.txt", "中文名.txt"}, files, "Files are not correct") content, truncated, err = GetFileContent("thomas", "gist1", "HEAD", "my_other_file.txt", false) require.NoError(t, err, "Could not get content") diff --git a/internal/git/output_parser.go b/internal/git/output_parser.go index cefb9d83..1598ad78 100644 --- a/internal/git/output_parser.go +++ b/internal/git/output_parser.go @@ -193,28 +193,28 @@ loopLog: case strings.HasPrefix(line, "dissimilarity index"): continue case strings.HasPrefix(line, "rename from "): - currentFile.OldFilename = line[12 : len(line)-1] + currentFile.OldFilename = convertOctalToUTF8(line[12 : len(line)-1]) case strings.HasPrefix(line, "rename to "): - currentFile.Filename = line[10 : len(line)-1] + currentFile.Filename = convertOctalToUTF8(line[10 : len(line)-1]) parseRename = false case strings.HasPrefix(line, "copy from "): - currentFile.OldFilename = line[10 : len(line)-1] + currentFile.OldFilename = convertOctalToUTF8(line[10 : len(line)-1]) case strings.HasPrefix(line, "copy to "): - currentFile.Filename = line[8 : len(line)-1] + currentFile.Filename = convertOctalToUTF8(line[8 : len(line)-1]) parseRename = false case strings.HasPrefix(line, "new file"): currentFile.IsCreated = true case strings.HasPrefix(line, "deleted file"): currentFile.IsDeleted = true case strings.HasPrefix(line, "--- "): - name := line[4 : len(line)-1] + name := convertOctalToUTF8(line[4 : len(line)-1]) if parseRename && currentFile.IsDeleted { currentFile.Filename = name[2:] } else if parseRename && strings.HasPrefix(name, "a/") { currentFile.OldFilename = name[2:] } case strings.HasPrefix(line, "+++ "): - name := line[4 : len(line)-1] + name := convertOctalToUTF8(line[4 : len(line)-1]) if parseRename && strings.HasPrefix(name, "b/") { currentFile.Filename = name[2:] }