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 | } |