Configuration¶
There are three places configuration lives, and the split is deliberate. Each has exactly one owner.
.config/git.tpl.toml → the project. Versioned in Git. Everyone gets it.
~/.config/git-tpl/config.toml → you. Never committed, never read by anyone else.
.git/config, ~/.gitconfig → tpl.* preferences
A freshly cloned repository is fully understandable from .config/git.tpl.toml
alone. Neither of the other two is required for git-tpl to work, and nothing in
either can change what a template renders.
The test for which file something belongs in: would a new contributor cloning
this repository need it to be true? If yes, .config/git.tpl.toml. If it is
about your machine or your habits, one of the other two — Git config if Git
already models it, the user configuration otherwise.
.config/git.tpl.toml¶
Versioned with the project. It contains only the template reference and the answers used to render it.
[template]
source = "https://github.com/noirbizarre/rust-library-template"
ref = "main"
[answers]
project_name = "example"
license = "MIT"
ci = true
[template]¶
| Key | Type | Default | Meaning |
|---|---|---|---|
source |
string | required | Any Git URL, or a local path. |
ref |
string | the remote's default branch | Branch, tag or commit SHA. |
id |
string | derived from source |
The template identity. Determines the ref name. |
root |
string | from the manifest | Override the rendered subdirectory. |
ref takes anything Git does:
ref = "main" # a branch — moves as the template moves
ref = "v1.4.0" # a tag — pinned until you change it
ref = "8b3e7d1" # a commit — fully pinned
A branch is the usual choice. git tpl update resolves it fresh each time, which
is the point: update is how you find out the template moved.
id is normally derived and normally left out. It determines the ref name,
refs/tpl/<id>:
source |
derived id |
|---|---|
https://github.com/noirbizarre/rust-library-template |
github-com-noirbizarre-rust-library-template |
git@github.com:noirbizarre/rust-library-template.git |
github-com-noirbizarre-rust-library-template |
../rust-library-template |
rust-library-template |
The SSH and HTTPS forms of the same repository derive the same id, so switching between them does not orphan the ref.
Set it explicitly when the template moves address but is conceptually the same template — see Pointing at a different template.
[answers]¶
Every answered question, with its type preserved. Written by init, updated by
update when a template adds a question.
Editing this file by hand is supported and expected — change a value, run
git tpl update, and the ref advances with a new rendering. That is how you
change your mind about a choice you made at init time.
Questions skipped by a when condition have no entry.
What does not go here¶
No sync state. No record of what has been merged. No local preferences. No credentials. No paths that only exist on your machine.
The rendered ref is the state. Duplicating it into a file would create two sources of truth that can disagree, and the file would be the one that is wrong.
~/.config/git-tpl/config.toml¶
Yours. Never committed, and never read by anyone else — so nothing in it may change what a template renders. See ADR-013.
The path follows XDG: $XDG_CONFIG_HOME/git-tpl/config.toml, falling back to
~/.config/git-tpl/config.toml. An absent file is the normal case, not an
error. Unknown keys are an error — nothing generates this file, so a key that
is not understood is a typo.
[defaults]
author = "Axel Haustant"
license = "MIT"
[shortcuts]
gh = "https://github.com/"
ghs = "ssh://git@github.com/"
[trust]
templates = ["github.com/noirbizarre/*"]
Three sections, and deliberately nothing else.
| Section | What it does |
|---|---|
[defaults] |
Pre-fills a prompt whose question has the same name. |
[shortcuts] |
Expands a leading <name>: in a template URL you type. |
[trust] |
Templates whose remote data is fetched without a confirmation. |
[defaults]¶
A key matching a question's name pre-fills that question's prompt. Press Enter
and it becomes your answer, recorded in .config/git.tpl.toml like any other.
It seeds a prompt and nothing else. If the question is not asked —
--defaults, tpl.interactive false, CI — this file is ignored entirely and
the template's own default applies. Anything else would mean the same template
rendered two different trees on two machines, and then an unchanged template
would no longer produce no commit. It is the same rule
default_from already follows.
Any question type may be seeded, not only string.
Precedence, highest first:
1. --answer
2. --answers-from (the last file given wins)
3. answers in .config/git.tpl.toml
4. [defaults] ← this file
5. default_from
6. the question's own default
[defaults] sits above default_from because the two are different kinds of
statement: default_from is the template author guessing where the answer
usually comes from, and [defaults] is you saying it outright.
A key naming no question — or naming one of another type — is skipped in
silence. That is deliberate, and differs from --answers-from: you write this
file once for every template you will ever generate, so it is expected to
overshoot, and warning about author on every template that has no author
question is how a warning stops being read.
[shortcuts]¶
A prefix substitution on a leading <name>: in a template URL you type.
[shortcuts]
gh = "https://github.com/"
ghs = "ssh://git@github.com/"
mine = "https://github.com/noirbizarre/"
The expanded URL is what gets recorded
.config/git.tpl.toml receives https://github.com/org/..., and the
template id — and so refs/tpl/<id> — is derived from that. A shortcut
never leaves your machine. If it did, a project you created would be
unusable by anyone without your file, and every contributor would derive a
different ref for the same template.
The rules, all of them:
- Only the URL you type on the command line is expanded. A source read out of a repository never is, because expansion happens before git-tpl's internals see the argument at all.
- An unknown
foo:is left alone — it may be a real scheme. Only names present in this file expand. - Expansion happens once, never recursively.
ghs = "ssh://git@github.com/"does not then expand asssh:. - A name may not contain
/, and may not behttps,http,ssh,gitorfile. Those are refused when the file is read, not when a shortcut is used.
gh and ghs are separate names rather than one name whose scheme git-tpl
guesses. The reason to want the SSH form is a private repository, and inferring
which one you meant from whether a clone failed is exactly the retry logic that
produces incomprehensible authentication errors.
[trust]¶
Templates whose remote data sources are fetched without asking you first.
Patterns are matched against the template's source URL, normalised first: the
scheme, any userinfo, the port, a trailing .git and a trailing slash are
dropped, backslashes are read as path separators, and what is left is folded to
lower case. One entry therefore covers every way of writing the same
repository:
github.com/org/t matches https://github.com/org/t
git@github.com:org/t.git
ssh://git@github.com:22/org/t/
gh:org/t (shortcuts expand first)
You may write a pattern as a full URL if that is what you have to hand — both sides are normalised the same way.
Globs only, over /-separated segments:
* |
any run of characters within one segment |
** |
any number of segments |
No regular expressions and no negation. This list decides whether a fetch happens, and a trust list that needs debugging is a trust list that will be got wrong.
A match grants even when nothing can be asked — under --defaults, in CI,
anywhere. The entry is prior consent, deliberately written, and no weaker than
--trust. A template the list does not name is still refused, loudly, when
there is nobody to ask: nothing is granted by omission.
What trust gates. Only what a template asks git-tpl to do on its behalf, which today is one thing: a remote data fetch. Rendering never requires trust, because a template cannot execute anything, trusted or not.
Note the name: the project file is git.tpl.toml, mirroring git tpl; this one
is named after the binary. Two shapes, so a stray copy of one is never mistaken
for the other.
Git configuration¶
Local, per-user, per-repository behaviour. All keys are under tpl..
| Key | Type | Default | Meaning |
|---|---|---|---|
tpl.remote |
string | origin |
Remote used by git tpl fetch and git tpl push. |
tpl.autoPush |
bool | false |
Push the rendered ref after a successful update. |
tpl.interactive |
bool | true |
Prompt for unanswered questions. false behaves as --defaults. |
One key outside tpl. is read: a question declaring
default_from = "git:<key>" has
that key read, read-only, to pre-fill its prompt. It is never read when nobody
is being asked, and its value reaches the project only as an answer you
accepted.
Precedence¶
Highest wins:
1. command-line flags --remote upstream
2. repository config .git/config
3. user config ~/.gitconfig
4. system config /etc/gitconfig
5. built-in default
This is Git's own precedence, unchanged, because git-tpl reads these keys through
libgit2's configuration snapshot rather than parsing files itself. git config
tpl.remote tells you what git-tpl will use.
Why these are not in .config/git.tpl.toml¶
They are preferences, not project identity. tpl.autoPush is a statement about
how you work; committing it would impose it on every contributor. Conversely,
the template source is a property of the project, and putting it in
.git/config would mean a fresh clone had no idea where the project came from.
Why these are not in the user configuration either¶
Git already models remotes, and it already has a precedence chain across
system, user and repository files that people know. Reimplementing either would
mean git config tpl.remote no longer told you the truth. The user
configuration holds the three things Git has no opinion about: what a prompt
should be pre-filled with, what a URL prefix abbreviates, and which templates
you have already agreed to let reach the network.