Documentation / Caching
The checkout cache
runners-io/checkout is actions/checkout with a git mirror on the repository's sticky disk, so a job fetches only what the repository gained. What it keeps, who can read it, and when it steps aside.
A job starts on a fresh machine, so actions/checkout fetches your repository from GitHub every time. For a small repository at the default depth that is a second or two. For a large one, or a workflow that asks for the whole history with fetch-depth: 0, it is most of a minute spent downloading objects the previous job already had.
runners-io/checkout is actions/checkout with one addition: when the job has a sticky disk, it keeps a mirror of your repository on it and checks out from there, fetching only what the repository gained since the last job. When the job cannot use the mirror, it runs actions/checkout itself, unchanged.
Replace actions/checkout with it:
jobs:
build:
runs-on: runnersio-4vcpu-ubuntu-2404
steps:
- uses: runners-io/checkout@v1
with:
fetch-depth: 0
- run: make testEvery input of actions/checkout is accepted, with the same default, and the ref and commit outputs are the same. There is nothing to configure and no token to add. Sticky disks must be turned on for your organisation, because the mirror lives on the repository's disk; Sticky disks says how.
The same step works on a job that has no sticky disk, on GitHub-hosted runners and on any other machine: it does the plain checkout, and says in one line why the mirror was not used. A workflow that runs in more than one place can use it everywhere.
It keeps a mirror. A bare copy of your repository, objects and refs only, on the repository's sticky disk. Each job fetches into it from GitHub, and only the objects it does not already have are downloaded.
It checks out from the mirror. The workspace borrows the mirror's objects instead of copying them, so it is ready as soon as the files are written.
It leaves the workspace as
actions/checkoutwould. The branch, or a detached commit for a tag or a pull request, theoriginremote, and, unlesspersist-credentialsisfalse, the token in the workspace's git config for your owngit pushandgit fetch.
At the default fetch-depth: 1 the workspace is not shallow: it has the repository's whole history, borrowed from the mirror, because that is cheaper than asking GitHub for a one-commit pack. Tools that read git log or git describe see more history than they would with actions/checkout, and nothing else changes.
It saves the download. A workflow that fetches the whole history of a large repository saves the most, because that is where the download is largest; at fetch-depth: 1 the saving is a couple of seconds on a large repository and close to nothing on a small one. The first build of your default branch on a disk that has no mirror yet is slower than a plain checkout, once, because it builds the mirror from your full history. Pull requests never do that: they use a mirror that is already there and otherwise run the plain checkout.
The mirror is used when all of these hold; otherwise the job gets actions/checkout, with the reason on one log line:
| the job | what happens |
|---|---|
| has a sticky disk, and the repository is its own | mirror |
| has no sticky disk: sticky disks are off, the machine was prepared before sticky disks were turned on, or it is not one of our runners | plain checkout |
| comes from a fork's pull request, runs a tag, or is a workflow run we could not read | no sticky disk, so plain checkout |
is pull_request_target or workflow_run | plain checkout: actions/checkout's own guard against checking out a fork's code applies unchanged |
| is a pull request or a branch build on a disk with no mirror yet | plain checkout; the next build of the default branch makes the mirror |
sets repository to another repository, ssh-key, submodules, lfs, sparse-checkout or filter | plain checkout |
sets fetch-depth to anything but 0 or 1 | plain checkout |
| checks out into a folder that is not empty | plain checkout |
| finds the mirror damaged | the mirror is deleted; a push or schedule on the default branch rebuilds it in the same job, any other build gets a plain checkout |
| is in a repository whose mirror is over 20 GiB | plain checkout, and a warning; reset the disk to start again |
clean and show-progress have no effect on the mirror path: it only works in an empty folder, and it is quiet unless you set verbose.
A mirror that cannot be refreshed never fails a job. If GitHub cannot be reached, or the disk is nearly full, or anything about the mirror is wrong, the step logs a warning and the job gets the plain checkout it would have had without us. If that fails too, it fails as actions/checkout fails.
One mirror per repository, at
.runners-io/checkout-mirror/on that repository's sticky disk, named by the repository's permanent id and not its name. It holds the repository's git objects and refs and nothing else. A mirror that is found under another repository's id is discarded, not read.Only your repository's own jobs can read it, and only through its sticky disk. A sticky disk is attached to the one job that is given it, and no two repositories or organisations share one.
Only a successful build of your default branch changes it. A push, schedule or dispatch of the default branch keeps what it left. Every other build, including pull requests from branches of the repository, starts from the last kept mirror and works on a private copy that is thrown away when the job ends. Protecting a sticky disk has the whole rule, including protected repositories, where builds other than the default branch's start with no mirror at all.
No credential is stored in it. The token reaches git for the one fetch that needs it and is never written to the mirror, its config, or a remote address. The workspace gets the token in its own git config when
persist-credentialsis on, asactions/checkoutdoes, and the workspace is not on the sticky disk. The disk's own credential check still applies to everything on it.It is billed as sticky-disk storage and nothing else. It counts against the same cap and is priced at the same rate as the rest of the disk, which the pricing page has; there is no separate price. The log of the step prints the mirror's size on every run, so you can see it. It is deleted with the disk after seven days without a job, or when you reset the disk.
It is kept tidy. Each run runs
git gc --autoon the mirror before the job ends, and a fullgit gcabout once a week.
A repository too big to mirror is skipped: above 20 GiB the step uses the plain checkout instead.
| input | what it does |
|---|---|
dissociate | true gives the workspace its own copy of the objects instead of borrowing the mirror's, as git clone --dissociate does, so the repository is complete without the sticky disk. Slower, because it copies. Default false. |
verbose | true prints each step of the mirror path with its time, and git's own trace. The token is never in it. Default false. |
Both are accepted when the plain checkout does the work, and do nothing then.
If your workflows use a git-mirror checkout action from a different runner vendor, the migration pull request described in Moving from GitHub-hosted runners swaps it for this one and keeps its dissociate and verbose inputs, so the job stays portable: on a runner that is not ours the step does a plain checkout. An input only that provider's action has, such as one that retries a stalled fetch, has no equivalent here, and a job that sets one is left where it is, with the reason in the pull request.