No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

fzgit 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/usr/bin/env bash
  2. # /usr/share/bash-completion/completions/git
  3. # https://github.com/junegunn/fzf/wiki/examples#git
  4. # declare BOLD=$(tput bold || tput md)
  5. declare c_reset=$(tput sgr0)
  6. declare c_green=$(tput setaf 2 || tput AF 2)
  7. declare c_red=$(tput setaf 1 || tput AF 1)
  8. has() { # {{{
  9. command -v "$1" &> /dev/null
  10. }
  11. # }}}
  12. ask() { # {{{
  13. read -r -n1 -p "$* " ans
  14. echo
  15. [[ ${ans^} == Y* ]]
  16. }
  17. # }}}
  18. err() { # {{{
  19. printf "${c_red}%s${c_reset}" "$*" >&2
  20. }
  21. # }}}
  22. die() { # {{{
  23. [[ -n "$1" ]] && err "$1"
  24. exit 1
  25. }
  26. # }}}
  27. has fzf || die 'fzf not found'
  28. # [[ -d "$PWD/.git" ]] || die 'not a git repo'
  29. fzf() { # {{{
  30. local prompt
  31. if [[ $1 == --prompt=* ]]; then
  32. prompt="${1##*=}>"
  33. shift
  34. fi
  35. branch=$(git status 2> /dev/null | sed 's/On branch />/;q')
  36. opts=( +s -e -i --reverse --cycle --prompt="fzgit${branch}>${prompt} " )
  37. [[ -v FZMP_FZF_OPTIONS ]] && opts=( $FZMP_FZF_OPTIONS )
  38. command fzf "${opts[@]}" \
  39. --inline-info \
  40. --ansi \
  41. --no-clear \
  42. "$@"
  43. }
  44. # }}}
  45. declare -A git_cmds_descriptions=( # {{{
  46. ['add']='Add file contents to the index'
  47. ['am']='Apply a series of patches from a mailbox'
  48. ['annotate']='Annotate file lines with commit information'
  49. ['apply']='Apply a patch to files and/or to the index'
  50. ['archive']='Create an archive of files from a named tree'
  51. ['bisect']='Find by binary search the change that introduced a bug'
  52. ['blame']='Show what revision and author last modified each line of a'
  53. ['branch']='List, create, or delete branches'
  54. ['bundle']='Move objects and refs by archive'
  55. ['cat-file']='Provide content or type and size information for'
  56. ['check-attr']='Display gitattributes information'
  57. ['check-ignore']='Debug gitignore / exclude files'
  58. ['check-mailmap']='Show canonical names and email addresses of'
  59. ['check-ref-format']='Ensures that a reference name is well formed'
  60. ['checkout']='Checkout a branch or paths to the working tree'
  61. ['checkout-index']='Copy files from the index to the working tree'
  62. ['cherry']='Find commits yet to be applied to upstream'
  63. ['cherry-pick']='Apply the changes introduced by some existing commits'
  64. ['citool']='Graphical alternative to git-commit'
  65. ['clean']='Remove untracked files from the working tree'
  66. ['clone']='Clone a repository into a new directory'
  67. ['column']='Display data in columns'
  68. ['commit']='Record changes to the repository'
  69. ['commit-tree']='Create a new commit object'
  70. ['config']='Get and set repository or global options'
  71. ['count-objects']='Count unpacked number of objects and their disk'
  72. ['credential']='Retrieve and store user credentials'
  73. ['daemon']='A really simple server for Git repositories'
  74. ['describe']='Show the most recent tag that is reachable from a commit'
  75. ['diff']='Show changes between commits, commit and working tree, etc'
  76. ['diff-files']='Compares files in the working tree and the index'
  77. ['diff-index']='Compare a tree to the working tree or index'
  78. ['diff-tree']='Compares the content and mode of blobs found via two'
  79. ['difftool']='Show changes using common diff tools'
  80. ['fast-export']='Git data exporter'
  81. ['fast-import']='Backend for fast Git data importers'
  82. ['fetch']='Download objects and refs from another repository'
  83. ['fetch-pack']='Receive missing objects from another repository'
  84. ['filter-branch']='Rewrite branches'
  85. ['fmt-merge-msg']='Produce a merge commit message'
  86. ['for-each-ref']='Output information on each ref'
  87. ['format-patch']='Prepare patches for e-mail submission'
  88. ['fsck']='Verifies the connectivity and validity of the objects in the'
  89. ['fsck-objects']='Verifies the connectivity and validity of the'
  90. ['gc']='Cleanup unnecessary files and optimize the local repository'
  91. ['get-tar-commit-id']='Extract commit ID from an archive created using'
  92. ['grep']='Print lines matching a pattern'
  93. ['gui']='A portable graphical interface to Git'
  94. ['hash-object']='Compute object ID and optionally creates a blob from'
  95. ['help']='Display help information about Git'
  96. ['http-backend']='Server side implementation of Git over HTTP'
  97. ['http-fetch']='Download from a remote Git repository via HTTP'
  98. ['http-push']='Push objects over HTTP/DAV to another repository'
  99. ['imap-send']='Send a collection of patches from stdin to an IMAP'
  100. ['index-pack']='Build pack index file for an existing packed archive'
  101. ['init']='Create an empty Git repository or reinitialize an existing'
  102. ['init-db']='Creates an empty Git repository'
  103. ['instaweb']='Instantly browse your working repository in gitweb'
  104. ['log']='Show commit logs'
  105. ['ls-files']='Show information about files in the index and the'
  106. ['ls-remote']='List references in a remote repository'
  107. ['ls-tree']='List the contents of a tree object'
  108. ['mailinfo']='Extracts patch and authorship from a single e-mail'
  109. ['mailsplit']='Simple UNIX mbox splitter program'
  110. ['merge']='Join two or more development histories together'
  111. ['merge-base']='Find as good common ancestors as possible for a merge'
  112. ['merge-file']='Run a three-way file merge'
  113. ['merge-index']='Run a merge for files needing merging'
  114. ['merge-one-file']='The standard helper program to use with'
  115. ['merge-tree']='Show three-way merge without touching index'
  116. ['mergetool']='Run merge conflict resolution tools to resolve merge'
  117. ['mktag']='Creates a tag object'
  118. ['mktree']='Build a tree-object from ls-tree formatted text'
  119. ['mv']='Move or rename a file, a directory, or a symlink'
  120. ['notes']='Add or inspect object notes'
  121. ['pack-objects']='Create a packed archive of objects'
  122. ['pack-redundant']='Find redundant pack files'
  123. ['pack-refs']='Pack heads and tags for efficient repository access'
  124. ['patch-id']='Compute unique ID for a patch'
  125. ['prune']='Prune all unreachable objects from the object database'
  126. ['prune-packed']='Remove extra objects that are already in pack files'
  127. ['pull']='Fetch from and integrate with another repository or a local'
  128. ['push']='Update remote refs along with associated objects'
  129. ['quiltimport']='Applies a quilt patchset onto the current branch'
  130. ['read-tree']='Reads tree information into the index'
  131. ['rebase']='Forward-port local commits to the updated upstream head'
  132. ['receive-pack']='Receive what is pushed into the repository'
  133. ['reflog']='Manage reflog information'
  134. ['relink']='Hardlink common objects in local repositories'
  135. ['remote']='Manage set of tracked repositories'
  136. ['remote-ext']='Bridge smart transport to external command.'
  137. ['remote-fd']='Reflect smart transport stream back to caller'
  138. ['repack']='Pack unpacked objects in a repository'
  139. ['replace']='Create, list, delete refs to replace objects'
  140. ['request-pull']='Generates a summary of pending changes'
  141. ['rerere']='Reuse recorded resolution of conflicted merges'
  142. ['reset']='Reset current HEAD to the specified state'
  143. ['rev-list']='Lists commit objects in reverse chronological order'
  144. ['rev-parse']='Pick out and massage parameters'
  145. ['revert']='Revert some existing commits'
  146. ['rm']='Remove files from the working tree and from the index'
  147. ['send-pack']='Push objects over Git protocol to another repository'
  148. ['sh-i18n--envsubst']="Git's own envsubst(1) for i18n fallbacks"
  149. ['shell']='Restricted login shell for Git-only SSH access'
  150. ['shortlog']="Summarize 'git log' output"
  151. ['show']='Show various types of objects'
  152. ['show-branch']='Show branches and their commits'
  153. ['show-index']='Show packed archive index'
  154. ['show-ref']='List references in a local repository'
  155. ['stage']='Add file contents to the staging area'
  156. ['stash']='Stash the changes in a dirty working directory away'
  157. ['status']='Show the working tree status'
  158. ['stripspace']='Remove unnecessary whitespace'
  159. ['submodule']='Initialize, update or inspect submodules'
  160. ['subtree']='Merge subtrees together and split repository into'
  161. ['symbolic-ref']='Read, modify and delete symbolic refs'
  162. ['tag']='Create, list, delete or verify a tag object signed with GPG'
  163. ['unpack-file']="Creates a temporary file with a blob's contents"
  164. ['unpack-objects']='Unpack objects from a packed archive'
  165. ['update-index']='Register file contents in the working tree to the'
  166. ['update-ref']='Update the object name stored in a ref safely'
  167. ['update-server-info']='Update auxiliary info file to help dumb'
  168. ['upload-archive']='Send archive back to git-archive'
  169. ['upload-pack']='Send objects packed back to git-fetch-pack'
  170. ['var']='Show a Git logical variable'
  171. ['verify-commit']='Check the GPG signature of commits'
  172. ['verify-pack']='Validate packed Git archive files'
  173. ['verify-tag']='Check the GPG signature of tags'
  174. ['web--browse']='Git helper script to launch a web browser'
  175. ['whatchanged']='Show logs with difference each commit introduces'
  176. ['write-tree']='Create a tree object from the current index'
  177. )
  178. # }}}
  179. declare -A implemented_git_cmds=(
  180. ['stash']='git_stash'
  181. ['add']='git_add'
  182. ['checkout']='git_checkout'
  183. ['commit']='git commit -v'
  184. ['push']='git push'
  185. ['log']='git_log'
  186. )
  187. git_log() { # {{{
  188. local show="git show --color=always \"\$(grep -m1 -o \"[a-f0-9]\{7\}\" <<< {})\""
  189. fzf --prompt='log' -e --no-sort --tiebreak=index \
  190. --bind="enter:execute:$show | less -R" \
  191. --preview="$show" \
  192. < <(git log --graph --color=always \
  193. --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@")
  194. }
  195. # }}}
  196. git_checkout() { # {{{
  197. local list response key branch header
  198. list=$(git branch --all --color -vv; git tag) || return 1
  199. mapfile -t response < <(fzf --prompt='checkout' \
  200. --header="$header" --expect=ctrl-x <<< "$list")
  201. key="${response[0]}"
  202. branch=$(perl -pe 's/^\*?\s*(remotes\/[^\/]*\/)?([^ ]+).*/\2/' <<< "${response[1]}")
  203. git checkout "$branch" || return 1
  204. }
  205. # }}}
  206. git_add() { # {{{
  207. local out response query key header
  208. header='use ctrl-p to add in patch mode'
  209. while out=$(git ls-files -mo --exclude-standard |
  210. fzf --prompt='add' --tac --multi \
  211. --header="$header" --query="$query" --print-query \
  212. --preview='git diff --color=always {} 2>&1' \
  213. --preview-window=up \
  214. --bind=ctrl-p:accept --expect=ctrl-p)
  215. do
  216. mapfile -t response <<< "$out"
  217. query="${response[0]}" && unset response[0]
  218. key="${response[1]}" && unset response[1]
  219. [[ "${#response[@]}" == 0 ]] && continue
  220. if [[ "$key" == 'ctrl-p' ]]; then
  221. git add -p "${response[@]}" < /dev/tty
  222. else
  223. git add "${response[@]}"
  224. fi
  225. done
  226. }
  227. # }}}
  228. git_stash() { # {{{
  229. local out response query key sha header
  230. header='use ctrl-d to show a diff or ctrl-b to create a new branch'
  231. while out=$(git stash list \
  232. --pretty="%C(yellow)%h %>(14)%Cgreen%cr %C(blue)%gs" |
  233. fzf --prompt='stash' --no-sort --header="$header" \
  234. --query="$query" --print-query \
  235. --preview='git diff --color {1}' \
  236. --expect=ctrl-d,ctrl-b)
  237. do
  238. mapfile -t response <<< "$out"
  239. query="${response[0]}" && unset response[0]
  240. key="${response[1]}" && unset response[1]
  241. sha="${response[-1]}"
  242. sha="${sha%% *}"
  243. [[ -z "$sha" ]] && continue
  244. case "$key" in
  245. 'ctrl-d') git diff "$sha" --color=always | less -R ;;
  246. 'ctrl-b') git stash branch "stash-$sha" "$sha" ;;
  247. *) git stash show -p "$sha" --color=always | less -R ;;
  248. esac
  249. done
  250. }
  251. # }}}
  252. pick_cmd() {
  253. for c in "${!implemented_git_cmds[@]}"; do
  254. printf '%s%-15s%s -- %s\n' "${c_green}" "$c" "${c_reset}" "${git_cmds_descriptions[$c]}"
  255. done | fzf | awk '{print $1}'
  256. }
  257. main() {
  258. local pick
  259. while pick=$(pick_cmd); do
  260. if [[ -n $pick ]] && has "${implemented_git_cmds[${pick%% *}]}"; then
  261. ${implemented_git_cmds[$pick]}
  262. else
  263. break
  264. fi
  265. done
  266. }
  267. reset_screen() {
  268. tput rmcup
  269. }
  270. finish() {
  271. reset_screen
  272. }
  273. trap finish EXIT SIGINT SIGTERM
  274. main