tree: 031f37eb4b4649229587f4958478e9e64b4c1c60 [path history] [tgz]
  1. example.go
  2. gerrit.go
  3. git.go
  4. git_test.go
  5. github.go
  6. pull_request_example.go
  7. PULL_REQUEST_README.md
  8. README.md
server/git/README.md

Git Interface for Go

A comprehensive Go interface for Git operations that provides a clean, type-safe API for interacting with Git repositories.

Features

  • Complete Git Operations: Supports all major Git operations including repository management, commits, branches, remotes, tags, and more
  • Type-Safe API: Strongly typed interfaces and structs for better code safety
  • Context Support: All operations support context for cancellation and timeouts
  • Error Handling: Comprehensive error handling with detailed error messages
  • Flexible Configuration: Configurable timeouts and environment variables
  • Easy to Use: Simple and intuitive API design

Installation

The Git interface is part of the github.com/iomodo/staff module. No additional dependencies are required beyond the standard library.

Quick Start

package main

import (
    "context"
    "fmt"
    "log"
    
    "github.com/iomodo/staff/git"
)

func main() {
    ctx := context.Background()
    
    // Create a Git instance
    git := git.DefaultGit("/path/to/your/repo")
    
    // Open an existing repository
    if err := git.Open(ctx, "/path/to/your/repo"); err != nil {
        log.Fatalf("Failed to open repository: %v", err)
    }
    
    // Get repository status
    status, err := git.Status(ctx)
    if err != nil {
        log.Fatalf("Failed to get status: %v", err)
    }
    
    fmt.Printf("Current branch: %s\n", status.Branch)
    fmt.Printf("Repository is clean: %t\n", status.IsClean)
}

Core Interface

GitInterface

The main interface that defines all Git operations:

type GitInterface interface {
    // Repository operations
    Init(ctx context.Context, path string) error
    Clone(ctx context.Context, url, path string) error
    IsRepository(ctx context.Context, path string) (bool, error)
    
    // Status and information
    Status(ctx context.Context) (*Status, error)
    Log(ctx context.Context, options LogOptions) ([]Commit, error)
    Show(ctx context.Context, ref string) (*Commit, error)
    
    // Branch operations
    ListBranches(ctx context.Context) ([]Branch, error)
    CreateBranch(ctx context.Context, name string, startPoint string) error
    Checkout(ctx context.Context, ref string) error
    DeleteBranch(ctx context.Context, name string, force bool) error
    GetCurrentBranch(ctx context.Context) (string, error)
    
    // Commit operations
    Add(ctx context.Context, paths []string) error
    AddAll(ctx context.Context) error
    Commit(ctx context.Context, message string, options CommitOptions) error
    
    // Remote operations
    ListRemotes(ctx context.Context) ([]Remote, error)
    AddRemote(ctx context.Context, name, url string) error
    RemoveRemote(ctx context.Context, name string) error
    Fetch(ctx context.Context, remote string, options FetchOptions) error
    Pull(ctx context.Context, remote, branch string) error
    Push(ctx context.Context, remote, branch string, options PushOptions) error
    
    // Merge operations
    Merge(ctx context.Context, ref string, options MergeOptions) error
    MergeBase(ctx context.Context, ref1, ref2 string) (string, error)
    
    // Configuration
    GetConfig(ctx context.Context, key string) (string, error)
    SetConfig(ctx context.Context, key, value string) error
    GetUserConfig(ctx context.Context) (*UserConfig, error)
    SetUserConfig(ctx context.Context, config UserConfig) error
}

Data Structures

Status

Represents the current state of the repository:

type Status struct {
    Branch     string
    IsClean    bool
    Staged     []FileStatus
    Unstaged   []FileStatus
    Untracked  []string
    Conflicts  []string
}

Commit

Represents a Git commit:

type Commit struct {
    Hash      string
    Author    Author
    Committer Author
    Message   string
    Parents   []string
    Timestamp time.Time
    Files     []CommitFile
}

Branch

Represents a Git branch:

type Branch struct {
    Name      string
    IsCurrent bool
    IsRemote  bool
    Commit    string
    Message   string
}

Usage Examples

Repository Management

// Initialize a new repository
git := git.DefaultGit("/path/to/new/repo")
if err := git.Init(ctx, "/path/to/new/repo"); err != nil {
    log.Fatal(err)
}

// Clone a repository
if err := git.Clone(ctx, "https://github.com/user/repo.git", "/path/to/clone"); err != nil {
    log.Fatal(err)
}

Basic Git Workflow

// Stage files
if err := git.Add(ctx, []string{"file1.txt", "file2.txt"}); err != nil {
    log.Fatal(err)
}

// Or stage all changes
if err := git.AddAll(ctx); err != nil {
    log.Fatal(err)
}

// Commit changes
commitOptions := git.CommitOptions{
    AllowEmpty: false,
}
if err := git.Commit(ctx, "Add new feature", commitOptions); err != nil {
    log.Fatal(err)
}

Branch Operations

// List all branches
branches, err := git.ListBranches(ctx)
if err != nil {
    log.Fatal(err)
}

for _, branch := range branches {
    fmt.Printf("Branch: %s (current: %t)\n", branch.Name, branch.IsCurrent)
}

// Create a new branch
if err := git.CreateBranch(ctx, "feature/new-feature", ""); err != nil {
    log.Fatal(err)
}

// Switch to a branch
if err := git.Checkout(ctx, "feature/new-feature"); err != nil {
    log.Fatal(err)
}

// Get current branch
currentBranch, err := git.GetCurrentBranch(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Current branch: %s\n", currentBranch)

Remote Operations

// Add a remote
if err := git.AddRemote(ctx, "origin", "https://github.com/user/repo.git"); err != nil {
    log.Fatal(err)
}

// List remotes
remotes, err := git.ListRemotes(ctx)
if err != nil {
    log.Fatal(err)
}

for _, remote := range remotes {
    fmt.Printf("Remote: %s -> %s\n", remote.Name, remote.URL)
}

// Fetch from remote
fetchOptions := git.FetchOptions{
    All:  true,
    Tags: true,
}
if err := git.Fetch(ctx, "", fetchOptions); err != nil {
    log.Fatal(err)
}

// Push to remote
pushOptions := git.PushOptions{
    SetUpstream: true,
}
if err := git.Push(ctx, "origin", "main", pushOptions); err != nil {
    log.Fatal(err)
}

Configuration

// Get user configuration
userConfig, err := git.GetUserConfig(ctx)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("User: %s <%s>\n", userConfig.Name, userConfig.Email)

// Set user configuration
newConfig := git.UserConfig{
    Name:  "John Doe",
    Email: "john@example.com",
}
if err := git.SetUserConfig(ctx, newConfig); err != nil {
    log.Fatal(err)
}

// Get/set custom config
value, err := git.GetConfig(ctx, "core.editor")
if err != nil {
    log.Fatal(err)
}

if err := git.SetConfig(ctx, "core.editor", "vim"); err != nil {
    log.Fatal(err)
}

Error Handling

The Git interface provides detailed error information through the GitError type:

if err := git.Commit(ctx, "Invalid commit", git.CommitOptions{}); err != nil {
    if gitErr, ok := err.(*git.GitError); ok {
        fmt.Printf("Git command '%s' failed: %v\n", gitErr.Command, gitErr.Err)
        fmt.Printf("Output: %s\n", gitErr.Output)
    }
}

Configuration

You can customize the Git instance with specific configuration:

config := git.GitConfig{
    Timeout: 60 * time.Second,
    Env: map[string]string{
        "GIT_AUTHOR_NAME":  "Custom Author",
        "GIT_AUTHOR_EMAIL": "author@example.com",
    },
}

git := git.NewGit("/path/to/repo", config)

Thread Safety

The Git interface is not thread-safe. If you need to use it from multiple goroutines, you should either:

  1. Use separate Git instances for each goroutine
  2. Use a mutex to synchronize access
  3. Use channels to serialize operations

Requirements

  • Go 1.24.4 or later
  • Git installed and available in PATH
  • Appropriate permissions to access the repository

License

This code is part of the github.com/iomodo/staff project and follows the same license terms.