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
32 changes: 32 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ on:
- main

workflow_dispatch:
inputs:
go_git_ref:
description: 'go-git ref (commit SHA, tag, or branch) to build gogit against. Empty = go.mod default.'
required: false
default: ''

permissions:
contents: none
Expand All @@ -34,3 +39,30 @@ jobs:

- name: Validate
run: make validate

conformance:
strategy:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest, windows-latest]

runs-on: ${{ matrix.platform }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
with:
go-version: stable
- name: Run conformance
shell: bash
env:
GO_GIT_REF: ${{ inputs.go_git_ref }}
run: make conformance
- name: Upload TAP results
if: failure()
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
with:
name: conformance-tap-${{ matrix.platform }}
path: conformance/.cache/results/
if-no-files-found: ignore
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
build/
conformance/.cache/
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,7 @@ ifneq ($(shell git status --porcelain --untracked-files=no),)
@git --no-pager diff
@exit 1
endif

.PHONY: conformance
conformance:
./conformance/run.sh
Comment thread
pjbgf marked this conversation as resolved.
38 changes: 38 additions & 0 deletions cmd/gogit/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package main

import (
"fmt"

"github.com/go-git/go-git/v6"
"github.com/spf13/cobra"
)

func init() {
rootCmd.AddCommand(addCmd)
}

var addCmd = &cobra.Command{
Use: "add <pathspec>...",
Short: "Add file contents to the index",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}

w, err := r.Worktree()
if err != nil {
return fmt.Errorf("failed to open worktree: %w", err)
}

for _, path := range args {
if _, err := w.Add(path); err != nil {
return fmt.Errorf("failed to add %s: %w", path, err)
}
}

return nil
},
DisableFlagsInUseLine: true,
}
49 changes: 49 additions & 0 deletions cmd/gogit/add_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"os"
"path/filepath"
"testing"
)

func TestAddSingleFile(t *testing.T) {
t.Parallel()
repo := t.TempDir()

if _, _, err := runGogit(t, repo, "init"); err != nil {
t.Fatalf("init: %v", err)
}

if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("base\n"), 0o644); err != nil {
t.Fatal(err)
}

if _, _, err := runGogit(t, repo, "add", "file0"); err != nil {
t.Fatalf("add: %v", err)
}
}

func TestAddMultiplePaths(t *testing.T) {
t.Parallel()
repo := t.TempDir()

if _, _, err := runGogit(t, repo, "init"); err != nil {
t.Fatalf("init: %v", err)
}

if err := os.MkdirAll(filepath.Join(repo, "dir1"), 0o755); err != nil {
t.Fatal(err)
}

if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("a\n"), 0o644); err != nil {
t.Fatal(err)
}

if err := os.WriteFile(filepath.Join(repo, "dir1", "file1"), []byte("b\n"), 0o644); err != nil {
t.Fatal(err)
}

if _, _, err := runGogit(t, repo, "add", "file0", "dir1/file1"); err != nil {
t.Fatalf("add: %v", err)
}
}
94 changes: 94 additions & 0 deletions cmd/gogit/cat-file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package main

import (
"bufio"
"errors"
"fmt"
"io"
"os"
"strings"

"github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/spf13/cobra"
)

var (
catFileExists bool
catFileBatchCheck bool
)

func init() {
catFileCmd.Flags().BoolVarP(&catFileExists, "exists", "e", false,
"Check whether object exists; exit 0 if so, 1 otherwise")
catFileCmd.Flags().BoolVar(&catFileBatchCheck, "batch-check", false,
"Read object IDs from stdin and print <oid> <type> <size> per line (or '<oid> missing')")
rootCmd.AddCommand(catFileCmd)
}

var catFileCmd = &cobra.Command{
Use: "cat-file (-e <oid> | --batch-check)",
Short: "Provide content or check existence of repository objects",
RunE: func(cmd *cobra.Command, args []string) error {
if catFileExists && catFileBatchCheck {
return errors.New("-e and --batch-check are mutually exclusive")
}

if !catFileExists && !catFileBatchCheck {
return errors.New("one of -e or --batch-check is required")
}

r, err := git.PlainOpenWithOptions(".", &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return fmt.Errorf("failed to open repository: %w", err)
}

if catFileExists {
if len(args) != 1 {
return errors.New("-e requires exactly one <oid> argument")
}

return catFileExistsCheck(r, args[0])
}

return catFileBatchCheckRun(cmd, r, os.Stdin)
},
DisableFlagsInUseLine: true,
SilenceUsage: true,
SilenceErrors: true,
}

func catFileExistsCheck(r *git.Repository, oid string) error {
h := plumbing.NewHash(oid)
if _, err := r.Storer.EncodedObject(plumbing.AnyObject, h); err != nil {
os.Exit(1)
}

return nil
}

func catFileBatchCheckRun(cmd *cobra.Command, r *git.Repository, stdin io.Reader) error {
w := bufio.NewWriter(cmd.OutOrStdout())
defer w.Flush()

scanner := bufio.NewScanner(stdin)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}

h := plumbing.NewHash(line)

obj, err := r.Storer.EncodedObject(plumbing.AnyObject, h)
if err != nil {
fmt.Fprintf(w, "%s missing\n", line)

continue
}

fmt.Fprintf(w, "%s %s %d\n", line, obj.Type(), obj.Size())
}

return scanner.Err()
}
82 changes: 82 additions & 0 deletions cmd/gogit/cat-file_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package main

import (
"os"
"path/filepath"
"testing"
)

const baseBlobOID = "df967b96a579e45a18b8251732d16804b2e56a55" // sha1 of "blob 5\0base\n"

func setupRepoWithBaseBlob(t *testing.T) string {
t.Helper()

repo := t.TempDir()

if _, _, err := runGogit(t, repo, "init"); err != nil {
t.Fatalf("init: %v", err)
}

if err := os.WriteFile(filepath.Join(repo, "file0"), []byte("base\n"), 0o644); err != nil {
t.Fatal(err)
}

if _, _, err := runGogit(t, repo, "add", "file0"); err != nil {
t.Fatalf("add: %v", err)
}

if _, _, err := runGogitEnv(t, repo, gitIdentityEnv(repo), "commit", "-m", "x"); err != nil {
t.Fatalf("commit: %v", err)
}

return repo
}

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

repo := setupRepoWithBaseBlob(t)

if _, _, err := runGogit(t, repo, "cat-file", "-e", baseBlobOID); err != nil {
t.Fatalf("cat-file -e <existing>: expected exit 0, got %v", err)
}
}

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

repo := t.TempDir()

if _, _, err := runGogit(t, repo, "init"); err != nil {
t.Fatalf("init: %v", err)
}

stdout, _, err := runGogit(t, repo, "cat-file", "-e", "0000000000000000000000000000000000000000")
if err == nil {
t.Fatalf("expected non-zero exit, got success")
}

if stdout != "" {
t.Fatalf("expected no stdout, got %q", stdout)
}
}

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

repo := setupRepoWithBaseBlob(t)

const missingOID = "0000000000000000000000000000000000000000"

input := baseBlobOID + "\n" + missingOID + "\n"
want := baseBlobOID + " blob 5\n" + missingOID + " missing\n"

stdout, stderr, err := runGogitStdin(t, repo, input, "cat-file", "--batch-check")
if err != nil {
t.Fatalf("cat-file --batch-check failed: %v\nstderr: %s", err, stderr)
}

if stdout != want {
t.Fatalf("batch-check output mismatch:\n got: %q\nwant: %q", stdout, want)
}
}
Loading
Loading