From 9901ec5da021c6253f651ba707c1b08faba54c5f Mon Sep 17 00:00:00 2001 From: bchanot Date: Wed, 7 Oct 2026 18:22:34 +0200 Subject: [PATCH] feat(install): ask the gitflow push mode at install, fixed hooksPath, identity into gitconfig gitconfig template: [gitflow] autopush = @AUTOPUSH@ (exact true/false, the hooks fail closed on anything else) and a fixed core.hooksPath = ~/.claude/githooks (created by `make link` in claude-config, git expands ~). install.sh: resolve_autopush at the identity step. A true/false already in ~/.gitconfig wins silently, else DOTFILES_GITFLOW_AUTOPUSH (any other value aborts before a file is touched), else a prompt that re-asks until the answer is exactly true or false (Enter = true), else true. render_gitconfig refuses to write when the mode is not a boolean or @AUTOPUSH@ survives. Fix: deploy_gitconfig received the repo bashrc template, so every install wrote `name = @USER@` / `email = @EMAIL@`. It now takes the resolved identity directly. patsub_replacement is turned off so an `&` in a name is not expanded on bash >= 5.2. --- README.md | 6 ++-- gitconfig | 14 +++++++--- install.sh | 81 ++++++++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 83 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 9765625..0f4342f 100644 --- a/README.md +++ b/README.md @@ -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. | | `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-`; 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. 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 (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-`; 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`. 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. diff --git a/gitconfig b/gitconfig index e2ec94b..684bf06 100644 --- a/gitconfig +++ b/gitconfig @@ -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] diff --git a/install.sh b/install.sh index 2f7a357..4b2b13c 100755 --- a/install.sh +++ b/install.sh @@ -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-, 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