Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4f60fc7
chore: update dependencies to dappco.re tagged versions
Mar 22, 2026
b280022
chore: update dependencies to dappco.re tagged versions
Mar 22, 2026
1e03033
feat: upgrade to core v0.8.0-alpha.1, migrate to Action-based API
Mar 26, 2026
a4c489f
fix: enforce absolute-path and upstream validation
Mar 29, 2026
6f6e19b
refactor(ax): add usage examples to git API docs
Mar 30, 2026
a14d7d5
fix: use core context in git query handler
Mar 30, 2026
484ad8e
fix: restore git task query handling
Apr 1, 2026
35edd29
feat: add behind repos query and iterator
Apr 1, 2026
7528e20
fix: count git type changes in status
Apr 1, 2026
2fd6877
fix: count conflicted git status entries
Apr 1, 2026
a80466e
fix: enforce absolute paths in push multiple
Apr 1, 2026
e2d8c7c
feat: handle git task messages on action bus
Apr 1, 2026
de3a93f
refactor(go-git): align with AX string helpers
Apr 1, 2026
acb79d4
feat(git): add iterator helpers for status and push multiple
Apr 1, 2026
c221f51
refactor(git): keep task dispatch off query bus
Apr 1, 2026
7a2356e
feat(git): add batch pull support
Apr 1, 2026
3cfd684
fix(git): report batch task failures
Apr 1, 2026
3a82399
refactor(git): centralise service task dispatch
Apr 1, 2026
34dcf83
feat(git): decouple standalone API from core helpers
Apr 1, 2026
b8148ca
refactor(git): centralize service path validation
Apr 1, 2026
a170f2f
refactor(git): centralise repository name resolution and action const…
Apr 3, 2026
7014642
docs: add agent index for go-git
Apr 3, 2026
fb140ce
fix: migrate module paths from forge.lthn.ai to dappco.re
Snider Apr 4, 2026
0ddde93
fix: tidy deps after dappco.re migration
Snider Apr 4, 2026
1f5c6b8
feat(git): small RFC alignments — 5.4-mini pass
Snider Apr 14, 2026
31c5d4b
feat(git): RFC module path alignment + graceful unsupported message h…
Snider Apr 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

Multi-repository git operations library. Parallel status checks, sequential push/pull (for SSH passphrase prompts), error handling with stderr capture.

**Module:** `forge.lthn.ai/core/go-git`
**Module:** `dappco.re/go/git`
**Go:** 1.26+

## Build & Test
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
[![Go Reference](https://pkg.go.dev/badge/forge.lthn.ai/core/go-git.svg)](https://pkg.go.dev/forge.lthn.ai/core/go-git)
[![Go Reference](https://pkg.go.dev/badge/dappco.re/go/git.svg)](https://pkg.go.dev/dappco.re/go/git)
[![License: EUPL-1.2](https://img.shields.io/badge/License-EUPL--1.2-blue.svg)](LICENSE.md)
[![Go Version](https://img.shields.io/badge/Go-1.26-00ADD8?style=flat&logo=go)](go.mod)

# go-git

Go module: `forge.lthn.ai/core/go-git`
Go module: `dappco.re/go/git`

Agent index: [`llms.txt`](llms.txt)

Comment on lines +9 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

README still advertises the old module path.

Adding the agent index is helpful, but the badge and module line above still point to forge.lthn.ai/core/go-git. Please update those to dappco.re/go/core/git as part of the same migration.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 9 - 10, The README still references the old module
path "forge.lthn.ai/core/go-git" in the badge and module declaration; update
both occurrences to the new module path "dappco.re/go/core/git" and ensure the
Agent index reference to `llms.txt` remains intact (check the badge image URL
and any "module" or "go get" line in README and replace the host/path to
`dappco.re/go/core/git` so all references are consistent with the migration).

## License

Expand Down
33 changes: 25 additions & 8 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,19 @@ type PushResult struct {
}
```

### PullResult

Returned by `PullMultiple`, one per repository:

```go
type PullResult struct {
Name string
Path string
Success bool
Error error
}
```

## Data flow

### Parallel status checking
Expand All @@ -88,10 +101,10 @@ The `--porcelain` output is parsed character by character. Each line has a two-c
| Position X (index) | Position Y (working tree) | Interpretation |
|---------------------|---------------------------|----------------|
| `?` | `?` | Untracked file |
| `A`, `D`, `R`, `M` | any | Staged change |
| any | `M`, `D` | Working tree modification |
| `A`, `D`, `R`, `M`, `U` | any | Staged change |
| any | `M`, `D`, `U` | Working tree modification |

A single file can increment both `Staged` and `Modified` if it has been staged and then further modified.
A single file can increment both `Staged` and `Modified` if it has been staged and then further modified. Unmerged paths (`U`) increment both counters, which keeps conflicted repositories visibly dirty.

### Interactive push and pull

Expand Down Expand Up @@ -129,13 +142,13 @@ The factory constructs a `Service` embedding `core.ServiceRuntime[ServiceOptions

### Lifecycle

`Service` implements the `Startable` interface. On startup, it registers a query handler and a task handler with the Core message bus:
`Service` implements the `Startable` interface. On startup, it registers a query handler and a broadcast action handler with the Core message bus:

```go
func (s *Service) OnStartup(ctx context.Context) error {
func (s *Service) OnStartup(ctx context.Context) core.Result {
s.Core().RegisterQuery(s.handleQuery)
s.Core().RegisterTask(s.handleTask)
return nil
s.Core().RegisterAction(s.handleTaskMessage)
return core.Result{OK: true}
}
```

Expand All @@ -146,6 +159,7 @@ func (s *Service) OnStartup(ctx context.Context) error {
| `QueryStatus` | `[]RepoStatus` | Checks Git status for a set of paths (runs in parallel). Updates the cached `lastStatus`. |
| `QueryDirtyRepos` | `[]RepoStatus` | Filters `lastStatus` for repos with uncommitted changes. |
| `QueryAheadRepos` | `[]RepoStatus` | Filters `lastStatus` for repos with unpushed commits. |
| `QueryBehindRepos` | `[]RepoStatus` | Filters `lastStatus` for repos with unpulled commits. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.

`QueryStatus` has the same fields as `StatusOptions` and can be type-converted directly:

Expand All @@ -160,6 +174,7 @@ statuses := Status(ctx, StatusOptions(queryStatus))
| `TaskPush` | `nil` | Pushes a single repository (interactive). |
| `TaskPull` | `nil` | Pulls a single repository with `--rebase` (interactive). |
| `TaskPushMultiple` | `[]PushResult` | Pushes multiple repositories sequentially. |
| `TaskPullMultiple` | `[]PullResult` | Pulls multiple repositories sequentially with `--rebase`. |

### Path validation

Expand Down Expand Up @@ -193,10 +208,12 @@ The `Service` caches the most recent `QueryStatus` result in `lastStatus` (prote
| `All()` | `iter.Seq[RepoStatus]` | Iterator over all cached statuses. |
| `Dirty()` | `iter.Seq[RepoStatus]` | Iterator over repos where `IsDirty()` is true and `Error` is nil. |
| `Ahead()` | `iter.Seq[RepoStatus]` | Iterator over repos where `HasUnpushed()` is true and `Error` is nil. |
| `Behind()` | `iter.Seq[RepoStatus]` | Iterator over repos where `HasUnpulled()` is true and `Error` is nil. |
| `DirtyRepos()` | `[]RepoStatus` | Collects `Dirty()` into a slice. |
| `AheadRepos()` | `[]RepoStatus` | Collects `Ahead()` into a slice. |
| `BehindRepos()` | `[]RepoStatus` | Collects `Behind()` into a slice. |

Errored repositories are excluded from `Dirty()` and `Ahead()` iterators.
Errored repositories are excluded from `Dirty()`, `Ahead()`, and `Behind()` iterators.

## Concurrency model

Expand Down
2 changes: 1 addition & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ description: How to build, test, lint, and contribute to go-git.
- **Git** (system binary -- the library shells out to `git`)
- **golangci-lint** (for linting)

go-git is part of the Go workspace at `~/Code/go.work`. If you are working within that workspace, module resolution is handled automatically. Otherwise, ensure `GOPRIVATE=forge.lthn.ai/*` is set so Go can fetch private modules.
go-git is part of the Go workspace at `~/Code/go.work`. If you are working within that workspace, module resolution is handled automatically. Otherwise, ensure `GOPRIVATE=dappco.re/*` is set so Go can fetch private modules.

## Running tests

Expand Down
18 changes: 9 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ description: Multi-repository Git operations library for Go with parallel status

# go-git

**Module:** `forge.lthn.ai/core/go-git`
**Module:** `dappco.re/go/git`

**Go version:** 1.26+

**Licence:** [EUPL-1.2](../LICENSE.md)

## What it does

go-git is a Go library for orchestrating Git operations across multiple repositories. It was extracted from `forge.lthn.ai/core/go-scm/git/` into a standalone module.
go-git is a Go library for orchestrating Git operations across multiple repositories. It was extracted from `dappco.re/go/core/scm/git/` into a standalone module.

The library provides two layers:

Expand All @@ -33,7 +33,7 @@ import (
"context"
"fmt"

git "forge.lthn.ai/core/go-git"
git "dappco.re/go/git"
)

func main() {
Expand Down Expand Up @@ -64,8 +64,8 @@ package main
import (
"fmt"

"forge.lthn.ai/core/go/pkg/core"
git "forge.lthn.ai/core/go-git"
"dappco.re/go/core"
git "dappco.re/go/git"
)

func main() {
Expand All @@ -89,7 +89,7 @@ func main() {

statuses := result.([]git.RepoStatus)
for _, s := range statuses {
fmt.Printf("%s: dirty=%v ahead=%v\n", s.Name, s.IsDirty(), s.HasUnpushed())
fmt.Printf("%s: dirty=%v ahead=%v behind=%v\n", s.Name, s.IsDirty(), s.HasUnpushed(), s.HasUnpulled())
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
```
Expand All @@ -98,10 +98,10 @@ func main() {

| File | Purpose |
|------|---------|
| `git.go` | Standalone Git operations -- `Status`, `Push`, `Pull`, `PushMultiple`, error types. Zero framework dependencies. |
| `service.go` | Core framework integration -- `Service`, query types (`QueryStatus`, `QueryDirtyRepos`, `QueryAheadRepos`), task types (`TaskPush`, `TaskPull`, `TaskPushMultiple`). |
| `git.go` | Standalone Git operations -- `Status`, `StatusIter`, `Push`, `Pull`, `PushMultiple`, `PushMultipleIter`, `PullMultiple`, `PullMultipleIter`, error types. Zero framework dependencies. |
| `service.go` | Core framework integration -- `Service`, query types (`QueryStatus`, `QueryDirtyRepos`, `QueryAheadRepos`, `QueryBehindRepos`), task types (`TaskPush`, `TaskPull`, `TaskPushMultiple`, `TaskPullMultiple`). |
| `git_test.go` | Tests for standalone operations using real temporary Git repositories. |
| `service_test.go` | Tests for `Service` filtering helpers (`DirtyRepos`, `AheadRepos`, iterators). |
| `service_test.go` | Tests for `Service` filtering helpers (`DirtyRepos`, `AheadRepos`, `BehindRepos`, iterators). |
| `service_extra_test.go` | Integration tests for `Service` query/task handlers against the Core framework. |

## Dependencies
Expand Down
Loading