>_EXECUTIVE SUMMARY
zsh-git-acp compresses the most-frequent inner loop of an active engineer — git pull && git add . && git commit -m "..." && git push — into a single ZLE keystroke. The current $BUFFER becomes the commit message; Ctrl-S dispatches zsh-gacp-NoCheck (fire-and-forget), Ctrl-F Ctrl-S dispatches zsh-gacp-CheckDiff (side-by-side diff piped to less, single-key confirmation). To free Ctrl-S from the terminal-driver flow-control trap, the plugin runs setopt noflowcontrol at load. Both widgets respect $ZSH_GACP_BLACKLISTED_DIRECTORIES — the user's /etc, ~/.ssh, or any other “do not auto-commit here” path opts out. Source: 510 zsh lines across a 268-line plugin entry + 6 autoloaded helper functions. The plugin also ships 159 git aliases — the curated “everything you actually type” shorthand set. Pinned by 37 @test blocks across 7 zunit files.
~ARCHITECTURE
| File | Lines | Role |
|---|---|---|
zsh-git-acp.plugin.zsh |
268 | Plugin entry. Declares the 159 git aliases (branch / checkout / diff / log / merge / rebase / push / pull / stash / submodule / worktree / cherry-pick / reset / show / status / tag families — with origin/upstream-aware variants like gbuom/gbuod/gbuum/gbuud, gmom/gmod/gmum/gmud, gdom/gdod/gdum/gdud). Provides fall-back stub definitions for zpwrExists / zpwrIsGitDir / zpwrLoggErr / zpwrLoggNotGit when zpwr is not loaded. Runs the Zsh Plugin Standard 0= header. Prepends ${0:h}/autoload to fpath, autoloads all 6 helper functions, registers the two ZLE widgets, runs setopt noflowcontrol, binds Ctrl-S and Ctrl-F Ctrl-S in all three keymaps. |
autoload/zsh-gacp-CommitAndPush |
45 | The shared backend. Validates commit message, validates git-dir, validates blacklist, then guards on nothing to commit from git status. Captures git remote; if any remote exists it runs git pull --rebase, then always git add . && git commit -m "...", then a plain git push (no --force) when a remote exists. If no remote is configured, the pull and push steps are skipped — useful for local-only repos. |
autoload/zsh-gacp-CheckDiff |
80 | Diff-confirm widget. Validates blacklist + git-dir + non-empty buffer + nothing to commit guard, runs git add ., then shows the diff: gitSdiffColorizer.pl | less -R if that script is on $PATH, else git difftool -y -x HEAD * | less -R. It prints a >>>>>> Push? prompt, reads one key with read -k 1, and dispatches zsh-gacp-CommitAndPush on y. Largest single autoload file because it carries the inline-confirmation UX. |
autoload/zsh-gacp-NoCheck |
52 | Fire-and-forget widget. Validates blacklist + git-dir + non-empty buffer, dispatches zsh-gacp-CommitAndPush with $BUFFER as the message. No confirmation prompt — one keystroke to ship. |
autoload/zsh-gacp-mainBranch |
25 | Detect the main branch on the named remote (default origin). Tries master first, then main — returns the first one that exists as refs/remotes/$remote/$branch. Powers the gbuom / gmom / gdom / gcm alias family. |
autoload/zsh-gacp-devBranch |
26 | Detect the dev branch on the named remote. Same shape as mainBranch but probes development / develop / devel in that order, falling back to dev. Powers gbuod / gmod / gdod / gcd. |
autoload/zsh-gacp-CommitCount |
14 | Standalone helper that echoes git rev-list HEAD | wc -l — the commit count of the current branch. Not wired into the widgets. Note: its inner function is mis-named zsh-gacp-CommitAndPush and self-invokes that name (pinned as a known bug by t-contract3.zsh). |
| Total source | 510 | 1 entry + 6 autoload helpers · pure zsh |
#TEST COVERAGE
| File | Tests | Pins |
|---|---|---|
t-aliases.zsh | 13 | The 159-alias surface enumerated against expected git ... bodies (sampling-based since 159 individual @test blocks would be wasteful); branch-aware aliases evaluate their $(zsh-gacp-mainBranch) / $(zsh-gacp-devBranch) sub-shell calls correctly. |
t-syntax.zsh | 2 | zsh -n over plugin entry + autoload tree. |
t-contract.zsh | 3 | Entrypoint stem matches plugin directory basename; entrypoint parses cleanly under zsh -n; every completion file starts with a #compdef directive. |
t-contract2.zsh | 7 | zle -N registers both widgets; setopt noflowcontrol frees ^S; ^S and ^F^S are wired on viins AND vicmd AND emacs; ZPWR_DEV_BRANCH defaults to devBranch and ZPWR_MAIN_BRANCH to master; regression-guard that helper fns redirect to /dev/null. |
t-contract3.zsh | 5 | mainBranch fallback covers master AND main; devBranch covers development/develop/devel; mainBranch returns 1 outside a git dir; the flagship gacp alias chains via &&; BUG-PIN that zsh-gacp-CommitCount ends with the wrong self-invocation. |
t-contract4.zsh | 5 | CommitAndPush rejects an empty message before any git call; sequence is pull --rebase → add → commit → push; push is plain git push with NO --force; pull-rebase and push BOTH gated on [[ -n $remotes ]]; the blacklist loop iterates ZSH_GACP_BLACKLISTED_DIRECTORIES. |
t-nocheck-blacklist.zsh | 2 | BUG-PIN: zsh-gacp-NoCheck reads the un-prefixed $GACP_BLACKLISTED_DIRECTORIES, NOT the documented $ZSH_GACP_BLACKLISTED_DIRECTORIES; the two non-buggy widgets (CheckDiff + CommitAndPush) agree on the documented name. |
| Total | 37 | 7 zunit files · alias-surface + widget-contract + install-contract |
/INTEGRATION
Bindkey across three keymaps
Both widgets are bound in viins + vicmd + emacs. Vim-emulation users hit Ctrl-S from insert OR command mode; emacs-mode users hit it directly. Ctrl-F Ctrl-S is the safer two-key escape for diff-confirm.
setopt noflowcontrol
By default the terminal driver eats Ctrl-S as XOFF and Ctrl-Q as XON — the keystroke never reaches ZLE. The plugin runs setopt noflowcontrol at source time to release the binding, then ZLE sees the raw Ctrl-S. Side effect: Ctrl-Q is also freed and available for user binding.
Buffer as commit message
The pre-press $BUFFER — whatever the user has typed at the prompt — becomes the literal commit message via git commit -m "$BUFFER". No editor pop-up, no $EDITOR dance. Type the message, hit Ctrl-S, ship.
Blacklist guard
ZSH_GACP_BLACKLISTED_DIRECTORIES=(/etc ~/.ssh /var/log/repo) opts out per directory. Both widgets check $(pwd -P) against the array before any git side effects. Inverse of opt-in: blacklist is empty by default; users add the few paths they want to protect, not the many they want to ship from.
Remote-aware push
zsh-gacp-CommitAndPush captures git remote once and gates BOTH the git pull --rebase and the final git push on [[ -n $remotes ]]. The push is a plain git push — no --force / --force-with-lease. No-remote setups skip pull and push entirely, so the widget is still safe for local-only experiments.
Branch detection is dynamic
The gbuom / gcm / gmom / gdom aliases call $(zsh-gacp-mainBranch) at expansion time — the result is whatever main-or-master ref the current repo has on its remote. Same plugin works against a legacy master-named repo AND against a freshly-renamed main repo with zero re-config.
!DESIGN DECISIONS
Two widgets, not a flag
NoCheck and CheckDiff are separate widgets, separate bindings, separate code paths. The user picks the safety level at keystroke time rather than at ~/.zshrc time. The 2-character difference between Ctrl-S and Ctrl-F Ctrl-S is the entire UX of "I trust this" vs "show me first."
noflowcontrol as side effect, not opt-in
Many plugins document the setopt noflowcontrol requirement and leave it to the user. zsh-git-acp just runs it at source time — the binding doesn't work without it, so making it opt-in would guarantee broken installs. The cost is one global side effect; the gain is "it just works after zinit load."
Stub fallbacks for zpwr helpers
The plugin defines zpwrExists, zpwrIsGitDir, zpwrLoggErr, zpwrLoggNotGit ONLY when they don't already exist ((( $+functions[...] )) guard). Loaded under zpwr, the real implementations win; loaded standalone, the stubs keep the widgets working.
Single-key confirmation, not Y/N
The diff-confirm widget uses read -k 1 — one keystroke commits or aborts. No y+Enter dance. Aligns with the “every keystroke costs” ethos of the rest of the plugin.
159 aliases as a curated set, not auto-generated
Each alias is hand-chosen for the “I type this multiple times an hour” threshold. Branch-aware variants (gbuom, gmom, gdom) handle the four combos of {origin, upstream} × {main, dev} explicitly — no per-alias config required.
Helper-as-file
Each of the 6 helper functions is its own file under autoload/, lazy-loaded via autoload -Uz "${0:h}/autoload/"*(.:t). Cold-startup cost is the 268-line entry parse only — the widgets and helpers are parsed on first call.
$ALIAS FAMILIES
The 159 aliases (plugin lines 12–218) are organized by git verb. The naming scheme is consistent: a leading g, the verb (p=push, l=pull, f=fetch, co=checkout, m=merge, d=diff, r=reset/remote, b=branch), then optional f for --force / r for --rebase / o/u for origin/upstream / m/d for main/dev branch.
| Family | Examples | Shape |
|---|---|---|
| Flagship acp | gacp, gac | git add . && git commit -m '' && git push chained via && (and the -c variant without push). |
| Push | gpo, gpa, gpf, gpfsup, gpom, gpfoat | plain / all / force / set-upstream / branch-targeted ($_m:$_m) / tags variants for origin, upstream and heroku. |
| Pull | glr, glom, glrom, glfom, glrfomd, gj | rebase / force / branch-mapping (main:dev) combinations; gj is pull --rebase --autostash -v. |
| Fetch | gfo, gfu, gfom, gffa | origin / upstream, per-branch, force, and --all --prune --tags. |
| Checkout | gcm, gcd, gcom, gcood, gcofum | main / dev, origin/upstream-tracking, force (-f) variants. |
| Merge / Diff | gmom, gmud, gdom, gdud | merge or diff -w against {origin,upstream}/{main,dev}. |
| Reset / Remote / Branch | grom, grhs, grao, grr, gbuom, gbv | reset --hard to a remote ref, soft/mixed reset, remote add/remove/rename, branch set-upstream, branch list. |
| Log / Inspect | glgf, gld, glaa, glz, glzz, gsa | fuller-format and pretty graph logs with --stat -p; gsa greps all history. |
| Tag / Clean | gt, gpot, bk, bki, allRebase | tag, push tags, hard clean + reset, interactive root rebase. |
Branch-targeted aliases (suffixes ending in m/d) use _m=$(zsh-gacp-mainBranch) / _d=$(zsh-gacp-devBranch) captured in a throwaway variable that is unset after the command, or inline $(...) expansion, so they resolve to the actual branch name of the current repo.
%KNOWN BUGS (PINNED BY TESTS)
The suite pins two real defects in the current source as BUG-PIN tests — documenting current behavior so a future change to it is a deliberate, visible diff rather than an accidental regression.
NoCheck reads the un-prefixed blacklist var
zsh-gacp-NoCheck iterates $GACP_BLACKLISTED_DIRECTORIES, while the documented name — used by CheckDiff and CommitAndPush — is $ZSH_GACP_BLACKLISTED_DIRECTORIES. To blacklist a directory for the ^S path too, export both names. Pinned by t-nocheck-blacklist.zsh.
CommitCount self-invokes the wrong name
The body of autoload/zsh-gacp-CommitCount defines and calls a function named zsh-gacp-CommitAndPush (rather than zsh-gacp-CommitCount). The intended git rev-list HEAD | wc -l still runs, but the symbol naming is wrong. Pinned by t-contract3.zsh.
@FOOTPRINT
- Disk: 1 plugin entry + 6 autoload files + 1 README. No compiled state.
- Memory: 159 alias entries in
$aliases+ 2 ZLE widget registrations + 6 lazy-loadable autoload symbols. - Startup cost: source the entry (268 lines), no autoload bodies parsed yet.
- Runtime cost: 1 widget invocation + 1
gitsubprocess chain per Ctrl-S. No background process, no polling. - External tools:
git(required),less+ a diff tool (gitSdiffColorizer.plif present, elsegit difftool) for the diff-confirm view. - Side effect on the user's shell:
setopt noflowcontrol(intentional, documented).