Version Control
The Version Control component provides integrated Git functionality and repository management capabilities for GTB applications. It enables seamless integration with multiple VCS providers (GitHub and GitLab), automated version checking, and repository operations.
Overview¶
The VCS component provides two distinct areas of functionality:
-
Pluggable VCS API Integration - Standardized integration with GitHub Enterprise and GitLab APIs for repository management, pull requests, releases, and asset downloads.
-
Git Repository Operations - The
Repostruct enables direct Git operations on repositories, supporting both local filesystem and in-memory repository management.
Abstraction Strategy¶
The VCS package implements custom abstractions over upstream Git and GitHub libraries to provide several key benefits:
- Enhanced Testing and Mocking Strategy
- Many upstream packages (like
go-gitandgoogle/go-github) don't provide comprehensive interfaces suitable for testing. Our abstractions (GitHubClient,RepoLike, andrelease.Providerinterfaces) enable clean dependency injection and comprehensive mocking capabilities that wouldn't otherwise be available. - Multi-Backend Support
- The component is architected to support multiple version control backends. While GitHub remains the primary focus, the system now includes robust GitLab support, especially for self-updating and release management.
- Simplified Authentication Layer
- Rather than dealing with the complex authentication patterns of multiple upstream libraries, our abstractions provide a unified authentication interface that handles SSH keys, basic authentication, and token-based authentication consistently across both GitHub API operations and Git repository operations.
- Filesystem Abstraction Integration
- Our implementations integrate seamlessly with the
aferofilesystem abstraction used throughout the GTB framework, allowing for consistent file operations whether working with local filesystems, in-memory filesystems, or other storage backends. - Unified Error Handling
- The abstractions provide consistent error handling patterns using the
cockroachdb/errorspackage, ensuring that all VCS operations follow the same error wrapping and context patterns used throughout the framework.
This separation allows for flexible integration patterns where you can use GitHub API operations independently of local Git operations, or combine them for comprehensive version control workflows, all while maintaining excellent testability and consistent interfaces.
GitHub Integration¶
The VCS package provides comprehensive GitHub Enterprise API integration. This component is specifically designed to interact with both public GitHub and GitHub Enterprise instances.
GHClient - GitHub Enterprise Client¶
The GHClient is the primary implementation for GitHub Enterprise API operations. Engineers should use this concrete type rather than the interface directly:
type GHClient struct {
Client *github.Client
cfg config.Containable
}
// Create a new GitHub Enterprise client
func NewGitHubClient(cfg config.Containable) (*GHClient, error)
// Core methods available on GHClient:
func (c *GHClient) GetClient() *github.Client
func (c *GHClient) CreatePullRequest(ctx context.Context, owner string, repo string, pull *github.NewPullRequest) (*github.PullRequest, error)
func (c *GHClient) GetPullRequestByBranch(ctx context.Context, owner, repo, branch, state string) (*github.PullRequest, error)
func (c *GHClient) AddLabelsToPullRequest(ctx context.Context, owner, repo string, number int, labels []string) error
func (c *GHClient) UpdatePullRequest(ctx context.Context, owner, repo string, number int, pull *github.PullRequest) (*github.PullRequest, *github.Response, error)
func (c *GHClient) CreateRepo(ctx context.Context, owner, slug string) (*github.Repository, error)
func (c *GHClient) UploadKey(ctx context.Context, name string, key []byte) error
func (c *GHClient) ListReleases(ctx context.Context, owner, repo string) ([]string, error)
func (c *GHClient) GetReleaseAssets(ctx context.Context, owner, repo, tag string) ([]*github.ReleaseAsset, error)
func (c *GHClient) GetReleaseAssetID(ctx context.Context, owner, repo, tag, assetName string) (int64, error)
func (c *GHClient) DownloadAsset(ctx context.Context, owner, repo string, assetID int64) (io.ReadCloser, error)
func (c *GHClient) DownloadAssetTo(ctx context.Context, fs afero.Fs, owner, repo string, assetID int64, filePath string) error
func (c *GHClient) GetFileContents(ctx context.Context, owner, repo, path, ref string) (string, error)
Creating a GitHub Enterprise Client¶
Create a GHClient instance to interact with your GitHub Enterprise instance:
func setupGitHubClient(props *props.Props) (*vcs.GHClient, error) {
// Create GitHub client using configuration
client, err := vcs.NewGitHubClient(props.Config.Sub("github"))
if err != nil {
return nil, errors.WrapPrefix(err, "failed to create GitHub client", 0)
}
return client, nil
}
Configuration for GitHub Enterprise:
github:
url:
api: "https://github.enterprise.com/api/v3" # GitHub Enterprise API URL
upload: "https://github.enterprise.com/api/v3" # GitHub Enterprise upload URL
auth:
env: "GITHUB_TOKEN" # Environment variable name for token
value: "ghp_xxxxxxxxxxxx" # Direct token value (not recommended)
Token Resolution Order:
auth.valuein configuration (if present)- Environment variable specified in
auth.env - Fallback to
GITHUB_TOKENenvironment variable
Repository Management Operations¶
Use the GHClient to interact with GitHub Enterprise repositories:
func listReleases(ctx context.Context, props *props.Props) error {
client, err := setupGitHubClient(props)
if err != nil {
return errors.WrapPrefix(err, "failed to setup GitHub client", 0)
}
owner := props.Config.GetString("github.owner")
repo := props.Config.GetString("github.repo")
releases, err := client.ListReleases(ctx, owner, repo)
if err != nil {
return errors.WrapPrefix(err, "failed to list releases", 0)
}
props.Logger.Info("Found releases", "count", len(releases))
for _, release := range releases {
props.Logger.Info("Release", "tag", release)
}
return nil
}
Pull Request Workflows¶
Create and manage pull requests using the GHClient:
Create and manage pull requests in GitHub Enterprise:
func createPullRequest(ctx context.Context, owner, repo, title, body, head, base string, props *props.Props) error {
client, err := setupGitHubClient(props)
if err != nil {
return err
}
newPR := &github.NewPullRequest{
Title: github.Ptr(title),
Body: github.Ptr(body),
Head: github.Ptr(head),
Base: github.Ptr(base),
}
pr, err := client.CreatePullRequest(ctx, owner, repo, newPR)
if err != nil {
return errors.WrapPrefix(err, "failed to create pull request", 0)
}
props.Logger.Info("Pull request created",
"number", pr.GetNumber(),
"url", pr.GetHTMLURL())
// Add labels if needed
labels := []string{"enhancement", "automated"}
if err := client.AddLabelsToPullRequest(ctx, owner, repo, pr.GetNumber(), labels); err != nil {
props.Logger.Warn("Failed to add labels", "error", err)
}
return nil
}
Release Asset Management¶
Download assets from GitHub Enterprise releases using the GHClient:
func downloadReleaseAsset(ctx context.Context, owner, repo, tag, assetName, outputPath string, props *props.Props) error {
client, err := setupGitHubClient(props)
if err != nil {
return err
}
// Get asset ID
assetID, err := client.GetReleaseAssetID(ctx, owner, repo, tag, assetName)
if err != nil {
return errors.WrapPrefix(err, "failed to get asset ID", 0)
}
props.Logger.Info("Downloading asset",
"asset", assetName,
"id", assetID,
"output", outputPath)
// Download asset to filesystem
if err := client.DownloadAssetTo(ctx, props.FS, owner, repo, assetID, outputPath); err != nil {
return errors.WrapPrefix(err, "failed to download asset", 0)
}
props.Logger.Info("Asset downloaded successfully")
return nil
}
File Content Retrieval¶
Retrieve file contents directly from GitHub Enterprise using the GHClient:
func getFileFromGitHub(ctx context.Context, owner, repo, path, ref string, props *props.Props) (string, error) {
client, err := setupGitHubClient(props)
if err != nil {
return "", err
}
content, err := client.GetFileContents(ctx, owner, repo, path, ref)
if err != nil {
return "", errors.WrapPrefix(err, "failed to get file contents", 0)
}
props.Logger.Info("Retrieved file from GitHub",
"repo", fmt.Sprintf("%s/%s", owner, repo),
"path", path,
"ref", ref,
"size", len(content))
return content, nil
}
GitHubClient Interface (For Testing and Mocking)¶
The GitHubClient interface is primarily used for testing and when working with provided mocks. In production code, use the concrete GHClient type:
type GitHubClient interface {
GetClient() *github.Client
CreatePullRequest(ctx context.Context, owner string, repo string, pull *github.NewPullRequest) (*github.PullRequest, error)
GetPullRequestByBranch(ctx context.Context, owner, repo, branch, state string) (*github.PullRequest, error)
AddLabelsToPullRequest(ctx context.Context, owner, repo string, number int, labels []string) error
UpdatePullRequest(ctx context.Context, owner, repo string, number int, pull *github.PullRequest) (*github.PullRequest, *github.Response, error)
CreateRepo(ctx context.Context, owner, slug string) (*github.Repository, error)
UploadKey(ctx context.Context, name string, key []byte) error
ListReleases(ctx context.Context, owner, repo string) ([]string, error)
GetReleaseAssets(ctx context.Context, owner, repo, tag string) ([]*github.ReleaseAsset, error)
GetReleaseAssetID(ctx context.Context, owner, repo, tag, assetName string) (int64, error)
DownloadAsset(ctx context.Context, owner, repo string, assetID int64) (io.ReadCloser, error)
DownloadAssetTo(ctx context.Context, fs afero.Fs, owner, repo string, assetID int64, filePath string) error
GetFileContents(ctx context.Context, owner, repo, path, ref string) (string, error)
}
## GitLab Integration
GitLab support is focused on release management and self-updating capabilities. It supports both GitLab.com and self-managed GitLab instances.
### GitLab Configuration
Configure GitLab in your `config.yaml`:
```yaml
gitlab:
url:
api: "https://gitlab.example.com/api/v4"
auth:
env: "GITLAB_TOKEN"
value: ""
Release Provider Abstraction¶
For features like self-updating, GTB uses a unified release.Provider interface that abstracts away the differences between GitHub and GitLab.
Release Provider Interfaces¶
type Provider interface {
GetLatestRelease(ctx context.Context, owner, repo string) (Release, error)
GetReleaseByTag(ctx context.Context, owner, repo, tag string) (Release, error)
ListReleases(ctx context.Context, owner, repo string, limit int) ([]Release, error)
DownloadReleaseAsset(ctx context.Context, owner, repo string, asset ReleaseAsset) (io.ReadCloser, string, error)
}
type Release interface {
GetName() string
GetTagName() string
GetBody() string
GetDraft() bool
GetAssets() []ReleaseAsset
}
type ReleaseAsset interface {
GetID() int64
GetName() string
GetBrowserDownloadURL() string
}
## Git Repository Operations
The VCS package provides comprehensive Git repository management through the `Repo` struct. This component handles direct Git operations on both local filesystem and in-memory repositories, making it ideal for different deployment scenarios including CI/CD pipelines and local development.
### Repo - Git Repository Manager
The `Repo` struct is the primary implementation for Git repository operations. Engineers should use this concrete type rather than the interface directly:
```go
type Repo struct {
source int
config *config.Config
repo *git.Repository
auth transport.AuthMethod
tree *git.Worktree
}
// Create a new repository manager
func NewRepo(props *props.Props, ops ...RepoOpt) (*Repo, error)
// Core methods available on Repo:
func (r *Repo) SourceIs(source int) bool
func (r *Repo) SetSource(source int)
func (r *Repo) SetRepo(repo *git.Repository)
func (r *Repo) GetRepo() *git.Repository
func (r *Repo) SetKey(key *ssh.PublicKeys)
func (r *Repo) SetBasicAuth(username, password string)
func (r *Repo) GetAuth() transport.AuthMethod
func (r *Repo) SetTree(tree *git.Worktree)
func (r *Repo) GetTree() *git.Worktree
func (r *Repo) Checkout(plumbing.ReferenceName) error
func (r *Repo) CheckoutCommit(plumbing.Hash) error
func (r *Repo) CreateBranch(string) error
func (r *Repo) Push(*git.PushOptions) error
func (r *Repo) Commit(string, *git.CommitOptions) (plumbing.Hash, error)
// Repository operations
func (r *Repo) OpenInMemory(string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
func (r *Repo) OpenLocal(string, string) (*git.Repository, *git.Worktree, error)
func (r *Repo) Open(RepoType, string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
func (r *Repo) Clone(string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
// File operations for in-memory repositories
func (r *Repo) WalkTree(func(*object.File) error) error
func (r *Repo) FileExists(string) (bool, error)
func (r *Repo) DirectoryExists(string) (bool, error)
func (r *Repo) GetFile(string) (*object.File, error)
func (r *Repo) AddToFS(fs afero.Fs, gitFile *object.File, fullPath string) error
Repository Types and Clone Options¶
Git repository operations support different storage strategies and cloning configurations:
type RepoType = string
var (
LocalRepo RepoType = "local" // Filesystem-based repository
InMemoryRepo RepoType = "inmemory" // Memory-based repository
)
// CloneOption represents a function that configures clone options.
type CloneOption func(*git.CloneOptions)
// WithShallowClone configures a shallow clone with the specified depth.
func WithShallowClone(depth int) CloneOption
// WithSingleBranch configures the clone to only fetch a single branch.
func WithSingleBranch(branch string) CloneOption
// WithNoTags configures the clone to skip fetching tags.
func WithNoTags() CloneOption
// WithRecurseSubmodules configures recursive submodule cloning.
func WithRecurseSubmodules() CloneOption
Repository Setup and Authentication¶
Create and configure a Repo instance with appropriate authentication:
func setupRepository(props *props.Props) (*vcs.Repo, error) {
repo, err := vcs.NewRepo(props)
if err != nil {
return nil, errors.WrapPrefix(err, "failed to create repo", 0)
}
// Configure authentication for Git operations
if props.Config.Has("github.ssh_key") {
keyPath := props.Config.GetString("github.ssh_key")
publicKey, err := ssh.NewPublicKeysFromFile("git", keyPath, "")
if err != nil {
return nil, errors.WrapPrefix(err, "failed to parse SSH key", 0)
}
repo.SetKey(publicKey)
} else if props.Config.Has("github.username") && props.Config.Has("github.password") {
repo.SetBasicAuth(
props.Config.GetString("github.username"),
props.Config.GetString("github.password"),
)
}
return repo, nil
}
Repository Cloning Strategies¶
Clone repositories using different strategies based on your requirements with the Repo:
func cloneRepository(ctx context.Context, repoURL, repoType string, props *props.Props) (*git.Repository, *git.Worktree, error) {
repo, err := setupRepository(props)
if err != nil {
return nil, nil, err
}
props.Logger.Info("Cloning repository",
"url", repoURL,
"type", repoType)
var gitRepo *git.Repository
var worktree *git.Worktree
switch repoType {
case vcs.InMemoryRepo:
// Clone into memory for temporary operations (CI/CD, analysis)
gitRepo, worktree, err = repo.OpenInMemory(repoURL, "")
case vcs.LocalRepo:
// Clone to local filesystem for persistent operations
gitRepo, worktree, err = repo.OpenLocal(repoURL, "./local-repo")
default:
return nil, nil, fmt.Errorf("unknown repository type: %s", repoType)
}
if err != nil {
return nil, nil, errors.WrapPrefix(err, "failed to clone repository", 0)
}
props.Logger.Info("Repository cloned successfully")
return gitRepo, worktree, nil
}
Advanced Cloning Options¶
Configure specific cloning behavior for different scenarios using the Repo:
func cloneWithOptions(ctx context.Context, repoURL string, props *props.Props) (*git.Repository, *git.Worktree, error) {
repo, err := setupRepository(props)
if err != nil {
return nil, nil, err
}
// Clone with optimized options for CI/CD
gitRepo, worktree, err := repo.Clone(repoURL, "./shallow-repo",
vcs.WithShallowClone(1), // Only latest commit
vcs.WithSingleBranch("main"), // Only main branch
vcs.WithNoTags(), // Skip tags for faster clone
)
if err != nil {
return nil, nil, errors.WrapPrefix(err, "failed to clone with options", 0)
}
return gitRepo, worktree, nil
}
Branch Management¶
Perform Git branch operations using the Repo:
func manageBranches(ctx context.Context, repo *vcs.Repo, props *props.Props) error {
// Create a new branch
if err := repo.CreateBranch("feature/new-feature"); err != nil {
return errors.WrapPrefix(err, "failed to create branch", 0)
}
// Checkout the branch
branchRef := plumbing.NewBranchReferenceName("feature/new-feature")
if err := repo.Checkout(branchRef); err != nil {
return errors.WrapPrefix(err, "failed to checkout branch", 0)
}
props.Logger.Info("Switched to branch", "branch", "feature/new-feature")
return nil
}
Commit Operations¶
Create commits with proper metadata using the Repo:
func commitChanges(ctx context.Context, repo *vcs.Repo, message string, props *props.Props) error {
// Create commit with author information
commitOpts := &git.CommitOptions{
Author: &object.Signature{
Name: props.Config.GetString("git.author.name"),
Email: props.Config.GetString("git.author.email"),
When: time.Now(),
},
}
commitHash, err := repo.Commit(message, commitOpts)
if err != nil {
return errors.WrapPrefix(err, "failed to create commit", 0)
}
props.Logger.Info("Changes committed",
"commit", commitHash.String(),
"message", message)
return nil
}
Repository File Operations¶
Access and manipulate files within Git repositories using the Repo:
func processRepositoryFiles(ctx context.Context, repo *vcs.Repo, props *props.Props) error {
// Walk through all files in the repository
err := repo.WalkTree(func(file *object.File) error {
props.Logger.Debug("Processing file", "path", file.Name)
// Check if it's a specific file type
if strings.HasSuffix(file.Name, ".go") {
// Process Go files
content, err := file.Contents()
if err != nil {
return errors.WrapPrefix(err, "failed to read file contents", 0)
}
props.Logger.Info("Found Go file",
"path", file.Name,
"size", len(content))
}
return nil
})
if err != nil {
return errors.WrapPrefix(err, "failed to walk repository tree", 0)
}
return nil
}
File Existence Checks¶
Verify repository structure and required files using the Repo:
func checkRepositoryStructure(ctx context.Context, repo *vcs.Repo, props *props.Props) error {
// Check for important files
requiredFiles := []string{"go.mod", "README.md", "LICENSE"}
for _, filename := range requiredFiles {
exists, err := repo.FileExists(filename)
if err != nil {
return errors.WrapPrefix(err, fmt.Sprintf("failed to check file %s", filename), 0)
}
if exists {
props.Logger.Info("Required file found", "file", filename)
} else {
props.Logger.Warn("Required file missing", "file", filename)
}
}
// Check for directories
directories := []string{"cmd", "pkg", "internal"}
for _, dirname := range directories {
exists, err := repo.DirectoryExists(dirname)
if err != nil {
return errors.WrapPrefix(err, fmt.Sprintf("failed to check directory %s", dirname), 0)
}
if exists {
props.Logger.Info("Directory found", "directory", dirname)
}
}
return nil
}
Extract Files to Filesystem¶
Extract repository files to the local filesystem using the Repo:
func extractRepositoryFiles(ctx context.Context, repo *vcs.Repo, targetDir string, props *props.Props) error {
return repo.WalkTree(func(file *object.File) error {
// Determine target path
targetPath := filepath.Join(targetDir, file.Name)
// Extract file to filesystem
if err := repo.AddToFS(props.FS, file, targetPath); err != nil {
return errors.WrapPrefix(err, fmt.Sprintf("failed to extract file %s", file.Name), 0)
}
props.Logger.Debug("Extracted file", "source", file.Name, "target", targetPath)
return nil
})
}
Testing VCS Operations¶
1. Mock GitHub Client¶
Test GitHub operations with mocks:
func TestGitHubOperations(t *testing.T) {
mockClient := vcs.NewMockGitHubClient(t)
expectedReleases := []string{"v1.0.0", "v2.0.0", "v2.1.0"}
mockClient.EXPECT().ListReleases(mock.Anything, "owner", "repo").Return(expectedReleases, nil)
// Test the operation
props := &props.Props{
Config: createTestConfig(),
Logger: log.New(io.Discard),
}
releases, err := mockClient.ListReleases(context.Background(), "owner", "repo")
assert.NoError(t, err)
assert.Equal(t, 3, len(releases))
assert.Equal(t, "v2.1.0", releases[2])
}
2. Mock Repository Testing¶
Test repository operations with mocks:
func TestRepoOperations(t *testing.T) {
mockRepo := vcs.NewMockRepoLike(t)
// Set up mock expectations
mockRepo.EXPECT().SourceIs(vcs.SourceMemory).Return(true)
mockRepo.EXPECT().CreateBranch("feature/test").Return(nil)
mockRepo.EXPECT().Checkout(mock.AnythingOfType("plumbing.ReferenceName")).Return(nil)
// Test branch operations
assert.True(t, mockRepo.SourceIs(vcs.SourceMemory))
err := mockRepo.CreateBranch("feature/test")
assert.NoError(t, err)
branchRef := plumbing.NewBranchReferenceName("feature/test")
err = mockRepo.Checkout(branchRef)
assert.NoError(t, err)
}
3. Integration Testing¶
Test with real repositories in controlled environments:
func TestRealGitOperations(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test in short mode")
}
// Create temporary directory for test repository
tempDir := t.TempDir()
// Set up props with temporary filesystem
logger := log.New(io.Discard)
props := &props.Props{
Logger: logger,
FS: afero.NewOsFs(),
Config: createTestConfig(),
}
// Create and configure repository
repo, err := vcs.NewRepo(props)
require.NoError(t, err)
// Test cloning into memory
gitRepo, worktree, err := repo.OpenInMemory("https://github.com/go-git/go-git.git", "")
require.NoError(t, err)
require.NotNil(t, gitRepo)
require.NotNil(t, worktree)
// Test file operations
exists, err := repo.FileExists("README.md")
assert.NoError(t, err)
assert.True(t, exists)
// Test walking tree
fileCount := 0
err = repo.WalkTree(func(file *object.File) error {
fileCount++
return nil
})
assert.NoError(t, err)
assert.Greater(t, fileCount, 0)
}
4. Testing Asset Downloads¶
Test GitHub asset download functionality:
func TestAssetDownload(t *testing.T) {
mockClient := vcs.NewMockGitHubClient(t)
mockFS := afero.NewMemMapFs()
// Mock asset download
mockClient.EXPECT().GetReleaseAssetID(mock.Anything, "owner", "repo", "v1.0.0", "asset.tar.gz").Return(int64(12345), nil)
mockClient.EXPECT().DownloadAssetTo(mock.Anything, mockFS, "owner", "repo", int64(12345), "/tmp/asset.tar.gz").Return(nil)
// Test download
assetID, err := mockClient.GetReleaseAssetID(context.Background(), "owner", "repo", "v1.0.0", "asset.tar.gz")
assert.NoError(t, err)
assert.Equal(t, int64(12345), assetID)
err = mockClient.DownloadAssetTo(context.Background(), mockFS, "owner", "repo", assetID, "/tmp/asset.tar.gz")
assert.NoError(t, err)
}
Configuration¶
VCS Configuration Options¶
# config.yaml
# Provider selection
vcs:
provider: github # Options: github, gitlab
github:
url:
api: "https://api.github.com" # GitHub API base URL
upload: "https://uploads.github.com" # GitHub upload API URL
auth:
env: "GITHUB_TOKEN" # Environment variable for token
owner: "your-org" # Default repository owner
repo: "your-repo" # Default repository name
gitlab:
url:
api: "https://gitlab.com/api/v4" # GitLab API base URL
auth:
env: "GITLAB_TOKEN" # Environment variable for token
owner: "your-org" # Default repository owner
repo: "your-repo" # Default repository name
git:
author:
name: "Your Name" # Git commit author name
email: "[email protected]" # Git commit author email
# Repository cloning preferences
repository:
default_type: "inmemory" # "inmemory" or "local"
clone_depth: 1 # Shallow clone depth
single_branch: true # Clone single branch only
include_tags: false # Include tags in clone
recurse_submodules: false # Recurse into submodules
Environment Variables¶
The VCS component recognizes these environment variables:
# GitHub authentication
GITHUB_TOKEN=ghp_xxxxxxxxxxxxxxxxxxxx # GitHub personal access token
GITHUB_CLIENT_ID=Iv1.xxxxxxxxxxxxxxxx # OAuth client ID for GitHub login
# SSH key configuration
GITHUB_KEY=~/.ssh/id_rsa # Path to SSH private key
# Git configuration
GIT_AUTHOR_NAME="Your Name" # Git commit author name
GIT_AUTHOR_EMAIL="[email protected]" # Git commit author email
# Debug options
GTB_GIT_ENABLE_PROGRESS=1 # Enable Git operation progress output
Authentication Priority¶
The authentication mechanism follows this priority order:
- SSH Key Authentication
(if
github.ssh_keyis configured) - Basic Authentication (if
github.usernameandgithub.passwordare configured) - Token Authentication (from
github.auth.valueor environment variable) - OAuth Flow (if
GITHUB_CLIENT_IDis set and other methods fail)
Repository Types¶
Choose the appropriate repository type based on your use case:
| Type | Use Case | Pros | Cons |
|---|---|---|---|
inmemory |
Temporary operations, CI/CD | Fast, no disk I/O | Limited by memory |
local |
Persistent work, development | Full Git features | Disk space required |
Clone Options¶
Configure cloning behavior based on requirements:
// Minimal clone for CI/CD
repo.Clone(repoURL, destination,
vcs.WithShallowClone(1), // Only latest commit
vcs.WithSingleBranch("main"), // Only main branch
vcs.WithNoTags(), // Skip tags
)
// Full clone for development
repo.Clone(repoURL, destination,
vcs.WithRecurseSubmodules(), // Include submodules
)
RepoLike Interface (For Testing and Mocking)¶
The RepoLike interface is primarily used for testing and when working with provided mocks. In production code, use the concrete Repo type:
type RepoLike interface {
SourceIs(int) bool
SetSource(int)
SetRepo(*git.Repository)
GetRepo() *git.Repository
SetKey(*ssh.PublicKeys)
SetBasicAuth(string, string)
GetAuth() transport.AuthMethod
SetTree(*git.Worktree)
GetTree() *git.Worktree
Checkout(plumbing.ReferenceName) error
CheckoutCommit(plumbing.Hash) error
CreateBranch(string) error
Push(*git.PushOptions) error
Commit(string, *git.CommitOptions) (plumbing.Hash, error)
OpenInMemory(string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
OpenLocal(string, string) (*git.Repository, *git.Worktree, error)
Open(RepoType, string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
Clone(string, string, ...CloneOption) (*git.Repository, *git.Worktree, error)
// Git tree operations for in-memory repositories
WalkTree(func(*object.File) error) error
FileExists(string) (bool, error)
DirectoryExists(string) (bool, error)
GetFile(string) (*object.File, error)
AddToFS(fs afero.Fs, gitFile *object.File, fullPath string) error
}
Best Practices¶
1. Use Concrete Types in Production¶
- Use
*vcs.GHClientfor GitHub Enterprise API operations - Use
*vcs.Repofor Git repository management - Reserve interfaces (
GitHubClient,RepoLike) for testing and mocking
2. Authentication¶
- Use environment variables for sensitive tokens
- Support multiple authentication methods
- Provide clear error messages for authentication failures
3. Error Handling¶
- Wrap errors with appropriate context
- Handle network timeouts gracefully
- Provide retry logic for transient failures
4. Repository Strategy Selection¶
- Use
InMemoryRepofor CI/CD pipelines and temporary operations - Use
LocalRepofor development and persistent operations - Consider shallow clones for large repositories when appropriate
5. Performance Optimization¶
- Use clone options to reduce bandwidth and storage
- Implement proper cleanup for temporary repositories
- Cache authentication credentials appropriately
6. Security¶
- Validate repository URLs and paths
- Sanitize user input for Git operations
- Use secure defaults for repository creation
The Version Control component provides robust Git and GitHub integration capabilities that enable powerful repository management and automated update functionality in GTB applications.