/ scripts / forgit.zsh
forgit.zsh
  1  #!/usr/bin/env zsh
  2  
  3  forgit::warn() { printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; }
  4  forgit::info() { printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$@" >&2; }
  5  forgit::inside_work_tree() { git rev-parse --is-inside-work-tree >/dev/null; }
  6  
  7  # https://github.com/so-fancy/diff-so-fancy
  8  hash diff-so-fancy &>/dev/null && forgit_fancy='|diff-so-fancy'
  9  # https://github.com/wfxr/emoji-cli
 10  hash emojify &>/dev/null && forgit_emojify='|emojify'
 11  
 12  # git commit viewer
 13  forgit::log() {
 14      forgit::inside_work_tree || return 1
 15      local cmd="echo {} |grep -o '[a-f0-9]\{7\}' |head -1 |xargs -I% git show --color=always % $* $forgit_emojify $forgit_fancy"
 16      eval "git log --graph --color=always --format='%C(auto)%h%d %s %C(black)%C(bold)%cr' $* $forgit_emojify" |
 17          forgit::fzf +s +m --tiebreak=index \
 18              --bind="enter:execute($cmd | $PAGER)" \
 19              --bind="ctrl-y:execute-silent(echo {} |grep -o '[a-f0-9]\{7\}' |${FORGIT_COPY_CMD:-pbcopy})" \
 20              --preview="$cmd"
 21  }
 22  
 23  # git diff viewer
 24  forgit::diff() {
 25      forgit::inside_work_tree || return 1
 26      local cmd files
 27      cmd="git diff --color=always -- {} $forgit_emojify $forgit_fancy"
 28      files="$*"
 29      [[ $# -eq 0 ]] && files=$(git rev-parse --show-toplevel)
 30      git ls-files --modified "$files"|
 31          forgit::fzf +m -0 \
 32              --bind="enter:execute($cmd | $PAGER)" \
 33              --preview="$cmd"
 34  }
 35  
 36  # git add selector
 37  forgit::add() {
 38      forgit::inside_work_tree || return 1
 39      local changed unmerged untracked files
 40      changed=$(git config --get-color color.status.changed red)
 41      unmerged=$(git config --get-color color.status.unmerged red)
 42      untracked=$(git config --get-color color.status.untracked red)
 43      files=$(git -c color.status=always status --short |
 44          grep -F -e "$changed" -e "$unmerged" -e "$untracked"|
 45          awk '{printf "[%10s]  ", $1; $1=""; print $0}' |
 46          forgit::fzf -0 -m --nth 2..,.. \
 47              --preview="git diff --color=always -- {-1} $forgit_emojify $forgit_fancy" |
 48          cut -d] -f2 |
 49          sed 's/.* -> //') # for rename case
 50      [[ -n "$files" ]] && echo "$files" |xargs -I{} git add {} && git status --short && return
 51      echo 'Nothing to add.'
 52  }
 53  
 54  # git checkout-restore selector
 55  forgit::restore() {
 56      forgit::inside_work_tree || return 1
 57      local cmd files
 58      cmd="git diff --color=always -- {} $forgit_emojify $forgit_fancy"
 59      files="$(git ls-files --modified "$(git rev-parse --show-toplevel)"|
 60          forgit::fzf -m -0 --preview="$cmd")"
 61      [[ -n "$files" ]] && echo "$files" |xargs -I{} git checkout {} && git status --short && return
 62      echo 'Nothing to restore.'
 63  }
 64  
 65  # git clean selector
 66  forgit::clean() {
 67      forgit::inside_work_tree || return 1
 68      local files
 69      # Note: Postfix '/' in directory path should be removed. Otherwise the directory itself will not be removed.
 70      files=$(git clean -xdfn "$@"| awk '{print $3}'| forgit::fzf -m -0 |sed 's#/$##')
 71      [[ -n "$files" ]] && echo "$files" |xargs -I{} git clean -xdf {}
 72      echo 'Nothing to clean.'
 73  }
 74  
 75  # git ignore generator
 76  export FORGIT_GI_REPO_REMOTE=${FORGIT_GI_REPO_REMOTE:-https://github.com/dvcs/gitignore}
 77  export FORGIT_GI_REPO_LOCAL=${FORGIT_GI_REPO_LOCAL:-~/.forgit/gi/repos/dvcs/gitignore}
 78  export FORGIT_GI_TEMPLATES=${FORGIT_GI_TEMPLATES:-$FORGIT_GI_REPO_LOCAL/templates}
 79  
 80  forgit::ignore() {
 81      [ -d "$FORGIT_GI_REPO_LOCAL" ] || forgit::ignore::update
 82      local IFS cmd args cat
 83      # https://github.com/sharkdp/bat.git
 84      hash bat &>/dev/null && cat='bat -l gitignore --color=always' || cat="cat"
 85      cmd="$cat $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null"
 86      # shellcheck disable=SC2206,2207
 87      IFS=$'\n' args=($@) && [[ $# -eq 0 ]] && args=($(forgit::ignore::list | nl -nrn -w4 -s'  ' |
 88          forgit::fzf -m --preview="$cmd" --preview-window="right:70%" | awk '{print $2}'))
 89      [ ${#args[@]} -eq 0 ] && return 1
 90      # shellcheck disable=SC2068
 91      if hash bat &>/dev/null; then
 92          forgit::ignore::get ${args[@]} | bat -l gitignore
 93      else
 94          forgit::ignore::get ${args[@]}
 95      fi
 96  }
 97  forgit::ignore::update() {
 98      if [[ -d "$FORGIT_GI_REPO_LOCAL" ]]; then
 99          forgit::info 'Updating gitignore repo...'
100          (cd "$FORGIT_GI_REPO_LOCAL" && git pull --no-rebase --ff) || return 1
101      else
102          forgit::info 'Initializing gitignore repo...'
103          git clone --depth=1 "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL"
104      fi
105  }
106  forgit::ignore::get() {
107      local item filename header
108      for item in "$@"; do
109          if filename=$(find -L "$FORGIT_GI_TEMPLATES" -type f \( -iname "${item}.gitignore" -o -iname "${item}" \) -print -quit); then
110              [[ -z "$filename" ]] && forgit::warn "No gitignore template found for '$item'." && continue
111              header="${filename##*/}" && header="${header%.gitignore}"
112              echo "### $header" && cat "$filename" && echo
113          fi
114      done
115  }
116  forgit::ignore::list() {
117      find "$FORGIT_GI_TEMPLATES" -print |sed -e 's#.gitignore$##' -e 's#.*/##' | sort -fu
118  }
119  
120  forgit::fzf() {
121      FZF_DEFAULT_OPTS="
122          $FZF_DEFAULT_OPTS
123          --ansi
124          --height '80%'
125          --bind='alt-k:preview-up,alt-p:preview-up'
126          --bind='alt-j:preview-down,alt-n:preview-down'
127          --bind='ctrl-r:toggle-all'
128          --bind='ctrl-s:toggle-sort'
129          --bind='?:toggle-preview'
130          --preview-window='right:60%'
131          --bind='alt-w:toggle-preview-wrap'
132          $FORGIT_FZF_DEFAULT_OPTS
133      " fzf "$@"
134  }
135  
136  "forgit::$1"