Skip to content

Instantly share code, notes, and snippets.

@Noitidart
Last active May 25, 2026 15:56
Show Gist options
  • Select an option

  • Save Noitidart/de33f3e482349d88025940ffa22333b3 to your computer and use it in GitHub Desktop.

Select an option

Save Noitidart/de33f3e482349d88025940ffa22333b3 to your computer and use it in GitHub Desktop.
Difftastic Git Shortcuts — structural diff aliases (gdu, gds, gdh, gdb, gdr, gl, glo)

Difftastic Git Shortcuts

Requires: difft (difftastic), installed via brew install difftastic.
Shell: zsh (these are shell functions, not git aliases — may work in bash but not in fish or sh)


Commands

Diff Commands

Command Action Notes Examples
gdl [-- <pathspec>] Show staged + unstaged changes vs HEAD. gdl — all current changes
gdl -- '*.ts' — filtered to .ts files
gdl -- src/ — directory
gdlu [-- <pathspec>] Show unstaged changes. gdlu — all filepaths
gdlu -- '*auth*' — paths containing "auth"
gdlu -- src/ — directory
gdls [-- <pathspec>] Show staged changes. gdls — all filepaths
gdls -- '*api*' — paths containing "api"
gdls -- lib/ — directory
gdc [N|ref|range] [-- <pathspec>] Show commit(s). Smart dispatch by input type. No arg = last commit. Number = last N. Range = diff across range. Ref = specific commit. gdc — last commit (HEAD)
gdc 3 — last 3 commits
gdc abc123 — specific commit
gdc HEAD~5..HEAD — range
gdc abc123 -- '*ts' — filtered
gdb [target] [-s <source>] [-- <pathspec>] Diff 2 branches. Defaults to current vs master. Shows only what your branch (source) changed — ignores new commits on master (target) since you branched off. This is git diff master...HEAD (merge-base). gdb — current vs master
gdb develop -s feature-x
gdb -- '*auth*' — filtered

Convention: [opt] = optional, <req> = required.

Log Commands

Many commands need hashes, like gdc <hash>, gdc <older>..<newer>, git cherry-pick <hash>, git cherry-pick <older>..<newer>, or git revert <hash>. The point of these commands is to help you find the commit and quickly copy its hash.

Workflow: run gl to find the commit number, q to exit, gl 3 to copy its short hash, then use it by pasting it in another command.

macOS: pbcopy is used to copy so this will not work with other operating systems.

Command Action Examples
gl [N] [-- <pathspec>] git log with numbered commits gl 3 — copy short hash of commit #3
gl -- package.json — log filtered to that file
gl 3 -- package.json — copy 3rd commit touching that file
glo [N] [-- <pathspec>] git log --oneline with numbered commits glo 5 — copy short hash of commit #5
glo -- '*.ts' — oneline filtered to .ts files
glo 5 -- '*.ts' — copy 5th .ts commit
  • -- <pathspec> filters to commits touching those files — same as native git.

Pathspec

All commands support -- <pathspec> — same as native git. A quick refresher:

  • Literal (no wildcards) — exact path from repo root. api.ts matches only ./api.ts.
  • fnmatch (has *, ?, [) — * crosses /. '*.ts' finds all .ts at any depth.
  • Anchored by default — '*/api.ts' matches pages/api.ts but NOT grapi.ts (root level).
  • Unanchored substring — wrap in *'*api*' matches any path containing "api".
  • Never lead with /'/api.*' is treated as absolute path (fatal).
  • Quote patterns with *, ?, [ to prevent shell expansion.
  • Multiple path patterns: -- 'api.ts' '*/api.ts' — needed to match root + any depth; a single pattern won't cover both.
Pathspec Matches Notes
-- src/ All files under src/ recursively Directory prefix
-- api.ts ./api.ts only Literal — exact path from repo root
-- '*.ts' foo.ts, src/bar.ts, a/b/baz.ts * crosses / — all .ts at any depth
-- '*/api.ts' pages/api.ts, a/b/api.ts Anchored — filename exactly api.ts
-- '*api*' pages/api/route.ts, useApi.ts, grapi.ts Unanchored — substring across full path
-- 'api.ts' '*/api.ts' ./api.ts, pages/api.ts, a/b/api.ts Multiple pathspecs — root + any depth

Example Workflow

glo                 # numbered one-line log, scroll with j/k
# note commit #3 looks interesting
q                   # exit less
glo 3               # copies abc1234 to clipboard
gdc abc1234         # structural diff of that commit
gdc 3               # or: structural diff of last 3 commits
gdb                 # what does this whole branch look like vs master?

Install / Update

Run this single command — it fetches the latest, prepends a version header (hash + date), compares with the installed version, and reloads:

_GIST=https://gist.githubusercontent.com/Noitidart/de33f3e482349d88025940ffa22333b3 && \
_REPO=https://gist.github.com/de33f3e482349d88025940ffa22333b3.git && \
_REPO_ID=$(basename "$_REPO" .git) && \
_TARGET=~/my-zsh-commands/difftastic-git-shortcuts.zsh && \
_REMOTE_HASH=$(git ls-remote "$_REPO" refs/heads/main | cut -c1-7) && \
_REMOTE_DATE=$(gh api "gists/$_REPO_ID/commits?per_page=1" --jq '.[0].committed_at[:10]' 2>/dev/null) && \
curl -sS "$_GIST/raw/difftastic-git-shortcuts.zsh" -o "$_TARGET.tmp" && { echo "# $_REMOTE_HASH ${_REMOTE_DATE:-unknown date}"; echo; cat "$_TARGET.tmp"; } > "$_TARGET.tmp~" && mv "$_TARGET.tmp~" "$_TARGET.tmp" && \
_NEW=$(head -1 "$_TARGET.tmp") && _OLD=$(head -1 "$_TARGET" 2>/dev/null || true) && \
if [[ "$_NEW" = "$_OLD" ]]; then \
  rm "$_TARGET.tmp" && echo "Already up to date"; \
else \
  mkdir -p ~/my-zsh-commands && mv "$_TARGET.tmp" "$_TARGET" && \
  if [[ -n "$_OLD" ]]; then \
    _OD=$(echo "$_OLD" | cut -d' ' -f3) && _ND=$(echo "$_NEW" | cut -d' ' -f3) && echo "Updated $_OD$_ND"; \
  else \
    echo "Installed ($(echo "$_NEW" | cut -d' ' -f3))"; \
  fi && \
  grep -q 'my-zsh-commands/difftastic-git-shortcuts.zsh' ~/.zshrc || echo 'source ~/my-zsh-commands/difftastic-git-shortcuts.zsh' >> ~/.zshrc && \
  source ~/.zshrc; \
fi

Reports Already up to date, Updated 2026-05-02 → 2026-05-03, or Installed (2026-05-03).

Uninstall

sed -i '' '/my-zsh-commands\/difftastic-git-shortcuts.zsh/d' ~/.zshrc && \
rm ~/my-zsh-commands/difftastic-git-shortcuts.zsh && \
source ~/.zshrc
gdl() {
local _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
echo "gdl: unexpected argument '$arg' before --" >&2
return 1
done
git -c diff.external=difft diff HEAD "${_pathargs[@]}"
}
gdlu() {
local _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
echo "gdlu: unexpected argument '$arg' before --" >&2
return 1
done
git -c diff.external=difft diff "${_pathargs[@]}"
}
gdls() {
local _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
echo "gdls: unexpected argument '$arg' before --" >&2
return 1
done
git -c diff.external=difft diff --staged "${_pathargs[@]}"
}
gdc() {
local _arg="" _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
if [[ -z "$_arg" ]]; then
_arg="$arg"
else
echo "gdc: unexpected argument '$arg' before --" >&2
return 1
fi
done
if [[ -z "$_arg" ]]; then
git -c diff.external=difft show --ext-diff HEAD "${_pathargs[@]}"
elif [[ "$_arg" =~ ^[0-9]+$ ]]; then
git -c diff.external=difft diff "HEAD~$_arg" HEAD "${_pathargs[@]}"
elif [[ "$_arg" == *..* ]]; then
local old="${_arg%%..*}" new="${_arg#*..}"
if [[ -z "$old" || -z "$new" ]]; then
echo "gdc: expected format OLD..NEW" >&2
return 1
fi
git -c diff.external=difft diff "$old" "$new" "${_pathargs[@]}"
else
git -c diff.external=difft show --ext-diff "$_arg" "${_pathargs[@]}"
fi
}
gdb() {
local src="" target="" _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
case "$arg" in
help|-h|--help)
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
;;
-s) src="next" ;;
--) _break=1 ;;
*)
if [[ "$src" == "next" ]]; then
src="$arg"
elif [[ -z "$target" ]]; then
target="$arg"
else
echo "gdb: too many arguments" >&2
return 1
fi
;;
esac
done
[[ -z "$src" ]] && src="HEAD"
[[ -z "$target" ]] && target="master"
git -c diff.external=difft diff "$target...$src" "${_pathargs[@]}"
}
gl() {
local _number="" _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
if [[ "$arg" =~ ^[0-9]+$ ]] && [[ -z "$_number" ]]; then
_number="$arg"
else
echo "gl: unexpected argument '$arg'" >&2
return 1
fi
done
if [[ -n "$_number" ]]; then
local hash=$(git log --format="%h" -- "${_pathargs[@]}" | sed -n "${_number}p")
if [[ -z "$hash" ]]; then
echo "gl: no commit at position $_number" >&2
return 1
fi
echo "$hash" | pbcopy
echo "Copied: $hash"
else
git log -- "${_pathargs[@]}" | awk '/^commit /{c++} /^commit /{printf "%3d) ", c} {print}' | less
fi
}
glo() {
local _number="" _break= _pathargs=()
for arg in "$@"; do
[[ -n "$_break" ]] && { _pathargs+=("$arg"); continue; }
[[ "$arg" == "help" || "$arg" == "-h" || "$arg" == "--help" ]] && {
open "https://gist.github.com/Noitidart/de33f3e482349d88025940ffa22333b3#file-difftastic-git-shortcuts-md" >/dev/null 2>&1
return
}
[[ "$arg" == "--" ]] && { _break=1; continue; }
if [[ "$arg" =~ ^[0-9]+$ ]] && [[ -z "$_number" ]]; then
_number="$arg"
else
echo "glo: unexpected argument '$arg'" >&2
return 1
fi
done
if [[ -n "$_number" ]]; then
local hash=$(git log --oneline -- "${_pathargs[@]}" | awk '{print $1}' | sed -n "${_number}p")
if [[ -z "$hash" ]]; then
echo "glo: no commit at position $_number" >&2
return 1
fi
echo "$hash" | pbcopy
echo "Copied: $hash"
else
git log --oneline -- "${_pathargs[@]}" | awk '{print NR") "$0}' | less
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment