This document explains how the Blacksmith-optimized checkout action accelerates repository cloning through persistent git mirror caching.
BLACKSMITH VM
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β STICKY DISK (Persistent) β β
β β /blacksmith-git-mirror/v1/ β β
β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β BARE MIRROR (owner-repo.git) β β β
β β β β β β
β β β objects/ β β β
β β β βββ pack/ β β β
β β β β βββ pack-abc123.pack (compressed objects) β β β
β β β β βββ pack-abc123.idx (pack index) β β β
β β β βββ info/ β β β
β β β refs/ β β β
β β β βββ heads/ (all branches) β β β
β β β βββ tags/ (all tags) β β β
β β β β β β
β β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β β alternates β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β WORKSPACE CHECKOUT β β
β β $GITHUB_WORKSPACE/repo/ β β
β β β β
β β .git/ β β
β β βββ objects/ β β
β β β βββ info/ β β
β β β βββ alternates βββΊ /blacksmith-git-mirror/.../ β β
β β β objects β β
β β βββ refs/ β β
β β βββ HEAD β β
β β β β
β β src/ β β
β β README.md β β
β β ... β β
β β β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββ
β Workflow Run β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ No ββββββββββββββββββββ
β Is Blacksmith ββββββββββββββΊβ Standard clone β
β environment? β β from GitHub β
ββββββββββ¬ββββββββββ ββββββββββββββββββββ
β Yes
βΌ
ββββββββββββββββββββ
β Request sticky β ββββββββ gRPC to VM agent (192.168.127.1:5557)
β disk from VM β GetStickyDisk(key: "owner-repo")
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Mount disk at β
β /blacksmith- β
β git-mirror/ β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ No ββββββββββββββββββββ
β Mirror exists? ββββββββββββββΊβ git clone β
β β β --mirror β
ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ
β Yes β
βΌ β
ββββββββββββββββββββ β
β git fetch β β
β --prune β β
ββββββββββ¬ββββββββββ β
β β
ββββββββββββββββββββββββββββββββββ
β
βΌ
ββββββββββββββββββββ
β git init in β
β workspace β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Write alternates β ββββββββ .git/objects/info/alternates
β file pointing to β points to mirror/objects
β mirror β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β git fetch from β ββββββββ Objects already in mirror
β origin β are NOT re-downloaded
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β git checkout β
β requested ref β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ Yes ββββββββββββββββββββ
β dissociate: ββββββββββββββΊβ git repack -a β
β true? β β (copy objects) β
ββββββββββ¬ββββββββββ ββββββββββ¬ββββββββββ
β No β
βΌ β
ββββββββββββββββββββ β
β Checkout done! ββββββββββββββββββββββββ
β (fast path) β
ββββββββββββββββββββ
GitHub βββββββββββββββββββββββββββββββββββββββββββββββΊ Workspace
Download ALL objects every time
(100MB+ for large repos)
First Run:
GitHub βββββββββββββββββββββββββββββββββββββββββββββββΊ Mirror (persistent)
Download ALL objects once β
β alternates
Subsequent Runs: βΌ
GitHub βββ fetch delta only (new commits) ββββββββββββΊ Mirror ββββΊ Workspace
(typically KB to small MB) (objects shared,
not copied)
Key optimizations:
- Bare mirror: Stores all branches/tags, no working tree overhead
- Persistent storage: Survives across workflow runs via sticky disk
- Alternates: Workspace references mirror objects without copying
- Delta fetch: Only new commits since last run are downloaded
The implementation is designed to be fail-safe - cache failures never break your workflow:
ββββββββββββββββββββ
β setupCache() β
β or ensureMirror()β
β fails β
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β core.warning() β ββββββββ "Blacksmith cache setup failed,
β Log the error β using standard checkout: <error>"
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β cacheInfo = null β ββββββββ Cache disabled for this run
ββββββββββ¬ββββββββββ
β
βΌ
ββββββββββββββββββββ
β Standard clone β ββββββββ Falls back to normal git clone
β proceeds without β from GitHub (slower but works)
β mirror β
ββββββββββββββββββββ
Failure scenarios handled:
- gRPC connection to VM agent fails
- Sticky disk unavailable
- Mirror clone/fetch fails
- Filesystem errors
Post-job cleanup also handles failures gracefully:
- If unmount fails: warning logged, continues
- If commit fails: warning logged, data may not persist (but workflow succeeded)
GC runs in the post-job cleanup phase to avoid impacting checkout performance or VM boot time.
WORKFLOW TIMELINE
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΊ
β β
β VM Boot Checkout (hot path) User Steps Post-job β
β β β β β β
β βΌ βΌ βΌ βΌ β
β βββββββ βββββββββββββ ββββββββββββββ ββββββββββββββ β
β βStartβ βsetupCache β β build/test β β git gc β β
β β VM β βensureMirrorβ β etc. β β--prune=now β β
β βββββββ β(fetch only)β ββββββββββββββ β unmount β β
β βββββββββββββ β commit β β
β β ββββββββββββββ β
β β β β
β NO GC HERE GC RUNS HERE β
β (fast checkout) (after user code) β
β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
We run git gc --prune=now which orchestrates:
- Repack: Consolidates all reachable objects into optimized pack files
- Prune: Removes all unreachable loose objects immediately
- Pack-refs: Compresses refs for faster lookups
- Reflog expire: Cleans up old reflog entries
Before GC: After GC:
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
β pack-aaa.pack (100MB) β β pack-consolidated.pack β
β pack-bbb.pack (5MB) β β (95MB - only reachable β
β pack-ccc.pack (3MB) β βββΊ β objects, deduplicated) β
β loose objects (2MB) β β β
β unreachable (10MB) β β Unreachable: removed β
β β β β
β Total: ~120MB β β Total: ~95MB β
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
Using --prune=now (immediate pruning) is safe in our context because:
- Post-job timing: GC runs after all user workflow steps complete
- No concurrent writes: The mirror is idle during cleanup
- Single consumer: Only the checkout action uses the mirror
The default git gc uses --prune=2.weeks.ago to protect against concurrent processes, but that's unnecessary here.
| Phase | Operation | Impact if GC ran here |
|---|---|---|
| VM Boot | Mount sticky disk | Would delay job start |
| Checkout | git fetch |
Would delay checkout |
| User Steps | build, test, etc | N/A (GC not applicable) |
| Post-Job | GC + commit | No user impact |
With GC, the mirror is bounded to approximately the size of reachable objects:
- Repository history: All reachable commits, trees, blobs
- All branches and tags: Objects referenced by any ref
Unreachable objects (from force-pushes, deleted branches) are removed immediately.
The mirror stays in sync with GitHub through incremental fetches:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β BARE MIRROR β
β β
β Before fetch: After fetch: β
β refs/heads/main β abc123 refs/heads/main β def456 β
β refs/heads/feature β xyz789 refs/heads/feature β (deleted)β
β refs/heads/newbranch β ghi012 β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
β git fetch --prune origin
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β WHAT HAPPENS β
β β
β 1. Download new commits (def456, ghi012) β
β 2. Update refs/heads/main to point to def456 β
β 3. Delete refs/heads/feature (--prune removes deleted refs) β
β 4. Create refs/heads/newbranch pointing to ghi012 β
β 5. Objects from deleted branch xyz789 remain (no GC) β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
What syncs:
- All branches (refs/heads/*)
- All tags (refs/tags/*)
- New commits and their objects
What gets cleaned up (in post-job GC):
- Redundant pack files (consolidated into single optimized pack)
- All unreachable objects (removed immediately via
git gc --prune=now)
When to use dissociate: true:
- uses: useblacksmith/checkout@v6
with:
dissociate: true # Copy objects locally WITHOUT dissociate (default): WITH dissociate: true:
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
β Docker Container β β Docker Container β
β β β β
β .git/objects/info/ β β .git/objects/ β
β alternates βββββββββββ β β pack/ β
β β β β pack-xxx.pack β
β βΌ β β (all objects local) β
β βββββββββ΄ββββ β β
β β MOUNT NOT β β No external dependency β
β β AVAILABLE β β β
β β β³ β³ β³ β ββββββββββββββββββββββββββββββ
β βββββββββββββ
β git commands fail! β
ββββββββββββββββββββββββββββββββββ
Use dissociate: true when:
- Running Docker-based actions that don't mount the cache volume
- Building container images that include the git repository
- Needing a fully self-contained checkout
Trade-off:
- Slower checkout (must copy all objects)
- Larger workspace (objects duplicated)
- But works in isolated environments
This action is a drop-in replacement for actions/checkout@v6:
# Before (standard GitHub Action)
- uses: actions/checkout@v6
# After (Blacksmith-optimized - just change the org)
- uses: useblacksmith/checkout@v6Compatibility guarantees:
- All existing inputs work identically
- Only addition: optional
dissociateinput - Non-Blacksmith environments fall back to standard behavior
- Cache failures fall back to standard clone