Automation

How to Automate Repetitive Git Workflows with Aliases and Hooks

Tech Setup4 min read
TS

Tech Setup

Published October 23, 2026 · Editorial policy

How to Automate Repetitive Git Workflows with Aliases and Hooks

You type out the same five Git commands a dozen times a day, context-switching between writing code and formatting commit messages. This friction burns mental energy and invites typos into your release pipeline. By the end of this guide, you will have a customized set of shell aliases and local Git hooks that execute your routine workflows in a single keystroke and block bad code before it ever reaches the staging area.

The 60-Second Shortcut Setup

If you want immediate relief from typing git checkout, git status, and long-winded log commands, configure your global ~/.gitconfig file right now.

Open your terminal and run these commands to inject standard shortcuts into your configuration:

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status
git config --global alias.unstage "reset HEAD --"
git config --global alias.last "log -1 HEAD"

To verify the setup worked, check your status using the new alias:

git st

Expected output:

On branch main
Your staging area matches 'main'
nothing to commit, working tree clean

These shortcuts save keystrokes, but real productivity gains happen when you chain multiple commands into composite aliases and automate validation with hooks.

Building Power Aliases for Daily Workflows

Single-word aliases are just the starting point. Git allows you to define complex command chains and execute shell scripts directly through aliases by prefixing them with an exclamation point (!).

The Clean-Up and Sync Pipeline

Developers often waste time running git fetch --prune, switching to main, and pulling the latest changes before starting a feature. You can map this entire routine to a single command named sync.

Add this block directly to the [alias] section of your ~/.gitconfig file:

[alias]
    sync = "!f() { \
        current=$(git symbolic-ref --short HEAD); \
        git fetch --prune && \
        git checkout main && \
        git pull origin main && \
        git checkout $current && \
        git merge main; \
    }; f"

Run your new command from any feature branch:

git sync

This sequence fetches remote updates, strips deleted branches, switches to main, pulls updates, returns to your working branch, and merges main into your current work. If a merge conflict occurs, Git pauses the execution right where it stops during a normal manual merge.

Pretty Logging with Graph Visualization

The default git log output is notoriously unhelpful for reading branching histories. Add a structured, colorized graph alias to make history traversal readable:

[alias]
    lg = "log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) <%an>%Creset' --abbrev-commit"

Run git lg to view your repository history as a concise, ASCII-art dependency tree with relative commit times and author names.

Enforcing Quality with Local Git Hooks

Aliases save time, but Git hooks prevent human error. Hooks live inside the .git/hooks directory of your local repository and execute automatically when specific events—like commit or push—occur.

Writing a Pre-Commit Linting Hook

Let's build a pre-commit hook that runs your project's linter and type-checker on staged files only, blocking the commit if errors are found. This stops broken syntax from entering your commit history.

Navigate to your repository's root directory and open .git/hooks/pre-commit in your text editor (create the file if it does not exist):

touch .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Paste the following POSIX-compliant shell script into .git/hooks/pre-commit:

#!/usr/bin/env sh

echo "Running pre-commit quality checks..."

# Find all staged JavaScript, TypeScript, or Python files
staged_files=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js$|ts$|py$)')

if [ -z "$staged_files" ]; then
    echo "No relevant files staged. Skipping checks."
    exit 0
fi

# Run your linter (e.g., ESLint for JS/TS) against staged files
# Replace this command with your project's specific validation tool
npx eslint $staged_files
lint_status=$?

if [ $lint_status -ne 0 ]; then
    echo "❌ Linting failed. Fix the errors above before committing."
    exit 1
fi

echo "✅ All checks passed successfully."
exit 0

Testing the Hook

Stage a file with deliberate syntax errors or unused variables, then attempt to commit:

git add src/broken-file.ts
git ci -m "WIP: add experimental feature"

Expected output:

Running pre-commit quality checks...

/path/to/repo/src/broken-file.ts
  10:5  error  'unusedVar' is assigned a value but never used  @typescript-eslint/no-unused-vars

❌ Linting failed. Fix the errors above before committing.

The commit is aborted instantly. Once you fix the error and re-stage the file, the commit proceeds normally.

Troubleshooting Common Automation Pitfalls

Automating your Git environment can occasionally introduce subtle edge cases. Keep these fixes handy when things break.

Hook Execution Failures (Exit Code 126 or Permission Denied)

If Git rejects your hook with a Permission denied or exit code 126 error, the script lacks execution permissions. Fix this by explicitly granting executable rights to the file:

chmod +x .git/hooks/pre-commit

Bypassing Hooks in an Emergency

When you are on a hotfix branch and need to push broken or draft code urgently, you can bypass local hooks by appending the --no-verify flag to your commands:

git commit -m "hotfix: temporary bypass" --no-verify
git push origin hotfix-branch --no-verify

Note: Use this sparingly. Relying on --no-verify defeats the purpose of automated quality gates.

Sharing Hooks Across a Team

Git hooks stored inside .git/hooks are local to your machine and are not tracked by version control by default. If you want your entire team to use the same pre-commit hooks, move them out of the hidden .git folder into a tracked directory like .githooks/ in your project root:

mkdir .githooks
mv .git/hooks/pre-commit .githooks/pre-commit
git add .githooks/pre-commit
git commit -m "chore: add shared pre-commit hook"

Then, configure Git globally or in your setup scripts to look in this directory for hooks:

git config core.hooksPath .githooks

Alternatives and Future-Proofing

Shell aliases and native Git hooks are lightweight, require zero external dependencies, and work on any machine with Git installed. However, as projects scale, managing raw shell scripts across different developer operating systems (macOS, Linux, Windows WSL) can become difficult.

If you outgrow native shell scripts, consider these alternatives:

  • Husky + lint-staged: The industry standard for Node.js environments. It simplifies installing and sharing hooks across teams via package.json configurations and automatically passes only modified files to linters like Prettier and ESLint.
  • Taskfile or Makefiles: If your Git aliases grow beyond simple command chaining into multi-step build processes, move the logic into a dedicated task runner like Task (taskfile.dev) or a standard Makefile and call those tasks from your aliases.