Migration This is my new public git source viewer. Source coming soon. Please contact with concerns/bugs. Contact me

internal/gitx/git_test.go

c0427d5fc7c0fcf8606c546071085e8af231fb81 · 3.6 KB · 93 lines raw

1 package gitx
2
3 import (
4 "os"
5 "os/exec"
6 "testing"
7 )
8
9 // runGit drives git directly (not through Repo.run, which doesn't exist
10 // yet at setup time) with the same kind of isolation Repo.run uses --
11 // GIT_CONFIG_NOSYSTEM and a throwaway HOME -- so the test behaves the same
12 // on a machine with commit.gpgsign=true globally as it does in CI.
13 func runGit(t *testing.T, dir string, args ...string) {
14 t.Helper()
15 cmd := exec.Command("git", append([]string{"-C", dir, "-c", "commit.gpgsign=false"}, args...)...)
16 cmd.Env = []string{
17 "PATH=" + os.Getenv("PATH"),
18 "HOME=" + dir,
19 "GIT_CONFIG_NOSYSTEM=1",
20 "GIT_AUTHOR_NAME=test", "GIT_AUTHOR_EMAIL=test@example.com",
21 "GIT_COMMITTER_NAME=test", "GIT_COMMITTER_EMAIL=test@example.com",
22 }
23 if out, err := cmd.CombinedOutput(); err != nil {
24 t.Fatalf("git %v: %v\n%s", args, err, out)
25 }
26 }
27
28 // newTestRepo builds a real repository with one empty commit, a branch
29 // "feature/x" (a branch name containing a slash), a second branch
30 // "release", and a tag "release/1.0". The last two coexist by construction
31 // deliberately: refs/heads and refs/tags are separate namespaces, so
32 // "release" (a branch) and "release/1.0" (a tag) can both exist even
33 // though a branch "release" and a branch "release/1.0" could not -- git
34 // refuses that, since a ref name is a path and "release" can't be both a
35 // file and a directory. Opened through gitx.Open, because SplitRefPath
36 // resolves against Refs(), which shells out to `git for-each-ref` -- there
37 // is no faking that, it has to be a real repo with real refs.
38 func newTestRepo(t *testing.T) *Repo {
39 t.Helper()
40 dir := t.TempDir()
41 runGit(t, dir, "init", "-q", "-b", "main")
42 runGit(t, dir, "commit", "-q", "--allow-empty", "-m", "init")
43 runGit(t, dir, "branch", "feature/x")
44 runGit(t, dir, "branch", "release")
45 runGit(t, dir, "tag", "release/1.0")
46
47 r, err := Open(dir)
48 if err != nil {
49 t.Fatalf("Open: %v", err)
50 }
51 return r
52 }
53
54 // TestSplitRefPath guards the longest-prefix-wins rule that lets ref names
55 // contain slashes. A naive split at the first "/" gets both of the two
56 // interesting cases here wrong: it would send "feature/x/src/main.go" to
57 // ref "feature" (not a ref at all) instead of branch "feature/x", and it
58 // would send "release/1.0/docs/readme.md" to branch "release" instead of
59 // the more specific tag "release/1.0" -- the real ambiguity SplitRefPath
60 // exists to resolve, since both "release" and "release/1.0" are valid refs
61 // here and only the longest match is the intended one.
62 func TestSplitRefPath(t *testing.T) {
63 r := newTestRepo(t)
64
65 cases := []struct {
66 name string
67 rest string
68 wantRef string
69 wantPath string
70 }{
71 {"single-segment branch with a path", "main/src/main.go", "main", "src/main.go"},
72 {"slashed branch name resolves in full, not just its first segment",
73 "feature/x/src/main.go", "feature/x", "src/main.go"},
74 {"slashed tag wins over a shorter branch that's also a valid prefix",
75 "release/1.0/docs/readme.md", "release/1.0", "docs/readme.md"},
76 {"shorter branch still resolves when no longer candidate matches",
77 "release/README.md", "release", "README.md"},
78 {"slashed branch with no path", "feature/x", "feature/x", ""},
79 {"empty rest falls back to HEAD", "", "main", ""},
80 {"no ref matches: whole thing is a path at HEAD",
81 "no/such/branch/file.go", "main", "no/such/branch/file.go"},
82 }
83
84 for _, tc := range cases {
85 t.Run(tc.name, func(t *testing.T) {
86 ref, path := r.SplitRefPath(tc.rest)
87 if ref != tc.wantRef || path != tc.wantPath {
88 t.Errorf("SplitRefPath(%q) = (%q, %q), want (%q, %q)",
89 tc.rest, ref, path, tc.wantRef, tc.wantPath)
90 }
91 })
92 }
93 }