Add "Git-Operations"
parent
1f69ce942c
commit
1ec7cac357
1 changed files with 196 additions and 0 deletions
196
Git-Operations.-.md
Normal file
196
Git-Operations.-.md
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
# 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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
// 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):
|
||||
|
||||
```go
|
||||
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
|
||||
|
||||
```go
|
||||
type PushResult struct {
|
||||
Name string
|
||||
Path string
|
||||
Success bool
|
||||
Error error
|
||||
}
|
||||
```
|
||||
|
||||
## GitError
|
||||
|
||||
Git command errors include captured stderr output for better diagnostics:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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:
|
||||
|
||||
```go
|
||||
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
|
||||
Loading…
Add table
Reference in a new issue