Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,8 @@ func TestShowDisplaysDirectories(t *testing.T) {
}

func TestCurrentShowsEffectiveProfileInCwd(t *testing.T) {
skipOnWindows(t)

if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not installed")
}
Expand Down
22 changes: 22 additions & 0 deletions cmd/dir_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,31 @@ package cmd
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"

"github.com/aanogueira/git-context/internal/config"
)

// skipOnWindows bails out of CLI tests that hard-code POSIX-shaped paths
// (`/tmp/...`) or that rely on `t.Setenv("HOME", ...)` to isolate the
// home directory. Neither assumption holds on Windows: `/tmp/x` becomes
// `<cwd-drive>:\tmp\x` after `filepath.Abs`, and `os.UserHomeDir` reads
// `USERPROFILE` rather than `HOME`. The production code is exercised on
// Windows by the unit-level tests in `internal/config` and `internal/git`;
// this file's tests are CLI smoke checks rather than feature contracts.
func skipOnWindows(t *testing.T) {
t.Helper()

if runtime.GOOS == "windows" {
t.Skip("uses POSIX path literals or HOME isolation; covered on Unix only")
}
}

func TestDirAddAssignsAndRegenerates(t *testing.T) {
skipOnWindows(t)

tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)

Expand Down Expand Up @@ -43,6 +61,8 @@ func TestDirAddAssignsAndRegenerates(t *testing.T) {
}

func TestDirAddRejectsDuplicate(t *testing.T) {
skipOnWindows(t)

tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)

Expand Down Expand Up @@ -91,6 +111,8 @@ func TestDirAddWarnsWhenNoDefaultProfile(t *testing.T) {
}

func TestDirRemoveUnassignsAndRegenerates(t *testing.T) {
skipOnWindows(t)

tmpHome := t.TempDir()
t.Setenv("HOME", tmpHome)

Expand Down
2 changes: 2 additions & 0 deletions cmd/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
// default profile, assign a directory to a different profile, verify that
// inside the assigned directory git resolves the right user.email.
func TestEndToEndDirectoryAssignment(t *testing.T) {
skipOnWindows(t)

if _, err := exec.LookPath("git"); err != nil {
t.Skip("git not installed")
}
Expand Down
5 changes: 5 additions & 0 deletions internal/config/dirs.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
// git-style glob).
// - `~` is expanded to the user's home directory.
// - Relative paths are resolved against the current working directory.
// - On Windows, backslashes are normalized to forward slashes so the
// output is in the canonical form git config expects in `gitdir:`
// and `path =` values (and so storage in YAML is platform-agnostic).
// - A trailing slash is always appended so the directive matches the
// whole subtree, not just the directory itself.
func NormalizeDir(path string) (string, error) {
Expand Down Expand Up @@ -46,6 +49,8 @@ func NormalizeDir(path string) (string, error) {
path = abs
}

path = strings.ReplaceAll(path, `\`, `/`)

if !strings.HasSuffix(path, "/") {
path += "/"
}
Expand Down
38 changes: 28 additions & 10 deletions internal/config/dirs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ package config
import (
"os"
"path/filepath"
"runtime"
"strings"
"testing"
)

// toFwd mirrors NormalizeDir's backslash-to-slash canonicalization so test
// expectations built via filepath.Join (which uses OS-native separators)
// stay comparable on Windows.
func toFwd(p string) string { return strings.ReplaceAll(p, `\`, `/`) }

func TestNormalizeDir(t *testing.T) {
t.Parallel()

Expand All @@ -20,23 +26,35 @@ func TestNormalizeDir(t *testing.T) {
t.Fatalf("Getwd failed: %v", err)
}

tests := []struct {
type tcase struct {
name string
in string
want string
}{
{"absolute path gets trailing slash", "/Users/x/projects/work", "/Users/x/projects/work/"},
{
"absolute path keeps existing trailing slash",
"/Users/x/projects/work/",
"/Users/x/projects/work/",
},
{"tilde expands to home", "~/projects/work", filepath.Join(home, "projects", "work") + "/"},
{"relative resolves against cwd", "./foo", filepath.Join(cwd, "foo") + "/"},
}

tests := []tcase{
{"tilde expands to home", "~/projects/work", toFwd(filepath.Join(home, "projects", "work")) + "/"},
{"relative resolves against cwd", "./foo", toFwd(filepath.Join(cwd, "foo")) + "/"},
{"single-star glob passes through unchanged", "~/work/*/repo", "~/work/*/repo"},
{"double-star glob passes through unchanged", "~/work/**", "~/work/**"},
}

// POSIX-rooted absolute paths (`/Users/x/...`) are not absolute on
// Windows — `filepath.Abs` resolves them against the cwd's drive,
// producing `D:/Users/x/...`. Skip those rows on Windows; the
// equivalent semantics are exercised by the tilde + relative cases
// above, which build absolute paths via filepath.Join.
if runtime.GOOS != "windows" {
tests = append(tests,
tcase{"absolute path gets trailing slash", "/Users/x/projects/work", "/Users/x/projects/work/"},
tcase{
"absolute path keeps existing trailing slash",
"/Users/x/projects/work/",
"/Users/x/projects/work/",
},
)
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
Expand Down
4 changes: 2 additions & 2 deletions internal/git/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ func TestWriteRootConfigDefaultAndAssignments(t *testing.T) {
t.Errorf("missing [include] block:\n%s", content)
}

if !strings.Contains(content, "path = "+defaultProfilePath) {
if !strings.Contains(content, "path = "+toGitPath(defaultProfilePath)) {
t.Errorf("missing default profile path:\n%s", content)
}

Expand Down Expand Up @@ -464,7 +464,7 @@ func TestRegenerate(t *testing.T) {
}

rootStr := string(root)
wantInclude := filepath.Join(profilesDir, "work.gitconfig")
wantInclude := toGitPath(filepath.Join(profilesDir, "work.gitconfig"))

if !strings.Contains(rootStr, "path = "+wantInclude) {
t.Errorf("root missing default include for %q:\n%s", wantInclude, rootStr)
Expand Down
Loading