git provider
A git stage brings one git checkout into the environment, pinned to one
revision: clone if it isn’t there yet, otherwise fetch and move it — always
detached, never on a branch — onto whatever revision: names now. It is
what the “git clone, then fetch/checkout a pinned tag by hand” shell script
(see the worked example in custom) looks like once it is a
provider — the same job, but idempotent and --fast-aware without every
project writing that logic again.
[pico-sdk]
provider = "git"
url = "https://github.com/raspberrypi/pico-sdk.git"
path = "${DENVER_ENV_WORKDIR}/pico-sdk/2.3.0"
revision = "2.3.0"
submodules = true
env-prepend = { PICO_SDK_PATH = "${DENVER_ENV_WORKDIR}/pico-sdk/2.3.0" }
(provider:/description:/disabled:/scripts:/env:/env-prepend:/env-append:/env-sep: are generic
keys every stage has — see “Generic stage keys” in Configuration. Everything
below is specific to git; env-prepend: above is that generic mechanism, not something this provider
implements itself — see “Where things go” below for why its value is the checkout’s own path:, spelled out,
rather than download’s package-relative ".".)
Key reference
url(required) — the repository to clone. Anythinggit cloneitself accepts (https://,git@host:..., a local path).path(required) — where the checkout lives. Resolves like every other denver path (against the env dir, then imported base envs), so it is almost always written against${DENVER_ENV_WORKDIR}(denver’s per-env state dir) — a checkout that size has no business inside the project’s own working tree, and deleting the env deletes it.revision(required) — a tag, a branch, or a raw commit sha. This provider always ends up withpath:in a detached state at exactly this commit — never on a branch — so a moving branch name is pinned to whatever it resolved to this run, the same guarantee a tag gives.remote— the remote name a fresh clone is created under, and the onerevision:is fetched/resolved against (default:"origin").submodules—truerunsgit submodule update --initafter checkout (default:false). Not recursive — a submodule that itself declares submodules needs those handled separately (most projects don’t need them merely to build against the checkout; see “Submodules” below).
This provider has no env-prepend:/env-append: keys of its own: a
checkout’s own location is already known in full wherever path: is
written, so the generic per-stage env:/env-prepend:/env-append: keys
(see “Generic stage keys” in Configuration)
already cover exporting it — spell out path:’s own value again (as the
worked example above does for PICO_SDK_PATH), rather than the
package-relative "." a download package’s own
env-prepend: can use.
What runs, and what is skipped
Per stage, in order:
Clone — skipped when
path:is already a git checkout (path/.gitexists). Never re-clones over one that’s already there, whateverurl:/revision:say — a checkout cloned from a different url is a config mistake to fix by hand (denver cleanthe env, or pointpath:elsewhere), not something this provider silently redoes.Fetch —
git fetch --tags --prune <remote>, every run:revision:may be a branch that has moved, or a tag pushed after the checkout was made, and this is how either is seen at all.Checkout — skipped when
path:is already detached at exactly the commitrevision:resolves to. Otherwise:git checkout --detach <sha>. A commit sha the fetch above didn’t already have (see “Unreachable commits” below) is fetched explicitly first.Submodules —
git submodule sync && git submodule update --init, only whensubmodules: true. Runs every time (there is no cheap way to tell “already up to date” apart from asking git, and asking is what these two commands do).
The generic per-stage env:/env-prepend:/env-append: keys run after
this, for every stage regardless of provider — see “Generic stage keys” in
Configuration.
Unreachable commits
git fetch --tags sees every tag and every branch tip, but not a commit
pinned by raw sha that sits behind one — most servers refuse to serve an
object that isn’t the tip of some advertised ref at all
(uploadpack.allowReachableSHA1InWant/allowAnySHA1InWant, off by default
on GitHub and most self-hosted setups). When the generic fetch above didn’t
leave revision: resolvable, this provider tries once more with an
explicit git fetch <remote> <revision> — which succeeds for a sha the
remote is willing to serve directly, and fails the same way git itself
would otherwise. There is no third fallback: a revision neither fetch found
is reported as not found, naming the remote it was fetched from.
Where things go
There is no separate “downloads vs. unpacked” split the way download has
one — a git checkout is both, and git’s own object store is what makes an
unchanged fetch cheap. path: is the whole of it:
<path>/ # exactly what 'git clone <url> <path>' would leave,
├── .git/ # then moved (detached) onto 'revision:'
└── ... # the checkout itself
Design notes
Why a provider and not a
custom: cmd:script. The shell version of this (seecustom’s worked example) is the same ten-odd lines every project migrating off a hand-pinned git checkout has to get right: recognise an existing clone, never re-clone over it, move a moving pin (a branch, a re-tagged release) forward without leaving the tree on a branch a latergit pullcould then drift. Here that logic exists once.Always detached. A stage’s checkout is denver’s own state, not something a person is meant to commit on top of —
--detachmakes that the checkout’s actual state, not just a convention, sogit statusinside it never reads as “on branch main, 40 commits ahead” for a branch nobody is developing against.submodules:is not recursive. Recursing by default would fetch a submodule’s own submodules whether or not anything actually needs them (mbedtls’s own test/build tooling submodule, inside pico-sdk, is exactly such a case) — every extra clone is bandwidth and time spent on a stage everydenver runre-checks. A project needing nested submodules reaches forcustom(or asks for it — this provider doesn’t have asubmodules-recursive:key yet).--fastskips clone/fetch/checkout/submodules entirely; it dies with a clear message ifpath:was never checked out — run once without--fastfirst. The genericenv:/env-prepend:/env-append:keys still apply regardless (see “Generic stage keys” in Configuration) — that’s the activation half, not a build step to skip.--forcerunsgit reset --hardandgit clean -fdxbefore checking out, discarding whatever local state (a hand-edited file, an untracked build artifact) would otherwise survive the move ontorevision:. Without it, a checkout already sitting at the right commit is left completely alone, local edits included.--dry-runreports the clone/fetch/checkout/submodule commands instead of running them, and leaves whatever is already on disk untouched. Resolvingrevision:to a commit is still attempted for real (it is a read, not a write, the same waydownload’s checksum check is) so the preview can say “already there” when that’s true; a checkout this preview would have created fresh has nothing to resolve against yet, and that limitation is reported rather than silently guessed past.No built-in credentials. Unlike
download’s[[download-auth]], this provider sends none of its own — a private repository is exactly whatgit’s own credential helper, an SSH agent, or aurl:already carrying a token are for, all of which a plaingit clone/git fetchalready honours with no denver-specific configuration at all.