# Git workflows in Build

> Per-session worktree git operations: status, diff, commit, push/pull, branch watch events, and GitExplorer UI bindings to git IPC channels.

- Repository: Parcha-ai/build
- GitHub: https://github.com/Parcha-ai/build
- Human docs: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b
- Complete Markdown: https://grok-wiki.com/public/docs/parcha-ai-build-bea5702b371b/llms-full.txt

## Source Files

- `src/main/services/git.service.ts`
- `src/main/ipc/git.ipc.ts`
- `src/shared/constants/channels.ts`
- `src/renderer/components/git/GitExplorer.tsx`
- `src/shared/types/index.ts`

---

---
title: "Git workflows in Build"
description: "Per-session worktree git operations: status, diff, commit, push/pull, branch watch events, and GitExplorer UI bindings to git IPC channels."
---

Build runs Git against each session’s `worktreePath` (falling back to `repoPath`) through `GitService` in the main process, `registerGitHandlers` IPC in `git.ipc.ts`, and `window.electronAPI.git` exposed from `preload.ts`. The Git Explorer panel and status bar branch picker call those invoke channels; branch switches outside the UI are detected by `fs.watch` on `.git/HEAD` and pushed to renderers as `git:branch-changed`.

## Session path resolution

Every Git operation is keyed by `sessionId`. `GitService.getWorktreePath` loads the session from the session electron-store (`claudette-sessions`, via `getSessionStoreName()`), checking `sessions.{id}` then `discoveredSessions.{id}`:

| Field | Role |
| --- | --- |
| `worktreePath` | Primary directory passed to `simple-git` |
| `repoPath` | Fallback when `worktreePath` is empty |
| `branch` | Cached display name; updated by status bar watcher and `refreshSessionBranch` |

If neither path is set, handlers throw `Session {sessionId} not found`.

<Note>
Conversation forks (`parentSessionId`, `childSessionIds`) are separate from Git worktree forks (`isWorktree`, `parentRepoPath`). Git always targets the session’s filesystem checkout, not the chat fork graph.
</Note>

## Worktree layout at session creation

Optional isolated checkouts are created when starting a **dev session** with `createWorktree: true` (`DEV_CREATE_SESSION` in `dev.ipc.ts`), not through the Git IPC surface.

```text
~/.claudette/worktrees/{repo-hash}/wt-{sessionId-prefix}/
  └── linked from main repo via: git worktree add <path> <branch>
```

- `getMainRepoPath` resolves nested worktrees back to the main repository before `git worktree add`.
- `repoPath` on the session stays the user-selected folder; `worktreePath` points at the central worktree directory.
- Optional `.claudette/worktree-setup.sh` runs in the new worktree; `.claudette/worktree-setup.md` is stored on the session as `worktreeInstructions`.
- GitHub clone sessions (`session.service.ts`) set `worktreePath = repoPath` after clone—no separate worktree.

`GitService.createWorktree` / `removeWorktree` exist for programmatic worktree management but are **not** registered as IPC handlers; dev creation uses `simple-git` raw commands inline.

## Main-process GitService

`GitService` wraps [simple-git](https://github.com/steveukx/git-js) instances rooted at the session worktree.

| Method | Behavior |
| --- | --- |
| `getStatus` | `git.status()` → `current`, `tracking`, `ahead`/`behind`, `files[]` as `FileChange` |
| `getLog` | `git.log({ maxCount })` → `Commit[]` |
| `getBranches` | `git.branch(['-a', '-v'])` → local + remote `Branch[]` |
| `checkout` | `git.checkout(branch)` |
| `getDiff` | With `commitHash`: diff between parent and commit; else staged + unstaged working tree |
| `commit` | `git.add('.')` then `git.commit(message)` → returns commit hash |
| `push` | `git.push()` if tracking exists; else `git.push(['-u', 'origin', current])` |
| `pull` | `git.pull()` |
| `clone` | Used by session creation, not per-session |
| `stash` / `stashPop` | Implemented on service only—no IPC exposure |

### Worktree-aware HEAD watching

For linked worktrees, `.git` is a file pointing at the real `gitdir`. `getHeadPath` resolves that path and watches the resolved `HEAD` file. `readCurrentBranch` parses `ref: refs/heads/...` or returns an 8-character detached HEAD prefix.

## IPC channels

Invoke handlers are registered in `registerGitHandlers`; one-way events are sent from main to all `BrowserWindow` instances.

| Channel constant | Pattern | Payload / return |
| --- | --- | --- |
| `GIT_STATUS` | invoke | `sessionId` → status object |
| `GIT_LOG` | invoke | `sessionId`, optional `limit` → `Commit[]` |
| `GIT_BRANCHES` | invoke | `sessionId` → `Branch[]` |
| `GIT_CHECKOUT` | invoke | `sessionId`, `branch` |
| `GIT_DIFF` | invoke | `sessionId`, optional `commitHash` → unified diff string |
| `GIT_COMMIT` | invoke | `sessionId`, `message` → commit hash |
| `GIT_PUSH` | invoke | `sessionId` |
| `GIT_PULL` | invoke | `sessionId` |
| `GIT_CLONE` | invoke | `url`, `targetPath` |
| `GIT_REMOTE_BRANCH` | invoke | `sessionId` → branch name or `null` (SSH only) |
| `GIT_WATCH_BRANCH` | invoke | `sessionId` → `{ success, branch?, error? }` |
| `GIT_UNWATCH_BRANCH` | invoke | `sessionId` → `{ success: true }` |
| `GIT_BRANCH_CHANGED` | **event** | `{ sessionId, branch }` |

On branch file changes, `gitService.onBranchChange` broadcasts `GIT_BRANCH_CHANGED` to every window.

### SSH remote branch

When `session.sshConfig` is set, `GIT_REMOTE_BRANCH` runs `git -C "{remoteWorkdir}" rev-parse --abbrev-ref HEAD` over the SSH bridge (`ssh.service.ts`). Local `getStatus` is not used for SSH sessions in `refreshSessionBranch`.

## Preload bridge

`preload.ts` maps `window.electronAPI.git` to the channels above. `onBranchChanged` registers an `ipcRenderer.on` listener and returns an unsubscribe function.

<Warning>
`GIT_COMMIT` is exposed on the bridge but **GitExplorer does not call it** today. Commits from the UI would require a new control or agent tooling; agents typically commit via the terminal harness.
</Warning>

## Renderer: materialized session retry

Git calls from the renderer often wrap:

```typescript
withMaterializedSession(sessionId, () => window.electronAPI.git.getStatus(sessionId))
```

If the main process reports `Session {id} not found`, the helper issues `sessions.update(sessionId, {})` to materialize the record in the store, then retries once. Status bar branch watching uses the same pattern when `watchBranch` fails with a not-found error.

## GitExplorer panel

Mounted from `MainContent.tsx` when the git side panel is open (`isGitPanelOpen`). Requires `session.status === 'running'`; otherwise shows “Start the session to view git”.

```mermaid
sequenceDiagram
  participant GE as GitExplorer
  participant API as electronAPI.git
  participant IPC as git.ipc handlers
  participant GS as GitService

  GE->>API: getLog / getBranches / getStatus
  API->>IPC: invoke git:*
  IPC->>GS: simple-git at worktreePath
  GS-->>IPC: typed results
  IPC-->>GE: React Query cache

  GE->>API: push / pull
  API->>IPC: GIT_PUSH / GIT_PULL
  GE->>GE: refetch queries
```

| UI area | Query / action | IPC |
| --- | --- | --- |
| Header | Current branch, ahead/behind badges | `getStatus` (5s `refetchInterval`) |
| History tab | Commit timeline | `getLog(sessionId, 100)` |
| Branches tab | Local / remote lists, checkout | `getBranches`, `checkout` |
| Changes tab | File list + diff preview | `getStatus`, `getDiff` (optional commit hash from history selection) |
| Toolbar | Pull, push, refresh | `pull`, `push`, manual refetch |

Diff preview truncates at 2000 characters in the Changes tab.

## Status bar branch workflow

Separate from GitExplorer, the status bar keeps `session.branch` in sync:

<Steps>
  <Step title="Start watcher">
    On active session change, call `git.watchBranch(activeSessionId)`. Retry after `sessions.update` if the session was not materialized.
  </Step>
  <Step title="Listen for events">
    Subscribe to `git.onBranchChanged` and call `updateSession(sessionId, { branch })` for any session—not only the active one.
  </Step>
  <Step title="Initial sync">
    When watch succeeds, `refreshSessionBranch` loads branch via `getRemoteBranch` (SSH) or `getStatus` (local).
  </Step>
  <Step title="Switch branch">
    Branch menu loads `getBranches`, then `checkout` + `updateSession` with the new branch name.
  </Step>
  <Step title="Cleanup">
    On session switch or unmount, `unwatchBranch(activeSessionId)`.
  </Step>
</Steps>

External `git checkout` in a terminal updates `HEAD`, triggers `fs.watch`, and flows through `GIT_BRANCH_CHANGED` without polling.

## Shared types

Defined in `src/shared/types/index.ts`:

<ResponseField name="Commit" type="object">
  `hash`, `message`, `author`, `authorEmail`, `date`, `parents`
</ResponseField>

<ResponseField name="Branch" type="object">
  `name`, `current`, optional `remote`, `commit`
</ResponseField>

<ResponseField name="FileChange" type="object">
  `path`, `status` (`added` | `modified` | `deleted` | `renamed`), `additions`, `deletions` (line counts default to 0 in status mapping)
</ResponseField>

## Failure modes

| Symptom | Likely cause |
| --- | --- |
| `Session … not found` | Session missing from `claudette-sessions` or paths unset; try materialize via `sessions.update` |
| Git panel empty while session “running” | Panel gated on `status === 'running'`; start the session |
| Branch stuck on SSH | `GIT_REMOTE_BRANCH` failed—check SSH connection and `remoteWorkdir` |
| Watch returns `success: false` | No `.git/HEAD` at resolved path (non-repo folder) |
| Push sets upstream | First push uses `-u origin <current>` when no tracking branch |

<Tip>
For full channel semantics across domains, see the IPC channels reference. Session fields (`worktreePath`, `isWorktree`, SSH config) are documented on the sessions and workspaces page.
</Tip>

## Related pages

<CardGroup>
  <Card title="Sessions and workspaces" href="/sessions-and-workspaces">
    Session schema, worktree forks, and claudette-sessions persistence.
  </Card>
  <Card title="IPC and preload bridge" href="/ipc-bridge">
    invoke vs push patterns and contextBridge exposure.
  </Card>
  <Card title="IPC channels reference" href="/ipc-channels-reference">
    Complete GIT_* channel catalog.
  </Card>
  <Card title="SSH remote sessions" href="/ssh-remote-sessions">
    Remote workdir and `GIT_REMOTE_BRANCH` over SSH.
  </Card>
</CardGroup>
