1
0
Fork 0
mirror of https://github.com/imjasonh/git-k8s synced 2026-07-06 22:12:25 +00:00
No description
Find a file
Claude e09c833936
workspace: isolate concurrent branch operations via git alternates
The workspace manager previously held a per-repo lock for the entire
workspace lifetime, serializing all operations on the same repo. This
meant concurrent reconciles for different branches of the same repo
could not run in parallel, and shared state (refs, objects) could
interfere between operations.

Refactor acquireOnDisk to:
1. Update the shared bare repo cache with a brief lock (fetch/clone)
2. Create an isolated workspace using git alternates that shares the
   cache's object store but has its own refs, HEAD, and local objects
3. Release cleans up the temp workspace and flushes any new objects
   (e.g. resolver merge commits) back to the cache

This enables true concurrent branch operations: each workspace has
isolated refs while sharing the bulk object store via alternates.
New objects created in a workspace are flushed to the cache on Release
so subsequent workspaces (e.g. push controller) can access them.

Key changes:
- Split acquireOnDisk into updateCache (locked) + createWorktree
- Use filesystem.NewStorageWithOptions with AlternatesFS to work
  around go-git's ChrootHelper path resolution limitation
- Add worktree tracking for GC to skip active workspaces
- Separate ref counting (incRef/decRef) from cache locking (getRef)
- GC now also cleans stale worktree dirs from crashes

Tests added for concurrent acquisition, ref isolation, object
flushing, and worktree lifecycle.

https://claude.ai/code/session_01KKqgn7QyoUmhna7YBTo3sP
2026-03-17 16:43:18 +00:00
.github Limit compile-workflows trigger to pull_request only 2026-02-28 17:46:35 +00:00
cmd Address PR review comments: fix ref counting, dedup resolveAuth, remove unused CacheStatus 2026-03-17 15:45:09 +00:00
config Address PR review comments: fix ref counting, dedup resolveAuth, remove unused CacheStatus 2026-03-17 15:45:09 +00:00
docs Add design doc for PVC-backed Git workspace cache 2026-03-17 14:46:01 +00:00
hack
pkg workspace: isolate concurrent branch operations via git alternates 2026-03-17 16:43:18 +00:00
test/e2e Implement PVC-backed Git workspace cache (design-pvc-workspace.md) 2026-03-17 15:20:57 +00:00
.gitattributes
.gitignore
.ko.yaml
CLAUDE.md
COVERAGE_REPORT.md Improve test coverage from 10.3% to 42.7% with diffcover analysis 2026-03-04 19:49:59 +00:00
go.mod workspace: isolate concurrent branch operations via git alternates 2026-03-17 16:43:18 +00:00
go.sum
README.md

git-k8s

Kubernetes-native controllers for declarative Git repository management. Define Git repositories, track branches, execute atomic pushes, and keep repositories in sync — all through Custom Resources.

Architecture

git-k8s runs four controllers that communicate through Kubernetes resources rather than direct APIs. Each controller is a separate deployment that watches specific CRDs and takes action when their state changes.

graph TB
    subgraph "Custom Resources"
        GR[GitRepository]
        GB[GitBranch]
        GPT[GitPushTransaction]
        GRS[GitRepoSync]
    end

    subgraph "Controllers"
        RW[Repo Watcher]
        SC[Sync Controller]
        RC[Resolver Controller]
        PC[Push Controller]
    end

    subgraph "External"
        Remote["Git Remote(s)"]
        Secret[K8s Secret]
    end

    RW -- "watches" --> GR
    RW -- "creates / updates / deletes" --> GB
    RW -- "polls refs via ls-remote" --> Remote

    SC -- "watches" --> GRS
    SC -- "reads" --> GB
    SC -- "creates" --> GPT

    RC -- "watches (Conflicted)" --> GRS
    RC -- "creates" --> GPT

    PC -- "watches" --> GPT
    PC -- "pushes commits" --> Remote
    PC -- "updates" --> GB

    PC -. "reads credentials" .-> Secret
    RW -. "reads credentials" .-> Secret

Controller responsibilities

Controller Watches Creates / Mutates Purpose
Repo Watcher GitRepository GitBranch Polls remotes on a configurable interval, mirrors branch state into GitBranch resources
Sync GitRepoSync, GitBranch GitPushTransaction Compares HEAD commits between two repos, calculates merge bases, creates push transactions to keep branches aligned
Resolver GitRepoSync (Conflicted) GitPushTransaction Performs automated 3-way merge, falls back to RequiresManualIntervention on file-level conflicts
Push GitPushTransaction GitBranch Clones into memory, executes atomic pushes with optional compare-and-swap, updates branch status

Resource lifecycle

stateDiagram-v2
    state "GitPushTransaction" as push {
        [*] --> Pending
        Pending --> InProgress
        InProgress --> Succeeded
        InProgress --> Failed
    }

    state "GitRepoSync" as sync {
        [*] --> InSync
        InSync --> Syncing : commits diverge
        Syncing --> InSync : push succeeds
        Syncing --> Conflicted : both sides changed
        Conflicted --> InSync : merge succeeds
        Conflicted --> RequiresManualIntervention : merge fails
    }

End-to-end flow

sequenceDiagram
    participant Remote as Git Remote
    participant RW as Repo Watcher
    participant GB as GitBranch
    participant Sync as Sync Controller
    participant GPT as GitPushTransaction
    participant Push as Push Controller

    RW->>Remote: git ls-remote
    Remote-->>RW: refs + SHAs
    RW->>GB: create / update branch

    GB-->>Sync: branch change triggers reconcile
    Sync->>Sync: compare commits, find merge base
    Sync->>GPT: create push transaction

    GPT-->>Push: new transaction triggers reconcile
    Push->>Remote: git push (in-memory clone)
    Push->>GB: update headCommit

Installation

Prerequisites

  • A Kubernetes cluster (KinD works for local development)
  • ko v0.15+
  • kubectl
  • Go 1.24.7+ (for building from source)

Deploy with ko

Set KO_DOCKER_REPO to a registry your cluster can pull from, then apply everything in order:

export KO_DOCKER_REPO=<your-registry>  # e.g. ghcr.io/you, kind.local

# Create namespace
kubectl create namespace git-system

# Install CRDs
kubectl apply -f config/crds/

# Install RBAC (ServiceAccount, ClusterRole, ClusterRoleBinding)
kubectl apply -f config/rbac/

# Build images and deploy all four controllers
ko apply -f config/deployments/

For local development with KinD, use the built-in local registry:

export KO_DOCKER_REPO=kind.local
ko apply -f config/deployments/

Verify

kubectl -n git-system get pods

You should see four controller pods running:

push-controller-...       1/1   Running
sync-controller-...       1/1   Running
resolver-controller-...   1/1   Running
repo-watcher-...          1/1   Running

Authentication

Controllers authenticate to Git remotes using Kubernetes Secrets referenced from GitRepository resources. The Secret must exist in the same namespace as the GitRepository.

Create a Secret

For HTTPS repositories using a username and personal access token:

kubectl create secret generic my-git-creds \
  --namespace=default \
  --from-literal=username=<git-username> \
  --from-literal=password=<personal-access-token>

Reference it from a GitRepository

apiVersion: git-k8s.imjasonh.com/v1alpha1
kind: GitRepository
metadata:
  name: my-repo
  namespace: default
spec:
  url: https://github.com/example/repo.git
  defaultBranch: main
  pollInterval: 30s
  auth:
    secretRef:
      name: my-git-creds

The auth field is optional — omit it for public repositories. When present, the Push Controller and Repo Watcher Controller both resolve the Secret to authenticate clone, push, and ls-remote operations.

RBAC

The controllers' ClusterRole already includes read access to Secrets:

- apiGroups: [""]
  resources: [secrets]
  verbs: [get, list, watch]

No additional RBAC configuration is needed.

Usage

Track a repository

apiVersion: git-k8s.imjasonh.com/v1alpha1
kind: GitRepository
metadata:
  name: upstream
spec:
  url: https://github.com/example/repo.git
  defaultBranch: main
  pollInterval: 1m

The Repo Watcher will poll the remote and create a GitBranch resource for each branch it discovers.

Push to a repository

apiVersion: git-k8s.imjasonh.com/v1alpha1
kind: GitPushTransaction
metadata:
  name: push-feature
spec:
  repositoryRef: upstream
  atomic: true
  refSpecs:
    - source: abc123def
      destination: refs/heads/main
      expectedOldCommit: 789fed456   # optional CAS check

Sync two repositories

apiVersion: git-k8s.imjasonh.com/v1alpha1
kind: GitRepoSync
metadata:
  name: keep-in-sync
spec:
  repoA:
    name: upstream
  repoB:
    name: fork
  branchName: main

The Sync Controller will detect when the branch diverges between the two repos and create GitPushTransaction resources to bring them back in sync. If both sides have diverged, the Resolver Controller attempts an automated 3-way merge.

Design decisions

  • Stateless controllers — All Git operations use in-memory storage (go-git with memory.NewStorage()). No persistent volumes required.
  • Separate binaries — Each controller is its own deployment. They communicate exclusively through Kubernetes resources.
  • Atomic pushes — Push transactions support compare-and-swap via expectedOldCommit to prevent race conditions.
  • No finalizers — Resource relationships use labels and owner references only.

License

Apache-2.0