about summary refs log tree commit diff stats
path: root/fzf-git
diff options
context:
space:
mode:
authorDavid Morgan <djm_uk@protonmail.com>2021-01-06 14:04:11 +0000
committerDavid Morgan <djm_uk@protonmail.com>2021-01-06 14:04:11 +0000
commita2629cfd8c142ee7dcc22a28020e3556628535b0 (patch)
tree7d9c37cdec211dd2fd5ddaa2894933b0f0bfcc17 /fzf-git
parent782c3991d8ff830820a2b2c0e84de9204e9dff33 (diff)
downloaddotfiles-a2629cfd8c142ee7dcc22a28020e3556628535b0.tar.gz
Add zsh/zprezto, starship and neovim config
Diffstat (limited to 'fzf-git')
-rw-r--r--fzf-git/forgit.plugin.zsh228
-rw-r--r--fzf-git/functions.sh52
-rw-r--r--fzf-git/key-binding.zsh17
3 files changed, 297 insertions, 0 deletions
diff --git a/fzf-git/forgit.plugin.zsh b/fzf-git/forgit.plugin.zsh
new file mode 100644
index 0000000..a9c0f20
--- /dev/null
+++ b/fzf-git/forgit.plugin.zsh
@@ -0,0 +1,228 @@
+# MIT (c) Wenxuan Zhang
+forgit::warn() { printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; }
+forgit::info() { printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$@" >&2; }
+forgit::inside_work_tree() { git rev-parse --is-inside-work-tree >/dev/null; }
+
+# https://github.com/wfxr/emoji-cli
+hash emojify &>/dev/null && forgit_emojify='|emojify'
+
+forgit_pager=$(git config core.pager || echo 'cat')
+
+# git commit viewer
+forgit::log() {
+    forgit::inside_work_tree || return 1
+    local cmd opts graph files
+    files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
+    cmd="echo {} |grep -Eo '[a-f0-9]+' |head -1 |xargs -I% git show --color=always % -- $files | $forgit_pager"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        +s +m --tiebreak=index
+        --bind=\"enter:execute($cmd | LESS='-R' less)\"
+        --bind=\"ctrl-y:execute-silent(echo {} |grep -Eo '[a-f0-9]+' | head -1 | tr -d '\n' |${FORGIT_COPY_CMD:-pbcopy})\"
+        $FORGIT_LOG_FZF_OPTS
+    "
+    graph=--graph
+    [[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
+    eval "git log $graph --color=always --format='%C(auto)%h%d %s %C(black)%C(bold)%cr' $* $forgit_emojify" |
+        FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd"
+}
+
+# git diff viewer
+forgit::diff() {
+    forgit::inside_work_tree || return 1
+    local cmd files opts commit repo
+    [[ $# -ne 0 ]] && {
+        if git rev-parse "$1" -- &>/dev/null ; then
+            commit="$1" && files=("${@:2}")
+        else
+            files=("$@")
+        fi
+    }
+
+    repo="$(git rev-parse --show-toplevel)"
+    cmd="echo {} |sed 's/.*]  //' |xargs -I% git diff --color=always $commit -- '$repo/%' |$forgit_pager"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        +m -0 --bind=\"enter:execute($cmd |LESS='-R' less)\"
+        $FORGIT_DIFF_FZF_OPTS
+    "
+    eval "git diff --name-status $commit -- ${files[*]} | sed -E 's/^(.)[[:space:]]+(.*)$/[\1]  \2/'" |
+        FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd"
+}
+
+# git add selector
+forgit::add() {
+    forgit::inside_work_tree || return 1
+    # Add files if passed as arguments
+    [[ $# -ne 0 ]] && git add "$@" && return
+
+    local changed unmerged untracked files opts preview extract
+    changed=$(git config --get-color color.status.changed red)
+    unmerged=$(git config --get-color color.status.unmerged red)
+    untracked=$(git config --get-color color.status.untracked red)
+
+    # NOTE: paths listed by 'git status -su' mixed with quoted and unquoted style
+    # remove indicators | remove original path for rename case | remove surrounding quotes
+    extract="
+        sed 's/^.*]  //' |
+        sed 's/.* -> //' |
+        sed -e 's/^\\\"//' -e 's/\\\"\$//'"
+    preview="
+        file=\$(echo {} | $extract)
+        if (git status -s -- \$file | grep '^??') &>/dev/null; then  # diff with /dev/null for untracked files
+            git diff --color=always --no-index -- /dev/null \$file | $forgit_pager | sed '2 s/added:/untracked:/'
+        else
+            git diff --color=always -- \$file | $forgit_pager
+        fi"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        -0 -m --nth 2..,..
+        $FORGIT_ADD_FZF_OPTS
+    "
+    files=$(git -c color.status=always -c status.relativePaths=true status -su |
+        grep -F -e "$changed" -e "$unmerged" -e "$untracked" |
+        sed -E 's/^(..[^[:space:]]*)[[:space:]]+(.*)$/[\1]  \2/' |
+        FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview" |
+        sh -c "$extract")
+    [[ -n "$files" ]] && echo "$files"| tr '\n' '\0' |xargs -0 -I% git add % && git status -su && return
+    echo 'Nothing to add.'
+}
+
+# git reset HEAD (unstage) selector
+forgit::reset::head() {
+    forgit::inside_work_tree || return 1
+    local cmd files opts
+    cmd="git diff --cached --color=always -- {} | $forgit_pager "
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        -m -0
+        $FORGIT_RESET_HEAD_FZF_OPTS
+    "
+    files="$(git diff --cached --name-only --relative | FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd")"
+    [[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git reset -q HEAD % && git status --short && return
+    echo 'Nothing to unstage.'
+}
+
+# git checkout-restore selector
+forgit::restore() {
+    forgit::inside_work_tree || return 1
+    local cmd files opts
+    cmd="git diff --color=always -- {} | $forgit_pager"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        -m -0
+        $FORGIT_CHECKOUT_FZF_OPTS
+    "
+    files="$(git ls-files --modified "$(git rev-parse --show-toplevel)"| FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd")"
+    [[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git checkout % && git status --short && return
+    echo 'Nothing to restore.'
+}
+
+# git stash viewer
+forgit::stash::show() {
+    forgit::inside_work_tree || return 1
+    local cmd opts
+    cmd="echo {} |cut -d: -f1 |xargs -I% git stash show --color=always --ext-diff % |$forgit_pager"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        +s +m -0 --tiebreak=index --bind=\"enter:execute($cmd | LESS='-R' less)\"
+        $FORGIT_STASH_FZF_OPTS
+    "
+    git stash list | FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd"
+}
+
+# git clean selector
+forgit::clean() {
+    forgit::inside_work_tree || return 1
+    local files opts
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        -m -0
+        $FORGIT_CLEAN_FZF_OPTS
+    "
+    # Note: Postfix '/' in directory path should be removed. Otherwise the directory itself will not be removed.
+    files=$(git clean -xdfn "$@"| sed 's/^Would remove //' | FZF_DEFAULT_OPTS="$opts" fzf |sed 's#/$##')
+    [[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git clean -xdf '%' && return
+    echo 'Nothing to clean.'
+}
+
+# git ignore generator
+export FORGIT_GI_REPO_REMOTE=${FORGIT_GI_REPO_REMOTE:-https://github.com/dvcs/gitignore}
+export FORGIT_GI_REPO_LOCAL=${FORGIT_GI_REPO_LOCAL:-~/.forgit/gi/repos/dvcs/gitignore}
+export FORGIT_GI_TEMPLATES=${FORGIT_GI_TEMPLATES:-$FORGIT_GI_REPO_LOCAL/templates}
+
+forgit::ignore() {
+    [ -d "$FORGIT_GI_REPO_LOCAL" ] || forgit::ignore::update
+    local IFS cmd args cat opts
+    # https://github.com/sharkdp/bat.git
+    hash bat &>/dev/null && cat='bat -l gitignore --color=always' || cat="cat"
+    cmd="$cat $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null"
+    opts="
+        $FORGIT_FZF_DEFAULT_OPTS
+        -m --preview-window='right:70%'
+        $FORGIT_IGNORE_FZF_OPTS
+    "
+    # shellcheck disable=SC2206,2207
+    IFS=$'\n' args=($@) && [[ $# -eq 0 ]] && args=($(forgit::ignore::list | nl -nrn -w4 -s'  ' |
+        FZF_DEFAULT_OPTS="$opts" fzf --preview="$cmd" |awk '{print $2}'))
+    [ ${#args[@]} -eq 0 ] && return 1
+    # shellcheck disable=SC2068
+    if hash bat &>/dev/null; then
+        forgit::ignore::get ${args[@]} | bat -l gitignore
+    else
+        forgit::ignore::get ${args[@]}
+    fi
+}
+forgit::ignore::update() {
+    if [[ -d "$FORGIT_GI_REPO_LOCAL" ]]; then
+        forgit::info 'Updating gitignore repo...'
+        (cd "$FORGIT_GI_REPO_LOCAL" && git pull --no-rebase --ff) || return 1
+    else
+        forgit::info 'Initializing gitignore repo...'
+        git clone --depth=1 "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL"
+    fi
+}
+forgit::ignore::get() {
+    local item filename header
+    for item in "$@"; do
+        if filename=$(find -L "$FORGIT_GI_TEMPLATES" -type f \( -iname "${item}.gitignore" -o -iname "${item}" \) -print -quit); then
+            [[ -z "$filename" ]] && forgit::warn "No gitignore template found for '$item'." && continue
+            header="${filename##*/}" && header="${header%.gitignore}"
+            echo "### $header" && cat "$filename" && echo
+        fi
+    done
+}
+forgit::ignore::list() {
+    find "$FORGIT_GI_TEMPLATES" -print |sed -e 's#.gitignore$##' -e 's#.*/##' | sort -fu
+}
+forgit::ignore::clean() {
+    setopt localoptions rmstarsilent
+    [[ -d "$FORGIT_GI_REPO_LOCAL" ]] && rm -rf "$FORGIT_GI_REPO_LOCAL"
+}
+
+FORGIT_FZF_DEFAULT_OPTS="
+$FZF_DEFAULT_OPTS
+--ansi
+--height='80%'
+--bind='alt-k:preview-up,alt-p:preview-up'
+--bind='alt-j:preview-down,alt-n:preview-down'
+--bind='ctrl-r:toggle-all'
+--bind='ctrl-s:toggle-sort'
+--bind='?:toggle-preview'
+--bind='alt-w:toggle-preview-wrap'
+--preview-window='right:60%'
+$FORGIT_FZF_DEFAULT_OPTS
+"
+
+# register aliases
+# shellcheck disable=SC2139
+if [[ -z "$FORGIT_NO_ALIASES" ]]; then
+    alias "${forgit_add:-ga}"='forgit::add'
+    alias "${forgit_reset_head:-grh}"='forgit::reset::head'
+    alias "${forgit_log:-glo}"='forgit::log'
+    alias "${forgit_diff:-gd}"='forgit::diff'
+    alias "${forgit_ignore:-gi}"='forgit::ignore'
+    alias "${forgit_restore:-gcf}"='forgit::restore'
+    alias "${forgit_clean:-gclean}"='forgit::clean'
+    alias "${forgit_stash_show:-gss}"='forgit::stash::show'
+fi
diff --git a/fzf-git/functions.sh b/fzf-git/functions.sh
new file mode 100644
index 0000000..c1f6fb7
--- /dev/null
+++ b/fzf-git/functions.sh
@@ -0,0 +1,52 @@
+# GIT heart FZF
+# -------------
+
+is_in_git_repo() {
+  git rev-parse HEAD > /dev/null 2>&1
+}
+
+fzf-down() {
+  fzf --height 50% "$@" --border
+}
+
+_gf() {
+  is_in_git_repo || return
+  git -c color.status=always status --short |
+  fzf-down -m --ansi --nth 2..,.. \
+    --preview '(git diff --color=always -- {-1} | sed 1,4d; cat {-1}) | head -500' |
+  cut -c4- | sed 's/.* -> //'
+}
+
+_gb() {
+  is_in_git_repo || return
+  git branch -a --color=always | grep -v '/HEAD\s' | sort |
+  fzf-down --ansi --multi --tac --preview-window right:70% \
+    --preview 'git log --oneline --graph --date=short --color=always --pretty="format:%C(auto)%cd %h%d %s" $(sed s/^..// <<< {} | cut -d" " -f1) | head -'$LINES |
+  sed 's/^..//' | cut -d' ' -f1 |
+  sed 's#^remotes/##'
+}
+
+_gt() {
+  is_in_git_repo || return
+  git tag --sort -version:refname |
+  fzf-down --multi --preview-window right:70% \
+    --preview 'git show --color=always {} | head -'$LINES
+}
+
+_gh() {
+  is_in_git_repo || return
+  git log --date=short --format="%C(green)%C(bold)%cd %C(auto)%h%d %s (%an)" --graph --color=always |
+  fzf-down --ansi --no-sort --reverse --multi --bind 'ctrl-s:toggle-sort' \
+    --header 'Press CTRL-S to toggle sort' \
+    --preview 'grep -o "[a-f0-9]\{7,\}" <<< {} | xargs git show --color=always | head -'$LINES |
+  grep -o "[a-f0-9]\{7,\}"
+}
+
+_gr() {
+  is_in_git_repo || return
+  git remote -v | awk '{print $1 "\t" $2}' | uniq |
+  fzf-down --tac \
+    --preview 'git log --oneline --graph --date=short --pretty="format:%C(auto)%cd %h%d %s" {1} | head -200' |
+  cut -d$'\t' -f1
+}
+
diff --git a/fzf-git/key-binding.zsh b/fzf-git/key-binding.zsh
new file mode 100644
index 0000000..b2d00c5
--- /dev/null
+++ b/fzf-git/key-binding.zsh
@@ -0,0 +1,17 @@
+join-lines() {
+  local item
+  while read item; do
+    echo -n "${(q)item} "
+  done
+}
+
+bind-git-helper() {
+  local c
+  for c in $@; do
+    eval "fzf-g$c-widget() { local result=\$(_g$c | join-lines); zle reset-prompt; LBUFFER+=\$result }"
+    eval "zle -N fzf-g$c-widget"
+    eval "bindkey '^g^$c' fzf-g$c-widget"
+  done
+}
+bind-git-helper f b t r h
+unset -f bind-git-helper