Skip to main content

Git Configuration

The git section in atmos.yaml makes Git a foundational Atmos capability — a peer to Toolchain, Auth, and Hooks. Define managed repositories once under git.repositories, then clone, pull, diff, commit, and push them by logical name from the atmos git commands, kind: git lifecycle hooks, and native CI workflows — with consistent authentication, safety rules, and workdir handling. It is the foundation for GitOps workflows with Atmos: commit generated artifacts to a source-of-truth repository that Argo CD, Flux, or your CI consumes.

You will learn

  • Define managed repositories by logical name under git.repositories
  • Authenticate through Atmos Auth identities and integrations — credentials are never embedded in URIs
  • Clone is always reconcile: idempotent and safe under restored CI caches
  • Automatic workdirs under the Atmos XDG cache root are captured by the native CI cache for free
  • Configure local Git hooks (pre-commit, commit-msg, ...) that run a command or a list of steps

Basic Configuration​

atmos.yaml
git:
repositories:
flux-deploy:
uri: https://github.com/acme/flux-deploy.git
auth:
identity: platform-admin
commit:
signing: auto

generated-terraform:
uri: https://github.com/acme/generated-terraform.git

hooks:
pre-commit:
command: atmos workflow pre-commit
commit-msg:
command: atmos workflow commit-msg -- "$1"

The keys under git.repositories (flux-deploy, generated-terraform) are arbitrary, user-defined logical names — not reserved values. Components, hooks, and commands refer to repositories by these names. Repository configuration follows standard Atmos deep-merge, so repositories can be defined in imported configuration and overridden per environment.

Repository Fields​

All fields a repository supports:

atmos.yaml
git:
repositories:
flux-deploy:
provider: cli # optional; cli is the default
uri: https://github.com/acme/flux-deploy.git
branch: main
remote: origin
workdir: .workdir/git/flux-deploy # optional; default is automatic XDG
clone:
depth: 1 # shallow clone depth; 0 = full history (default)
filter: blob:none # optional partial-clone filter
single_branch: true
submodules: false # default false
auth:
identity: platform-admin
commit:
signing: auto # auto | always | never
author:
name: atmos[bot]
email: atmos-bot@acme.com
push:
retries: 3
init:
from: https://github.com/acme/flux-template.git # optional seed source for `atmos git init`
keep_history: false # preserve source history as `upstream` (requires `from`)
provider

Execution backend for Git operations. Default and currently the only value: cli (shells out to the Git CLI). cli is host-agnostic — it works with GitHub, GitLab, Bitbucket, and bare repositories.

uri

Remote repository URI. Credentials must never be embedded in uri — authentication flows through the process environment and Atmos Auth, never through URL rewriting with tokens.

branch

Branch to check out. Empty means the current branch for existing workdirs, and Git's default-branch behavior for fresh clones.

remote

Remote name. Default: origin.

workdir

Explicit workdir location, overriding the automatic XDG location (see Automatic Workdirs).

clone.depth

Shallow clone depth. 0 (default) means full history. Also settable per invocation with --depth (flag wins: CLI > ENV > config > defaults).

clone.filter

Partial-clone filter spec (for example, blob:none). Also settable per invocation with --filter.

clone.single_branch

Limit the clone to the configured branch. Also settable per invocation with --single-branch.

clone.submodules

Initialize submodules after clone. Default: false.

auth.identity

The Atmos Auth identity used for Git operations against this repository. See Authentication Resolution.

commit.signing

Commit signing mode: auto (default — pass no signing flag; Git config decides), always (pass -S), or never (pass --no-gpg-sign). Overridable per invocation with --sign / --no-sign.

commit.author.name / commit.author.email

Commit author identity, passed per invocation (-c user.name=... -c user.email=...) without mutating repository or global Git config. Required in CI, where runners typically have no user.name/user.email configured. Locally, when Git already resolves an author, Atmos passes nothing — your own Git config wins.

push.retries

Bounded retry count for rejected non-fast-forward pushes (pull --rebase, then re-push). Default: 3. 0 disables retries.

init.from

Default seed source for atmos git init — the repository URI whose content bootstraps this repository (a template, or a repository being migrated). When omitted, init creates an empty repository. Overridable per invocation with --from (flag wins: CLI > ENV > config > defaults).

init.keep_history

When seeding via init.from, preserve the source's full history and keep it reachable as the upstream remote so future updates can be pulled. Default: false (fresh history — a single initial commit). Only valid together with init.from. Overridable per invocation with --keep-history.

Defaults​

When fields are omitted:

FieldDefault
providercli
remoteorigin
branchCurrent branch for existing repos; Git default-branch behavior for clone
workdirAutomatic XDG cache location (see below)
commit.signingauto
commit.authorIdentity-derived when available; otherwise an atmos[bot]-style default
push.retries3
clone.depth0 (full history)
clone.submodulesfalse

Automatic Workdirs​

When workdir is omitted, Atmos resolves a deterministic location under the Atmos XDG cache root:

$XDG_CACHE_HOME/atmos/git/repositories/<name>

with the standard XDG fallbacks and ATMOS_XDG_* overrides. Because the native CI cache archives the XDG cache root, managed clones are captured and restored across CI runs for free. An explicit workdir overrides the automatic location.

Clone Destinations​

The two-repo split (managed repositories vs. the current repository) extends to clone destinations:

Clone formDestination
Named managed repository (atmos git clone flux-deploy)Automatic XDG workdir (cache-captured)
Single configured repository outside CI (atmos git clone with exactly one repository)Automatic XDG workdir (cache-captured)
No-arg current-repo checkout replacement in CI (atmos git clone with ci.enabled: true and detected CI metadata)The working directory (e.g., GITHUB_WORKSPACE), exactly like actions/checkout — never an XDG workdir; subsequent Atmos commands expect the repo at CWD
Ad hoc URI (atmos git clone https://...)<cwd>/<repo-name>, like plain git clone

--workdir=<path> overrides the destination in all forms.

Clone Is Reconcile​

Workdirs persist across runs — and the native CI cache restores them, possibly stale. Clone semantics are therefore defined as reconcile, not "fail if exists":

  1. Workdir absent → clone.
  2. Workdir present and clean → fetch, check out the configured branch, fast-forward to the remote ref.
  3. Workdir present and dirty from a crashed or interrupted run → defined recovery: discard uncommitted changes inside managed paths only and re-reconcile; fail with a hint when unmanaged dirty files are present.

Reconcile makes clone behavior independent of cache freshness: a cache hit means fetching the delta instead of a full clone; a cache miss means a fresh clone; correctness is identical either way.

Authentication Resolution​

auth.identity is the only named auth reference on a repository. Integrations are never referenced by name from git config — they attach to identities via their via link, and naming an identity on a repository brings its integrations along automatically. For example, a github/sts integration linked to platform-admin materializes GIT_CONFIG_* credentials for every Git subprocess targeting the repository, with zero Git-specific auth wiring:

atmos.yaml
auth:
identities:
platform-admin:
kind: github/user # illustrative
integrations:
deploy-sts:
kind: github/sts
via:
identity: platform-admin

git:
repositories:
flux-deploy:
uri: https://github.com/acme/flux-deploy.git
auth:
identity: platform-admin # pulls in deploy-sts via the identity link

Per-operation resolution order:

  1. --identity=<name> flag (the global persistent flag).
  2. git.repositories.<name>.auth.identity.
  3. Ambient credential broker: when no identity is configured, the lazy ambient broker runs before the first Git subprocess. This is the zero-config CI path — atmos git clone of a private GitHub repository works with no auth block at all when an auto-provision github/sts integration (or an ambient GITHUB_TOKEN) is available.
  4. Process environment passthrough: your own Git credentials (credential helpers, SSH agent) always work, since the Git CLI provider inherits the process environment.

See Authentication for identities and integrations.

Local Git Hooks​

The git.hooks section maps Git hook names to commands or ordered step lists, invoked via thin .git/hooks/* shims installed by atmos git hooks install:

atmos.yaml
git:
hooks:
pre-commit:
command: atmos workflow pre-commit
commit-msg:
command: atmos workflow commit-msg -- "$1"
git.hooks.<hook-name>

A Git hook name: pre-commit, commit-msg, pre-push, and so on.

git.hooks.<hook-name>.steps
An ordered list of registered Atmos steps. Use this instead of command to run inline scripts and other step types inside Atmos. An empty list is not a hook implementation.
git.hooks.<hook-name>.command

The command to execute when the hook fires. Hook commands should usually invoke an Atmos workflow or custom command, which preserves existing toolchain, environment, and identity behavior. Hook arguments and stdin are forwarded.

The shim that atmos git hooks install writes pins the absolute path of the atmos binary that installed it, and falls back to atmos on PATH when that binary has moved or been removed. Hooks therefore keep working from IDEs and GUI Git clients that do not have atmos on their PATH.

Local Git hooks are triggered by Git itself in the current repository. They are separate from Atmos lifecycle events — for event-bound Git publishing (e.g., commit after terraform apply), use the kind: git hook.

Inline script steps​

Keep small checks inside the hook configuration. Starlark scripts run in-process and can call the step library:

git:
hooks:
pre-commit:
steps:
- name: check-instruction-size
type: script
interpreter: starlark
script: |
for path in fs.glob("CLAUDE.md"):
if fs.stat(path).size > 40000:
errors.build("CLAUDE.md exceeds 40,000 bytes").fail()
- name: report
type: script
interpreter: starlark
script: ui.success("Repository checks passed")

Choose exactly one of command or a non-empty steps list. Install the shim with atmos git hooks install, then run it directly with atmos git hooks run pre-commit. Existing command hooks keep their argument and stdin forwarding behavior.

Steps run in order and stop at the first failure. They run from the repository root, even when you invoke the hook from a subdirectory. They share named outputs. A type: env step exports variables to the processes and templates of later steps (for example {{ .env.X }}). The env global of a Starlark step contains only the env: that step declares, not variables exported by earlier steps. Each step supports its handler's timeout and retry behavior; embedded scripts also have access to steps.parallel. Git hook arguments are available as ctx.args in embedded scripts, for example the commit-message path supplied to commit-msg, and as positional parameters in shell steps ($1, "$@"). Git's pre-commit hook supplies no filenames: query the Git index explicitly when a check needs tracked or staged paths.

Relative paths resolve from the repository root, unless a step sets working_directory. These checks read working-tree files; Atmos does not stash unstaged changes or limit checks to the staged contents automatically.

Hook step lists use synchronous execution. Workflow scheduling fields (needs, when, continue, identity, background execution, and freshness policies) are rejected. Use an explicit container step for container operations. Background-job handlers require a workflow context. Use a workflow when a hook needs that scheduling.

See two inline repository checks for tracked symlink validation and separate size limits for CLAUDE.md and agent files.

Hook arguments and standard input​

Every step of a steps hook can read what Git passed to the hook:

Arguments

A shell step receives the arguments as positional parameters ($1, "$@"), which keeps an argument that contains spaces intact. Every step's environment also holds ATMOS_GIT_HOOK_ARGS, the arguments joined by single spaces. A Starlark step reads them from ctx.args.

Standard input

Git writes data to the standard input of pre-push, pre-receive, post-receive, post-rewrite, reference-transaction, and proc-receive. Atmos reads that input once and saves it to a temporary file. The environment variable ATMOS_GIT_HOOK_STDIN holds the path of the file for every step, so an embedded script can open it with fs.read_file. The first shell step also receives the same data on its standard input. Atmos removes the file when the hook ends. Other hooks capture nothing and leave ATMOS_GIT_HOOK_STDIN unset.

For example, a pre-push hook can read the refs Git is about to update:

git:
hooks:
pre-push:
steps:
- name: refs
type: shell
command: |
echo "pushing to $1 ($2)"
while read -r local_ref local_sha remote_ref remote_sha; do
echo "$local_ref -> $remote_ref"
done

Exit codes and prompts​

atmos git hooks run exits with the code of the step that failed, so Git blocks the operation with the status the step chose. A Starlark fail() exits with 1, and errors.build("message").with_exit_code(5).fail() exits with 5.

A step that prompts for input, such as confirm or input, needs a terminal. Git often runs hooks without one, for example from a GUI client. A prompt step without a default: fails there with a hint that setting default: lets the step run without a terminal.

Configuration layers​

When several configuration sources define the same hook, a later source that gives the hook steps: replaces an earlier command:, and a later command: replaces earlier steps:. Atmos never runs both forms.

List Output​

The git.list section configures atmos git list output, following the established <section>.list.columns pattern:

atmos.yaml
git:
list:
format: table
columns:
- name: Name
value: "{{ .name }}"
- name: URI
value: "{{ .uri }}"
git.list.format

Default output format: table, json, yaml, csv, or tsv.

git.list.columns

Column definitions (name + value template). Available row fields: name, uri, provider, branch, workdir, and status (populated only with --check-status). Custom columns also feed dynamic tab completion for --columns.

Native CI Lifecycle​

With ci.enabled: true, Git composes with the native CI cache and Atmos Auth into a complete job lifecycle:

  1. Job starts. CI cache auto-restore brings back the Atmos XDG cache root — toolchain binaries and managed-repo Git workdirs from the previous run.
  2. atmos git clone (no-arg) puts the current repository in the workspace, replacing actions/checkout. Fetch-depth guidance applies when atmos describe affected will run against this clone.
  3. atmos git clone --all reconciles the restored XDG workdirs: a cache hit fetches the delta instead of performing a full clone; a cache miss performs a fresh clone.
  4. First Git subprocess triggers the ambient broker — GitHub STS materializes GIT_CONFIG_* credentials for Git subprocesses.
  5. Operations run: render, publish, commit, push (with retry).
  6. Command end: the CI cache pending-save archives the XDG root for the next run.

A minimal GitHub Actions job replacing actions/checkout + actions/cache glue:

.github/workflows/publish.yaml
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: cloudposse/github-action-setup-atmos@v2
- run: atmos git clone # checkout replacement (workspace)
- run: atmos git clone --all # reconcile all deployment repos (XDG, cache-warm)
- run: atmos kubernetes deploy argocd -s prod # render + publish via hooks

Cache restore/save and STS credential minting happen inside the Atmos commands — no actions/checkout, actions/cache, or token-wiring steps.

Cache churn

Git workdirs change on every publish, so they churn cache archives. This is safe — workdirs are regenerable and always reconciled. To keep archives lean, scope ci.cache.paths to exclude git/repositories/, trading cache hits for full clones. atmos git list --check-status shows what is currently materialized under the cache root.