Context: A 2 AM Outage and the Cost of Absentmindedness
2:15 AM. A payment gateway timeout left over 200 transactions hanging. The entire team scrambled to their laptops to ship a hotfix. Bleary-eyed and exhausted, I botched a cherry-pick command and casually followed it up with git push --force. The result: four crucial commits on the shared branch vanished into thin air. The team spent nearly three hours reverting and restoring the lost history. That night taught me an unforgettable lesson: never entrust system safety to human alertness in the middle of the night.
Many developers rely heavily on git alias inside ~/.gitconfig. That works well for simple shortcuts like co = checkout or st = status. But as requirements grow more complex, aliases quickly hit their limits. You cannot parse flags flexibly, validate input properly, provide a proper --help screen, and cramming dozens of lines of shell logic into a single configuration file is a maintenance nightmare.
Git solved this problem long ago through Custom Git Subcommands. The mechanism is elegantly simple: any executable file found in your PATH environment variable named in the format git-<command_name> becomes a valid Git subcommand. When you type git safe-push, Git automatically invokes git-safe-push and forwards all arguments directly to it.
Setup: Resolution Mechanics and Your First Subcommand
Git’s command resolution is straightforward. Whenever you enter a command not built into Git’s core (such as git nuke), Git scans the directories listed in your $PATH to locate an executable named git-nuke.
Step 1: Set Up a Dedicated Directory for Personal Scripts
Avoid dumping personal scripts directly into system directories like /usr/bin or /bin. Keep them cleanly separated within your user home directory for easy backups and machine migrations:
mkdir -p ~/.local/bin
# Add the path to ~/.bashrc or ~/.zshrc
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
Step 2: Write a Script to Prevent Accidental Branch Overwrites
Create the file ~/.local/bin/git-safe-push. This script uses the safer --force-with-lease flag and completely blocks force-pushing against critical branches:
cat << 'EOF' > ~/.local/bin/git-safe-push
#!/usr/bin/env bash
set -euo pipefail
CURRENT_BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || true)
if [ -z "$CURRENT_BRANCH" ]; then
echo "[ERROR] Currently in a detached HEAD state. Processing aborted." >&2
exit 1
fi
# List of branches protected from force-pushing
PROTECTED_BRANCHES=("main" "master" "production" "release")
for b in "${PROTECTED_BRANCHES[@]}"; do
if [ "$CURRENT_BRANCH" = "$b" ]; then
echo "[BLOCKED] You are currently on protected branch: '$CURRENT_BRANCH'!" >&2
echo "Using safe-push on this branch is strictly prohibited." >&2
exit 2
fi
done
echo "=> Safely pushing branch: $CURRENT_BRANCH"
git push origin "$CURRENT_BRANCH" --force-with-lease
EOF
chmod +x ~/.local/bin/git-safe-push
Now, you can run git safe-push from inside any repository on your machine.
Advanced: Handling Complex Logic with Python
Shell scripts are great for quick, simple tasks. However, when you need to parse structured logs, run complex regex validations, or call external APIs, Python is much more readable and maintainable thanks to standard modules like argparse and subprocess.
Building a Pre-Release Audit Tool: git-audit
Create the file ~/.local/bin/git-audit. This script automatically audits recent commits to catch missing Jira Ticket IDs and alerts you if sensitive credential files were accidentally staged into HEAD:
#!/usr/bin/env python3
import argparse
import subprocess
import sys
import re
def run_cmd(cmd):
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
if result.returncode != 0:
print(f"[ERROR] Command failed: {cmd}\n{result.stderr}", file=sys.stderr)
sys.exit(result.returncode)
return result.stdout.strip()
def main():
parser = argparse.ArgumentParser(
prog="git audit",
description="Validate commit message formats and scan for sensitive files before deploying."
)
parser.add_argument("-n", "--count", type=int, default=5, help="Number of recent commits to check (default: 5)")
parser.add_argument("--strict", action="store_true", help="Fail the run if any commit is missing a Jira Ticket ID")
args = parser.parse_args()
print(f"[*] Checking the last {args.count} commits...")
log_output = run_cmd(f"git log -n {args.count} --pretty=format:'%h|%an|%s'")
if not log_output:
print("[!] No commits found.")
return
has_warning = False
for line in log_output.splitlines():
sha, author, msg = line.split("|", 2)
# Verify Ticket ID prefix in commit message (e.g. PROJ-1024)
ticket_pattern = r"^[A-Z]{2,10}-[0-9]+"
if not re.search(ticket_pattern, msg):
print(f" [WARNING] Commit {sha} ({author}) is missing a Ticket ID: '{msg}'")
has_warning = True
else:
print(f" [OK] {sha} - {msg}")
# Check for sensitive secret files accidentally committed in HEAD
changed_files = run_cmd("git diff-tree --no-commit-id --name-only -r HEAD")
sensitive_patterns = [".env", "credentials.json", "id_rsa", ".pem"]
for f in changed_files.splitlines():
if any(pat in f for pat in sensitive_patterns):
print(f"\n[HIGH RISK] Sensitive file detected in commit HEAD: {f}", file=sys.stderr)
sys.exit(3)
if has_warning and args.strict:
print("\n[FAILED] Strict mode enabled: Commit message format violation found.", file=sys.stderr)
sys.exit(1)
print("\n=> Audit completed, source code is ready!")
if __name__ == "__main__":
main()
Remember to grant execution permissions to the script:
chmod +x ~/.local/bin/git-audit
Testing & Debugging: Ensuring Your Script Runs Smoothly
Writing the script is only half the battle; validating its reliability before integrating it into daily workflows is essential.
1. Verify Git Detects Your Subcommand
Instead of manually inspecting folders, use Git’s built-in flag to list all discovered external subcommands:
git --list-cmds=others | grep -E "(safe-push|audit)"
2. Check Exit Codes
A well-behaved subcommand must return meaningful exit codes. This allows other shell scripts or CI/CD pipelines to catch failures reliably and abort execution when needed:
# Verify the Python script help menu
git audit --help
# Run a test in strict mode and inspect the exit code
git audit -n 3 --strict
echo "Exit Code: $?"
A non-zero exit code confirms that the script correctly intercepted the rule violation.
3. Troubleshooting the “is not a git command” Error
- Missing execute permissions: Run
ls -l $(which git-safe-push)to ensure the file has-rwxr-xr-xpermissions. - Incorrect Shebang: Always use
#!/usr/bin/env bashor#!/usr/bin/env python3. This ensures scripts run reliably across Linux and macOS environments without relying on hardcoded paths like#!/bin/bash. - Outdated shell PATH cache: If you just added a script and Git fails to discover it, run
hash -r(in Bash) orrehash(in Zsh) to refresh the command cache.
