Documentation

Last updated: 2026-08-26

Git history retention

A repository that collects build artifacts, uploads, or generated data grows forever, and a large file committed once keeps costing disk long after it was deleted. Everlock can bound that: a repository set to keep ninety days of history has everything older dropped by a daily job, and the objects only those commits referenced are reclaimed.

Truncation removes history, not files. A commit stores a complete snapshot, so every file present at a branch tip after a truncation was present before it. What you give up is the ability to look back: git log stops at the cut, and git blame attributes everything older to the commit that survived.

What a repository costs

Before changing anything, ask what the repository is actually holding:

/git repo size my-project
  my-project: 1.9 GiB in 14203 loose object(s)
  reachable 1.9 GiB (14180), unreachable 2.1 MiB (23) — the daily git.gc job reclaims the unreachable

  ref              reachable  exclusive
  ────────────────────────────────────────
  refs/heads/main  512.0 MiB  412.0 MiB
  refs/tags/v1     1.4 GiB    1.4 GiB

  largest object  size      kind  path
  ───────────────────────────────────────────
  9f2a1c04be31    1.2 GiB   blob  data/dump.bin

Two numbers per ref, because objects are shared: reachable is everything that ref can reach, counted for every ref that reaches it; exclusive is what no other ref reaches — the bytes deleting that ref alone would make prunable. Sizes are on-disk bytes, so a byte reported here is a byte a prune gives back.

Add a window to see what a truncation would achieve:

/git repo size my-project retention=90d
  retention 90d: drop 318 commit(s) older than 2026-05-21, keep 44 behind a shallow boundary, reclaim 512.0 MiB
  expires 2 pull-request ref(s): refs/pull/old-feature/main, refs/pull/spike/main
  1.4 GiB stays pinned by refs/tags/v1 (tip predates the window)

The command reads the repository and writes nothing. Repository reader access is enough to run it.

Setting a policy

To have it happen by itself:

/git repo set my-project retention=90d
  my-project: keeping 90d of history
  the daily git.retention job drops the rest and reclaims what it held; commit ids are left alone, so existing clones keep working

A clone of a repository under a policy is a shallow clone: git log ends at the cut and git fsck passes. Commit ids never change, so clones made before a cut keep working. git clone --depth=<n> works as it does anywhere else, and a repository under a policy has less to send in the first place.

The git.retention job truncates every repository that has a policy. Repositories without one are untouched, so nothing changes until you opt one in, and the job itself has nothing to configure — the window is its only input.

It runs once a day at a fixed time the scheduler derives from the job's name, so different jobs land at different times rather than all at once. /jobs list shows when it next runs; /jobs run git.retention runs it now:

/jobs list
  kind             schedule  next run
  ─────────────────────────────────────────────
  git.retention    daily     2026-08-20 03:00
  system.self-update  daily  2026-08-19 22:04

A run reports one line per repository it changed. A repository it could not truncate — missing, an unparsable window, a ref that moved mid-run — is named in the summary and fails the job, alongside the ones that succeeded, so the failure is visible in /jobs list rather than passed over. /git repo list shows each repository's window in its history column.

The window is the whole policy — there is nothing else to configure. Clear it with /git repo unset my-project retention, which stops future truncation but does not bring back history already dropped.

Nothing is backed up before a cut: a backup ref would keep the dropped objects reachable, and reclaiming their space is the point. /git repo size is how a cut is inspected before it is set — run it first.

Does a push undo it?

No. A client that cloned before a cut still holds the dropped history, but pushing new work does not send it back: git works out what to upload from the refs the server advertises, and the branch it is pushing to already points at the boundary commit, which the client has. Only the commits after it go over the wire. The history below the cut is never offered, and the repository stays bounded.

The one thing that client cannot do is push a ref that points below the boundary — an old feature branch, or a tag on a commit the server dropped:

git push origin 4f2a1c0:refs/heads/archive
 ! [remote rejected] 4f2a1c0 -> archive (missing objects)

git sends no objects for it — it assumes anything under an advertised ref is already there — so the server refuses the ref rather than record one pointing at nothing. Bounding history means giving up the ability to add branches reaching into the part that was dropped.

If the objects do arrive by some other route, the boundary retires itself: git commits are content-addressed, so restoring them gives back exactly the ids the boundary was missing, and the next push re-derives the shallow file and finds nothing shallow.

What happens to clones that already exist

A cut is server-side. It bounds what a new clone costs — the usual win for CI and throwaway checkouts — but it does not reach into the working copies people already have. A plain git fetch leaves a clone exactly as deep as it was: the commits below the server's cut are still reachable from the client's own branch, so even its git gc keeps them.

A client that wants to follow the server down asks for it:

git fetch --depth=50 origin main
git gc --prune=now

The fetch shortens the local history and records the client's own boundary; the prune is what actually frees the space. A fresh clone does the same thing in one step, and gets exactly the history the server still has.

So an old working copy is not wrong, just deep — nothing breaks, it simply keeps what it already downloaded until someone asks it not to.

What retention frees, and what it does not

A blob survives as long as any kept commit's tree names it. With a 90-day window:

Reclaimed?
Big file added 100 days ago, deleted 95 days agoyes — every commit holding it is outside the window
Big file added 100 days ago, deleted 10 days agonot yet — commits inside the window still carry it
Big file added 100 days ago, still in the treeno — it is live content

So a time-based policy bounds history; it does not delete content that is still there. A file added and deleted within the window costs disk until the deleting commit itself ages out.

Refs pin their own trees, too. A two-year-old tag is kept — it is a deliberate marker — and keeps every blob that existed at that commit alive; /git repo size reports those bytes per ref so a disappointing reclaim has a visible cause. A refs/pull/* ref whose tip predates the window is workflow state nobody closed, so the policy expires it.

A branch is never deleted by retention: however old its tip, the tip survives and becomes the start of that branch's history.

Repositories that back a store

The versioned stores behind sites, galleries, calendars, and the system configuration are Git repositories in the same namespace, and their commits are the data — history is what Store reads to answer for a path. Retention refuses those names:

/git repo set everlock-system retention=90d
  'everlock-system' backs a versioned store; its history is the data and is not truncated
git repositories maintenance storage