mirror of
https://github.com/paulirish/git-recent.git
synced 2026-09-10 07:26:16 -04:00
Merge branch 'master' of github.com:paulirish/git-recent
This commit is contained in:
commit
3b429ea804
44
git-recent
44
git-recent
|
|
@ -19,7 +19,7 @@ if [[ "$1" == "--help" ]]; then
|
|||
echo "git-recent: Browse and checkout recently used Git branches."
|
||||
echo
|
||||
echo "Keybindings:"
|
||||
echo " Enter: Checkout the selected branch"
|
||||
echo " Enter: Checkout the selected branch (or jump to its worktree if open elsewhere)"
|
||||
echo " Ctrl-O: Show the diff of the selected branch against the main/master branch"
|
||||
echo " Ctrl-C: Exit"
|
||||
exit 0
|
||||
|
|
@ -55,6 +55,24 @@ elif command -v xsel >/dev/null; then
|
|||
copy_cmd="printf '%s' {} | xsel --clipboard"
|
||||
fi
|
||||
|
||||
_get_worktree_for_branch() {
|
||||
local branch="$1"
|
||||
local current_toplevel
|
||||
current_toplevel=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||
|
||||
# Find the worktree path for the given branch, excluding the current one.
|
||||
# Using substr to handle paths with spaces correctly.
|
||||
git worktree list --porcelain | awk -v target="refs/heads/$branch" -v current="$current_toplevel" '
|
||||
/^worktree / { wt = substr($0, 10) }
|
||||
/^branch / && $2 == target {
|
||||
if (wt != current) {
|
||||
print wt
|
||||
exit
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
YELLOW='\033[0;33m'
|
||||
DIM='\033[2m'
|
||||
NC='\033[0m' # No Color
|
||||
|
|
@ -77,18 +95,26 @@ filterarg=${GIT_RECENT_QUERY:+"--filter=$GIT_RECENT_QUERY"}
|
|||
# Chromium hackers may want reference to their relevant CL.
|
||||
CL_STATUS=$([ "$show_cl" = true ] && git cl status --fast --no-branch-color | grep 'https://' | sed 's| (.*||')
|
||||
|
||||
# List of branches currently checked out in any worktree (including the current one)
|
||||
WT_BRANCHES=$(git worktree list --porcelain | grep "^branch " | sed 's|^branch refs/heads/||')
|
||||
|
||||
_browse_branches() {
|
||||
git for-each-ref --sort=-authordate "$heads" --format="%(refname:short)" \
|
||||
| while read -r branch_name; do
|
||||
wt_symbol=""
|
||||
if echo "$WT_BRANCHES" | grep -qFx "$branch_name"; then
|
||||
wt_symbol=" ⇶"
|
||||
fi
|
||||
|
||||
if [ "$show_cl" != true ]; then
|
||||
printf "$YELLOW%s$NC\n" "$branch_name"
|
||||
printf "$YELLOW%s$DIM%s$NC\n" "$branch_name" "$wt_symbol"
|
||||
continue
|
||||
fi
|
||||
review_url=$(echo "$CL_STATUS" | grep -E "\b${branch_name} :" | grep -o -E 'https://.*' | sed 's|https://||')
|
||||
# Using fancy integrated hyperlinks: https://iterm2.com/feature-reporting/Hyperlinks_in_Terminal_Emulators.html
|
||||
# TODO: maybe get rid of the crrev.com/c/ text as the link?
|
||||
# TODO: use `git config branch.$(git rev-parse --abbrev-ref HEAD).gerritissue` and gerritserver to avoid using `git cl status`
|
||||
printf "$YELLOW%s $DIM\033]8;;%s\a%s\033]8;;\a$NC\n" "$branch_name" "https://$review_url" "$review_url"
|
||||
printf "$YELLOW%s$DIM%s \033]8;;%s\a%s\033]8;;\a$NC\n" "$branch_name" "$wt_symbol" "https://$review_url" "$review_url"
|
||||
done \
|
||||
| fzf \
|
||||
$filterarg --ansi -- --layout=reverse --multi --height=90% --min-height=20 \
|
||||
|
|
@ -106,8 +132,16 @@ line_count=$(printf "%s" "$output" | wc -l)
|
|||
|
||||
if [[ -n "$output" ]] && (( line_count == 0 )); then
|
||||
chosen_branch=$(echo "$output" | cut -d' ' -f1)
|
||||
echo git checkout "$chosen_branch"
|
||||
git checkout "$chosen_branch"
|
||||
|
||||
wt_path=$(_get_worktree_for_branch "$chosen_branch")
|
||||
|
||||
if [[ -n "$wt_path" ]]; then
|
||||
echo "Branch '$chosen_branch' is open in worktree: $wt_path" >&2
|
||||
echo "$wt_path"
|
||||
else
|
||||
echo "git checkout $chosen_branch" >&2
|
||||
git checkout "$chosen_branch"
|
||||
fi
|
||||
else
|
||||
echo "$output"
|
||||
fi
|
||||
|
|
|
|||
144
git-watch
Executable file
144
git-watch
Executable file
|
|
@ -0,0 +1,144 @@
|
|||
#!/bin/bash
|
||||
|
||||
#
|
||||
# git-watch: A streaming perspective of git changes and commits.
|
||||
#
|
||||
# Monitors the current repository for:
|
||||
# - New commits (displays 'git show')
|
||||
# - Working directory & index changes (displays 'git diff HEAD' and untracked files)
|
||||
#
|
||||
# Requires 'watchexec' for responsiveness.
|
||||
#
|
||||
|
||||
# Check if we're in a git repo
|
||||
[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ] || {
|
||||
echo "Error: Not a git repository." >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check for dependencies
|
||||
if ! command -v watchexec >/dev/null 2>&1; then
|
||||
echo "Error: 'watchexec' is required for git-watch. Please install it (e.g., 'brew install watchexec')." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
|
||||
echo "git-watch: A streaming perspective of git changes and commits."
|
||||
echo
|
||||
echo "Usage: git-watch [--debug]"
|
||||
echo
|
||||
echo "Options:"
|
||||
echo " --debug Log what would be run instead of running it"
|
||||
echo
|
||||
echo "Features:"
|
||||
echo " - Shows 'git show' on new commits"
|
||||
echo " - Shows 'git diff HEAD' on working tree/index changes"
|
||||
echo " - Uses 'watchexec' for high-speed responsiveness"
|
||||
echo " - Uses your fancy pager (delta/diff-so-fancy) without blocking"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
DEBUG_MODE=false
|
||||
if [[ "$1" == "--debug" || "$2" == "--debug" ]]; then
|
||||
DEBUG_MODE=true
|
||||
fi
|
||||
|
||||
# Pager detection (compatible with delta, diff-so-fancy, etc.)
|
||||
pager_cmd=$(command -v delta || command -v diff-so-fancy)
|
||||
if [ -n "$pager_cmd" ]; then
|
||||
if [[ "$pager_cmd" == *"delta"* ]]; then
|
||||
pager_cmd="$pager_cmd --paging=never"
|
||||
fi
|
||||
pipe_to_pager=" | $pager_cmd"
|
||||
fi
|
||||
|
||||
git_run() {
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
echo -e "\033[0;35m[DEBUG] Would run: git --no-pager \"$@\" --color=always$pipe_to_pager\033[0m"
|
||||
return
|
||||
fi
|
||||
eval "git --no-pager \"\$@\" --color=always$pipe_to_pager"
|
||||
}
|
||||
|
||||
# Cross-platform hashing for change detection
|
||||
hash_cmd=$(command -v shasum || command -v sha1sum || echo "cksum")
|
||||
|
||||
update() {
|
||||
local mode="$1"
|
||||
local git_dir state_file
|
||||
git_dir=$(git rev-parse --git-dir 2>/dev/null)
|
||||
[ -n "$git_dir" ] || return
|
||||
state_file="$git_dir/watch-state"
|
||||
|
||||
local current_head last_head current_state_hash last_state_hash
|
||||
current_head=$(git rev-parse HEAD 2>/dev/null)
|
||||
|
||||
# Capture status and diff for hashing (ignoring untracked files for now)
|
||||
local status_output=$(git status --porcelain --untracked-files=no)
|
||||
local diff_output=$(git diff HEAD 2>/dev/null)
|
||||
# TODO: Re-evaluate untracked file support later.
|
||||
current_state_hash=$( (echo "$status_output"; echo "$diff_output") | $hash_cmd | cut -d' ' -f1)
|
||||
|
||||
if [[ -f "$state_file" && "$mode" != "--initial" ]]; then
|
||||
read -r last_head last_state_hash < "$state_file"
|
||||
fi
|
||||
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
echo -e "\033[0;35m[DEBUG] Mode: $mode\033[0m"
|
||||
echo -e "\033[0;35m[DEBUG] HEAD: $current_head (Last: ${last_head:-NONE})\033[0m"
|
||||
echo -e "\033[0;35m[DEBUG] State Hash: $current_state_hash (Last: ${last_state_hash:-NONE})\033[0m"
|
||||
fi
|
||||
|
||||
local did_something=false
|
||||
|
||||
# 1. Commit detection
|
||||
if [[ "$current_head" != "$last_head" || "$mode" == "--initial" ]]; then
|
||||
echo -e "\n\033[1;34m=== Current Commit: $(git log -1 --format='%h %s' 2>/dev/null) ===\033[0m"
|
||||
git_run show --summary --stat -p
|
||||
did_something=true
|
||||
fi
|
||||
|
||||
# 2. Working directory detection
|
||||
if [[ "$current_state_hash" != "$last_state_hash" || "$mode" == "--initial" ]]; then
|
||||
if [[ -n "$status_output" ]]; then
|
||||
echo -e "\n\033[1;32m=== Changes ===\033[0m"
|
||||
|
||||
# Show diff of tracked changes
|
||||
if [[ -n "$diff_output" ]]; then
|
||||
git_run diff HEAD
|
||||
fi
|
||||
|
||||
# TODO: Implement surgical untracked file support here if needed.
|
||||
did_something=true
|
||||
fi
|
||||
fi
|
||||
|
||||
# Update state so we don't repeat this in the next --step
|
||||
if [[ "$did_something" == "true" ]]; then
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
echo -e "\033[0;35m[DEBUG] Would update state file: $current_head $current_state_hash\033[0m"
|
||||
else
|
||||
echo "$current_head $current_state_hash" > "$state_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Internal flag for watchexec to call back into the script
|
||||
if [[ "$1" == "--step" ]]; then
|
||||
update "--step"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Main execution loop
|
||||
update "--initial"
|
||||
|
||||
pass_debug=""
|
||||
if [ "$DEBUG_MODE" = true ]; then
|
||||
pass_debug="--debug"
|
||||
fi
|
||||
|
||||
echo -e "\033[0;32mWatching with watchexec...\033[0m"
|
||||
# --postpone: don't run immediately on start (we already did update --initial)
|
||||
# --quiet: suppress watchexec status messages
|
||||
# exec is used so that Ctrl-C goes directly to watchexec and terminates the session
|
||||
exec watchexec --quiet --postpone -i .git -w . -w "$(git rev-parse --git-dir)/HEAD" -- "$0" --step $pass_debug
|
||||
|
|
@ -17,7 +17,7 @@ Display a help message.
|
|||
.SH KEYBINDINGS
|
||||
.TP
|
||||
.B Enter
|
||||
Checkout the selected branch.
|
||||
Checkout the selected branch. If the branch is already open in a separate git worktree, the script will output the path to jump there. (See README for automatic switching via a small shell wrapper).
|
||||
.TP
|
||||
.B Up/Down arrow keys
|
||||
Navigate the branch list.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
"description": "List recent git branches, select one for checkout. See branch commits and diffs, all formatted so fancy",
|
||||
"bin": {
|
||||
"git-recent": "git-recent",
|
||||
"git-recent-og": "git-recent-og"
|
||||
"git-recent-og": "git-recent-og",
|
||||
"git-watch": "git-watch"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
|
|
|||
24
readme.md
24
readme.md
|
|
@ -37,6 +37,30 @@ Type or use arrow keys to navigate your list of branches.
|
|||
|
||||
Hit `ctrl-o` to see the branch diff.
|
||||
|
||||
#### Worktree Support
|
||||
|
||||
If a branch is already open in a separate git worktree, `git recent` will detect it and provide instructions to `cd` there. To enable **automatic** directory switching, add one of these wrapper functions to your shell config:
|
||||
|
||||
##### Bash / Zsh (in `.bashrc` or `.zshrc`)
|
||||
```bash
|
||||
recent() {
|
||||
local o=$(command git-recent "$@")
|
||||
[ -d "$o" ] && cd "$o" || [ -n "$o" ] && echo "$o"
|
||||
}
|
||||
```
|
||||
|
||||
##### Fish (in `~/.config/fish/config.fish`)
|
||||
```fish
|
||||
function recent
|
||||
set -l o (command git-recent $argv)
|
||||
if test -d "$o"
|
||||
cd $o
|
||||
else if test -n "$o"
|
||||
echo $o
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
--------------------------------
|
||||
|
||||
# `git recent-og`
|
||||
|
|
|
|||
Loading…
Reference in a new issue