git-ents.gitmain
⌘K
foforge
commit 7ec1da7
setup: default commit/tag/push signing on globally, not just this repo

git ents setup already resolves and records a signing key for one repository; it left every push and commit elsewhere on the machine unsigned unless the operator remembered -S/--signed, and required --signed=if-asked specifically to fail soft against a remote (GitHub among them) that doesn’t support push certificates. git ents setup now also sets commit.gpgsign, tag.gpgsign, and push.gpgsign=if-asked at --global scope, once, so every later commit and push signs itself by default everywhere. Deliberately wired into the CLI dispatch layer rather than commands::setup::run itself, so this crate’s own integration tests (which call run() directly to configure a scratch repository) never touch the real machine’s ~/.gitconfig as a side effect.

Joseph D. Carpinelli · 29 days ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/cli/git-ents/src/exe.rs @@ -41,7 +41,12 @@ } => { let root = LocalRoot::discover(".")?; let key_path = commands::setup::run(&root, key)?; + commands::setup::configure_global_signing_defaults()?; let _ = writeln!(out, "signing key: {}", key_path.display()); + let _ = writeln!( + out, + "global git config: commit.gpgsign=true, tag.gpgsign=true, push.gpgsign=if-asked" + ); Ok(()) } Top::Bootstrap {
crates/cli/git-ents/src/commands/setup.rs @@ -1,6 +1,8 @@ //! `git ents setup`: resolve or generate a signing key, record it as this -//! repository's `user.signingkey` with `gpg.format=ssh`, and set -//! `receive.denyCurrentBranch=updateInstead` (`roots.worktree-update`). +//! repository's `user.signingkey` with `gpg.format=ssh`, set +//! `receive.denyCurrentBranch=updateInstead` (`roots.worktree-update`), +//! and ([`configure_global_signing_defaults`]) default every commit, tag, +//! and (when asked) push, in any repository, to sign itself. //! //! `receive.denyCurrentBranch=updateInstead` is the integration-test //! harness edge case `roots.worktree-update` names: it lets an external @@ -203,6 +205,59 @@ Ok(()) } +/// Sign every commit, tag, and (when the remote asks) push by default — +/// written to the operator's *global* (`~/.gitconfig`) config, not any +/// one repository's, so `git ents setup` needs running only once per +/// machine for every later `git commit`/`git push`, anywhere, to sign +/// itself without `-S`/`--signed`. `push.gpgsign=if-asked` in particular +/// is what makes a plain `git push` safe against both a hosted root +/// (which now advertises `push-cert`, so this signs) and a remote that +/// does not (GitHub among them, which never has — this silently pushes +/// unsigned there instead of failing outright). +/// +/// Deliberately not called from [`run`] itself: `run`'s callers configure +/// one *repository* (this crate's own integration tests among them), and +/// must never mutate the real machine's global git config as a side +/// effect of that; only the actual `git ents setup` CLI invocation calls +/// this. +/// +/// # Errors +/// +/// Propagates a `git config --global` failure. +pub fn configure_global_signing_defaults() -> Result<()> { + for (key, value) in [ + ("commit.gpgsign", "true"), + ("tag.gpgsign", "true"), + ("push.gpgsign", "if-asked"), + ] { + set_global_config(key, value)?; + } + Ok(()) +} + +/// Set `key` to `value` in the operator's global (`~/.gitconfig`) config +/// via `git config --global` — unlike [`set_local_config`], not scoped to +/// any one repository. +fn set_global_config(key: &str, value: &str) -> Result<()> { + let output = Command::new("git") + .args(["config", "--global", key, value]) + .output() + .map_err(|source| Error::Io { + path: PathBuf::from("~/.gitconfig"), + source, + })?; + if !output.status.success() { + return Err(Error::Io { + path: PathBuf::from("~/.gitconfig"), + source: std::io::Error::other(format!( + "git config --global {key} {value} failed: {}", + String::from_utf8_lossy(&output.stderr) + )), + }); + } + Ok(()) +} + /// Set `key` to `value` in `repo_path`'s own local config via `git config`. fn set_local_config(repo_path: &Path, key: &str, value: &str) -> Result<()> { let output = Command::new("git")