| # 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 |
| |
| ```go |
| 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: |
| |
| ```go |
| 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: |
| |
| ```go |
| type Status struct { |
| Branch string |
| IsClean bool |
| Staged []FileStatus |
| Unstaged []FileStatus |
| Untracked []string |
| Conflicts []string |
| } |
| ``` |
| |
| ### Commit |
| |
| Represents a Git commit: |
| |
| ```go |
| type Commit struct { |
| Hash string |
| Author Author |
| Committer Author |
| Message string |
| Parents []string |
| Timestamp time.Time |
| Files []CommitFile |
| } |
| ``` |
| |
| ### Branch |
| |
| Represents a Git branch: |
| |
| ```go |
| type Branch struct { |
| Name string |
| IsCurrent bool |
| IsRemote bool |
| Commit string |
| Message string |
| } |
| ``` |
| |
| ## Usage Examples |
| |
| ### Repository Management |
| |
| ```go |
| // 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 |
| |
| ```go |
| // 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 |
| |
| ```go |
| // 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 |
| |
| ```go |
| // 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 |
| |
| ```go |
| // 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: |
| |
| ```go |
| 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: |
| |
| ```go |
| 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. |