Skip to content

Configuration and provenance

Before interpreting settings, a tool has to decide which configuration sources it trusts and reads. girt keeps three operations separate: parse supplied bytes, resolve explicit sources and includes, and edit one direct file while holding its lock. An effective snapshot can contain many files; it cannot safely be written back as though it were one source document.

Select sources and publish a local override

Section titled “Select sources and publish a local override”

In a Cargo binary project, run cargo add girt tempfile and put this program in src/main.rs. It supplies a disposable global source, opens a repository with it, writes a local override, and explains the winning source:

use girt::config::{ConfigFile, ConfigInputs, ConfigScope};
use girt::{InitKind, ObjectFormat, Repository};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let directory = tempfile::tempdir()?;
let repo = Repository::init(
ObjectFormat::Sha1,
directory.path().join("project.git"),
InitKind::Bare,
)?;
let global = directory.path().join("global");
std::fs::write(&global, b"[core]\neditor = user-editor\n")?;
let inputs =
ConfigInputs::default().with_files(vec![ConfigFile::new(global, ConfigScope::Global)]);
let opened = Repository::open_with_config(repo.git_dir(), &inputs)?;
assert_eq!(
opened.config().string("core", None, "editor"),
Some(&b"user-editor"[..])
);
let mut edit = opened.edit_config(4096)?;
edit.document_mut()
.append("core", None, "editor", b"project-editor")?;
edit.commit()?;
// Publication does not mutate a previously opened configuration snapshot.
assert_eq!(
opened.config().string("core", None, "editor"),
Some(&b"user-editor"[..])
);
let refreshed = Repository::open_with_config(repo.git_dir(), &inputs)?;
assert_eq!(
refreshed.config().string("core", None, "editor"),
Some(&b"project-editor"[..])
);
let winning = refreshed
.config()
.entries()
.iter()
.rev()
.find(|entry| {
entry.section.eq_ignore_ascii_case(b"core")
&& entry.subsection.is_none()
&& entry.name.eq_ignore_ascii_case(b"editor")
})
.and_then(|entry| entry.origin.as_ref())
.ok_or("origin missing")?;
assert_eq!(winning.scope, ConfigScope::Local);
assert_eq!(
winning.location.path.as_deref(),
Some(refreshed.common_dir().join("config").as_path())
);
Ok(())
}

Run it with cargo run. Everything is removed with the temporary directory. The example treats an editor setting as data; it does not execute it. An application that launches configured commands must decide for itself which values to trust and how to run them.

Appending a local occurrence makes it the scalar winner while preserving earlier occurrences. Use Document::set_value to change a particular existing occurrence identified in the current draft.

File scopes run from system and global through local and enabled worktree configuration, followed by supplied environment and command overrides. Within a scope, caller order remains significant; select XDG configuration before the home file. Ordinary includes expand in place. Repository format bootstrap stays separate: an override cannot reinterpret SHA-1 storage as SHA-256.

Use entries() and each entry’s origin to explain where occurrences came from. Scalar lookup takes the last value, but each consumer of a repeated variable has its own rules for combining occurrences. Let the URL and refspec APIs interpret those values instead of flattening them yourself. Scope describes precedence, not permission to execute or trust a value.

An absent setting, an implicit assignment, and an explicit empty value are distinct. Boolean lookup interprets implicit as true and empty as false; string lookup treats implicit as absent. A successful parse does not establish that a value is a valid boolean, integer, URL, or safe command. Propagate a typed lookup error instead of silently substituting a default.

Supply environment and include context explicitly

Section titled “Supply environment and include context explicitly”

To see the configuration git would see, pass an environment snapshot such as workflow::Environment::from_process() to ConfigInputs::git_default, then open with Repository::discover_with_config or Repository::open_with_config. It selects the system file, the XDG and home global files, the GIT_CONFIG_* file selectors and the runtime pairs, in Git’s order. Use ConfigInputs::from_environment instead to choose the system file and pair budget yourself. Use Config::from_environment only to decode GIT_CONFIG_COUNT and numbered GIT_CONFIG_KEY_n / GIT_CONFIG_VALUE_n pairs, where n starts at zero. Neither reads or mutates process-global state itself.

Conditional includes can depend on branch names, logical/canonical Git-directory aliases, or remote URLs. Expanding home and prefix paths needs that context too. Supply the facts the application knows, then resolve again when those facts change. Default resolution matches ordinary include placement. An adapter that requires each top-level source’s URL conditions to ignore other top-level sources can select IncludeConditionVisibility::RootSnapshot; that mode can include different files than ordinary resolution. See the configuration module for both modes and their limits.

Edit the intended source and recover deliberately

Section titled “Edit the intended source and recover deliberately”

The repository edit entry point selects its common local config. To edit an approved global or other direct file, use ConfigEdit::open on that path. Includes are not followed for editing. Document retains comments and unrelated syntax; its occurrence indices and section ordinals refer to the current draft. Reacquire them after edits. Use require_source when publication should depend on the exact bytes or absence observed earlier.

A lock conflict does not prove the lock is stale. A changed-source error means the file changed since you read it: read it again and decide whether the edit still makes sense. Fix syntax errors in the source or the draft. If the source or the edited output exceeds the byte budget, decide whether you trust it enough to raise the budget. If cleanup fails, inspect the lock file your edit left behind. Call abort to see cleanup failures; dropping the edit only cleans up on a best-effort basis. Publication does not promise power-loss durability or an atomic transaction across several files.

After a successful commit, reopen or re-resolve before constructing dependent values. Existing repository, configuration, and remote values remain snapshots. The ConfigEdit contract describes the filesystem assumptions and error evidence that a retry must preserve.