arial
GitHub

Git Operations

Arial uses Git worktrees to provide isolated execution environments for each workstream. This document covers the Git operations layer.

Overview

Each workstream runs in its own Git worktree on a dedicated branch. This provides:

  • Isolation - Workstreams can't interfere with each other
  • Parallelism - Multiple agents can modify files simultaneously
  • Clean merges - Each workstream produces a single commit

Worktree Model

Repository Root
├── .git/                    # Main Git directory
├── .arial/
│   └── worktrees/
│       ├── auth-system/     # Worktree for auth workstream
│       │   ├── src/
│       │   └── ...
│       └── payment-flow/    # Worktree for payment workstream
│           ├── src/
│           └── ...
└── src/                     # Main working directory

Each worktree:

  • Has its own working directory
  • Shares the .git database with the main repo
  • Operates on its own branch
  • Can be modified independently

Git Module

All Git operations are wrapped in lib/git.ts using child process execution.

Worktree Operations

// Create a new worktree
async function createWorktree(
  sourceDir: string,
  worktreeDir: string,
  branch: string,
  baseBranch: string
): Promise<Result<void>>

// Remove a worktree
async function removeWorktree(
  sourceDir: string,
  worktreeDir: string
): Promise<Result<void>>

// List all worktrees
async function listWorktrees(
  cwd: string
): Promise<Result<WorktreeInfo[]>>

Branch Operations

// Create a new branch from base
async function createBranch(
  cwd: string,
  branch: string,
  baseBranch: string
): Promise<Result<void>>

// Checkout an existing branch
async function checkoutBranch(
  cwd: string,
  branch: string
): Promise<Result<void>>

// Delete a branch
async function deleteBranch(
  cwd: string,
  branch: string,
  force: boolean
): Promise<Result<void>>

// Get current branch name
async function getCurrentBranch(
  cwd: string
): Promise<Result<string>>

// Get default branch (main/master)
async function getDefaultBranch(
  cwd: string
): Promise<Result<string>>

Merge Operations

// Merge a branch into current
async function mergeBranch(
  cwd: string,
  branch: string
): Promise<Result<void>>

// Abort a merge in progress
async function abortMerge(
  cwd: string
): Promise<Result<void>>

// Check if there are conflicts
async function hasConflicts(
  cwd: string
): Promise<boolean>

// Get list of conflicted files
async function getConflictedFiles(
  cwd: string
): Promise<Result<string[]>>

Content Operations

// Stage all changes
async function stageAll(
  cwd: string
): Promise<Result<void>>

// Create a commit
async function commit(
  cwd: string,
  message: string
): Promise<Result<string>>  // Returns commit hash

// Push to remote
async function push(
  cwd: string,
  branch: string
): Promise<Result<void>>

// Pull from remote
async function pull(
  cwd: string,
  branch: string
): Promise<Result<void>>

// Get diff from base
async function getDiff(
  cwd: string,
  base: string
): Promise<Result<string>>

// Get working tree status
async function getStatus(
  cwd: string
): Promise<Result<GitStatus>>

Worktree Lifecycle

Creation

When a workstream starts:

// 1. Create the branch from base
await createBranch(repoRoot, `arial/${workstreamId}`, baseBranch)

// 2. Create the worktree
await createWorktree(
  repoRoot,
  `.arial/worktrees/${workstreamId}`,
  `arial/${workstreamId}`,
  baseBranch
)

Execution

The agent runs within the worktree:

// Agent operates in worktree directory
const workDir = `.arial/worktrees/${workstreamId}`

// All file operations happen here
// Changes are isolated from other workstreams

Completion

When the workstream finishes:

// 1. Stage all changes
await stageAll(worktreePath)

// 2. Create commit
await commit(worktreePath, `feat: ${workstream.title}`)

// 3. Merge into base branch (from main repo)
await checkoutBranch(repoRoot, baseBranch)
await mergeBranch(repoRoot, workstream.branch)

Cleanup

After successful merge:

// 1. Remove worktree
await removeWorktree(repoRoot, worktreePath)

// 2. Delete branch
await deleteBranch(repoRoot, workstream.branch, true)

Branch Naming

Workstream branches follow a consistent pattern:

arial/{workstream-id}

Examples:

  • arial/auth-system
  • arial/payment-flow
  • arial/user-profile

This prefix makes Arial branches easy to identify and clean up.

Merge Strategy

Arial uses a sequential merge strategy:

Base Branch (main)
    │
    ├── arial/auth-system ────┐
    │                         │ merge
    │                    ◄────┘
    │
    ├── arial/payment-flow ───┐
    │                         │ merge
    │                    ◄────┘
    │
    └── arial/user-profile ───┐
                              │ merge
                         ◄────┘

Benefits:

  • Predictable merge order
  • Each merge is a clean fast-forward or simple recursive merge
  • Conflicts are resolved one at a time

Conflict Resolution

When merge conflicts occur:

Cleanup Command

The arial cleanup command handles orphaned resources:

// Find stale worktrees
const worktrees = await listWorktrees(repoRoot)
const stale = worktrees.filter(w =>
  w.branch.startsWith('arial/') &&
  !activeWorkstreams.includes(w.branch)
)

// Find stale branches
const branches = await listBranches(repoRoot)
const staleBranches = branches.filter(b =>
  b.startsWith('arial/') &&
  !activeWorkstreams.includes(b)
)

// Clean up
for (const wt of stale) {
  await removeWorktree(repoRoot, wt.path)
}

for (const branch of staleBranches) {
  await deleteBranch(repoRoot, branch, true)
}

Error Handling

Git operations return Result types:

const result = await createWorktree(...)

if (!result.ok) {
  console.error(`Failed to create worktree: ${result.error}`)
  return
}

Common errors:

  • Branch already exists
  • Worktree path conflicts
  • Merge conflicts
  • Network errors (push/pull)

Performance Notes

  • Worktrees share Git objects, so creation is fast
  • Multiple worktrees can run Git operations in parallel
  • Merges are sequential to avoid race conditions
  • Large repos benefit from shallow clones in CI