Skip to content

Commit 286484d

Browse files
authored
Merge pull request #59 from go-git/conformance-tests
Add conformance tests based on upstream Git
2 parents a3439c2 + b867657 commit 286484d

28 files changed

Lines changed: 1695 additions & 9 deletions

.github/workflows/build.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ on:
88
- main
99

1010
workflow_dispatch:
11+
inputs:
12+
go_git_ref:
13+
description: 'go-git ref (commit SHA, tag, or branch) to build gogit against. Empty = go.mod default.'
14+
required: false
15+
default: ''
1116

1217
permissions:
1318
contents: none
@@ -34,3 +39,30 @@ jobs:
3439

3540
- name: Validate
3641
run: make validate
42+
43+
conformance:
44+
strategy:
45+
fail-fast: false
46+
matrix:
47+
platform: [ubuntu-latest, macos-latest, windows-latest]
48+
49+
runs-on: ${{ matrix.platform }}
50+
steps:
51+
- name: Checkout
52+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
53+
- name: Setup Go
54+
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
55+
with:
56+
go-version: stable
57+
- name: Run conformance
58+
shell: bash
59+
env:
60+
GO_GIT_REF: ${{ inputs.go_git_ref }}
61+
run: make conformance
62+
- name: Upload TAP results
63+
if: failure()
64+
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
65+
with:
66+
name: conformance-tap-${{ matrix.platform }}
67+
path: conformance/.cache/results/
68+
if-no-files-found: ignore

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
build/
2+
conformance/.cache/

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ ifneq ($(shell git status --porcelain --untracked-files=no),)
2424
@git --no-pager diff
2525
@exit 1
2626
endif
27+
28+
.PHONY: conformance
29+
conformance:
30+
./conformance/run.sh

cmd/gogit/add.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/go-git/go-git/v6"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
func init() {
11+
rootCmd.AddCommand(addCmd)
12+
}
13+
14+
var addCmd = &cobra.Command{
15+
Use: "add <pathspec>...",
16+
Short: "Add file contents to the index",
17+
Args: cobra.MinimumNArgs(1),
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
20+
if err != nil {
21+
return fmt.Errorf("failed to open repository: %w", err)
22+
}
23+
24+
w, err := r.Worktree()
25+
if err != nil {
26+
return fmt.Errorf("failed to open worktree: %w", err)
27+
}
28+
29+
for _, path := range args {
30+
if _, err := w.Add(path); err != nil {
31+
return fmt.Errorf("failed to add %s: %w", path, err)
32+
}
33+
}
34+
35+
return nil
36+
},
37+
DisableFlagsInUseLine: true,
38+
}

cmd/gogit/add_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
func TestAddSingleFile(t *testing.T) {
10+
t.Parallel()
11+
repo := t.TempDir()
12+
13+
if _, _, err := runGogit(t, repo, "init"); err != nil {
14+
t.Fatalf("init: %v", err)
15+
}
16+
17+
if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("base\n"), 0o644); err != nil {
18+
t.Fatal(err)
19+
}
20+
21+
if _, _, err := runGogit(t, repo, "add", "file0"); err != nil {
22+
t.Fatalf("add: %v", err)
23+
}
24+
}
25+
26+
func TestAddMultiplePaths(t *testing.T) {
27+
t.Parallel()
28+
repo := t.TempDir()
29+
30+
if _, _, err := runGogit(t, repo, "init"); err != nil {
31+
t.Fatalf("init: %v", err)
32+
}
33+
34+
if err := os.MkdirAll(filepath.Join(repo, "dir1"), 0o755); err != nil {
35+
t.Fatal(err)
36+
}
37+
38+
if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("a\n"), 0o644); err != nil {
39+
t.Fatal(err)
40+
}
41+
42+
if err := os.WriteFile(filepath.Join(repo, "dir1", "file1"), []byte("b\n"), 0o644); err != nil {
43+
t.Fatal(err)
44+
}
45+
46+
if _, _, err := runGogit(t, repo, "add", "file0", "dir1/file1"); err != nil {
47+
t.Fatalf("add: %v", err)
48+
}
49+
}

cmd/gogit/cat-file.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package main
2+
3+
import (
4+
"bufio"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"strings"
10+
11+
"github.com/go-git/go-git/v6"
12+
"github.com/go-git/go-git/v6/plumbing"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var (
17+
catFileExists bool
18+
catFileBatchCheck bool
19+
)
20+
21+
func init() {
22+
catFileCmd.Flags().BoolVarP(&catFileExists, "exists", "e", false,
23+
"Check whether object exists; exit 0 if so, 1 otherwise")
24+
catFileCmd.Flags().BoolVar(&catFileBatchCheck, "batch-check", false,
25+
"Read object IDs from stdin and print <oid> <type> <size> per line (or '<oid> missing')")
26+
rootCmd.AddCommand(catFileCmd)
27+
}
28+
29+
var catFileCmd = &cobra.Command{
30+
Use: "cat-file (-e <oid> | --batch-check)",
31+
Short: "Provide content or check existence of repository objects",
32+
RunE: func(cmd *cobra.Command, args []string) error {
33+
if catFileExists && catFileBatchCheck {
34+
return errors.New("-e and --batch-check are mutually exclusive")
35+
}
36+
37+
if !catFileExists && !catFileBatchCheck {
38+
return errors.New("one of -e or --batch-check is required")
39+
}
40+
41+
r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
42+
if err != nil {
43+
return fmt.Errorf("failed to open repository: %w", err)
44+
}
45+
46+
if catFileExists {
47+
if len(args) != 1 {
48+
return errors.New("-e requires exactly one <oid> argument")
49+
}
50+
51+
return catFileExistsCheck(r, args[0])
52+
}
53+
54+
return catFileBatchCheckRun(cmd, r, os.Stdin)
55+
},
56+
DisableFlagsInUseLine: true,
57+
SilenceUsage: true,
58+
SilenceErrors: true,
59+
}
60+
61+
func catFileExistsCheck(r *git.Repository, oid string) error {
62+
h := plumbing.NewHash(oid)
63+
if _, err := r.Storer.EncodedObject(plumbing.AnyObject, h); err != nil {
64+
os.Exit(1)
65+
}
66+
67+
return nil
68+
}
69+
70+
func catFileBatchCheckRun(cmd *cobra.Command, r *git.Repository, stdin io.Reader) error {
71+
w := bufio.NewWriter(cmd.OutOrStdout())
72+
defer w.Flush()
73+
74+
scanner := bufio.NewScanner(stdin)
75+
for scanner.Scan() {
76+
line := strings.TrimSpace(scanner.Text())
77+
if line == "" {
78+
continue
79+
}
80+
81+
h := plumbing.NewHash(line)
82+
83+
obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h)
84+
if err != nil {
85+
fmt.Fprintf(w, "%s missing\n", line)
86+
87+
continue
88+
}
89+
90+
fmt.Fprintf(w, "%s %s %d\n", line, obj.Type(), obj.Size())
91+
}
92+
93+
return scanner.Err()
94+
}

cmd/gogit/cat-file_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
)
8+
9+
const baseBlobOID = "df967b96a579e45a18b8251732d16804b2e56a55" // sha1 of "blob 5\0base\n"
10+
11+
func setupRepoWithBaseBlob(t *testing.T) string {
12+
t.Helper()
13+
14+
repo := t.TempDir()
15+
16+
if _, _, err := runGogit(t, repo, "init"); err != nil {
17+
t.Fatalf("init: %v", err)
18+
}
19+
20+
if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("base\n"), 0o644); err != nil {
21+
t.Fatal(err)
22+
}
23+
24+
if _, _, err := runGogit(t, repo, "add", "file0"); err != nil {
25+
t.Fatalf("add: %v", err)
26+
}
27+
28+
if _, _, err := runGogitEnv(t, repo, gitIdentityEnv(repo), "commit", "-m", "x"); err != nil {
29+
t.Fatalf("commit: %v", err)
30+
}
31+
32+
return repo
33+
}
34+
35+
func TestCatFileExistsExitsZero(t *testing.T) {
36+
t.Parallel()
37+
38+
repo := setupRepoWithBaseBlob(t)
39+
40+
if _, _, err := runGogit(t, repo, "cat-file", "-e", baseBlobOID); err != nil {
41+
t.Fatalf("cat-file -e <existing>: expected exit 0, got %v", err)
42+
}
43+
}
44+
45+
func TestCatFileMissingExitsOne(t *testing.T) {
46+
t.Parallel()
47+
48+
repo := t.TempDir()
49+
50+
if _, _, err := runGogit(t, repo, "init"); err != nil {
51+
t.Fatalf("init: %v", err)
52+
}
53+
54+
stdout, _, err := runGogit(t, repo, "cat-file", "-e", "0000000000000000000000000000000000000000")
55+
if err == nil {
56+
t.Fatalf("expected non-zero exit, got success")
57+
}
58+
59+
if stdout != "" {
60+
t.Fatalf("expected no stdout, got %q", stdout)
61+
}
62+
}
63+
64+
func TestCatFileBatchCheck(t *testing.T) {
65+
t.Parallel()
66+
67+
repo := setupRepoWithBaseBlob(t)
68+
69+
const missingOID = "0000000000000000000000000000000000000000"
70+
71+
input := baseBlobOID + "\n" + missingOID + "\n"
72+
want := baseBlobOID + " blob 5\n" + missingOID + " missing\n"
73+
74+
stdout, stderr, err := runGogitStdin(t, repo, input, "cat-file", "--batch-check")
75+
if err != nil {
76+
t.Fatalf("cat-file --batch-check failed: %v\nstderr: %s", err, stderr)
77+
}
78+
79+
if stdout != want {
80+
t.Fatalf("batch-check output mismatch:\n got: %q\nwant: %q", stdout, want)
81+
}
82+
}

0 commit comments

Comments
 (0)