# Git Store — Component Reference

## Purpose

`internal/gitstore` provides read-only and coordination infrastructure for bare
Git repositories on the local filesystem. Production repository mutations are
owned exclusively by the private
`internal/service/internal/repositorymutation` package.

`gitstore` is infrastructure, not business policy.
It does not make database decisions or shape HTTP responses.

For the full system overview see [docs/architecture.md](../architecture.md).
Core invariant: `agent-git-service` is Git-backed; see [architecture.md § Purpose](../architecture.md#purpose).

## Scope

Owns:

- repository path resolution and existence checks
- read-only ref, branch, object, compare, diff, and content access
- commit and code search
- archive streaming (zip, tar.gz)
- the wiki catalog's in-process per-name coordination mutex

Does not own:

- business rules around when to merge, who may push, or what happens after a push (that belongs to `service` and `githttp`)
- repository initialization, ref publication, merge/rebase/squash, file/object
  writes, receive-pack, or mutation child-process ownership (that belongs to
  the private `repositorymutation.Owner` and its `Session`)
- relational metadata such as PR records, issues, or users (that belongs to `db`)
- HTTP transport or response formatting

## Key Entry Points

| File | Responsibility |
|---|---|
| `store.go` | `Store` struct, constructor (`New`), wiki catalog coordination mutex, repo path helpers, and go-git open helpers |
| `refs.go` | Read-only ref resolution, ancestry, branch listing, and HEAD inspection |
| `internal/service/internal/repositorymutation/init.go` | Private repository initialization, fork, delete, and hook installation owner |
| `internal/service/internal/repositorymutation/session.go` | Exact identity/lifecycle session and mutation child-process owner; it serializes one immutable repository ID within an `Owner` process without filesystem locks |
| `internal/service/internal/repositorymutation/refs.go` | Private ref mutation owners, including atomic create-only `Session.CreateReference`, captured-old `Session.UpdateReference`, and `Session.SetHeadBranch` used by `Service.UpdateRepo` to keep Git HEAD and the bounded repository-row update in one process-local writer lifecycle |
| `internal/service/internal/repositorymutation/ref_locks.go` | Private exact ref-lock repair owner; `Session.RepairRefLock` retains one exact repository session across inspection, stale/force policy, exact deletion, and any dependent ref publication |
| `internal/service/internal/repositorymutation/pull_refs.go` | Private `Session.SetPullRequestHead` owner used by PR creation and commit-data refresh; cross-repository refs hold stable-ordered base/head sessions, validate both repository identities before the exact-commit transfer, and publish only after exact verification |
| `merge.go` | Read-only mergeability and repository-state helpers: `CanMerge`, `IsEmpty`, `DiskUsageKB` |
| `internal/service/internal/repositorymutation/merge.go` | Private `Session.MergePullRequest` and `Session.SimulateMerge` mutation owners |
| `internal/service/internal/repositorymutation/revert.go` | Private `Session.RevertMerge` mutation owner used by `Service.RevertPRMerge` |
| `internal/service/internal/repositorymutation/update_pr_branch.go` | Private `Session.UpdatePRBranch` owner used by `Service.UpdatePRBranchAndRecord` |
| `content.go` | Read-only `ReadFile`, `ListTreeFiles`, `ListTags`, `DiffNameStatus`, `DiffNumStat`, `DiffRaw`, and `GetDiffHunk` helpers |
| `internal/service/internal/repositorymutation/files.go` | Private `Session.WriteFile`, `Session.DeleteFile`, and wiki projection `Session.CommitFilesAt` owners; exact branch/path input, temporary-index children, fixed identity, and final old-tip CAS stay inside one session |
| `internal/service/internal/repositorymutation/objects.go` | Private immutable blob, commit, and annotated-tag object owners; commit tree/parent object IDs are exact-type checked before the bounded write child, and no object write publishes a ref |
| `internal/service/internal/repositorymutation/trees.go` | Private immutable tree-object owner used by REST, GraphQL, and wiki compaction; inline blobs, temporary-index children, and exact tree validation remain inside one session without publishing a ref |
| `compare.go` | `Compare` (concurrent 4-goroutine diff), `Contributors`, `LogBetweenTags`, `PRCommitsLog` |
| `search.go` | `SearchCommits`, `ListCommits`, `SearchCode` |
| `archive.go` | `Archive` (stream zip/tar.gz to `io.Writer`) |
| `rev_validator.go` | `IsValidRev` (input validation to prevent command injection) |

## Main Flows

### Repository Lifecycle

```
Service repository create/delete/fork flow
  → resolve one exact durable repository ID and full name
  → acquire private repositorymutation.Session
  → initialize, fork, or delete through the Session
  → retain the Session through the bounded DB publication or compensation

Service RenameRepo / TransferRepo / UpdateRepo
  → acquire one Session for the durable repository ID and current full name
  → retain it across filesystem rename and the repository-row transaction
  → after the durable row retarget, Owner.RefreshSessionIdentity reloads the
    canonical name; callers cannot choose the retarget identity
  → UpdateRepo reuses that same refreshed Session for a default-branch HEAD
    change and retains its rollback token through the repository-row save

Service release-row mutations
  → acquire one Session for the release's repository
  → retain it through the transaction and final row reload
```

`gitstore.Store` exposes no production mutation or authority method. In
particular it cannot acquire migration seals, issue writer leases, refresh a
writer identity, bind mutation authority into a context, or start an
authoritative child. The private Owner/Session boundary is the sole owner of
those operations; service code passes or reuses an exact Session rather than
creating a parallel capability facade.

### Merge Family

```
Service.mergePRRecordWithActor(ctx, actor, pullRequest, method, message)
  → freeze the PR row's exact base/head repository IDs and Git identities
  → acquire the one or two private Sessions in stable repository-ID order
  → for a fork, require its exact source branch, frozen head SHA, and the
    base repository's refs/pull/N/head object to agree (no branch fallback)
  → freeze exact base/head commits
  → clone to an isolated worktree and compute merge, squash, or rebase result
  → verify the exact result commit, tree, and strategy-specific parents
  → import immutable objects without publishing FETCH_HEAD or another ref
  → git update-ref BASE resultSHA capturedBase (old-tip CAS; sole publication)
  → update the single PR row while retaining the Session
  → on projection failure, restore BASE capturedBase resultSHA with the opaque token
```

`Session.MergePullRequest` is the only production merge-family mutation owner.
Same-repository PRs reuse one Session; fork PRs retain both exact repository
Sessions through ref publication, PR projection, and any opaque rollback.
All children use the owner-pinned Git binary, fixed C locale, bounded output,
cancellation propagation, and complete process-tree joining. Conflict,
validation, computation, import, cancellation, and CAS-loss failures do not
change a durable ref or PR row. The service projection is one compare-and-set
row update whose query cost does not grow with unrelated pull requests.

`Service.SimulatePRMerge` acquires a private `repositorymutation.Session`; its
`SimulateMerge` owner uses `git merge-tree` and `git commit-tree` to create a
dangling commit without altering any branch.
`Service.RevertPRMerge` likewise retains one private session across its bounded
temporary clone, revert, and final branch push; failures before that push do not
publish a partial ref.

`Service.UpdateRepo` uses private `Session.SetHeadBranch` for default-branch
changes. Rename plus default-branch updates use one Session: after the durable
repository name changes, `Owner.RefreshSessionIdentity` reloads the canonical
name before `SetHeadBranch` runs. The session validates one exact existing
commit branch, remains held through the single repository-row save, and uses a
session-bound opaque token to restore the exact captured symbolic HEAD if that
save fails.

`Service.CreatePR` resolves the base and optional fork repository rows first,
then acquires the one or two Sessions in immutable repository-ID order. Those
same Sessions cover exact head/base observation, PR-row publication, and
`refs/pull/N/head` publication; the base Session is never reacquired underneath
an already-held cross-repository session.

`Service.UpdatePRBranchAndRecord` acquires private sessions for the explicitly
requested head repository and the PR's base repository in stable ID order. The
owner resolves and freezes the exact upstream base commit from the base session,
imports that object into a bounded head-repository clone, and retains both
sessions through every merge-or-rebase/push child and the single PR-row
projection update. If that DB update fails or is canceled, the same live head
session uses an opaque CAS token to restore the exact previous branch tip.

### Compare (Concurrent)

```
Compare(fullName, base, head)
  → launch 4 goroutines via sync.WaitGroup:
      1. git rev-list base..head --count   (ahead)
      2. git rev-list head..base --count   (behind)
      3. git log base..head                (commits)
      4. git diff --numstat base...head     (files)
  → wait, return combined result
```

### Content Write

```
Service.WriteRepositoryFile(ctx, repository, branch, path, message, content, expectedBlobSHA)
  → acquire private Session for exact repository ID + Git identity
  → capture exact branch tip (or prove the branch absent)
  → require expectedBlobSHA to match the captured exact path (empty means absent)
  → isolated temporary index + bounded hash-object/update-index/write-tree
  → bounded commit-tree with fixed identity
  → git update-ref refs/heads/branch NEW captured-old (sole publication)
```

Content write and deletion are owned by private `Session.WriteFile` and
`Session.DeleteFile`: they validate one exact branch and literal path, build the
replacement tree through an isolated bounded index, then publish one
fixed-identity commit with an exact old-tip CAS. Failures before the final
`update-ref` leave the branch unchanged. REST does not pre-read existence or
blob equality: it maps the private owner's conflict result, and derives the
create/update response status only from whether the request supplied an
expected blob SHA.

Wiki catalog projection batches use private `Session.CommitFilesAt`. The outer
catalog serialization acquires one base-repository-ID/wiki-identity Session and
passes it through the changeset post-commit hook, compaction, ref-lock repair,
and ref publication. No nested Session acquisition occurs under that boundary.
The post-commit owner batch-loads the explicitly changed page bodies, validates
the complete ordered mutation set before Git initialization, then retains the
same Session across lazy repository initialization, literal-path
blob/tree/commit children, the captured-old-tip CAS, and the two bounded
projection updates. Body lookup and projection DB work use a fixed number of
query groups independent of the changeset size. Missing bodies, invalid paths,
cancellation, and CAS loss do not publish a ref.

### Create Reference

```
Service.CreateRepositoryRef(ctx, repository, ref, objectSHA)
  → acquire private Session for exact repository ID + base/wiki Git identity
  → require an exact fully-qualified ref and existing exact object ID
  → git update-ref REF objectSHA "" (absent-old CAS; sole publication)
```

`Session.CreateReference` is the only production create-only ref owner. A
concurrent creator cannot overwrite the winner: the loser receives
`gitstore.ErrRefAlreadyExists`. Wiki compaction passes the already resolved
repository identity into this owner while its existing catalog write lock and
single private Session cover the DB transaction and projection-resume
lifecycle; it does not re-enumerate repositories, reacquire a writer, or
introduce a second ref creation authority.

### Update Reference

```
Service.UpdateRepositoryRef(ctx, repository, ref, objectSHA, force)
  → acquire private Session for exact repository ID + base/wiki Git identity
  → capture one exact fully-qualified hash ref and its old object ID
  → same object ID: idempotent success
  → force: require only an existing target object
  → non-force: require old and new targets to be commits with old reachable from new
  → git update-ref REF objectSHA capturedOld (old-tip CAS; sole publication)
```

`Session.UpdateReference` is the only production existing-ref update owner.
Generic custom refs retain force-update compatibility for blobs and annotated
tags, while non-force updates retain commit fast-forward semantics. The final
child uses a fixed C locale, bounded output, complete process-tree join, and a
captured-old CAS so a concurrent winner is never overwritten.

### Repair Reference Lock

```
Service compact/admin wiki flow
  → resolve one exact base repository ID and owned `.wiki` Git identity
  → acquire private Session
  → validate one fully-qualified ref and derive its exact in-repository lock path
  → stat lock; absent is idempotent, fresh non-force conflicts
  → remove only that exact lock when stale or forced
  → for compaction, retain the same Session through create/update publication
```

`Session.RepairRefLock` is the only production ref-lock repair owner. It keeps the same exact repository session from lock observation through
deletion, but does not serialize other repository mutations. Wiki compaction retains that same session until its compact
ref has been created or updated; the admin repair path retains it until the
bounded result is returned. Invalid or escaping refs, directories,
cancellation, and owner/identity failures do not remove a lock. Repair performs only fixed
repository-identity queries, independent of unrelated repositories. The existing wiki catalog serialization remains the outer
catalog/Git ordering boundary; ref-lock repair introduces no parallel facade.

## Invariants and Design Constraints

- **Single private authority.** `repositorymutation.Owner` permanently binds the
  repository root, Git executables, construction-bound canonical database, and
  service-owned database selection policy. Ordinary mutation acquisition
  selects a database from the caller context once and pins it to the resulting
  `Session`. Background push-outbox paths continue to use the
  construction-bound database. `gitstore.Store` exposes no raw capability,
  process runner, or mutation method.
- **Process-local repository serialization only.** Sessions from the same `Owner` serialize concurrent mutations by immutable repository ID; cross-repository operations acquire IDs in stable order. AGS creates no coordination lock files and provides no cross-process or cross-Pod serialization. Git may still create its own temporary lock files, and individual ref publication continues to rely on Git ref lock/CAS behavior. Migration seal/proof stays unavailable until a distributed fencing authority exists.
- **Temporary clones for merge safety.** Merge-family operations clone to a temp directory and compute there. They import only immutable objects before the sole captured-old ref CAS, so a failed computation never publishes a partial result.
- **Hybrid go-git and CLI git usage.** go-git is used for initialization, repo opening, config, and some ref operations. CLI git (via `os/exec`) handles merge, rebase, diff, log, content, archive, and search. The split exists because go-git lacks some plumbing operations.
- **Revision validation.** `IsValidRev` rejects empty strings, leading dashes, and whitespace to prevent command injection in git CLI calls.
- **Bare-repo layout.** Git store manages standard bare Git repositories rooted at `GIT_REPO_DIR/{owner}/{repo}.git`.

For the full dependency-boundary rules see [module-contracts.md § gitstore](../module-contracts.md#gitstore).

## Extension and Change Guidance

**Adding a new git operation:**

1. Add read-only infrastructure to `Store` in the appropriate file.
2. Add repository mutations to the operation-specific private owner under `internal/service/internal/repositorymutation`; do not add a writable public `Store` method.
3. Retain one exact `Session` through every Git child, ref publication, and related bounded DB projection or compensation.
4. Validate exact repository identity, refs, revisions, and object types before starting mutation children.
5. Keep service orchestration responsible for DB policy and transport-neutral error mapping; neither `gitstore` nor the private owner shapes HTTP responses.

**Common patterns:**

- Private mutation children use the owner-pinned Git binary through `Session.StartCommand`, bounded output, cancellation, and complete `Wait` joining. Cross-repository operations validate their exact sessions in immutable repository-ID order before starting the child.
- Read-only CLI Git invocations use `exec.CommandContext` with the repo path as the working directory.
- go-git read operations open the repo with `s.open(fullName)`.
- Error returns are plain `fmt.Errorf` wraps; gitstore does not use the service sentinel errors.

## Related Tests

- `internal/gitstore/store_test.go` — covers read-side repository and content behavior using test-only direct fixture setup, not production mutation authority
- `internal/gitstore/stubs_test.go` — covers `GetDiffHunk` and `Archive`
- `internal/service/internal/repositorymutation/init_test.go` — covers private mutation-owner behavior, including merge simulation and merge/non-merge reverts
- `internal/service/internal/repositorymutation/files_write_test.go` — covers existing/new branch file writes, binary and literal paths, fixed identity, input rejection, and zero-ref failures
- `internal/service/internal/repositorymutation/files_write_unix_test.go` — covers deterministic old-tip CAS loss without overwriting the concurrent winner
- `internal/service/internal/repositorymutation/files_commit_test.go` — covers pinned-time batch commits, exact parent/tree/blob state, literal paths, input rejection, bounded diagnostics, and zero-ref failures
- `internal/service/internal/repositorymutation/files_commit_unix_test.go` — covers deterministic batch old-tip CAS loss plus final-publication cancellation and complete child joining
- `internal/service/internal/repositorymutation/objects_test.go` — covers immutable commit payloads, exact tree/parent types, identity validation, and zero-ref failure behavior
- `internal/service/internal/repositorymutation/refs_create_test.go` — covers create-only ref CAS races, exact input/object checks, cancellation, and zero-ref failures
- `internal/service/internal/repositorymutation/update_pr_branch_test.go` — covers private PR branch merge/rebase publication and exact rollback
- `internal/service/internal/repositorymutation/merge_pr_test.go` — covers merge, squash, and rebase result parents/trees, conflicts, validation, and exact rollback
- `internal/service/internal/repositorymutation/merge_pr_unix_test.go` — covers old-tip CAS races, fixed locale, cancellation, bounded diagnostics, and complete descendant joining
- `internal/service/pr_merge_private_test.go` — covers projection rollback, missing-owner fail-closed behavior, and fixed DB cost

For the phased test roadmap see [docs/test-strategy.md](../test-strategy.md).

## Related Docs

- [docs/architecture.md](../architecture.md) — system overview
- [docs/module-contracts.md](../module-contracts.md) § gitstore — dependency rules and ownership
- [Service Layer](service.md) — coordinates Git and DB state through gitstore
- [Git Smart HTTP](git-http.md) — uses gitstore directly for transport
