Last active
July 28, 2023 13:11
-
-
Save thanharrow/d6a1c43642c51c3ebe1dfce8040342de to your computer and use it in GitHub Desktop.
fzf-git.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://github.com/junegunn/fzf-git.sh | |
# The MIT License (MIT) | |
# | |
# Copyright (c) 2022 Junegunn Choi | |
# | |
# Permission is hereby granted, free of charge, to any person obtaining a copy | |
# of this software and associated documentation files (the "Software"), to deal | |
# in the Software without restriction, including without limitation the rights | |
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
# copies of the Software, and to permit persons to whom the Software is | |
# furnished to do so, subject to the following conditions: | |
# | |
# The above copyright notice and this permission notice shall be included in | |
# all copies or substantial portions of the Software. | |
# | |
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
# THE SOFTWARE. | |
if [[ $# -eq 1 ]]; then | |
branches() { | |
git branch "$@" --sort=-committerdate --sort=-HEAD --format=$'%(HEAD) %(color:yellow)%(refname:short) %(color:green)(%(committerdate:relative))\t%(color:blue)%(subject)%(color:reset)' --color=always | column -ts$'\t' | |
} | |
refs() { | |
git for-each-ref --sort=-creatordate --sort=-HEAD --color=always --format=$'%(refname) %(color:green)(%(creatordate:relative))\t%(color:blue)%(subject)%(color:reset)' | | |
eval "$1" | | |
sed 's#^refs/remotes/#\x1b[95mremote-branch\t\x1b[33m#; s#^refs/heads/#\x1b[92mbranch\t\x1b[33m#; s#^refs/tags/#\x1b[96mtag\t\x1b[33m#; s#refs/stash#\x1b[91mstash\t\x1b[33mrefs/stash#' | | |
column -ts$'\t' | |
} | |
case "$1" in | |
branches) | |
echo $'CTRL-O (open in browser) β± ALT-A (show all branches)\n' | |
branches | |
;; | |
all-branches) | |
echo $'CTRL-O (open in browser)\n' | |
branches -a | |
;; | |
refs) | |
echo $'CTRL-O (open in browser) β± ALT-E (examine in editor) β± ALT-A (show all refs)\n' | |
refs 'grep -v ^refs/remotes' | |
;; | |
all-refs) | |
echo $'CTRL-O (open in browser) β± ALT-E (examine in editor)\n' | |
refs 'cat' | |
;; | |
nobeep) ;; | |
*) exit 1 ;; | |
esac | |
elif [[ $# -gt 1 ]]; then | |
set -e | |
branch=$(git rev-parse --abbrev-ref HEAD 2> /dev/null) | |
if [[ $branch = HEAD ]]; then | |
branch=$(git describe --exact-match --tags 2> /dev/null || git rev-parse --short HEAD) | |
fi | |
# Only supports GitHub for now | |
case "$1" in | |
commit) | |
hash=$(grep -o "[a-f0-9]\{7,\}" <<< "$2") | |
path=/commit/$hash | |
;; | |
branch|remote-branch) | |
branch=$(sed 's/^[* ]*//' <<< "$2" | cut -d' ' -f1) | |
remote=$(git config branch."${branch}".remote || echo 'origin') | |
branch=${branch#$remote/} | |
path=/tree/$branch | |
;; | |
remote) | |
remote=$2 | |
path=/tree/$branch | |
;; | |
file) path=/blob/$branch/$(git rev-parse --show-prefix)$2 ;; | |
tag) path=/releases/tag/$2 ;; | |
*) exit 1 ;; | |
esac | |
remote=${remote:-$(git config branch."${branch}".remote || echo 'origin')} | |
remote_url=$(git remote get-url "$remote" 2> /dev/null || echo "$remote") | |
if [[ $remote_url =~ ^git@ ]]; then | |
url=${remote_url%.git} | |
url=${url#git@} | |
url=https://${url/://} | |
elif [[ $remote_url =~ ^http ]]; then | |
url=${remote_url%.git} | |
fi | |
case "$(uname -s)" in | |
Darwin) open "$url$path" ;; | |
*) xdg-open "$url$path" ;; | |
esac | |
exit 0 | |
fi | |
if [[ $- =~ i ]]; then | |
# ----------------------------------------------------------------------------- | |
# Redefine this function to change the options | |
_fzf_git_fzf() { | |
fzf-tmux -p80%,60% -- \ | |
--layout=reverse --multi --height=50% --min-height=20 --border \ | |
--border-label-pos=2 \ | |
--color='header:italic:underline,label:blue' \ | |
--preview-window='right,50%,border-left' \ | |
--bind='ctrl-/:change-preview-window(down,50%,border-top|hidden|)' "$@" | |
} | |
_fzf_git_check() { | |
git rev-parse HEAD > /dev/null 2>&1 && return | |
[[ -n $TMUX ]] && tmux display-message "Not in a git repository" | |
return 1 | |
} | |
__fzf_git=${BASH_SOURCE[0]:-${(%):-%x}} | |
__fzf_git=$(readlink -f "$__fzf_git" 2> /dev/null || /usr/bin/ruby --disable-gems -e 'puts File.expand_path(ARGV.first)' "$__fzf_git" 2> /dev/null) | |
if [[ -z $_fzf_git_cat ]]; then | |
# Sometimes bat is installed as batcat | |
export _fzf_git_cat="cat" | |
_fzf_git_bat_options="--style='${BAT_STYLE:-full}' --color=always --pager=never" | |
if command -v batcat > /dev/null; then | |
_fzf_git_cat="batcat $_fzf_git_bat_options" | |
elif command -v bat > /dev/null; then | |
_fzf_git_cat="bat $_fzf_git_bat_options" | |
fi | |
fi | |
_fzf_git_files() { | |
_fzf_git_check || return | |
(git -c color.status=always status --short --no-branch | |
git ls-files | grep -vxFf <(git status -s | grep '^[^?]' | cut -c4-; echo :) | sed 's/^/ /') | | |
_fzf_git_fzf -m --ansi --nth 2..,.. \ | |
--border-label 'π Files' \ | |
--header $'CTRL-O (open in browser) β± ALT-E (open in editor)\n\n' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git file {-1}" \ | |
--bind "alt-e:execute:${EDITOR:-vim} {-1} > /dev/tty" \ | |
--preview "git diff --no-ext-diff --color=always -- {-1} | sed 1,4d; $_fzf_git_cat {-1}" "$@" | | |
cut -c4- | sed 's/.* -> //' | |
} | |
_fzf_git_branches() { | |
_fzf_git_check || return | |
bash "$__fzf_git" branches | | |
_fzf_git_fzf --ansi \ | |
--border-label 'π² Branches' \ | |
--header-lines 2 \ | |
--tiebreak begin \ | |
--preview-window down,border-top,40% \ | |
--color hl:underline,hl+:underline \ | |
--no-hscroll \ | |
--bind 'ctrl-/:change-preview-window(down,70%|hidden|)' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git branch {}" \ | |
--bind "alt-a:change-prompt(π³ All branches> )+reload:bash \"$__fzf_git\" all-branches" \ | |
--preview 'git log -n 100 --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" {1}' "$@" | | |
sed 's/^..//' | cut -d' ' -f1 | |
} | |
_fzf_git_tags() { | |
_fzf_git_check || return | |
git tag --sort -version:refname | | |
_fzf_git_fzf --preview-window right,70% \ | |
--border-label 'π Tags' \ | |
--header $'CTRL-O (open in browser)\n\n' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git tag {}" \ | |
--preview 'git show --color=always {}' "$@" | |
} | |
_fzf_git_hashes() { | |
_fzf_git_check || return | |
git log -n 100 --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" --graph --color=always | | |
_fzf_git_fzf --ansi --no-sort --bind 'ctrl-s:toggle-sort' \ | |
--border-label 'π‘ Hashes' \ | |
--header $'CTRL-O (open in browser) β± CTRL-D (diff) β± CTRL-S (toggle sort)\n\n' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git commit {}" \ | |
--bind 'ctrl-d:execute:echo {} | grep -o "[a-f0-9]\{7,\}" | head -n 1 | xargs git diff > /dev/tty' \ | |
--color hl:underline,hl+:underline \ | |
--preview 'echo {} | grep -o "[a-f0-9]\{7,\}" | head -n 1 | xargs git show --color=always' "$@" | | |
awk 'match($0, /[a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9][a-f0-9]*/) { print substr($0, RSTART, RLENGTH) }' | |
} | |
_fzf_git_remotes() { | |
_fzf_git_check || return | |
git remote -v | awk '{print $1 "\t" $2}' | uniq | | |
_fzf_git_fzf --tac \ | |
--border-label 'π‘ Remotes' \ | |
--header $'CTRL-O (open in browser)\n\n' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git remote {1}" \ | |
--preview-window right,70% \ | |
--preview 'git log -n 100 --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" {1}/"$(git rev-parse --abbrev-ref HEAD)"' "$@" | | |
cut -d$'\t' -f1 | |
} | |
_fzf_git_stashes() { | |
_fzf_git_check || return | |
git stash list | _fzf_git_fzf \ | |
--border-label 'π₯‘ Stashes' \ | |
--header $'CTRL-X (drop stash)\n\n' \ | |
--bind 'ctrl-x:execute-silent(git stash drop {1})+reload(git stash list)' \ | |
-d: --preview 'git show --color=always {1}' "$@" | | |
cut -d: -f1 | |
} | |
_fzf_git_oreflogs() { | |
_fzf_git_check || return | |
git reflog -n 100 --color=always --format="%C(blue)%gD %C(yellow)%h%C(auto)%d %gs" | _fzf_git_fzf --ansi \ | |
--border-label 'π Reflogs' \ | |
--preview 'git show --color=always {1}' "$@" | | |
awk '{print $1}' | |
} | |
_fzf_git_logs() { | |
_fzf_git_check || return | |
git log -n 100 --color=always --graph --date=short --format="%C(auto)%cd %h%d %s %C(magenta)[%an]" | _fzf_git_fzf --ansi \ | |
--border-label 'π Logs' \ | |
--preview 'git show --color=always (echo {} | grep -o "[a-f0-9]\{7,\}") | head -'$LINES | \ | |
awk '{print $3}' "$@" | | |
awk '{print $1}' | |
} | |
_fzf_git_each_ref() { | |
_fzf_git_check || return | |
bash "$__fzf_git" refs | _fzf_git_fzf --ansi \ | |
--nth 2,2.. \ | |
--tiebreak begin \ | |
--border-label 'βοΈ Each ref' \ | |
--header-lines 2 \ | |
--preview-window down,border-top,40% \ | |
--color hl:underline,hl+:underline \ | |
--no-hscroll \ | |
--bind 'ctrl-/:change-preview-window(down,70%|hidden|)' \ | |
--bind "ctrl-o:execute-silent:bash $__fzf_git {1} {2}" \ | |
--bind "alt-e:execute:${EDITOR:-vim} <(git show {2}) > /dev/tty" \ | |
--bind "alt-a:change-prompt(π Every ref> )+reload:bash \"$__fzf_git\" all-refs" \ | |
--preview 'git log -n 100 --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" {2}' "$@" | | |
awk '{print $2}' | |
} | |
if [[ -n "${BASH_VERSION:-}" ]]; then | |
__fzf_git_init() { | |
bind '"\er": redraw-current-line' | |
local o | |
for o in "$@"; do | |
bind '"\C-g\C-'${o:0:1}'": "`_fzf_git_'$o'`\e\C-e\er"' | |
bind '"\C-g'${o:0:1}'": "`_fzf_git_'$o'`\e\C-e\er"' | |
done | |
} | |
elif [[ -n "${ZSH_VERSION:-}" ]]; then | |
__fzf_git_join() { | |
local item | |
while read item; do | |
echo -n "${(q)item} " | |
done | |
} | |
__fzf_git_init() { | |
local o | |
for o in "$@"; do | |
eval "fzf-git-$o-widget() { local result=\$(_fzf_git_$o | __fzf_git_join); zle reset-prompt; LBUFFER+=\$result }" | |
eval "zle -N fzf-git-$o-widget" | |
eval "bindkey '^g^${o[1]}' fzf-git-$o-widget" | |
eval "bindkey '^g${o[1]}' fzf-git-$o-widget" | |
done | |
eval "bindkey '^b' fzf-git-branches-widget" | |
} | |
fi | |
__fzf_git_init files branches tags remotes hashes stashes oreflogs each_ref logs | |
# ----------------------------------------------------------------------------- | |
fi | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment