1 Git-Operations
Virgil edited this page 2026-02-19 17:02:15 +00:00

Git Operations

Package: forge.lthn.ai/core/go-scm/git

Multi-repository git utilities providing parallel status checks, push/pull operations, and integration with the Core framework as a registered service.

Overview

The git package wraps git CLI commands to operate across multiple repositories simultaneously. It is used by the core dev CLI commands (work, status, push, pull) to manage the federated monorepo.

RepoStatus

The RepoStatus struct captures a snapshot of a single repository's state:

type RepoStatus struct {
    Name      string // Display name
    Path      string // Filesystem path
    Modified  int    // Files modified in working tree
    Untracked int    // Untracked files
    Staged    int    // Files staged in index
    Ahead     int    // Commits ahead of upstream
    Behind    int    // Commits behind upstream
    Branch    string // Current branch name
    Error     error  // Any error during status check
}

Helper Methods

Method Returns Description
IsDirty() bool Has uncommitted changes (Modified > 0 or Untracked > 0 or Staged > 0)
HasUnpushed() bool Has commits to push (Ahead > 0)
HasUnpulled() bool Has commits to pull (Behind > 0)

Parallel Status Checks

The Status function checks multiple repositories concurrently using goroutines and a WaitGroup:

statuses := git.Status(ctx, git.StatusOptions{
    Paths: []string{
        "/Users/snider/Code/host-uk/core-php",
        "/Users/snider/Code/host-uk/core-tenant",
        "/Users/snider/Code/host-uk/core-admin",
    },
    Names: map[string]string{
        "/Users/snider/Code/host-uk/core-php":    "core-php",
        "/Users/snider/Code/host-uk/core-tenant": "core-tenant",
        "/Users/snider/Code/host-uk/core-admin":  "core-admin",
    },
})

for _, s := range statuses {
    if s.Error != nil {
        fmt.Printf("%s: error: %v\n", s.Name, s.Error)
        continue
    }
    if s.IsDirty() {
        fmt.Printf("%s [%s]: %d modified, %d untracked, %d staged\n",
            s.Name, s.Branch, s.Modified, s.Untracked, s.Staged)
    }
    if s.HasUnpushed() {
        fmt.Printf("%s: %d commits ahead\n", s.Name, s.Ahead)
    }
}

StatusOptions

type StatusOptions struct {
    Paths []string            // Repository paths to check
    Names map[string]string   // Maps paths to display names
}

Push and Pull

Push and pull operations use interactive mode to support SSH passphrase prompts:

// Push a single repository
err := git.Push(ctx, "/path/to/repo")

// Pull a single repository (with --rebase)
err := git.Pull(ctx, "/path/to/repo")

// Check if error is a non-fast-forward rejection
if git.IsNonFastForward(err) {
    fmt.Println("Need to pull first")
}

PushMultiple

Push multiple repositories sequentially (sequential because SSH passphrase prompts need user interaction):

results := git.PushMultiple(ctx, paths, names)

for _, r := range results {
    if r.Success {
        fmt.Printf("%s: pushed successfully\n", r.Name)
    } else {
        fmt.Printf("%s: push failed: %v\n", r.Name, r.Error)
    }
}

PushResult

type PushResult struct {
    Name    string
    Path    string
    Success bool
    Error   error
}

GitError

Git command errors include captured stderr output for better diagnostics:

type GitError struct {
    Err    error  // Underlying exec error
    Stderr string // Captured stderr output
}

func (e *GitError) Error() string  // Returns stderr if available, else Err.Error()
func (e *GitError) Unwrap() error  // Returns Err for error chain inspection

Core Service Integration

The git package integrates with the Core framework as a registered service, exposing git operations via the query/task message-passing system:

import (
    "forge.lthn.ai/core/go/pkg/framework"
    "forge.lthn.ai/core/go-scm/git"
)

core, err := framework.New(
    framework.WithService(git.NewService(git.ServiceOptions{
        WorkDir: "/Users/snider/Code/host-uk",
    })),
)

Queries

Query Type Returns Description
QueryStatus{Paths, Names} []RepoStatus Run parallel status check
QueryDirtyRepos{} []RepoStatus Get repos with uncommitted changes (from last status)
QueryAheadRepos{} []RepoStatus Get repos with unpushed commits (from last status)

Tasks

Task Type Description
TaskPush{Path, Name} Push a single repository
TaskPull{Path, Name} Pull a single repository
TaskPushMultiple{Paths, Names} Push multiple repositories sequentially

Service Methods

The service also exposes direct method access:

svc := framework.ServiceFor[*git.Service](core)

// Get last status results
statuses := svc.Status()

// Get filtered views
dirty := svc.DirtyRepos()
ahead := svc.AheadRepos()

Implementation Details

  • Parallel execution: Status uses one goroutine per repository with sync.WaitGroup, maintaining result order via indexed slice
  • Branch detection: Uses git rev-parse --abbrev-ref HEAD
  • Status parsing: Uses git status --porcelain and parses the two-character status codes (X = index, Y = working tree)
  • Ahead/behind: Uses git rev-list --count @{u}..HEAD and HEAD..@{u} respectively; silently returns 0 if no upstream is configured
  • Interactive push/pull: Connects stdin/stdout/stderr to the terminal for SSH passphrase prompts, while also capturing stderr via io.MultiWriter for error reporting

See Also

  • Home -- Package overview and quick start
  • Job-Runner -- Automated pipeline that may trigger push operations