# Git & Source-Control Integration Layer

> The git runner, multi-provider source-control IPC handlers (GitHub, GitLab, Azure DevOps, Bitbucket, Gitea), hosted-review (PR/MR) flows, commit-message generation, and the shared git-history and upstream-status types that the renderer consumes.

- Repository: stablyai/orca
- GitHub: https://github.com/stablyai/orca
- Human wiki: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457
- Complete Markdown: https://grok-wiki.com/public/wiki/stablyai-orca-47ffb1f68457/llms-full.txt

## Source Files

- `src/main/git/runner.ts`
- `src/main/git/worktree.ts`
- `src/main/git/status.ts`
- `src/main/ipc/github.ts`
- `src/main/ipc/gitlab.ts`
- `src/main/ipc/hosted-review.ts`
- `src/shared/git-history.ts`
- `src/shared/hosted-review.ts`
- `src/shared/commit-message-generation.ts`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [src/main/git/runner.ts](src/main/git/runner.ts)
- [src/main/git/status.ts](src/main/git/status.ts)
- [src/main/git/worktree.ts](src/main/git/worktree.ts)
- [src/main/git/history.ts](src/main/git/history.ts)
- [src/main/git/upstream.ts](src/main/git/upstream.ts)
- [src/main/ipc/github.ts](src/main/ipc/github.ts)
- [src/main/ipc/gitlab.ts](src/main/ipc/gitlab.ts)
- [src/main/ipc/hosted-review.ts](src/main/ipc/hosted-review.ts)
- [src/main/ipc/worktrees.ts](src/main/ipc/worktrees.ts)
- [src/main/source-control/hosted-review.ts](src/main/source-control/hosted-review.ts)
- [src/main/source-control/hosted-review-creation.ts](src/main/source-control/hosted-review-creation.ts)
- [src/shared/git-history.ts](src/shared/git-history.ts)
- [src/shared/git-history-types.ts](src/shared/git-history-types.ts)
- [src/shared/hosted-review.ts](src/shared/hosted-review.ts)
- [src/shared/commit-message-generation.ts](src/shared/commit-message-generation.ts)
- [src/shared/git-effective-upstream.ts](src/shared/git-effective-upstream.ts)
</details>

# Git & Source-Control Integration Layer

Orca's source-control integration layer bridges the Electron renderer process and the local or remote git repository. It owns every subprocess invocation of `git`, `gh`, and `glab`; parses working-tree and upstream status; provides a provider-neutral abstraction over hosted reviews (GitHub PRs, GitLab MRs, Bitbucket, Azure DevOps, and Gitea); and exposes commit-history data and AI-generated commit messages to the UI. Because Orca also runs on Windows with repos living on WSL filesystems and over SSH, the layer must transparently route subprocess calls through `wsl.exe` or SSH relay RPCs without requiring callers to know the execution environment.

This page documents the architecture and responsibilities of each module in the layer, the IPC channel contract between renderer and main process, the multi-provider review flow, and the shared types that both sides of the IPC boundary consume.

---

## Architecture Overview

```text
┌──────────────────────────────────────────────────────────────┐
│ Renderer process                                             │
│   UI components  ──► preload IPC bridge                      │
└────────────────────────────┬─────────────────────────────────┘
                             │ Electron ipcMain channels
┌────────────────────────────▼─────────────────────────────────┐
│ Main process – IPC handlers (src/main/ipc/)                  │
│   github.ts  │  gitlab.ts  │  hosted-review.ts  │ worktrees  │
└──────┬────────────┬─────────────────┬────────────────────────┘
       │            │                 │
       ▼            ▼                 ▼
┌─────────────────────────────────────────────────────────────┐
│ Domain services                                             │
│  source-control/hosted-review.ts  (provider router)         │
│  source-control/hosted-review-creation.ts  (eligibility)    │
│  git/status.ts   git/history.ts   git/upstream.ts           │
│  git/worktree.ts                                            │
└────────────────────────┬────────────────────────────────────┘
                         │
                         ▼
┌─────────────────────────────────────────────────────────────┐
│ git/runner.ts  (subprocess execution + WSL routing)          │
│  gitExecFileAsync │ ghExecFileAsync │ glabExecFileAsync      │
└─────────────────────────────────────────────────────────────┘
       │ shell          │ gh CLI           │ glab CLI
       ▼                ▼                 ▼
   git binary     GitHub remote      GitLab remote
```

```text
Shared types consumed by both sides of IPC boundary:
  src/shared/git-history-types.ts   – GitHistoryItem, GitHistoryResult, lane colors
  src/shared/hosted-review.ts       – HostedReviewInfo, HostedReviewProvider, creation types
  src/shared/commit-message-generation.ts – CommitMessageDraftContext, prompt builder
  src/shared/git-effective-upstream.ts    – GitUpstreamStatus resolution
```

---

## The Git Runner (`src/main/git/runner.ts`)

The runner is the single subprocess execution gateway for all git operations in the main process. Its central job is transparent WSL routing: when a repository lives on a WSL filesystem (a UNC path like `\\wsl.localhost\Ubuntu\...`), native Windows binaries cannot access it efficiently. The runner detects these paths and rewrites the invocation as `wsl.exe -d <distro> -- bash -c "cd '<linuxPath>' && git ..."`, including translating any Windows paths that appear in command arguments.

Sources: [src/main/git/runner.ts:1-60]()

### Exported runners

| Function | Purpose |
|---|---|
| `gitExecFileAsync(args, opts)` | Async git command; WSL-aware; wrapped in an observability span. |
| `gitExecFileAsyncBuffer(args, opts)` | Returns `Buffer` stdout for binary blobs (`git show`). |
| `gitExecFileSync(args, opts)` | Synchronous fallback. |
| `gitSpawn(args, opts)` | Streaming spawn; used for long-running git operations. |
| `ghExecFileAsync(args, opts)` | GitHub CLI with retry/backoff. |
| `glabExecFileAsync(args, opts)` | GitLab CLI, parallel retry policy. |
| `commandExecFileAsync(cmd, args, opts)` | Generic WSL-aware exec (ripgrep, etc.). |
| `wslAwareSpawn(cmd, args, opts)` | Streaming spawn for non-git binaries. |

Sources: [src/main/git/runner.ts:224-310]()

### `gh` / `glab` retry and idempotency

Both `ghExecFileAsync` and `glabExecFileAsync` implement a structured retry policy against transient HTTP failures (5xx, connection resets, 429 without `Retry-After`). Critically, retries are gated on **idempotency detection** to avoid duplicating writes:

- Explicit `-X POST/PATCH/PUT/DELETE` args → non-idempotent, no retry.
- GraphQL `query=mutation ...` → non-idempotent.
- `gh issue close`, `gh pr merge`, etc. (known write verbs) → non-idempotent.
- All reads default to idempotent and will retry up to two times with 250 ms → 1 s backoff.
- When the server returns a `Retry-After` header, that delay is honored (capped at 30 s).

```typescript
// src/main/git/runner.ts
const GH_RETRY_DELAYS_MS = [250, 1000] as const
const GH_RETRY_AFTER_MAX_MS = 30_000
```

Sources: [src/main/git/runner.ts:370-470]()

### WSL CLI fallback chain

On Windows, if `gh.exe` is absent from the host PATH (WSL-only setups), the runner:
1. Detects `ENOENT` on the host binary.
2. Attempts to resolve the default WSL distro.
3. Retries the call through `wsl.exe -d <distro> -- gh ...`.

For repo-scoped calls the distro is derived automatically from the UNC cwd. For global calls (rate-limit checks, auth) a `wslDistro` hint parameter is provided.

Sources: [src/main/git/runner.ts:97-160]()

---

## Status & Diff Engine (`src/main/git/status.ts`)

`getStatus()` is the primary read path for working-tree state. It runs `git status --porcelain=v2 --branch --untracked-files=all` (with `-c core.quotePath=false` so non-ASCII filenames pass through as raw UTF-8) and parses the structured output into `GitStatusEntry[]`.

```typescript
// Simplified from src/main/git/status.ts
export async function getStatus(worktreePath, options): Promise<GitStatusResult> {
  const conflictPromise = detectConflictOperation(worktreePath)  // concurrent fs probe
  const statusPromise = gitExecFileAsync(statusArgs, { cwd, env: gitOptionalLocksDisabledEnv() })
  // ...parse ahead/behind, entries, upstream...
  await attachLineStats(worktreePath, entries)
  return { entries, conflictOperation, head, branch, upstreamStatus }
}
```

The `conflictOperation` field indicates whether the repository is mid-merge, mid-rebase, or mid-cherry-pick, detected by probing `.git/MERGE_HEAD`, `.git/rebase-merge/`, and `.git/CHERRY_PICK_HEAD`. Rebase detection uses the directory rather than `REBASE_HEAD` because the directory persists across all rebase steps.

Sources: [src/main/git/status.ts:37-105](), [src/main/git/status.ts:340-385]()

### Line-count attachment

After collecting status entries, `attachLineStats` concurrently runs `git diff --numstat` (staged and unstaged separately) plus counts additions in untracked files. This gives the renderer per-file addition/deletion counts for the sidebar without blocking on status. The calls are always concurrent:

```typescript
const [stagedStats, unstagedStats, untrackedStats] = await Promise.all([...])
```

Sources: [src/main/git/status.ts:130-160]()

### Diff functions

| Function | Description |
|---|---|
| `getDiff(path, file, staged, compareAgainstHead)` | Returns `GitDiffResult` for a file in a given area. |
| `getBranchCompare(path, baseRef)` | Full compare from merge-base to HEAD; used by branch-compare panel. |
| `getBranchDiff(path, { headOid, mergeBase, filePath })` | File-level diff within a branch compare. |
| `getCommitCompare(path, commitId)` | Per-commit diff with parent detection and root-commit handling. |
| `getCommitDiff(path, { commitOid, parentOid, filePath })` | File diff for a specific commit. |

Binary files (images, PDFs) are detected via a buffer heuristic (`isBinaryBuffer`) and returned as base64 when the extension is in the previewable MIME type map.

Sources: [src/main/git/status.ts:410-480]()

### Staged commit context

`getStagedCommitContext()` collects the current branch name, the `--name-status` summary, and the full `--patch` of the staged index. This struct (`CommitMessageDraftContext`) is the input to AI commit-message generation.

Sources: [src/main/git/status.ts:575-610]()

---

## Upstream Status (`src/main/git/upstream.ts`)

`getUpstreamStatus()` computes the `GitUpstreamStatus` value that the renderer displays as the ahead/behind indicator. It delegates to `getEffectiveGitUpstreamStatus` (from `src/shared/git-effective-upstream.ts`), which handles three cases:

1. **Configured upstream** — `git rev-parse HEAD@{u}` succeeds; use ahead/behind from that ref.
2. **No configured upstream but `origin/<branch>` exists** — fall back to the inferred remote branch.
3. **Patch-equivalence** — when the branch is behind its upstream, `git log --cherry-mark --right-only HEAD...upstream` is used to determine if the "behind" commits are already applied as equivalent patches (indicating a clean rebase). This affects push-button label selection.

Sources: [src/main/git/upstream.ts:1-55](), [src/shared/git-effective-upstream.ts:1-60]()

---

## Git History (`src/main/git/history.ts`, `src/shared/git-history.ts`)

The history module is designed around a **pluggable executor** pattern so the same history-loading logic can run both locally (routing through `gitExecFileAsync`) and over SSH (routing through a relay RPC):

```typescript
// src/main/git/history.ts
export async function getHistory(worktreePath, options): Promise<GitHistoryResult> {
  return loadGitHistoryFromExecutor(
    (args, cwd) => gitExecFileAsync(args, { cwd }),
    worktreePath, options
  )
}
```

`loadGitHistoryFromExecutor` (in `src/shared/git-history.ts`) resolves the current ref, the configured upstream ref, and any optional base ref, then runs a single `git log --topo-order --decorate=full` covering all reachable commits. From the result it computes:

- `hasIncomingChanges` — upstream OID differs from merge-base.
- `hasOutgoingChanges` — local HEAD differs from merge-base.
- `hasMore` — whether the log was truncated at the `limit` cap.

Sources: [src/main/git/history.ts:1-18](), [src/shared/git-history.ts:95-160]()

### Shared history types (`src/shared/git-history-types.ts`)

| Type | Role |
|---|---|
| `GitHistoryItem` | Single commit row: id, parentIds, subject, author, timestamp, statistics, references. |
| `GitHistoryItemRef` | A branch, tag, remote-tracking, or detached HEAD ref attached to a commit. |
| `GitHistoryResult` | Full result: items, currentRef, remoteRef, baseRef, mergeBase, hasIncoming/Outgoing, hasMore. |
| `GitHistoryGraphColorId` | Semantic color token IDs (`git-graph-lane-1`…`git-graph-lane-5`, ref, remote-ref, base-ref) consumed by the renderer's commit graph. |
| `GitHistoryExecutor` | `(args: string[], cwd: string) => Promise<{stdout: string}>` — the seam that enables SSH routing. |

Sources: [src/shared/git-history-types.ts:1-70]()

---

## Worktree Management (`src/main/git/worktree.ts`)

The worktree module wraps `git worktree add`, `git worktree remove`, and `git worktree list`. Key behaviors:

- **Base-ref persistence**: After creating a worktree, the effective base branch is written to `git config branch.<name>.base` so the branch-compare panel can show a sensible default diff target. If the write fails (e.g., config lock), the stale value is also cleared so downstream consumers never trust outdated lineage.
- **WSL path translation**: `git worktree list` output contains Linux-native paths when running inside WSL. The runner's `translateWslOutputPaths` helper rewrites these to Windows UNC form so Node's `fs` module can open them.
- **Safety bounds**: `discardChanges` and `bulkDiscardChanges` call `isWithinWorktree` before any `rm -rf` to prevent path-traversal outside the worktree root.

Sources: [src/main/git/worktree.ts:1-90]()

---

## IPC Handler Layer

### GitHub handlers (`src/main/ipc/github.ts`)

`registerGitHubHandlers` registers all `ipcMain.handle` channels under the `gh:` namespace. It applies a consistent **security gate** (`assertRegisteredRepo`) that resolves the repo path and verifies it matches a path the user explicitly registered in Orca's store before any CLI or API call is issued.

Representative channels:

| Channel | Action |
|---|---|
| `gh:prForBranch` | Fetch the open PR for the current branch, with linked PR and fallback PR hints. |
| `gh:prChecks` / `gh:prCheckDetails` | CI check status and per-job detail. |
| `gh:prComments` | Review comments and threads. |
| `gh:mergePR` | Merge with selected strategy. |
| `gh:rerunPRChecks` | Re-trigger failed CI checks. |
| `gh:getProjectViewTable` | GitHub Projects (Projects v2) table data. |
| `gh:listAccessibleProjects` | Cross-org project enumeration for the project picker. |

After every successful write mutation, a `gh:workItemMutated` push event is broadcast to all renderer `webContents` **except** the originating one (which already updated its cache optimistically).

Sources: [src/main/ipc/github.ts:100-160]()

### GitLab handlers (`src/main/ipc/gitlab.ts`)

`registerGitLabHandlers` mirrors the same `assertRegisteredRepo` pattern and registers channels under `gitlab:`:

| Channel | Action |
|---|---|
| `gitlab:viewer` | Authenticated user identity. |
| `gitlab:mrForBranch` | Merge request for the current branch. |
| `gitlab:mr` | MR by IID. |
| `gitlab:listMRs` / `gitlab:listIssues` | Paginated list queries. |
| `gitlab:mergeMR` / `gitlab:closeMR` / `gitlab:reopenMR` | MR lifecycle mutations. |
| `gitlab:addMRComment` / `gitlab:addIssueComment` | Discussion thread actions. |

Sources: [src/main/ipc/gitlab.ts:1-80]()

### Hosted-review handlers (`src/main/ipc/hosted-review.ts`)

`registerHostedReviewHandlers` exposes provider-neutral channels under `hostedReview:`:

| Channel | Description |
|---|---|
| `hostedReview:forBranch` | Resolve the current review for a branch across all providers. |
| `hostedReview:getCreationEligibility` | Preflight check before showing the create-review UI. |
| `hostedReview:create` | Execute the create flow (push + PR/MR creation). |

The handler also performs **worktree path authorization**: for SSH-connected repos it normalizes POSIX remote paths; for local repos it verifies via `resolveRegisteredWorktreePath` that the worktree belongs to the repo before proceeding.

Sources: [src/main/ipc/hosted-review.ts:1-138]()

---

## Multi-Provider Review Abstraction (`src/main/source-control/`)

### Provider detection and routing (`hosted-review.ts`)

`getHostedReviewForBranch` probes each provider client in priority order to discover which service hosts the remote:

```
GitLab (glab project slug) → GitHub (gh repo slug)
  → Bitbucket → Azure DevOps → Gitea
```

Each branch resolves to at most one `HostedReviewInfo` object, which carries a normalized `provider` discriminant (`'github' | 'gitlab' | 'bitbucket' | 'azure-devops' | 'gitea' | 'unsupported'`) and common fields (`number`, `title`, `state`, `url`, `status`, `mergeable`, `headSha`, `conflictSummary`).

Sources: [src/main/source-control/hosted-review.ts:94-212]()

### Eligibility and creation (`hosted-review-creation.ts`)

Before the renderer shows the create-review button, `getHostedReviewCreationEligibility` runs a preflight that checks:

- Provider detection (sequentially queries each provider slug endpoint).
- Authentication (gh auth status).
- Dirty working tree.
- Detached HEAD.
- Branch is not the default branch.
- Upstream presence (`hasUpstream`), ahead-count (`> 0`), and behind-count (blocks if behind—must sync first).

Blocked states are typed as `HostedReviewCreationBlockedReason` and map to a `HostedReviewCreationNextAction` that the UI uses to route the user to the right next step (commit, push, sync, authenticate, or open existing review).

The `createHostedReview` function re-runs eligibility at submission time as a last-chance guard against stale renderer state, then delegates to the provider-specific client (currently only GitHub PR creation is implemented via `createGitHubPullRequest`; other providers surface `unsupported_provider`).

Sources: [src/main/source-control/hosted-review-creation.ts:38-100](), [src/main/source-control/hosted-review-creation.ts:201-300]()

---

## Shared Types and Contracts

### `src/shared/hosted-review.ts`

The canonical type file shared by both sides of the IPC boundary. Key types:

| Type | Description |
|---|---|
| `HostedReviewProvider` | String union: `'github' \| 'gitlab' \| 'bitbucket' \| 'azure-devops' \| 'gitea' \| 'unsupported'`. |
| `HostedReviewInfo` | Normalized review record consumed by the renderer's source-control panel. |
| `HostedReviewCreationEligibility` | Full preflight result including `canCreate`, `blockedReason`, `nextAction`, `defaultBaseRef`. |
| `HostedReviewCreationBlockedReason` | Discriminated string for each possible blocking state. |
| `HostedReviewQueueSummary` | Review queue row with decision, thread summary, reviewer list. |

Sources: [src/shared/hosted-review.ts:1-140]()

### `src/shared/commit-message-generation.ts`

Defines the prompt-building contract for AI commit-message generation:

- `CommitMessageDraftContext` — branch name, staged summary (`--name-status`), and staged patch.
- `buildCommitMessagePrompt(context, customInstructions)` — assembles the LLM prompt, truncating the patch to a safe size and appending optional user instructions. Rules are embedded in the prompt: imperative mood, ≤ 72-char subject, no trailing period, no `Co-authored-by` trailers.
- `splitGeneratedCommitMessage(message)` — splits the raw LLM output into `subject` / `body` / `message`, trimming trailing periods and enforcing the 72-char cap.

Sources: [src/shared/commit-message-generation.ts:1-75]()

---

## Security Boundary: `assertRegisteredRepo`

Every IPC handler that operates on a repository path calls a local `assertRegisteredRepo` function before any git or API call. This function:

1. Resolves the provided `repoPath` to an absolute path using `path.resolve`.
2. Looks up the path in the application's persistent store.
3. Throws `'Access denied: unknown repository path'` if not found.

The pattern is replicated independently in `ipc/github.ts`, `ipc/gitlab.ts`, and `ipc/hosted-review.ts`. The hosted-review handler additionally validates that the `worktreePath` argument belongs to the registered repo before passing it to the creation logic, using `listRepoWorktrees` to enumerate valid paths.

Sources: [src/main/ipc/gitlab.ts:36-44](), [src/main/ipc/hosted-review.ts:18-33]()

---

## SSH and Remote Execution

The entire git execution layer is designed to work transparently over SSH. When a repo has a `connectionId`, operations that require git subprocess calls delegate to `getSshGitProvider(connectionId).exec(args, repoPath)` or provider-specific RPCs (`getStatus`, `getUpstreamStatus`). The `GitHistoryExecutor` interface abstraction in `src/shared/git-history.ts` is the canonical example: the same `loadGitHistoryFromExecutor` function runs locally or over SSH by swapping the executor argument.

Sources: [src/main/source-control/hosted-review-creation.ts:81-100](), [src/shared/git-history-types.ts:64-68]()

---

## Summary

The git and source-control integration layer is organized as a strict layered stack: a subprocess execution core (`runner.ts`) with WSL and Windows compatibility; domain services (`status.ts`, `history.ts`, `upstream.ts`, `worktree.ts`) that build structured results from raw git output; a multi-provider source-control adapter (`source-control/hosted-review.ts`) that normalizes GitHub, GitLab, Bitbucket, Azure DevOps, and Gitea behind a single `HostedReviewInfo` type; and Electron IPC handlers (`ipc/github.ts`, `ipc/gitlab.ts`, `ipc/hosted-review.ts`) that enforce repo-path authorization before exposing any operation to the renderer. Shared type files in `src/shared/` let both the renderer and the main process reason about history, review state, upstream divergence, and commit-message prompts without duplicating definitions across the IPC boundary.
