Merge feature/gitconfig-autopush into develop
This commit is contained in:
@@ -120,3 +120,6 @@ Done: `bin/repo-sync` (gitlab/github/gitea/bitbucket cloud via curl+jq, daily ca
|
||||
|
||||
## 2026-10-07 (2) — macOS zsh default, remote-install prompts
|
||||
Done: choose_macos_shell default bash → zsh (Enter, no tty, unknown value). Found: `curl | bash` path never asked USER/EMAIL (stdin = pipe → `[ -t 0 ]` false → login name + empty email, bash, no offers); remote-install.sh now runs install.sh `</dev/tty` when openable. Found: BRANCH default `master`, origin has only `main` (raw URL 404) → `main`. Lint OK, chooser tested 4 values. Branch feature/macos-zsh-default, not merged. Other session: feature/repo-sync open (CLAUDE.md layout already lists repo-sync).
|
||||
|
||||
## 2026-10-07 (3) — gitflow.autopush asked at install, hooksPath fixed, identity bug
|
||||
Done: `gitconfig` template `[gitflow] autopush = @AUTOPUSH@` + fixed `core.hooksPath = ~/.claude/githooks`; install.sh `resolve_autopush` (existing ~/.gitconfig value via sed → `DOTFILES_GITFLOW_AUTOPUSH` strict, bad value aborts → prompt re-asks exact true/false, Enter = true → true), `render_gitconfig` fail-closed (non-boolean / leaked placeholder = nothing written). Found by challenge: install.sh:688 passed the repo bashrc TEMPLATE to deploy_gitconfig → every install wrote `name = @USER@`; fixed, identity passed directly. `shopt -u patsub_replacement` (bash ≥ 5.2 `&` in names). Oracle harness `.claude/tasks/contracts/check-autopush-render.sh` (12 cases, scratch HOMEs, zero `git config`: that command family is denied to the session). 3 challengers + 1 confirm, verifier ECARTS(1) → CONFORME, security PASS (MEDIUM noted: newline in IDENTITY_* env injects gitconfig lines, pre-existing for rc too). Code commit 9901ec5 on feature/gitconfig-autopush; merge on user signal.
|
||||
|
||||
@@ -135,3 +135,7 @@ Port of the Alphalink dotfiles `repo` / `repo-reset` zsh functions, bash + zsh,
|
||||
- [x] README.md (table + CLI section) + CLAUDE.md layout
|
||||
- [x] shellcheck + bash -n; stub-curl harness per forge (fixtures), live GitLab run
|
||||
- [ ] commit on feature branch; registries. No finish without explicit signal.
|
||||
|
||||
## Feature — gitflow.autopush asked at install, written to ~/.gitconfig (2026-10-07)
|
||||
Branch: feature/gitconfig-autopush (off develop). Plan: .claude/tasks/plans/2026-10-07-gitconfig-autopush-1734.md
|
||||
- [ ] /feat run: gitconfig `[gitflow] autopush = @AUTOPUSH@` + fixed `core.hooksPath`; install.sh `resolve_autopush` (exact true/false: existing ~/.gitconfig value via sed → DOTFILES_GITFLOW_AUTOPUSH (bad value aborts) → prompt re-asks → true), deploy_gitconfig takes name/email/autopush (fixes `name = @USER@` deployed from the template path) + fail-closed leak check; README/CLAUDE.md; oracle harness .claude/tasks/contracts/check-autopush-render.sh
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
# CONTRACT — gitconfig-autopush
|
||||
- date: 2026-10-07 | flow: feat | branch: develop → feature/gitconfig-autopush
|
||||
- status: active
|
||||
|
||||
## REQUEST (verbatim — IMMUTABLE)
|
||||
est-ce qu'on deploi un gitconfig ? Oui peut-on faire en sorte de demander a l'installation si on ajoute git config --global gitflow.autopush false ou git config --global gitflow.autopush true . Au choix a l'installation et mis dnas le gitconfig. d'ailleurs on a bien le user et le mail dnas le gitconfig aui sont bien ceux qu'on configure a l'installation ?
|
||||
|
||||
## CLARIFICATIONS
|
||||
Q: Default when Enter is pressed or no terminal is attached? / A: `true` [gated 2026-10-07] — confirmed by the user's correction after the second message ("non pardon je voulais dire defaut true").
|
||||
Q: Env preset name? / A: `GITFLOW_AUTOPUSH` [gated 2026-10-07] — SUPERSEDED by the user spec: `DOTFILES_GITFLOW_AUTOPUSH`.
|
||||
Q: install.sh:688 passes the repo template to deploy_gitconfig, so ~/.gitconfig gets `name = @USER@`; fix in this run? / A: yes, deploy_gitconfig takes the resolved name/email/autopush [gated 2026-10-07]
|
||||
Q: redeploy drops `core.hooksPath` (set by `make link`)? / A: warn + note [gated 2026-10-07] — SUPERSEDED by the user spec: fixed template line `hooksPath = ~/.claude/githooks` in `[core]`, git expands `~`, the installer neither creates nor checks the directory.
|
||||
Q: how is an existing value read, given `git config … gitflow.*` is denied to this session? / A: sed over ~/.gitconfig, exact true/false reused silently; no git config access in installer or oracle [gated 2026-10-07]
|
||||
USER SPEC (second message, 2026-10-07, verbatim constraints) [gated 2026-10-07]:
|
||||
- Question at install: « Push automatique des commits par les hooks gitflow sur cette machine ? [true/false] » default true (corrected from the spec's "défaut : false" by the user's next message). Rendered in English like the other install prompts; wording open to change at the diff review.
|
||||
- Only the exact values `true` and `false` are accepted; anything else re-asks; empty → `true`.
|
||||
- The render fails explicitly if `@AUTOPUSH@` remains in the final content (grep after render); nothing written.
|
||||
- Non-interactive: `DOTFILES_GITFLOW_AUTOPUSH=true|false`, default true. (Orchestrator choice, flagged for the diff review: any other preset value aborts the install at the identity step, before any file is touched.)
|
||||
- Keep the existing backup `~/.gitconfig.backup-<timestamp>`; no other template section touched.
|
||||
- No test suite / no --dry-run exists: the oracle harness carries the two requested cases (rendered @AUTOPUSH@, leaked placeholder = failure).
|
||||
- The user sees the full diff before anything is committed.
|
||||
|
||||
## ACCEPTANCE CRITERIA
|
||||
1. `gitconfig` template: `[gitflow]` section with `autopush = @AUTOPUSH@`, and `hooksPath = ~/.claude/githooks` inside `[core]`; other sections unchanged except the header comment.
|
||||
CHECK: grep -q '^\[gitflow\]' gitconfig && grep -q '^ autopush = @AUTOPUSH@' gitconfig && grep -q '^ hooksPath = ~/.claude/githooks' gitconfig && echo GITCONFIG_TEMPLATE_OK
|
||||
EXPECT: GITCONFIG_TEMPLATE_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: GITCONFIG_TEMPLATE_OK
|
||||
2. `install.sh` resolves the push mode: an exact `true`/`false` already in ~/.gitconfig wins silently; else `DOTFILES_GITFLOW_AUTOPUSH` (exact true/false; any other value aborts with a message); else a tty prompt accepting only `true`/`false`, re-asking otherwise, Enter = `true`; else `true`.
|
||||
3. Oracle harness: rendered file carries the resolved autopush, name, email, hooksPath and no placeholder; a leaked `@AUTOPUSH@` makes deploy_gitconfig fail without writing; existing value beats the preset; strict preset; prompt loop; empty email skip; idempotent re-run.
|
||||
CHECK: bash .claude/tasks/contracts/check-autopush-render.sh
|
||||
EXPECT: AUTOPUSH_RENDER_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: AUTOPUSH_RENDER_OK
|
||||
4. install.sh stays syntactically valid and shellcheck clean.
|
||||
CHECK: bash -n install.sh && shellcheck install.sh && echo LINT_OK
|
||||
EXPECT: LINT_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: LINT_OK
|
||||
5. README.md (gitconfig row, remote-install question list, install step 6) and CLAUDE.md layout line document the question, the strict values, the env preset, the reuse of an existing value and the fixed hooksPath.
|
||||
6. Call site passes the resolved identity, never the repo template path.
|
||||
CHECK: grep -q 'deploy_gitconfig "\$identity_name" "\$identity_email" "\$autopush"' install.sh && ! grep -q 'deploy_gitconfig "\$SCRIPT_DIR' install.sh && echo CALLSITE_OK
|
||||
EXPECT: CALLSITE_OK
|
||||
EVIDENCE: MET exit=0 marker-found :: CALLSITE_OK
|
||||
7. `.githooks/post-commit` and `.githooks/post-merge` are not part of the change (pre-existing session-hook refresh).
|
||||
CHECK: git diff --cached --name-only | grep -q '^\.githooks/' && exit 1; echo HOOKS_UNTOUCHED
|
||||
EXPECT: HOOKS_UNTOUCHED
|
||||
EVIDENCE: MET exit=0 marker-found :: HOOKS_UNTOUCHED
|
||||
|
||||
## FILE SCOPE
|
||||
gitconfig, install.sh, README.md, CLAUDE.md, .claude/tasks/contracts/check-autopush-render.sh (oracle harness, LRN-011 pattern)
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# Oracle for the contract: exercises install.sh functions on scratch HOMEs under
|
||||
# mktemp; never reads or writes the real ~/.gitconfig and never calls git config.
|
||||
set -euo pipefail
|
||||
cd "$(git rev-parse --show-toplevel)"
|
||||
SCRIPT_DIR="$PWD"
|
||||
|
||||
fail() { echo "FAIL: $*" >&2; exit 1; }
|
||||
fresh_home() { HOME="$(mktemp -d)"; export HOME; }
|
||||
guard_home() {
|
||||
case "$HOME" in /tmp/*|/private/*|/var/*) ;; *) fail "refusing HOME=$HOME" ;; esac
|
||||
}
|
||||
has() { grep -qF -- "$2" "$1" || fail "$1 lacks: $2"; }
|
||||
|
||||
funcs="$(mktemp)"
|
||||
for fn in ask_autopush resolve_autopush render_identity_template \
|
||||
render_gitconfig deploy_gitconfig; do
|
||||
sed -n "/^$fn() {/,/^}/p" install.sh >> "$funcs"
|
||||
done
|
||||
source "$funcs"
|
||||
shopt -u patsub_replacement 2>/dev/null || true
|
||||
unset DOTFILES_GITFLOW_AUTOPUSH
|
||||
|
||||
# Case A: render
|
||||
fresh_home; guard_home
|
||||
[ "$(DOTFILES_GITFLOW_AUTOPUSH=false resolve_autopush </dev/null)" = false ] \
|
||||
|| fail "A: preset false"
|
||||
deploy_gitconfig x x@y false >/dev/null
|
||||
for l in 'autopush = false' 'name = x' 'email = x@y' 'hooksPath = ~/.claude/githooks'; do
|
||||
has "$HOME/.gitconfig" "$l"
|
||||
done
|
||||
! grep -qE '@(USER|EMAIL|AUTOPUSH)@' "$HOME/.gitconfig" || fail "A: placeholder left"
|
||||
|
||||
# Case I: idempotent re-run in A's HOME
|
||||
out="$(deploy_gitconfig x x@y false)"
|
||||
case "$out" in *"already up to date"*) ;; *) fail "I: not skipped: $out" ;; esac
|
||||
ls "$HOME"/.gitconfig.backup-* >/dev/null 2>&1 && fail "I: backup created"
|
||||
|
||||
# Case A2: ampersand
|
||||
fresh_home; guard_home
|
||||
deploy_gitconfig 'a & b' x@y true >/dev/null
|
||||
has "$HOME/.gitconfig" 'name = a & b'
|
||||
|
||||
# Case B: leaked placeholder
|
||||
fresh_home; guard_home
|
||||
set +e; deploy_gitconfig x x@y '@AUTOPUSH@' 2>"$HOME/err"; rc=$?; set -e
|
||||
[ "$rc" -ne 0 ] || fail "B: rc 0"
|
||||
has "$HOME/err" '@AUTOPUSH@'
|
||||
[ ! -e "$HOME/.gitconfig" ] || fail "B: file written"
|
||||
|
||||
# Case C: existing wins over preset
|
||||
fresh_home; guard_home
|
||||
printf '[gitflow]\n autopush = true\n' > "$HOME/.gitconfig"
|
||||
[ "$(DOTFILES_GITFLOW_AUTOPUSH=false resolve_autopush </dev/null)" = true ] || fail "C"
|
||||
|
||||
# Case D: defaults
|
||||
fresh_home; guard_home
|
||||
[ "$(resolve_autopush </dev/null)" = true ] || fail "D"
|
||||
|
||||
# Case E: strict preset
|
||||
set +e; DOTFILES_GITFLOW_AUTOPUSH=yes resolve_autopush 2>"$HOME/err" </dev/null; rc=$?; set -e
|
||||
[ "$rc" -ne 0 ] || fail "E: rc 0"
|
||||
has "$HOME/err" exactly
|
||||
|
||||
# Case F: prompt loop
|
||||
[ "$(printf 'yes\ntrue\n' | ask_autopush 2>"$HOME/err")" = true ] || fail "F1"
|
||||
has "$HOME/err" exactly
|
||||
[ "$(printf '\n' | ask_autopush)" = true ] || fail "F2"
|
||||
|
||||
# Case G: empty email
|
||||
fresh_home; guard_home
|
||||
deploy_gitconfig x "" true 2>"$HOME/err"
|
||||
[ ! -e "$HOME/.gitconfig" ] || fail "G: file written"
|
||||
has "$HOME/err" "not written"
|
||||
|
||||
# Case H: call site
|
||||
grep -q 'deploy_gitconfig "\$identity_name" "\$identity_email" "\$autopush"' install.sh \
|
||||
|| fail "H: call site"
|
||||
! grep -q 'deploy_gitconfig "\$SCRIPT_DIR' install.sh || fail "H: template path"
|
||||
|
||||
# Case J: non-exact existing value
|
||||
fresh_home; guard_home
|
||||
printf '[gitflow]\n autopush = off\n' > "$HOME/.gitconfig"
|
||||
[ "$(resolve_autopush 2>"$HOME/err" </dev/null)" = true ] || fail "J: value"
|
||||
has "$HOME/err" off
|
||||
|
||||
# Case K: refused value
|
||||
fresh_home; guard_home
|
||||
set +e; deploy_gitconfig x x@y yes 2>"$HOME/err"; rc=$?; set -e
|
||||
[ "$rc" -ne 0 ] || fail "K: rc 0"
|
||||
has "$HOME/err" "not exactly"
|
||||
[ ! -e "$HOME/.gitconfig" ] || fail "K: file written"
|
||||
|
||||
echo AUTOPUSH_RENDER_OK
|
||||
@@ -0,0 +1,247 @@
|
||||
# PLAN — gitconfig-autopush (feat) — REVISED v4 (round 1 + user spec + confirmation pass, default true)
|
||||
Contract: .claude/tasks/contracts/2026-10-07-gitconfig-autopush-1734.md
|
||||
Branch: feature/gitconfig-autopush (off develop)
|
||||
|
||||
## Goal (user spec, verbatim constraints in the contract)
|
||||
1. `gitconfig` template, section `[core]`: fixed line `hooksPath = ~/.claude/githooks`
|
||||
(git expands `~` itself for core.hooksPath; no absolute path, no expansion by the
|
||||
script; the installer neither creates nor checks that directory).
|
||||
2. install.sh asks at install: automatic push by the gitflow hooks on this machine?
|
||||
`[true/false]` (default: true — user correction). Answer rendered into a new section
|
||||
`[gitflow]` / `autopush = @AUTOPUSH@`.
|
||||
- Only the exact strings `true` and `false` are accepted. Anything else re-asks;
|
||||
an empty answer gives `true`.
|
||||
- The render FAILS explicitly (grep after render) if `@AUTOPUSH@` is still in the
|
||||
final content. A non-boolean value blocks every push on the machine (fail-closed),
|
||||
so a leaked placeholder would be a silent outage.
|
||||
- Non-interactive install: env `DOTFILES_GITFLOW_AUTOPUSH=true|false`, default true.
|
||||
3. Keep the existing backup (`~/.gitconfig.backup-<timestamp>`). Do not touch the other
|
||||
template sections.
|
||||
4. Human-gated 2026-10-07 (kept from round 1): fix the identity call site so the deployed
|
||||
file carries the resolved name/email, not the literal `@USER@`/`@EMAIL@`.
|
||||
5. The user sees the full diff BEFORE anything is committed (working tree only until then).
|
||||
|
||||
## Context (verified)
|
||||
- `gitconfig` is a template; `render_identity_template` (install.sh:497) replaces
|
||||
@USER@/@EMAIL@ by bash substitution over every line (comments included).
|
||||
- `deploy_gitconfig` (install.sh:532) takes an rc PATH and reads USER/EMAIL from it.
|
||||
BUG: the call site install.sh:688 passes `"$SCRIPT_DIR/$bashrc"` = the repo TEMPLATE
|
||||
whose exports are `export USER="@USER@"` (bash/bashrc-linux:33-34). The placeholders
|
||||
are non-empty, the guard passes, the deployed file reads `name = @USER@`.
|
||||
- Identity resolved at install.sh:584-585 into `identity_name` / `identity_email`, before
|
||||
package installs (prompts first). EMAIL default "" when no tty and no env.
|
||||
- Readers (~/.claude/lib/gitflow.sh gitflow_push_mode, post-commit/post-merge hooks):
|
||||
`git config --bool gitflow.autopush` → false = manual, true/unset = auto, other =
|
||||
invalid → nothing pushed (fail closed). Only exact `true`/`false` are ever written.
|
||||
- `core.hooksPath` is a pathname-typed key: git tilde-expands `~/.claude/githooks`.
|
||||
Today `make link` writes it with `git config --global`; the redeploy dropped it. With
|
||||
the fixed template line the redeploy keeps it and the file matches the render again.
|
||||
- This session's permission layer denies every `git config … gitflow.*` command, so the
|
||||
installer and the oracle must NOT read or write the key through git: an existing value
|
||||
is read from ~/.gitconfig with sed (same shape as rc_export_value). Trade-off: a value
|
||||
set in XDG/system/included config is not seen; ~/.gitconfig is the only file written.
|
||||
- install.sh runs under macOS /bin/bash 3.2 before brew bash exists (LRN-014): plain
|
||||
string compares only, no `${var,,}`.
|
||||
- The repo has no test suite and install.sh has no --dry-run: the oracle harness below is
|
||||
the test, run by the contract gate.
|
||||
|
||||
## Checklist
|
||||
[ ] gitconfig — header comment (lines 1-4) reworded WITHOUT literal placeholder tokens
|
||||
(today it renders as "bchanot and x@y are replaced at install time"): "Template for
|
||||
the user-scope ~/.gitconfig, rendered by install.sh. Git never expands $VARS, so the
|
||||
identity and the gitflow push mode are placeholders filled at install time with the
|
||||
installer's answers. A repo .git/config still overrides these values for that repo."
|
||||
In `[core]`, after `excludesfile`, add the fixed line:
|
||||
hooksPath = ~/.claude/githooks
|
||||
with a one-line comment above it: "# gitflow hooks (created by `make link` in
|
||||
claude-config); git expands ~ itself." After `[user]`, add:
|
||||
[gitflow]
|
||||
# Push mode of the gitflow hooks: false = manual, you run `git push`;
|
||||
# true = every commit and merge is pushed. Exact true/false only (fail-closed).
|
||||
autopush = @AUTOPUSH@
|
||||
4-space indent like the other keys. No other section touched.
|
||||
[ ] install.sh — two new functions right after `resolve_identity` (~line 525), tabs:
|
||||
# Ask the push question on the terminal until the answer is exactly true or false;
|
||||
# Enter (or EOF) = true. Prints the value.
|
||||
ask_autopush() {
|
||||
local answer=""
|
||||
while :; do
|
||||
read -rp "Automatic push of commits by the gitflow hooks on this machine? [true/false] (default: true) " answer || true
|
||||
case "${answer:-true}" in
|
||||
true|false) printf '%s\n' "${answer:-true}"; return 0 ;;
|
||||
*) echo "Answer exactly true or false." >&2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Push mode of the gitflow hooks (gitflow.autopush), exact true/false only: the
|
||||
# readers fail closed on anything else. Never asked twice: a true/false already in
|
||||
# ~/.gitconfig wins silently (read with sed, like rc_export_value, so a hand-set
|
||||
# value survives the redeploy; a non-exact spelling is named and re-asked), else
|
||||
# DOTFILES_GITFLOW_AUTOPUSH (anything but true/false aborts the install here,
|
||||
# before any file is touched, whatever ~/.gitconfig holds), else the prompt
|
||||
# on a terminal, else true (today's unset = auto). Prints the value.
|
||||
resolve_autopush() {
|
||||
local value="" preset="${DOTFILES_GITFLOW_AUTOPUSH:-}"
|
||||
case "$preset" in
|
||||
true|false|"") ;;
|
||||
*) echo "DOTFILES_GITFLOW_AUTOPUSH='$preset' — must be exactly true or false" >&2; return 1 ;;
|
||||
esac
|
||||
if [ -f "$HOME/.gitconfig" ]; then
|
||||
value="$(sed -n 's/^[[:space:]]*autopush[[:space:]]*=[[:space:]]*//p' "$HOME/.gitconfig" | tail -n 1)"
|
||||
case "$value" in
|
||||
true|false) printf '%s\n' "$value"; return 0 ;;
|
||||
"") ;;
|
||||
*) echo "gitflow.autopush='$value' in ~/.gitconfig is not exactly true/false — asking again" >&2 ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$preset" ]; then printf '%s\n' "$preset"; return 0; fi
|
||||
if [ -t 0 ]; then ask_autopush; else echo true; fi
|
||||
}
|
||||
Order: preset syntax is validated FIRST (a bad preset always aborts, even on a
|
||||
re-run), then an existing exact value wins, then a valid preset, prompt, default.
|
||||
Each ≤ 25 logic lines. `read … || true` on EOF → empty → true → loop ends.
|
||||
[ ] install.sh — after `set -euo pipefail` (line 4) add, with a one-line comment
|
||||
("bash ≥ 5.2 expands `&` in ${var//pat/rep} replacements: an `&` in a name would
|
||||
corrupt the rendered identity; no-op on bash 3.2"):
|
||||
shopt -u patsub_replacement 2>/dev/null || true
|
||||
[ ] install.sh — main sequence, right after identity_email (line 585):
|
||||
autopush="$(resolve_autopush)"
|
||||
(set -e: a `return 1` from an invalid preset aborts the install right here.)
|
||||
[ ] install.sh — `deploy_gitconfig` becomes `deploy_gitconfig <name> <email> <autopush>`
|
||||
(no rc path, no rc_export_value call):
|
||||
local name="$1" email="$2" autopush="$3" rendered backup
|
||||
if [ -z "$name" ] || [ -z "$email" ]; then
|
||||
echo "Name or email empty — skipping ~/.gitconfig (push mode $autopush not written)" >&2
|
||||
return 0
|
||||
fi
|
||||
rendered="$(render_identity_template "$SCRIPT_DIR/gitconfig" "$name" "$email")"
|
||||
case "$autopush" in true|false) ;; *)
|
||||
echo "gitconfig render refused: push mode '$autopush' is not exactly true/false — nothing written" >&2
|
||||
return 1 ;;
|
||||
esac
|
||||
rendered="${rendered//@AUTOPUSH@/$autopush}"
|
||||
case "$rendered" in *@AUTOPUSH@*)
|
||||
echo "gitconfig render failed: @AUTOPUSH@ left in the output — nothing written" >&2
|
||||
return 1 ;;
|
||||
esac
|
||||
(no pipe: `printf | grep -q` under pipefail can read SIGPIPE as "no leak")
|
||||
… cmp/skip, backup, write unchanged …
|
||||
echo "Deploying gitconfig to ~/.gitconfig ($name <$email>, autopush=$autopush)"
|
||||
Header comment rewritten: $1 $2 = the identity rendered into the rc, $3 = push mode;
|
||||
one line on the two fail-closed checks. If > 25 logic lines, extract
|
||||
`render_gitconfig <name> <email> <autopush>` (render + substitution + grep).
|
||||
[ ] install.sh:688 call site → `deploy_gitconfig "$identity_name" "$identity_email" "$autopush"`
|
||||
comment: "User-scope git config, same identity as the rc just rendered."
|
||||
[ ] README.md:31 gitconfig row — "`@USER@`, `@EMAIL@` and `@AUTOPUSH@` (gitflow push
|
||||
mode) are filled at install with the installer's answers; `core.hooksPath` points at
|
||||
the gitflow hooks `make link` creates."
|
||||
[ ] README.md:50 — "(identity, push mode, macOS shell, offers)".
|
||||
[ ] README.md:70 step 6 — replace "with the same `USER` / `EMAIL`" by "with the same name
|
||||
and email", then add: the installer asks once whether the gitflow hooks push every
|
||||
commit and merge (`true`/`false` exactly, Enter = `true`); a `true`/`false` already
|
||||
in `~/.gitconfig` is reused without asking and survives the redeploy;
|
||||
`DOTFILES_GITFLOW_AUTOPUSH=true|false` presets it for a non-interactive install (no
|
||||
terminal and no preset → `true`; any other preset aborts the install); the render
|
||||
refuses to write a file where `@AUTOPUSH@` leaked, since a non-boolean value blocks
|
||||
every push. To switch later: `git config --global gitflow.autopush true|false`.
|
||||
[ ] CLAUDE.md:25 — `gitconfig user-scope ~/.gitconfig template, @USER@/@EMAIL@/@AUTOPUSH@
|
||||
(gitflow push mode, exact true/false) filled at install; core.hooksPath fixed`
|
||||
[ ] .claude/tasks/contracts/check-autopush-render.sh — oracle harness (LRN-011 pattern):
|
||||
`set -euo pipefail`; `cd "$(git rev-parse --show-toplevel)"` (the only git call, no
|
||||
config access); `SCRIPT_DIR="$PWD"`. Extract ask_autopush, resolve_autopush,
|
||||
render_identity_template, deploy_gitconfig (and render_gitconfig if extracted) from
|
||||
install.sh via `sed -n '/^fn() {/,/^}/p'` into `$(mktemp)` and source it.
|
||||
`fresh_home() { HOME="$(mktemp -d)"; export HOME; }` before every case; guard
|
||||
`case "$HOME" in /tmp/*|/private/*|/var/*) ;; *) fail "refusing HOME=$HOME";; esac`
|
||||
before any deploy call. No rm of temp dirs (never rm -rf through a variable).
|
||||
`fail() { echo "FAIL: $*" >&2; exit 1; }`; every assertion goes through it.
|
||||
stderr captures go to `"$HOME/err"` (the scratch HOME), never a relative path in
|
||||
the repo. Header comment: "Oracle for the contract: exercises install.sh functions
|
||||
on scratch HOMEs under mktemp; never reads or writes the real ~/.gitconfig and never
|
||||
calls git config." If the permission layer refuses a step of this harness, the
|
||||
refusal is reported with its rule and the harness is handed to the user to run; it
|
||||
is never reworked to get around the rule.
|
||||
Case A (render): fresh_home; DOTFILES_GITFLOW_AUTOPUSH=false </dev/null →
|
||||
resolve_autopush = false; deploy_gitconfig x x@y false → file has
|
||||
`autopush = false`, `name = x`, `email = x@y`, `hooksPath = ~/.claude/githooks`,
|
||||
and `! grep -qE '@(USER|EMAIL|AUTOPUSH)@'`.
|
||||
Case A2 (ampersand): fresh_home; deploy_gitconfig 'a & b' x@y true → file has
|
||||
`name = a & b` (patsub_replacement off; bash 3.2 passes trivially).
|
||||
Case B (leaked placeholder = failure): fresh_home; `set +e; deploy_gitconfig x x@y
|
||||
'@AUTOPUSH@' 2>err; rc=$?; set -e` → rc != 0, err contains "@AUTOPUSH@", no
|
||||
~/.gitconfig written. (Passing the placeholder as the value is the only way to
|
||||
make the substitution a no-op; it stands in for a broken template.)
|
||||
Case C (existing wins): fresh_home; printf '[gitflow]\n autopush = true\n' >
|
||||
"$HOME/.gitconfig"; DOTFILES_GITFLOW_AUTOPUSH=false </dev/null → true.
|
||||
Case D (defaults): fresh_home; no file, env unset, </dev/null → true.
|
||||
Case E (strict preset): DOTFILES_GITFLOW_AUTOPUSH=yes </dev/null → `set +e;
|
||||
resolve_autopush 2>err; rc=$?; set -e` → rc != 0, err contains "exactly".
|
||||
Case F (prompt loop): `printf 'yes\ntrue\n' | ask_autopush 2>err` → true, err
|
||||
contains "exactly"; `printf '\n' | ask_autopush` → true.
|
||||
Case G (empty email): deploy_gitconfig x "" true 2>err → no file, err has "not written".
|
||||
Case H (call site): `grep -q 'deploy_gitconfig "\$identity_name" "\$identity_email" "\$autopush"' install.sh`
|
||||
and `! grep -q 'deploy_gitconfig "\$SCRIPT_DIR' install.sh`.
|
||||
Case I (idempotent): run right after Case A in Case A's HOME (no fresh_home between
|
||||
them): deploy_gitconfig x x@y false again → stdout contains "already up to date",
|
||||
no `.gitconfig.backup-*` created.
|
||||
Case J (non-exact existing): fresh_home; printf '[gitflow]\n autopush = off\n' >
|
||||
"$HOME/.gitconfig"; env unset, </dev/null → prints true and stderr names "off".
|
||||
Case K (refused value): `set +e; deploy_gitconfig x x@y yes 2>"$HOME/err"; rc=$?; set -e`
|
||||
→ rc != 0, err contains "not exactly", no file.
|
||||
Print AUTOPUSH_RENDER_OK only at the end.
|
||||
|
||||
## Edge cases
|
||||
- Non-exact existing value: `off`/`no`/`0` are VALID false for the readers (`--bool`),
|
||||
`maybe` is invalid (nothing pushed). Neither is reused (strict grammar): the value is
|
||||
named on stderr, then preset/prompt/default decide. With no tty and no preset a
|
||||
hand-set `off` becomes `true`: accepted consequence of the strict grammar + default
|
||||
true, documented in the README sentence on switching the mode.
|
||||
- Re-run: value found in ~/.gitconfig → no prompt → identical render → "already up to
|
||||
date — skipping" (now holds with hooksPath in the template).
|
||||
- No tty (curl | bash without /dev/tty) and no preset → true: same as today's unset = auto.
|
||||
- Invalid preset aborts before Oldconfig/rm or any write (resolution happens at the
|
||||
identity step).
|
||||
- Empty email → file skipped, dropped push mode named in the warning.
|
||||
- `.githooks/post-commit` / `.githooks/post-merge` are dirty from the session hook
|
||||
refresh: do not touch, do not stage.
|
||||
- Known, not handled: a system-scope `/etc/gitconfig` value is shadowed by the written
|
||||
global one (blockers entry at CAPITALIZE).
|
||||
|
||||
## Tests
|
||||
- `bash -n install.sh`, `shellcheck install.sh`.
|
||||
- `bash .claude/tasks/contracts/check-autopush-render.sh` → AUTOPUSH_RENDER_OK.
|
||||
|
||||
## Disposition (memory read-before)
|
||||
- honors BDR-001 (bash) — bash substitutions, `read -rp`.
|
||||
- honors BDR-002 — template read via `$SCRIPT_DIR/gitconfig`.
|
||||
- BDR-012 pattern (installer prompts, re-ask on bad input, keep-existing, no TTY →
|
||||
default) honored on the interactive path; DEVIATION flagged: an invalid
|
||||
DOTFILES_GITFLOW_AUTOPUSH preset aborts the install (user's fail-closed requirement),
|
||||
before any file is touched.
|
||||
- honors LRN-001 idempotency — unchanged ~/.gitconfig skipped; existing value reused.
|
||||
- honors LRN-011 — oracle is a stub harness on extracted functions, no live install, no git config.
|
||||
- honors LRN-014 — bash 3.2 safe.
|
||||
- Non-binding: BDR-016 (backup naming, kept as is).
|
||||
|
||||
## CHALLENGE SUMMARY (round 1 → v2)
|
||||
- BLOCKER (correctness+robustness): template path at the call site → placeholders deployed
|
||||
→ closed: deploy_gitconfig takes name/email/autopush; call site passes the resolved
|
||||
identity; Case A asserts no placeholder; Case H pins the call site. [gated]
|
||||
- BLOCKER (simplicity+robustness): `git config … gitflow.*` denied to this session →
|
||||
closed: sed read of ~/.gitconfig, harness writes files directly, zero config access.
|
||||
- MAJOR: loose boolean grammar / fail-open normaliser → superseded by the user spec:
|
||||
exact true/false only, re-ask, default true (user correction), invalid preset aborts.
|
||||
- MAJOR: empty email drops the answer → closed: named in the skip warning.
|
||||
- MAJOR: hooksPath wiped on redeploy → superseded by the user spec: fixed template line.
|
||||
- MAJOR: shared scratch HOME → closed: fresh_home per case + guard.
|
||||
- MINORs: header comment reworded, `@(USER|EMAIL|AUTOPUSH)@` assertion, README:50, README
|
||||
wording (precedence + how to switch), call-site grep. Deferred: system-scope shadowing.
|
||||
- Added by the user spec: fail-closed grep after render (Case B), DOTFILES_ prefix.
|
||||
|
||||
## CHALLENGE SUMMARY (confirmation pass, v3 → v4): SOLID, 8 MINOR
|
||||
- Accepted: preset validated first; non-exact existing value named; positive true/false
|
||||
guard + no-pipe leak check; patsub_replacement off + Case A2; err files in scratch HOME;
|
||||
Case I in Case A's HOME; harness header + refusal rule; TODO line rewritten.
|
||||
- Edge-case text corrected (off/no/0 are valid false for the readers).
|
||||
- BDR-012 deviation (preset abort) flagged in the Disposition.
|
||||
@@ -5,6 +5,14 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Versioning: se
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
- `install.sh` asks the gitflow push mode (`gitflow.autopush`, exactly `true` or `false`, Enter = `true`) and writes it to `~/.gitconfig`; an existing value is reused, `DOTFILES_GITFLOW_AUTOPUSH` presets it, any other preset aborts the install.
|
||||
- `gitconfig`: fixed `core.hooksPath = ~/.claude/githooks`.
|
||||
|
||||
### Fixed
|
||||
- `~/.gitconfig` was deployed with the literal `@USER@` / `@EMAIL@` placeholders: the installer read the identity from the repo bashrc template instead of the answers.
|
||||
- An `&` in the name no longer corrupts the rendered identity on bash 5.2 and later (`patsub_replacement` turned off).
|
||||
|
||||
## [1.0.0] — 2026-10-06
|
||||
|
||||
### Added
|
||||
|
||||
@@ -22,7 +22,8 @@ vim/autoload/ pathogen loader (committed)
|
||||
vim/colors/ molokai colorscheme (committed)
|
||||
bash/bashrc-{linux,osx} OS-detected bashrc; USER/EMAIL identity = @USER@/@EMAIL@ placeholders,
|
||||
asked at install (reused from an existing rc), never tracked
|
||||
gitconfig user-scope ~/.gitconfig template, @USER@/@EMAIL@ filled at install
|
||||
gitconfig user-scope ~/.gitconfig template, @USER@/@EMAIL@/@AUTOPUSH@
|
||||
(gitflow push mode, exact true/false) filled at install; core.hooksPath fixed
|
||||
tmux.conf tmux config (vi keys, tpm plugins) → ~/.config/tmux/tmux.conf, both OSes (tmux ≥ 3.1)
|
||||
zsh/{zshrc-osx,bchanot.zsh-theme} macOS zsh option: oh-my-zsh zshrc + theme porting the bash prompt
|
||||
bin/{dt,dtach-router,claude-provider} CLI scripts deployed to ~/.local/bin
|
||||
|
||||
@@ -28,7 +28,7 @@ curl -fsSL https://git.bchanot.fr/bchanot/config/raw/branch/main/remote-install.
|
||||
| `vim/vimrc` | Vim config: pathogen, molokai, syntastic (C with `-Wall -Werror -Wextra`), NERDTree, 42-style canonical class generators (`:ClassH`, `:ClassC`). |
|
||||
| `vim/autoload/` | `pathogen.vim` plugin loader (committed). |
|
||||
| `vim/colors/` | `molokai.vim` colorscheme (committed). |
|
||||
| `gitconfig` | Template of the user-scope `~/.gitconfig`. `@USER@` and `@EMAIL@` are filled at install with the `USER` and `EMAIL` exported by the bashrc (git never expands `$VARS` itself). |
|
||||
| `gitconfig` | Template of the user-scope `~/.gitconfig`. `@USER@`, `@EMAIL@` and `@AUTOPUSH@` (gitflow push mode) are filled at install with the installer's answers (git never expands `$VARS` itself); `core.hooksPath` points at the gitflow hooks `make link` creates in the claude-config repo. |
|
||||
| `bash/bashrc-linux` | bashrc for desktop Linux (git-aware prompt + command timer). |
|
||||
| `bash/bashrc-osx` | bashrc for macOS: `bashrc-linux` adapted (Homebrew on `PATH`, BSD `ls -G`, bash 5 clock for the timer, `cc` without `systemd-run`). |
|
||||
| `zsh/zshrc-osx` | zshrc for macOS when zsh is chosen: oh-my-zsh + the same env, aliases and dtach menu as `bashrc-osx`. Loads `~/.zshrc.local` for machine-specific lines. |
|
||||
@@ -47,7 +47,7 @@ curl -fsSL https://git.bchanot.fr/bchanot/config/raw/branch/main/remote-install.
|
||||
curl -fsSL https://git.bchanot.fr/bchanot/config/raw/branch/main/remote-install.sh | bash
|
||||
```
|
||||
|
||||
`remote-install.sh` ensures `git` is present, clones the repo to `~/config` (or pulls if already there), then runs `install.sh` with the terminal as its stdin, so the questions below (identity, macOS shell, offers) are asked even though the script arrives through a pipe. Override with env vars: `REPO_URL=... CLONE_DIR=... BRANCH=... curl ... | bash`.
|
||||
`remote-install.sh` ensures `git` is present, clones the repo to `~/config` (or pulls if already there), then runs `install.sh` with the terminal as its stdin, so the questions below (identity, push mode, macOS shell, offers) are asked even though the script arrives through a pipe. Override with env vars: `REPO_URL=... CLONE_DIR=... BRANCH=... curl ... | bash`.
|
||||
|
||||
> Piping a remote script into `bash` runs unreviewed code over the network. Read [`remote-install.sh`](remote-install.sh) first, or use the manual clone below.
|
||||
|
||||
@@ -67,7 +67,7 @@ What it does:
|
||||
3. Moves any existing `~/.vim`, `~/.vimrc`, `~/.bashrc`, `~/.Sublivim` to `~/Oldconfig`.
|
||||
4. Clones the `syntastic` and `nerdtree` vim plugins into `~/.vim/bundle/`.
|
||||
5. Copies the tracked vim files into `~/.vim` and symlinks `~/.vimrc`.
|
||||
6. Picks the bashrc by OS: macOS → `bashrc-osx` (falls back to `bashrc-linux` if missing), everything else → `bashrc-linux`. Renders it into `~/.bashrc` with the identity asked at the very start: a name and an email for git commits and vim headers. An `export USER=` / `export EMAIL=` already present in `~/.bashrc` or `~/.zshrc` is reused without asking, so a re-run never prompts twice; `IDENTITY_USER` / `IDENTITY_EMAIL` preset them; with no terminal attached it falls back to the login name and an empty email. The values live only in the deployed files, never in the repo. Then renders `gitconfig` into `~/.gitconfig` with the same `USER` / `EMAIL`. A different existing `~/.gitconfig` is saved as `~/.gitconfig.backup-<date>`; an identical one is left alone. It is the global level only: a repo's own `.git/config` still overrides it. `core.excludesfile` points at `~/.gitignore`, ignored by git when the file does not exist. Then deploys `tmux.conf` to `~/.config/tmux/tmux.conf` (both OSes, tmux ≥ 3.1: Ubuntu 22.04+, brew), clones [tpm](https://github.com/tmux-plugins/tpm) and fetches the listed plugins headlessly; details under [macOS](#macos) step 6, the step is the same. On Linux, `y` copies into tmux's buffer and the terminal clipboard through OSC 52; macOS uses `pbcopy`/`pbpaste`.
|
||||
6. Picks the bashrc by OS: macOS → `bashrc-osx` (falls back to `bashrc-linux` if missing), everything else → `bashrc-linux`. Renders it into `~/.bashrc` with the identity asked at the very start: a name and an email for git commits and vim headers. An `export USER=` / `export EMAIL=` already present in `~/.bashrc` or `~/.zshrc` is reused without asking, so a re-run never prompts twice; `IDENTITY_USER` / `IDENTITY_EMAIL` preset them; with no terminal attached it falls back to the login name and an empty email. The values live only in the deployed files, never in the repo. Then renders `gitconfig` into `~/.gitconfig` with the same name and email (skipped, push mode included, when the name or email is empty, e.g. a first install with no terminal and no `IDENTITY_EMAIL`). The installer asks once whether the gitflow hooks push every commit and merge (`true` or `false` exactly, Enter = `true`); a `true`/`false` already in `~/.gitconfig` is reused without asking and survives the redeploy; `DOTFILES_GITFLOW_AUTOPUSH=true|false` presets it for a non-interactive install, an existing `~/.gitconfig` value still winning (no terminal and no preset gives `true`; any other preset aborts the install); the render refuses to write a file where `@AUTOPUSH@` leaked, since a non-boolean value blocks every push. To switch later: `git config --global gitflow.autopush true|false`. A different existing `~/.gitconfig` is saved as `~/.gitconfig.backup-<date>`; an identical one is left alone. It is the global level only: a repo's own `.git/config` still overrides it. `core.excludesfile` points at `~/.gitignore`, ignored by git when the file does not exist. `core.hooksPath` is fixed to `~/.claude/githooks`, the gitflow hooks created by `make link` in the claude-config repo; the installer neither creates nor checks that directory. Being global, it makes git ignore each repo's `.git/hooks/` unless that repo sets its own `core.hooksPath`. Then deploys `tmux.conf` to `~/.config/tmux/tmux.conf` (both OSes, tmux ≥ 3.1: Ubuntu 22.04+, brew), clones [tpm](https://github.com/tmux-plugins/tpm) and fetches the listed plugins headlessly; details under [macOS](#macos) step 6, the step is the same. On Linux, `y` copies into tmux's buffer and the terminal clipboard through OSC 52; macOS uses `pbcopy`/`pbpaste`.
|
||||
7. Installs Python CLIs via `pipx` (`PyMuPDF` → `pymupdf`, `Markdown` → `markdown_py`) — skipped if `pipx` is absent.
|
||||
8. Copies the `bin/` scripts (`dt`, `dtach-router`, `claude-provider`) into `~/.local/bin`. The dtach session-resume menu ships in the deployed bashrc (both OSes), so every interactive shell offers it — including VS Code Remote-SSH terminals, which are non-login and never read `~/.profile`. The installer also strips any older dtach block left in `~/.profile` so a plain SSH login doesn't prompt twice.
|
||||
9. On Linux, installs `etc/profile.d/disk-usage-warning.sh` to `/etc/profile.d/` (needs `sudo`) so each login warns when `/` or `/home` cross 85% usage.
|
||||
@@ -99,7 +99,7 @@ The script is re-runnable: each run re-backs up to `~/Oldconfig` (overwriting th
|
||||
|
||||
### macOS
|
||||
|
||||
The same `./install.sh` detects macOS and replaces `apt-get` with Homebrew. It first asks which login shell you want, **zsh** or **bash** (`[zsh]` by default; answer in advance with `MACOS_SHELL=bash ./install.sh`, and with no terminal attached it picks zsh):
|
||||
The same `./install.sh` detects macOS and replaces `apt-get` with Homebrew. After the identity and push-mode questions, it asks which login shell you want, **zsh** or **bash** (`[zsh]` by default; answer in advance with `MACOS_SHELL=bash ./install.sh`, and with no terminal attached it picks zsh):
|
||||
|
||||
1. Installs Homebrew with its official script when `brew` is missing (this also pulls the Xcode Command Line Tools: clang, make, git), then `brew update` + `brew upgrade`.
|
||||
2. Installs the apt list mapped to formulae: `vim git git-lfs git-filter-repo gitleaks pkgconf shellcheck gh git-delta curl gnupg lftp inetutils unzip tree tmux fzf dtach node python pipx php mariadb imagemagick ffmpeg weasyprint poppler qpdf webp libavif bash`. Brew's `php` already ships gd, mbstring, xml, intl, curl and mysql.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
# Template for the user-scope ~/.gitconfig, rendered by install.sh.
|
||||
# Git never expands $VARS: @USER@ and @EMAIL@ are replaced at install
|
||||
# time with the USER and EMAIL exported by the deployed bashrc.
|
||||
# A repo .git/config still overrides these values for that repo.
|
||||
# Template for the user-scope ~/.gitconfig, rendered by install.sh. Git never
|
||||
# expands $VARS, so the identity and the gitflow push mode are placeholders
|
||||
# filled at install time with the installer's answers. A repo .git/config
|
||||
# still overrides these values for that repo.
|
||||
[user]
|
||||
name = @USER@
|
||||
email = @EMAIL@
|
||||
[gitflow]
|
||||
# Push mode of the gitflow hooks: false = manual, you run `git push`;
|
||||
# true = every commit and merge is pushed. Exact true/false only (fail-closed).
|
||||
autopush = @AUTOPUSH@
|
||||
[push]
|
||||
default = current
|
||||
[color]
|
||||
@@ -15,6 +19,8 @@
|
||||
editor = vim
|
||||
pager = delta
|
||||
excludesfile = ~/.gitignore
|
||||
# gitflow hooks (created by `make link` in claude-config); git expands ~ itself.
|
||||
hooksPath = ~/.claude/githooks
|
||||
[advice]
|
||||
detachedHead = false
|
||||
[merge]
|
||||
|
||||
+70
-11
@@ -2,6 +2,9 @@
|
||||
# install.sh — deploy the vim + bash dotfiles. OS is auto-detected.
|
||||
# Usage: ./install.sh
|
||||
set -euo pipefail
|
||||
# bash >= 5.2 expands `&` in ${var//pat/rep} replacements: an `&` in a name would
|
||||
# corrupt the rendered identity. No-op on bash 3.2.
|
||||
shopt -u patsub_replacement 2>/dev/null || true
|
||||
|
||||
# Resolve the repo root so the script works from any working directory.
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
@@ -524,20 +527,75 @@ resolve_identity() {
|
||||
printf '%s\n' "${value:-$default}"
|
||||
}
|
||||
|
||||
# Ask the push question on the terminal until the answer is exactly true or false;
|
||||
# Enter (or EOF) = true. Prints the value.
|
||||
ask_autopush() {
|
||||
local answer=""
|
||||
while :; do
|
||||
read -rp "Automatic push of commits by the gitflow hooks on this machine? [true/false] (default: true) " answer || true
|
||||
case "${answer:-true}" in
|
||||
true|false) printf '%s\n' "${answer:-true}"; return 0 ;;
|
||||
*) echo "Answer exactly true or false." >&2 ;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
# Push mode of the gitflow hooks (gitflow.autopush), exact true/false only: the
|
||||
# readers fail closed on anything else. Never asked twice: a true/false already in
|
||||
# ~/.gitconfig wins silently (read with sed, like rc_export_value, so a hand-set
|
||||
# value survives the redeploy; a non-exact spelling is named and re-asked), else
|
||||
# DOTFILES_GITFLOW_AUTOPUSH (anything but true/false aborts the install here,
|
||||
# before any file is touched, whatever ~/.gitconfig holds), else the prompt
|
||||
# on a terminal, else true (today's unset = auto). Prints the value.
|
||||
resolve_autopush() {
|
||||
local value="" preset="${DOTFILES_GITFLOW_AUTOPUSH:-}"
|
||||
case "$preset" in
|
||||
true|false|"") ;;
|
||||
*) echo "DOTFILES_GITFLOW_AUTOPUSH='$preset' — must be exactly true or false" >&2; return 1 ;;
|
||||
esac
|
||||
if [ -f "$HOME/.gitconfig" ]; then
|
||||
value="$(sed -n 's/^[[:space:]]*autopush[[:space:]]*=[[:space:]]*//p' "$HOME/.gitconfig" | tail -n 1)"
|
||||
case "$value" in
|
||||
true|false) printf '%s\n' "$value"; return 0 ;;
|
||||
"") ;;
|
||||
*) echo "gitflow.autopush='$value' in ~/.gitconfig is not exactly true/false — asking again" >&2 ;;
|
||||
esac
|
||||
fi
|
||||
if [ -n "$preset" ]; then printf '%s\n' "$preset"; return 0; fi
|
||||
if [ -t 0 ]; then ask_autopush; else echo true; fi
|
||||
}
|
||||
|
||||
# Print the gitconfig template with the identity ($1 name, $2 email) and the push
|
||||
# mode ($3) filled in. Fails, printing nothing, when the mode is not exactly
|
||||
# true/false or when @AUTOPUSH@ survives the render: a non-boolean
|
||||
# gitflow.autopush blocks every push, so a leaked placeholder is an outage.
|
||||
render_gitconfig() {
|
||||
local name="$1" email="$2" autopush="$3" rendered
|
||||
case "$autopush" in
|
||||
true|false) ;;
|
||||
*) echo "gitconfig render refused: push mode '$autopush' is not exactly true/false — nothing written" >&2; return 1 ;;
|
||||
esac
|
||||
rendered="$(render_identity_template "$SCRIPT_DIR/gitconfig" "$name" "$email")"
|
||||
rendered="${rendered//@AUTOPUSH@/$autopush}"
|
||||
case "$rendered" in
|
||||
*@AUTOPUSH@*) echo "gitconfig render failed: @AUTOPUSH@ left in the output — nothing written" >&2; return 1 ;;
|
||||
esac
|
||||
printf '%s\n' "$rendered"
|
||||
}
|
||||
|
||||
# Install the user-scope ~/.gitconfig (a repo's own .git/config still wins).
|
||||
# The identity is read from the USER/EMAIL exports of the deployed rc ($1), so
|
||||
# git and the shell agree. A ~/.gitconfig that differs is kept as
|
||||
# $1 $2 = the identity rendered into the rc, so git and the shell agree; $3 = the
|
||||
# gitflow push mode (true/false). A ~/.gitconfig that differs is kept as
|
||||
# ~/.gitconfig.backup-<date>, outside ~/Oldconfig which every run wipes.
|
||||
# Idempotent: an identical ~/.gitconfig is left alone.
|
||||
# Idempotent: an identical ~/.gitconfig is left alone. Two fail-closed checks
|
||||
# live in render_gitconfig: exact push mode, no leaked placeholder.
|
||||
deploy_gitconfig() {
|
||||
local rc="$1" name email rendered backup
|
||||
name="$(rc_export_value USER "$rc")"
|
||||
email="$(rc_export_value EMAIL "$rc")"
|
||||
local name="$1" email="$2" autopush="$3" rendered backup
|
||||
if [ -z "$name" ] || [ -z "$email" ]; then
|
||||
echo "USER/EMAIL not exported by $rc — skipping ~/.gitconfig" >&2
|
||||
echo "Name or email empty — skipping ~/.gitconfig (push mode $autopush not written)" >&2
|
||||
return 0
|
||||
fi
|
||||
rendered="$(render_identity_template "$SCRIPT_DIR/gitconfig" "$name" "$email")"
|
||||
rendered="$(render_gitconfig "$name" "$email" "$autopush")" || return 1
|
||||
if printf '%s\n' "$rendered" | cmp -s - "$HOME/.gitconfig"; then
|
||||
echo "$HOME/.gitconfig already up to date — skipping"
|
||||
return 0
|
||||
@@ -547,7 +605,7 @@ deploy_gitconfig() {
|
||||
echo "Saving the current ~/.gitconfig to $backup"
|
||||
mv "$HOME/.gitconfig" "$backup"
|
||||
fi
|
||||
echo "Deploying gitconfig to ~/.gitconfig ($name <$email>)"
|
||||
echo "Deploying gitconfig to ~/.gitconfig ($name <$email>, autopush=$autopush)"
|
||||
printf '%s\n' "$rendered" > "$HOME/.gitconfig"
|
||||
}
|
||||
|
||||
@@ -584,6 +642,7 @@ EOF
|
||||
identity_name="$(resolve_identity USER "Name for git commits and vim headers" "$(id -un)")"
|
||||
identity_email="$(resolve_identity EMAIL "Email for git commits and vim headers" "")"
|
||||
echo "Identity: $identity_name <${identity_email:-no email}>"
|
||||
autopush="$(resolve_autopush)"
|
||||
|
||||
# System packages: apt-get on Debian/Ubuntu, Homebrew on macOS.
|
||||
if command -v apt-get >/dev/null 2>&1; then
|
||||
@@ -684,8 +743,8 @@ fi
|
||||
echo "Deploying $bashrc ($identity_name <$identity_email>)"
|
||||
render_identity_template "$SCRIPT_DIR/$bashrc" "$identity_name" "$identity_email" > "$HOME/.bashrc"
|
||||
|
||||
# User-scope git config, identity taken from the bashrc just deployed.
|
||||
deploy_gitconfig "$SCRIPT_DIR/$bashrc"
|
||||
# User-scope git config, same identity as the rc just rendered.
|
||||
deploy_gitconfig "$identity_name" "$identity_email" "$autopush"
|
||||
|
||||
# tmux config + plugins (tmux comes from the apt or brew list above).
|
||||
if command -v tmux >/dev/null 2>&1; then
|
||||
|
||||
Reference in New Issue
Block a user