paulirish.git-recent/git-recent
google-labs-jules[bot] 66e9793904 Refactor git-recent to be robust and pluggable
- Replaced fragile .git/ path parsing with git plumbing commands to support worktrees and packed-refs.
- Decoupled Chromium logic into a separate `git-recent-cl` script.
- Implemented a plugin system via `GIT_RECENT_SOURCE` environment variable.
- Improved shell hygiene and fzf command injection protection.
2025-12-30 09:28:57 +00:00

141 lines
5.5 KiB
Bash
Executable file

#!/bin/bash
#
# git recent 2.0 - switching branches, but so fancy
#
# - view recently edited local branches
# - see unique commits to that branch, and optionally the branch diff (against the main/master/primary branch) with Ctrl-o
# - hit Enter to checkout the selected branch.
# - text filtering against branch names, too.
#
if ! command -v fzf >/dev/null 2>&1; then
echo "Error: fzf is not installed. Please install fzf to use git-recent." >&2
exit 1
fi
[ "$(git rev-parse --is-inside-work-tree)" = "true" ] || exit
if [[ "$1" == "--help" ]]; then
echo "git-recent: Browse and checkout recently used Git branches."
echo
echo "Keybindings:"
echo " Enter: Checkout the selected branch"
echo " Ctrl-O: Show the diff of the selected branch against the main/master branch"
echo " Ctrl-C: Exit"
exit 0
fi
# ---------------------------------------------------------------------------------------
# The HEAD of the primary branch (eg main or master or w/e), for diffing.
# Correctly resolve the default branch using git plumbing.
diff_base=$(git rev-parse --abbrev-ref origin/HEAD 2>/dev/null)
if [[ -z "$diff_base" || "$diff_base" == "origin/HEAD" ]]; then
# Fallback: Try to guess main or master, or just error gracefully later if needed.
if git rev-parse --verify origin/main >/dev/null 2>&1; then
diff_base="origin/main"
elif git rev-parse --verify origin/master >/dev/null 2>&1; then
diff_base="origin/master"
else
# Last resort: just use HEAD, though diffing HEAD..branch is weird if you are on the branch.
# But usually this is for diffing against upstream.
diff_base="HEAD"
fi
fi
# Extract branch name (without any trailing text, like the Chromium link)
# Use more robust extraction: take the first field, strip whitespace.
define_branchname="branchname=\\\$(echo '{1}' | awk '{print \$1}')"
# Colorized hash, author, date, then commit subject followed by commit message body (wrapped and indented).
commits_format="%C(red bold)%h %C(bold blue)%an %C(bold green)%ad %Creset%s%w(0,4,4)%+b%w(0,0,0)"
# Use 'sh -c' carefully. We pass branchname as an environment variable or argument to avoid injection if possible.
# But fzf preview commands are shell strings. We must rely on the extraction logic.
# The 'awk' above ensures branchname has no spaces.
uniqcommits_cmd="sh -c \"$define_branchname; git log --date=human --color=always --format='$commits_format' --no-merges $diff_base..\\\$branchname\""
# Progressive enhancement if you have delta or diff-so-fancy
diff_pager_cmd=$(command -v delta || command -v diff-so-fancy)
pipe_to_pager=${diff_pager_cmd:+" | $diff_pager_cmd"}
diffbranch_cmd="sh -c \"$define_branchname; git diff --color=always $diff_base...\\\$branchname $pipe_to_pager\""
# Copy to clipboard, variants for mac/linux
copy_cmd="echo 'Could not copy {} to clipboard.'"
if command -v pbcopy >/dev/null; then
copy_cmd="printf '%s' {} | pbcopy"
elif command -v wl-copy >/dev/null; then
copy_cmd="printf '%s' {} | wl-copy"
elif command -v xclip >/dev/null; then
copy_cmd="printf '%s' {} | xclip -selection clipboard"
elif command -v xsel >/dev/null; then
copy_cmd="printf '%s' {} | xsel --clipboard"
fi
YELLOW='\033[0;33m'
DIM='\033[2m'
NC='\033[0m' # No Color
# Backward compatibility for --cl flag
if [[ "$1" == "--cl" || "$1" == "-cl" ]]; then
export GIT_RECENT_SOURCE="git-recent-cl"
shift
fi
# Determine source of branches
# If GIT_RECENT_SOURCE is set, use it.
# Otherwise default to local branches.
if [[ -n "$GIT_RECENT_SOURCE" ]]; then
if command -v "$GIT_RECENT_SOURCE" >/dev/null 2>&1; then
# If the user passed arguments (e.g. a remote name), pass them to the source provider
source_cmd="$GIT_RECENT_SOURCE $@"
else
echo "Error: GIT_RECENT_SOURCE '$GIT_RECENT_SOURCE' not found." >&2
exit 1
fi
else
# Default behavior
# if extra arg passed (eg `git recent remotename`), then list those remote branches, rather than local ones
if [[ -n "$1" ]]; then
heads="refs/remotes/$1"
else
heads="refs/heads"
fi
# Standard listing
source_cmd="git for-each-ref --sort=-authordate \"$heads\" --format=\"$YELLOW%(refname:short)$NC\""
fi
# fzf git inspiration:
# - https://github.com/junegunn/fzf/wiki/Examples#git
# - https://github.com/junegunn/fzf/wiki/Examples-(fish)#git
# - https://github.com/junegunn/fzf-git.sh (intense.)
# If there's a GIT_RECENT_QUERY environment variable, use it for non-interactive filtering. (Primarily added for testing: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/g/git-recent.rb#L41-L46)
filterarg=${GIT_RECENT_QUERY:+"--filter=$GIT_RECENT_QUERY"}
_browse_branches() {
eval "$source_cmd" \
| fzf \
$filterarg --ansi -- --layout=reverse --multi --height=90% --min-height=20 \
--border-label-pos=2 --border-label '🌲 Branches' --border \
--no-hscroll --no-multi \
--preview-window='right,70%,border-left,border-rounded' --preview="$uniqcommits_cmd" --preview-label="Commits unique to branch" \
--header $'ENTER (checkout)\nCTRL-O (show branch diff)\nCTRL-Y (copy name to clipboard)' \
--bind 'preview-scroll-up:preview-up+preview-up+preview-up' \
--bind 'preview-scroll-down:preview-down+preview-down+preview-down' \
--bind "ctrl-y:execute-silent($copy_cmd)" \
--bind "ctrl-o:preview:$diffbranch_cmd"
}
output="$(_browse_branches)"
line_count=$(printf "%s" "$output" | wc -l)
if [[ -n "$output" ]] && (( line_count == 0 )); then
chosen_branch=$(echo "$output" | awk '{print $1}')
echo git checkout "$chosen_branch"
git checkout "$chosen_branch"
else
echo "$output"
fi