Custom Git Commands and Aliases
A practical guide to creating Git aliases, custom commands, shell functions, and workflow automation scripts that accelerate daily Git operations.
Custom Git Commands and Aliases
The default Git commands are verbose. git status, git log --oneline --graph --decorate, git diff --staged — you type these dozens of times a day. Aliases reduce them to git s, git l, git ds. Custom commands go further, combining multiple operations into single commands that match your workflow.
I have accumulated aliases over ten years. Every time I type the same sequence of commands three times, it becomes an alias. The result is a Git interface that feels like it was designed for my specific workflow.
Prerequisites
- Git installed (v2.20+)
- A shell (bash, zsh, fish, or PowerShell)
- Your
.gitconfigfile location:~/.gitconfig - Terminal access
Git Aliases Basics
Setting Aliases
# Command line (writes to ~/.gitconfig)
git config --global alias.s "status --short --branch"
git config --global alias.l "log --oneline --graph --decorate -20"
# Or edit ~/.gitconfig directly
# ~/.gitconfig
[alias]
s = status --short --branch
l = log --oneline --graph --decorate -20
d = diff
ds = diff --staged
co = checkout
cb = checkout -b
cm = commit -m
ca = commit --amend --no-edit
Using Aliases
git s # git status --short --branch
git l # git log --oneline --graph --decorate -20
git ds # git diff --staged
git cb new-feature # git checkout -b new-feature
git cm "Fix bug" # git commit -m "Fix bug"
Essential Aliases
Status and Diff
[alias]
s = status --short --branch
d = diff
ds = diff --staged
dw = diff --word-diff
dt = difftool
stat = diff --stat
Log
[alias]
l = log --oneline --graph --decorate -20
la = log --oneline --graph --decorate --all -30
ll = log --pretty=format:'%C(yellow)%h%Creset %C(green)%ad%Creset %s %C(cyan)<%an>%Creset%C(red)%d%Creset' --date=short -20
lf = log --pretty=format:'%C(yellow)%h%Creset %s' --name-only -10
today = log --since='midnight' --oneline --author='Shane'
week = log --since='1 week ago' --oneline --author='Shane'
standup = log --since='yesterday' --oneline --author='Shane'
Branching
[alias]
co = checkout
cb = checkout -b
br = branch
bra = branch -a --sort=-committerdate
brd = branch -d
brD = branch -D
branches = branch -a --sort=-committerdate --format='%(color:yellow)%(refname:short)%(color:reset) %(color:green)%(committerdate:relative)%(color:reset) %(subject)'
merged = branch --merged main
unmerged = branch --no-merged main
Committing
[alias]
cm = commit -m
ca = commit --amend --no-edit
cam = commit --amend -m
wip = commit -am "WIP"
undo = reset --soft HEAD~1
unstage = reset HEAD --
Stashing
[alias]
sl = stash list
sp = stash pop
sa = stash apply
ss = stash push -m
sd = stash drop
Remote Operations
[alias]
f = fetch --all --prune
pl = pull --rebase
ps = push
psu = push -u origin HEAD
psf = push --force-with-lease
Shell Command Aliases
Aliases starting with ! run shell commands:
[alias]
# Open the repository in the browser
browse = !git remote get-url origin | sed 's/git@/https:\\/\\//' | sed 's/\\.git$//' | sed 's/com:/com\\//' | xargs open
# Show the root directory of the repo
root = !pwd
# Count commits by author
who = !git shortlog -sn --no-merges
# Show files changed in the last commit
last = show --stat HEAD
# Delete all merged branches
cleanup = !git branch --merged main | grep -v main | xargs -r git branch -d
# Quick amend and add all
oops = !git add -A && git commit --amend --no-edit
# Show the current branch name
current = rev-parse --abbrev-ref HEAD
# Create a save point
save = !git tag save/$(date +%Y%m%d-%H%M%S)
Shell Functions in Aliases
For complex logic, use shell functions:
[alias]
# Interactive rebase last N commits
ri = "!f() { git rebase -i HEAD~${1:-5}; }; f"
# Create and checkout branch with prefix
feature = "!f() { git checkout -b feature/$1; }; f"
bugfix = "!f() { git checkout -b bugfix/$1; }; f"
hotfix = "!f() { git checkout -b hotfix/$1; }; f"
# Find a commit by message
find = "!f() { git log --all --oneline --grep=\"$1\"; }; f"
# Find commits that changed a file
history = "!f() { git log --oneline --follow -- \"$1\"; }; f"
# Show diff between current branch and main
review = "!f() { git diff main...HEAD; }; f"
# Publish current branch
pub = "!f() { git push -u origin $(git rev-parse --abbrev-ref HEAD); }; f"
# Sync branch with main
sync = "!f() { git fetch origin && git rebase origin/main; }; f"
# Done with branch — merge to main and delete
done = "!f() { \
local branch=$(git rev-parse --abbrev-ref HEAD); \
git checkout main && \
git pull origin main && \
git merge $branch && \
git branch -d $branch; \
}; f"
Usage:
git ri 10 # Interactive rebase last 10 commits
git feature search # Creates feature/search branch
git find "payment" # Find commits mentioning "payment"
git history app.js # Show all commits that changed app.js
git pub # Push current branch with -u
git sync # Rebase onto latest origin/main
git done # Merge current branch to main, delete it
Custom Git Commands
Any executable named git-<name> on your PATH becomes a Git command:
# Create ~/bin/git-standup
#!/bin/bash
# Show what you worked on recently
AUTHOR="${1:-$(git config user.name)}"
SINCE="${2:-yesterday}"
echo "Commits by $AUTHOR since $SINCE:"
echo ""
git log --all --oneline --author="$AUTHOR" --since="$SINCE" --no-merges
chmod +x ~/bin/git-standup
# Now use it as a git command
git standup
git standup "Shane" "3 days ago"
Useful Custom Commands
git-recent — Show recently worked branches:
#!/bin/bash
# ~/bin/git-recent
git for-each-ref --sort=-committerdate refs/heads/ \
--format='%(color:yellow)%(refname:short)%(color:reset) %(color:green)(%(committerdate:relative))%(color:reset) %(subject)' \
--count="${1:-10}"
git-changelog — Generate changelog between tags:
#!/bin/bash
# ~/bin/git-changelog
FROM="${1:-$(git describe --tags --abbrev=0 2>/dev/null || echo '')}"
TO="${2:-HEAD}"
if [ -z "$FROM" ]; then
echo "No tags found. Showing all commits."
git log --oneline --no-merges
exit 0
fi
echo "Changes from $FROM to $TO:"
echo ""
echo "### Features"
git log "$FROM".."$TO" --oneline --no-merges --grep="^feat:" | sed 's/^[a-f0-9]* feat: /- /'
echo ""
echo "### Bug Fixes"
git log "$FROM".."$TO" --oneline --no-merges --grep="^fix:" | sed 's/^[a-f0-9]* fix: /- /'
echo ""
echo "### Other"
git log "$FROM".."$TO" --oneline --no-merges --grep="^feat:" --grep="^fix:" --invert-grep | sed 's/^[a-f0-9]* /- /'
git-stats — Repository statistics:
#!/bin/bash
# ~/bin/git-stats
echo "=== Repository Statistics ==="
echo ""
echo "Commits: $(git rev-list --count HEAD)"
echo "Contributors: $(git shortlog -sn --no-merges | wc -l)"
echo "Files: $(git ls-files | wc -l)"
echo "Branches: $(git branch | wc -l)"
echo "Tags: $(git tag | wc -l)"
echo ""
echo "Size: $(git count-objects -vH | grep size-pack | awk '{print $2, $3}')"
echo ""
echo "Top 5 contributors:"
git shortlog -sn --no-merges | head -5
echo ""
echo "Most changed files (last 100 commits):"
git log --pretty=format: --name-only -100 | sort | uniq -c | sort -rn | head -10
git-ignore — Add patterns to .gitignore:
#!/bin/bash
# ~/bin/git-ignore
if [ -z "$1" ]; then
echo "Usage: git ignore <pattern>"
echo " git ignore node_modules"
echo " git ignore '*.log'"
exit 1
fi
echo "$1" >> .gitignore
echo "Added '$1' to .gitignore"
git add .gitignore
Complete Working Example: Full Git Configuration
# ~/.gitconfig
[user]
name = Shane
email = [email protected]
[core]
editor = code --wait
autocrlf = input
pager = less -FRX
excludesfile = ~/.gitignore_global
[init]
defaultBranch = main
[pull]
rebase = true
[push]
default = current
autoSetupRemote = true
[merge]
conflictStyle = zdiff3
tool = vscode
[mergetool "vscode"]
cmd = code --wait --merge $REMOTE $LOCAL $BASE $MERGED
[diff]
algorithm = histogram
colorMoved = default
[rerere]
enabled = true
[fetch]
prune = true
writeCommitGraph = true
[branch]
sort = -committerdate
[alias]
# === Status & Diff ===
s = status --short --branch
d = diff
ds = diff --staged
dw = diff --word-diff
# === Log ===
l = log --oneline --graph --decorate -20
la = log --oneline --graph --decorate --all -30
ll = log --pretty=format:'%C(yellow)%h%Creset %C(green)%ad%Creset %s %C(cyan)<%an>%Creset%C(red)%d%Creset' --date=short -20
today = log --since='midnight' --oneline
week = log --since='1 week ago' --oneline
# === Branching ===
co = checkout
cb = checkout -b
branches = branch -a --sort=-committerdate --format='%(color:yellow)%(refname:short)%(color:reset) %(color:green)(%(committerdate:relative))%(color:reset) %(subject)'
merged = branch --merged main
unmerged = branch --no-merged main
# === Committing ===
cm = commit -m
ca = commit --amend --no-edit
cam = commit --amend -m
wip = !git add -A && git commit -m "WIP"
undo = reset --soft HEAD~1
unstage = reset HEAD --
# === Remote ===
f = fetch --all --prune
pl = pull --rebase
pub = "!f() { git push -u origin $(git rev-parse --abbrev-ref HEAD); }; f"
psf = push --force-with-lease
# === Stash ===
sl = stash list
sp = stash pop
ss = stash push -m
# === Workflow ===
feature = "!f() { git checkout -b feature/$1; }; f"
bugfix = "!f() { git checkout -b bugfix/$1; }; f"
sync = "!f() { git fetch origin && git rebase origin/main; }; f"
done = "!f() { local b=$(git rev-parse --abbrev-ref HEAD); git checkout main && git pull && git merge $b && git branch -d $b; }; f"
cleanup = !git branch --merged main | grep -v main | xargs -r git branch -d
ri = "!f() { git rebase -i HEAD~${1:-5}; }; f"
# === Info ===
last = show --stat HEAD
who = shortlog -sn --no-merges
find = "!f() { git log --all --oneline --grep=\"$1\"; }; f"
history = "!f() { git log --oneline --follow -- \"$1\"; }; f"
current = rev-parse --abbrev-ref HEAD
root = rev-parse --show-toplevel
# === Safety ===
save = "!f() { git tag save/$(date +%Y%m%d-%H%M%S); }; f"
oops = !git add -A && git commit --amend --no-edit
Common Issues and Troubleshooting
Alias conflicts with existing Git commands
You cannot override built-in Git commands with aliases:
Fix: Git ignores aliases that match built-in command names. Use different names. For example, git ci instead of trying to alias git commit.
Shell alias with special characters fails
Quotes, pipes, and dollar signs need careful escaping in .gitconfig:
Fix: Use double quotes around the alias value and escape internal quotes. Or use !f() { ...; }; f function syntax for complex shell commands. Test with git config --get alias.name to verify the stored value.
Alias works in terminal but not in scripts
Shell aliases (starting with !) use /bin/sh, which may not support all bash features:
Fix: Explicitly call bash in the alias: !bash -c '...'. Or write a standalone script and put it on PATH as git-commandname.
Custom command not found
The git-name script is not on PATH or is not executable:
Fix: Ensure the script directory is in your PATH. Add export PATH="$HOME/bin:$PATH" to your shell profile. Make the script executable: chmod +x ~/bin/git-name.
Best Practices
- Start with the top 10 aliases.
s,l,d,ds,co,cb,cm,ca,f,plcover most daily operations. Add more as patterns emerge. - Use consistent naming conventions. Two-letter aliases for single commands (
co,cm). Descriptive words for compound operations (sync,cleanup,done). - Avoid aliasing destructive operations. Keep dangerous commands explicit.
git push --forceshould not becomegit pf— the extra typing is a safety feature. - Use
--force-with-leaseinstead of--force. If you alias force push, always use--force-with-leaseto prevent overwriting someone else's work. - Put custom commands in
~/bin/and add them to PATH. This keeps them organized and accessible. Name themgit-nameso they work asgit name. - Document your aliases in comments. Future you will not remember what
git ri 10does. Add comments in.gitconfig. - Share aliases with your team. Create a documented
.gitconfigtemplate for the team. Common aliases reduce communication friction. - Test aliases before relying on them. Run the expanded command manually first to verify it does what you expect. Then alias it.