Configuration
A clone conf is a TOML file: host/namespace, optional global flags and
hooks, and a [[repo]] block per repo. It is the only state gkit clone needs.
host = "tlbb" # ssh Host alias (from ~/.ssh/config)
namespace = "example-org" # GitHub org / GitLab group / user (optional — see below)
# global, all optional
git-flags = ["-c", "http.lowSpeedLimit=1000"] # raw, BEFORE `clone`
clone-flags = ["--filter=blob:none"] # raw, AFTER `clone`
pre-clone = "echo starting $GKIT_REPO" # string OR list of strings
post-clone = ["direnv allow ."]
[[repo]]
dir = "$CP_HOME/cp-conf"
[[repo]]
dir = "$CP_COMMON_LIBS/cosp"
namespace = "other-org" # overrides the global namespace for THIS repo
depth = 1
branch = "dev"
clone-flags = ["--no-tags"]
pre-clone = "echo prepping cosp"
post-clone = ["mill compile"]
The clone URL is <host>:<namespace>/<repo>.git, where repo = basename(dir).
Because host/namespace live in the file (not the filename), one ssh key can
back many conf files — e.g. one per namespace.
Namespace: global or per-repo
namespace may be set globally, per [[repo]], or both. A repo’s effective
namespace is its own namespace if present, otherwise the global one — so a
single conf can span repos from different orgs/users (same host):
host = "gh" # one ssh alias; global namespace omitted
[[repo]]
dir = "$HOME/work/foo"
namespace = "alice" # -> gh:alice/foo.git
[[repo]]
dir = "$HOME/work/bar"
namespace = "bob-org" # -> gh:bob-org/bar.git
The global namespace is optional, but every repo must resolve one (its
own or the global). If any repo has neither, gkit clone errors before cloning
anything, naming the offending dir.
Top-level keys
| Key | Meaning |
|---|---|
host | ssh Host alias. Required. |
namespace | org/group/user; the URL’s owner segment. Optional — a [[repo]] may set its own; every repo must resolve one. |
git-flags | raw flags injected before clone (git-level). |
clone-flags | raw flags injected after clone, every repo. |
pre-clone / post-clone | global hook commands (string or list). |
[[repo]] keys
| Key | Meaning |
|---|---|
dir | local destination; $VAR/${VAR}/~ expanded. |
namespace | org/group/user for this repo; overrides the global namespace. Required only if there’s no global one. |
name | remote repo name (URL’s last segment). Defaults to basename(dir); set it to clone a repo into a differently-named dir (e.g. dir = ".../cosp-mirror", name = "cosp"). |
depth = N | shallow clone (--depth N, implies single-branch). |
branch = "B" | check out branch/tag B after clone (--branch B). Full clone by default (all branches fetched); add single-branch = true to fetch only B. |
single-branch = true | add --single-branch. With branch → fetch only that branch; on its own → clone only the remote’s default branch (bare git clone --single-branch). |
clone-flags | per-repo raw flags after clone. |
pre-clone / post-clone | per-repo hook commands (string or list). |
Execution order (per repo)
- global
pre-clone - repo
pre-clone git <git-flags> clone [--depth N] [--branch B] [--single-branch] --recurse-submodules <clone-flags> <repo clone-flags> <url> <dir>— printed, output captured- built-ins (unless disabled): git identity (
user.name/user.emailon the repo and recursively on every submodule, if resolved — printed), submodule init + branch-switch,direnv allow - global
post-clone - repo
post-clone
Each hook command runs as its own sh -ec '<cmd>' (so set -e is on), output
shown live, with $GKIT_REPO, $GKIT_DIR, $GKIT_URL, $GKIT_HOST,
$GKIT_NAMESPACE set — plus $GKIT_USER_NAME / $GKIT_USER_EMAIL (the resolved
identity, empty when unset). Pre runs in the parent of the target dir; post runs
inside the cloned repo. Fail-fast at two levels: a hook line stops at its first
failing step (set -e — e.g. cd sub; cmd won’t run cmd if cd sub fails), and
the first hook line that exits non-zero fails that repo (the rest don’t run). So you
don’t need defensive || true to stop a bad step from doing damage — write
cmd || true only on the rare command you deliberately want to tolerate.
cd is per line. Each line starts fresh from the repo root, so a cd on one
line does not carry into the next (no cross-line leak). Keep a cd with its
command: cd sub && git config … (or just use git -C sub config …).
Git identity is not a conf key (the conf is shared across a team): it comes
from the clone --user-name/--user-email flags, or an interactive prompt when
omitted — see gkit clone.
The post-clone hooks run only at clone time. To re-apply them over repos that
already exist (e.g. to stamp gkit.baseBranch/gkit.solo on a submodule added
after the initial clone), run gkit stamp.
The gkit.conf git-config key
gkit clone stamps gkit.conf — the absolute path of the conf that cloned the
repo — into each repo’s local git config (and gkit stamp --conf
back-fills it on older clones). It lets gkit stamp run inside a repo with no
argument: it reads gkit.conf, re-parses that conf, and re-applies this repo’s
post-clone. The value is an absolute, machine-local path — if the tree moves,
re-anchor it with git config gkit.conf <new-path> or gkit stamp --conf.
Project-specific config that isn’t a gkit concept (e.g. core.hooksPath .githooks)
belongs in post-clone, not in a gkit built-in — so gkit stamp re-applies it like
any other hook.
The ssh alias vs checked-in URLs (insteadOf routing)
The conf’s host is an ssh Host alias (tlbb) — purely local key-selection
(tlbb ≡ git@bitbucket.org + ~/.ssh/id_tlbb). It must not end up in checked-in
URLs: a teammate without that alias can’t resolve tlbb:org/repo.git. So submodule URLs
in .gitmodules should be canonical — git@<hostname>:<ns>/repo.git — and each gkit
developer gets a local rewrite that routes them through the alias’s key:
git config url."tlbb:codogenics/".insteadOf "git@bitbucket.org:codogenics/"
gkit clone writes this rule for you (see clone → SSH-alias
routing), namespace-scoped so multiple aliases on the same host (different clients)
keep their own keys, into a gkit-owned ~/.gitconfig-gkit that ~/.gitconfig
[include]s. The rules are derived (alias + namespace from the conf, hostname from
git_users), so they’re regenerable. Migrating an existing repo’s .gitmodules from
alias to canonical URLs is a one-time manual git op (git config -f .gitmodules submodule.<n>.url git@<host>:<ns>/x.git + git submodule sync).
Built-in, stateless post-clone
Derived from each repo’s own on-disk metadata — no config needed:
- git identity →
git config user.name/user.emailwhen resolved from--user-name/--user-emailor the prompt (your input, not the conf), applied to the repo and every submodule (submodule foreach --recursive). Printed. - submodules →
update --init --recursive, then each switched onto its.gitmodulesbranch (no detached HEAD). Disable with--no-submodule-branch. .envrc→direnv allow(trust-only; it does not evaluate the file, so an.envrcthat runs e.g.glow ReadMe.mdwon’t taint output). Disable with--no-direnv.