Skip to content
J-MaFfPublic

About

Git configuration and setup scripts

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Dotfiles

Personal Git configuration and utilities for cross-machine synchronization.

Current Version: v0.1.0-pre | Changelog

Installation

macOS

Requirements: macOS 12+, Homebrew, Python 3

git clone https://github.com/J-MaFf/gitconfig.git ~/Documents/Scripts/gitconfig
cd ~/Documents/Scripts/gitconfig
bash scripts/unix/install.sh --force

(Optional) Enable SSH commit signing with 1Password:

brew install 1password-cli
bash scripts/unix/install.sh --force

Windows (PowerShell)

Requirements: PowerShell 5.1+, Administrator privileges, Python 3

git clone https://github.com/J-MaFf/gitconfig.git "$HOME\Documents\Scripts\gitconfig"
cd "$HOME\Documents\Scripts\gitconfig"
& ".\scripts\windows version\install.ps1" -Force

Use $HOME, not ~, in the clone command: PowerShell before 7.5 doesn't expand ~ for native programs such as git, so the clone would land in a folder literally named ~.

Linux

Requirements: bash, Python 3, and a systemd user session or cron (optional, for auto-update)

git clone https://github.com/J-MaFf/gitconfig.git ~/Documents/Scripts/gitconfig
cd ~/Documents/Scripts/gitconfig
bash scripts/unix/install.sh --force

macOS and Linux share one set of scripts in scripts/unix/; they detect the OS. The older scripts/mac version/ and scripts/linux version/ commands still work: they run the same scripts and refuse to run on the other OS. Pass --no-scheduler to skip the auto-update job (--no-launchd and --no-cron still work too).

The setup script handles generating ~/.gitconfig from the template, creating symlinks, installing the rich Python dependency, and registering an auto-update job (launchd on macOS, Task Scheduler on Windows; on Linux a systemd user timer, or cron where systemctl --user doesn't work).

The auto-update job is pull + install + prune: at each login it pulls the latest commits and, if .gitconfig.template changed in that pull, regenerates ~/.gitconfig so template changes take effect without a manual re-run. It also prunes merged branches in the gitconfig repo — dropping stale remote-tracking refs (fetch --prune) and deleting local branches whose remote has been deleted — so old feature branches don't pile up. It also ensures the optional textual dependency (which powers the interactive git alias browser) is installed — best-effort and only when missing, so existing machines pick it up on their next update without a manual pip install. Your existing ~/.gitconfig is backed up first, and ~/.gitconfig.local is never modified. Run the same pull-install-prune on demand any time with git selfupdate. Run that way it also prints its progress (the log is docs/update-gitconfig.log) and exits non-zero if the pull failed; the scheduled job only logs a failed pull as a warning, since being offline at login is routine (macOS/Linux).

Put your own git settings in ~/.gitconfig.local, not ~/.gitconfig. ~/.gitconfig is generated from .gitconfig.template and is rewritten whenever the template changes. Before a rewrite, the scripts print the names (never the values) of any settings in ~/.gitconfig that the template doesn't have, so you can move them. On macOS and Linux, regenerating ~/.gitconfig.local (install.sh --force) keeps the settings you added to it; only the signing, credential-helper and core.excludesfile settings are rebuilt. A ~/.gitconfig or ~/.gitconfig.local that is a symlink into your own dotfiles is replaced by a regular file instead of being written through.

Commit signing and the editor are machine settings, so they live in ~/.gitconfig.local. The template doesn't set user.signingkey, commit.gpgsign or core.editor (#231). Install writes them into ~/.gitconfig.local: the signing key the machine already uses, commit.gpgsign = true only when there is a key to sign with (on macOS/Linux, a key file that exists or a key the SSH agent holds; on Windows, a key file that exists, or a literal key when 1Password's op-ssh-sign.exe is installed or the SSH agent holds the key), and core.editor = code --wait only when VS Code is installed and you're not in an SSH session (or, on Linux, a session with no display). Otherwise git falls back to GIT_EDITOR, VISUAL, EDITOR, then its default. An install from before this change keeps working: the next auto-update copies whichever of the three settings ~/.gitconfig.local doesn't already set from the old ~/.gitconfig into it before rewriting ~/.gitconfig, and leaves ~/.gitconfig alone if it can't. On macOS/Linux, a machine whose first sync dropped them instead (before #297 was fixed) gets them back on the next sync, from the newest ~/.gitconfig.bak.* that has them. That recovery runs once per machine: gitconfig.templateKeysMigrated = true in ~/.gitconfig.local records that it ran, so a setting you remove later stays removed.

Nothing machine-specific is hardcoded (#235). ~/.gitconfig.local trusts this clone of the repo as a safe.directory wherever you cloned it. To trust other paths on one machine (a network share, a repo owned by another account), list them in ~/.config/gitconfig/safe-directories, one path per line (blank lines and lines starting with # are skipped), then regenerate ~/.gitconfig.local (install.sh --force, or Initialize-LocalConfig.ps1 -Force on Windows). On Windows a UNC path (\\server\share\repo) is written in git's %(prefix)///server/share/repo form. Regenerating keeps the safe.directory entries the old file already had, so nothing you trusted before is dropped. gpg.ssh.program points at 1Password's op-ssh-sign only when it is installed: on Windows, the Microsoft Store build (AppData\Local\Microsoft\WindowsApps) and the standard installer (AppData\Local\1Password\app\8) are both checked; on macOS, Homebrew and the 1Password app; on Linux, /opt/1Password. Without it, the setting is left out.

Backups

Whenever a script replaces or removes one of your files, it first saves a timestamped copy next to it, for example ~/.gitconfig.bak.20261003-142501. The newest 5 backups of each file are kept and older ones are deleted. Set GITCONFIG_BACKUP_KEEP to change the limit, or set it to 0 to keep every backup. Your original file is also kept for good: the first time a file is backed up, a copy is saved as <file>.pre-gitconfig (for example ~/.gitconfig.pre-gitconfig). That copy is never pruned or overwritten, so you can always get back the file you had before installing. On an install from before this copy existed, the oldest backup still on disk is used instead. Older *.bak files from previous versions (~/.gitconfig.bak, ~/Existing.*.bak) are never touched. Symlinks that already point into this repo aren't backed up, so running the installer again doesn't create new backups.

Reinstalling

Running the installer again is safe. It doesn't remove anything first. To wipe the previous install before setting up again, pass --reinstall (bash) or -Reinstall (PowerShell). Everything it removes is backed up first.

Exit status and unattended runs (macOS/Linux)

install.sh exits 1 when any step fails or its final check finds something missing; it still runs every step and prints the summary first. cleanup-gitconfig.sh exits 1 when something is still present afterwards. cleanup-gitconfig.sh, scripts/shared/initialize-gitconfig.sh and scripts/unix/initialize-local-config.sh exit 2 when you decline a prompt. Each script's --help lists its exit codes.

A yes/no prompt with no input to read (cron, CI, < /dev/null) counts as "no": the script says so and carries on as if you had answered no, instead of stopping with no message. Pass --force to answer yes to every prompt in an unattended run.

Exit status and elevation (Windows)

install.ps1 exits 1 when any step fails or its final check finds a file missing; it still runs every step, then lists the failed steps under Setup finished with errors instead of printing Setup Complete!. Cleanup-GitConfig.ps1 exits 1 when something is still present afterwards. Both exit 0 when you decline a prompt. When either script relaunches itself elevated, the new window pauses before it closes and the original window exits with its result, so a calling script sees a failed run.

If the administrator prompt asks for another account's password (a standard user elevating with an admin's credentials), the scripts still set up the account that started them: its home folder, its PowerShell profile and its login task. They print a [WARN] naming both accounts. Python packages go to the Python the administrator account can run; the login task installs any that are missing for you at your next login.

Uninstall

Each platform has a cleanup script that removes symlinks, local config, and the auto-update job.

macOS

bash scripts/unix/cleanup-gitconfig.sh

Removes: ~/.gitconfig, ~/.gitignore_global symlink, ~/gitconfig_helper.py symlink, ~/.gitconfig.local, and the launchd login agent.

Windows (PowerShell)

& ".\scripts\windows version\Cleanup-GitConfig.ps1"

Removes: ~/.gitconfig, symlinks, ~/.gitconfig.local, and the GitConfig Pull at Login scheduled task.

Pass -KeepTask to leave the scheduled task registered.

Linux

bash scripts/unix/cleanup-gitconfig.sh

Removes: ~/.gitconfig, ~/.gitignore_global symlink, ~/gitconfig_helper.py symlink, ~/.gitconfig.local, and the auto-update job: the gitconfig-update systemd user timer and this clone's cron job, whichever is present (the cron line tagged # gitconfig-autoupdate, plus an untagged one from an older install, that runs this clone's update-gitconfig.sh; other clones' jobs, commented-out lines and other cron entries are kept). The timer logs to the journal (journalctl --user -u gitconfig-update); the cron job logs to ${XDG_STATE_HOME:-~/.local/state}/gitconfig/gitconfig-update.log.

Linux auto-update: systemd timer or cron. When systemctl --user reaches a running user manager, the installer writes ~/.config/systemd/user/gitconfig-update.{service,timer} and runs systemctl --user enable --now gitconfig-update.timer: a daily sync that runs at the next start when the machine was off (Persistent=true) and needs no crontab. Otherwise (containers, WSL without systemd, no user session) it writes the daily 9 AM cron line. Installing one removes the other, so the sync never runs twice. A user timer runs only while you are logged in unless linger is on; the installer says so when it's off, and doesn't change it. On a headless machine, turn it on with loginctl enable-linger $USER.

Usage

Git Aliases

git alias opens an interactive, categorized browser: clickable/arrow-key tabs per category, a search box that filters by alias name or description, and a result table you move through with up/down. Press Enter (or click a row) to pick an alias. It needs a terminal and the optional textual package; when piped, in scripts, or without textual it falls back to a static grouped table. Force the static table with git alias --plain.

git alias          # Browse all aliases (interactive in a terminal)
git alias --plain  # Static grouped table (good for piping: git alias --plain | grep pr)

In the browser: type to search, up/down to move, Enter/click to select, Ctrl+Left/Right to switch category, Esc to clear the search (or quit), Ctrl+C to quit.

When you open the browser by typing git alias, selecting an alias copies git <alias> to your clipboard (paste with Cmd/Ctrl-V) — a typed git alias runs as a subprocess and can't type at your prompt. To have it inserted at the prompt directly, open the browser with the Ctrl-G keybinding instead.

Insert an alias at your prompt — Ctrl-G

The installer adds a Ctrl-G keybinding to your shell (bash/zsh) and to your PowerShell profile — on Windows, to both the Windows PowerShell 5.1 and the PowerShell 7 profile (Documents\WindowsPowerShell\profile.ps1 and Documents\PowerShell\profile.ps1), and re-running the installer after moving the repo updates the path. Press Ctrl-G at the prompt to open the browser; the alias you pick is typed onto your command line, ready to run or edit. A program launched by git alias can't type at your prompt itself, so this keybinding does the insertion — like fzf's Ctrl-T. The bash widget needs bash 4.0+: on macOS use zsh (the default shell), or Homebrew's bash (brew install bash). Under the stock /bin/bash 3.2 the widget doesn't bind Ctrl-G and prints a one-line note instead; git alias works either way. Enable it manually by sourcing the matching widget:

# bash (~/.bashrc) or zsh (~/.zshrc)
source /path/to/gitconfig/scripts/shell/git-alias-widget.bash   # or .zsh
# PowerShell ($PROFILE)
. 'C:\path\to\gitconfig\scripts\shell\git-alias-widget.ps1'

Inspect

git s              # Short, branch-aware status (status -sb)
git lg             # Pretty, decorated commit graph across all branches
git last           # Show the most recent commit with its diffstat
git recent         # Local branches ordered by most recent commit
git find <string>  # Commits that added or removed <string> (log -S)

Commit

git amend          # Fold staged changes into the last commit, keep its message
git reword         # Edit the last commit's message
git undo           # Undo the last commit but keep its changes staged
git unstage <path> # Unstage files while keeping working-tree changes (git restore --staged)
git wip            # Park all current work as a WIP commit (skips hooks)

Branch & Sync

git nb <name>      # Create and switch to a new branch (switch -c)
git pushf          # Force-push safely (--force-with-lease --force-if-includes; git 2.30+)
git sync           # Update the current branch with rebase + autostash
git start <issue#> # Start or resume the branch for a GitHub issue: <type>/<issue#>-<title-slug>
git branches       # Track all remote branches
git cleanup        # Delete local branches whose remote is gone (--force: merged local-only too)
git main           # Switch to the default branch: fetch, fast-forward only, branch cleanup
git main --all     # Run the above for every git repo directly under the Scripts root (alias: -a)

The Scripts root is the folder that holds this repo's clone (~/Documents/Scripts on a standard install). To point it somewhere else, set it in your machine-local config: git localconfig gitconfig.scriptsRoot ~/code. git main --all won't sweep your home folder, its parent (/home, /Users, C:\Users) or a filesystem root. It stops with exit 1 and asks you to set gitconfig.scriptsRoot, or to pass --force if you really mean it. This guards against a clone at ~/gitconfig, which would otherwise make the home folder the root.

git main and git cleanup work from any worktree. git won't check a branch out in two worktrees, so when the default branch is checked out in another one, your current worktree stays where it is: the default branch is fast-forwarded in its own worktree (only if that one is clean) and branches are cleaned up from there. If your current worktree's own branch is gone upstream, you get a git worktree remove <path> hint. Repos with no remote skip the fetch and the fast-forward.

The Python-backed aliases (git alias, start, cleanup, main, skill, issues) run nothing when given -h/--help (they print a usage line) or an argument they don't recognise (usage on stderr, exit 2). There is no --dry-run.

git start 123 names the branch <type>/123-<title-slug>: fix/, docs/ or feat/ from the issue's labels (feat/ by default), then the issue number, then the title in kebab case. Existing work is found by the issue number: a local branch for the issue is switched to, and a branch for it on a remote (started on another machine) is checked out tracking that remote branch, so a later git pushf can't overwrite it. Otherwise a new branch is created from the fetched default branch, or from the local default branch if the remote one is missing (never from the branch you're on). A failed fetch is reported as a warning. Branches from before the issue number was added (fix/<title-slug>) aren't reused, since that name may belong to another issue; git start points one out if it exists.

GitHub

git pr             # Open the current branch's pull request in the browser
git prs            # Show the status of your pull requests
git issues         # Open issues in this repo (--mine/-m, --label/-l <name>, --web/-w)
git issues --all   # Open issues across every repo you own (alias: -a; --local: repos under the Scripts root)

git issues --all --web opens GitHub's issue search with the same --mine and --label filters as the table. git issues --all --local lists the repos it couldn't check (no GitHub remote, gh not logged in) and exits 1 if it couldn't check any of them.

Maintenance

git localconfig          # Edit machine-specific git config (~/.gitconfig.local) in your editor
git localconfig <args>   # Run git config on that file, e.g. git localconfig user.email you@example.com
git selfupdate           # Pull this repo and reinstall ~/.gitconfig from the template
git h <subcommand>       # Run this repo's Python helper with the first working Python (the helper aliases go through it)

Claude Skills

git skill                # Print available subcommands (same as git skill help)
git skill list           # Table of installed skills: name, description, last updated
git skill sync           # Sync ~/.claude/skills: status -> pull --ff-only -> status
git skill status         # Show ~/.claude/skills state: last background sync + unpublished local changes
git skill diff [<name>]  # Show what changed in drifted skills (staged and unstaged, plus untracked files)
git skill publish        # Publish new/edited skills via a PR (prompts for a message, auto-merges)

If skill-audit itself fails, git skill status, sync and diff show its error and exit 1. They don't report it as changes to publish, or as nothing to diff.

Setup Script Options (Windows)

& ".\scripts\windows version\install.ps1" -Force           # Full setup
& ".\scripts\windows version\install.ps1" -Force -NoTask   # Don't register the scheduled task
& ".\scripts\windows version\install.ps1" -Force -Reinstall -NoTask   # Reinstall, keeping the existing task
& ".\scripts\windows version\Initialize-GitConfig.ps1" -Force      # Regenerate .gitconfig from template
& ".\scripts\windows version\Initialize-Symlinks.ps1" -Force       # Recreate symlinks (never touches the scheduled task)
& ".\scripts\windows version\Register-LoginTask.ps1" -Force         # Re-register the login task
& ".\scripts\windows version\Initialize-LocalConfig.ps1" -Force    # Regenerate local config

Contents

  • .gitconfig.template - Template for generating machine-specific Git configuration
  • .gitignore_global - Global gitignore patterns for editor/OS junk and secrets only (language and build patterns belong in each project's own .gitignore)
  • gitconfig_helper.py - Python utility for managing git aliases, branch cleanup, and main branch operations
  • scripts/ - Platform-specific setup and automation scripts

Troubleshooting

Symlink creation fails (Windows): Run PowerShell as Administrator (or turn on Developer Mode). A failed link leaves the existing ~/.gitignore_global or ~/gitconfig_helper.py in place, prints [FAIL], and Initialize-Symlinks.ps1 exits 1.

Python dependency issues:

python -m pip install --upgrade pip
python -m pip install rich textual

rich is required; textual is optional and only powers the interactive git alias browser. Without it, git alias shows the static grouped table. These are declared in pyproject.toml (rich required; textual under the optional tui extra); the setup/update scripts read it and install them for you, so the manual pip install above is only needed as a fallback.

git alias shows a static table instead of the interactive browser: install textual (pip install textual) and run git alias directly in a terminal — the interactive UI is skipped when output is piped/redirected or when stdout is not a TTY. git alias prints a one-line reason to stderr when it skips the browser, so the cause is visible. If a crashed TUI ever leaves the terminal in a weird state (the browser looks broken or the keybinding stops responding), run reset or open a fresh tab to restore it.

Aliases not working: The aliases live in the generated ~/.gitconfig (not a symlink) and run gitconfig_helper.py from this repo. Check that git config --global --get alias.alias prints a definition; if not, run git selfupdate or re-run install to regenerate ~/.gitconfig.

git log --show-signature says "No signature" on a signed commit: Install writes a ~/.ssh/allowed_signers entry for your signing key and points gpg.ssh.allowedSignersFile at it (in ~/.gitconfig.local) so git can verify SSH signatures locally. Only the local-config step of install writes these; git selfupdate and the auto-update job don't touch either file. If one is missing (say you installed before this existed, or set up signing later), re-run that step: on Windows, & ".\scripts\windows version\Initialize-LocalConfig.ps1" adds the allowed_signers entry but keeps your existing ~/.gitconfig.local, so if that file has no allowedSignersFile line, add -Force to regenerate it (the old file is backed up first); on macOS/Linux, bash scripts/unix/initialize-local-config.sh --force regenerates ~/.gitconfig.local (backing up the old one and keeping the settings you added) and adds the entry. GitHub verifies signatures independently, so commits show as Verified there regardless.

License

Personal configuration repository.

About

Git configuration and setup scripts

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages