diff --git a/commands/status/process.go b/commands/status/process.go index 32b84ab..30a5c9a 100644 --- a/commands/status/process.go +++ b/commands/status/process.go @@ -132,14 +132,23 @@ ProcessFile extracts the filename from a status change, and determines the absolute and relative paths. Parameters: - - c: the raw bytes representing a status change + - c: the raw bytes representing a status change from `git status --porcelain` - root: the absolute path to the git working tree */ func extractFile(chunk []byte, root, wd string) (absPath, relPath string) { + // file identifier starts at pos4 and continues to EOL file := string(chunk[3:len(chunk)]) - absPath = filepath.Join(root, file) - relPath, err := filepath.Rel(wd, absPath) + // try to unquote it, for instances where git --porcelain quotes for special + // characters + unquoted, err := strconv.Unquote(file) + if err == nil { + file = unquoted + } + + // determine absolute and relative paths + absPath = filepath.Join(root, file) + relPath, err = filepath.Rel(wd, absPath) if err != nil { relPath = absPath } diff --git a/commands/status/process_test.go b/commands/status/process_test.go index 9e5dfe7..6e82fcf 100644 --- a/commands/status/process_test.go +++ b/commands/status/process_test.go @@ -66,6 +66,39 @@ var testCasesExtractFile = []struct { expectedAbs: "/tmp/foo/bar/narwhal/disco/yyy", expectedRel: "../../narwhal/disco/yyy", }, + { + root: "/tmp/foo", + wd: "/tmp/foo", + chunk: []byte("R foo.txt -> bar.txt"), + expectedAbs: "/tmp/foo/foo.txt -> bar.txt", // same as scmbreeze, but problematic? + expectedRel: "foo.txt -> bar.txt", // same as scmbreeze, but problematic? + }, + // following examples are ones where scm_breeze strips the escaping that + // git status --porcelain does in certain cases. Using -z would have probably + // been a better way to go, but for now lets see if we can replicate it... + // (note: scm_breeze fails on complex cases of this, we dont use it as source + // of truth for correctness!) + { + root: "/tmp/foo", + wd: "/tmp/foo", + chunk: []byte(`A "hi there mom.txt"`), + expectedAbs: "/tmp/foo/hi there mom.txt", + expectedRel: "hi there mom.txt", + }, + { + root: "/tmp/foo", + wd: "/tmp/foo/bar", + chunk: []byte(`?? "\"x.txt"`), + expectedAbs: `/tmp/foo/"x.txt`, + expectedRel: `../"x.txt`, + }, + { + root: "/tmp/foo", + wd: "/tmp/foo", + chunk: []byte(`?? "hi m\"o\"m.txt"`), + expectedAbs: `/tmp/foo/hi m"o"m.txt`, //scmbreeze fails these with `hi m"o\` + expectedRel: `hi m"o"m.txt`, + }, } func TestExtractFile(t *testing.T) { diff --git a/features/shell_wrappers.feature b/features/shell_wrappers.feature index f961630..c275ad5 100644 --- a/features/shell_wrappers.feature +++ b/features/shell_wrappers.feature @@ -31,3 +31,44 @@ Feature: optional wrapping of normal git cmds in the shell | shell | | bash | | zsh | + + + Scenario Outline: Wrapped `git add` can handle files with spaces properly + Given I am in a git repository + And an empty file named "file with spaces.txt" + When I run `` interactively + And I type `eval "$(scmpuff init -ws)"` + And I type "scmpuff_status" + And I type "git add 1" + And I type "exit" + Then the exit status should be 0 + And the output should match /new file:\s+\[1\] file with spaces.txt/ + Examples: + | shell | + | bash | + | zsh | + + + Scenario Outline: Wrapped `git reset` can handle files with spaces properly + This is different and more complex because `git status --porcelain` puts it + inside quotes for the case where it is already added (but doesnt in the ?? + case surprisingly), and also it expands using --relative. + + Given I am in a git repository + And an empty file named "file with spaces.txt" + And I successfully run `git add "file with spaces.txt"` + When I run `` interactively + And I type `eval "$(scmpuff init -ws)"` + And I type "scmpuff_status" + And I type "git reset 1" + And I type "exit" + Then the exit status should be 0 + When I run `scmpuff status` + Then the stdout from "scmpuff status" should contain: + """ + untracked: [1] file with spaces.txt + """ + Examples: + | shell | + | bash | + | zsh |