mirror of
https://github.com/wfxr/forgit.git
synced 2026-09-10 15:26:16 -04:00
Merge pull request #241 from carlfriedrich/move-implementation-to-standalone-forgit
Move implementation to standalone forgit
This commit is contained in:
commit
43f5f1c58e
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
|
|
@ -47,7 +47,7 @@ jobs:
|
|||
fish --version; echo
|
||||
|
||||
- name: Shellcheck
|
||||
run: shellcheck forgit.plugin.sh
|
||||
run: shellcheck forgit.plugin.sh bin/git-forgit
|
||||
|
||||
- name: Test bash
|
||||
run: bash forgit.plugin.sh
|
||||
|
|
|
|||
45
README.md
45
README.md
|
|
@ -41,7 +41,7 @@ zgen load 'wfxr/forgit'
|
|||
# for antigen
|
||||
antigen bundle 'wfxr/forgit'
|
||||
|
||||
# for fisher
|
||||
# for fisher (requires fisher v4.4.3 or higher)
|
||||
fisher install wfxr/forgit
|
||||
|
||||
# for omf
|
||||
|
|
@ -51,16 +51,7 @@ omf install https://github.com/wfxr/forgit
|
|||
zinit load wfxr/forgit
|
||||
|
||||
# manually
|
||||
# Clone the repository and source it in your shell's rc file.
|
||||
```
|
||||
|
||||
You can run the following command to try `forgit` without installing:
|
||||
|
||||
``` bash
|
||||
# for bash / zsh
|
||||
source <(curl -sSL git.io/forgit)
|
||||
# for fish
|
||||
source (curl -sSL git.io/forgit-fish | psub)
|
||||
# Clone the repository and source it in your shell's rc file or put bin/git-forgit into your $PATH
|
||||
```
|
||||
|
||||
### Fig
|
||||
|
|
@ -69,7 +60,7 @@ source (curl -sSL git.io/forgit-fish | psub)
|
|||
|
||||
Install `forgit` in just one click.
|
||||
|
||||
<a href="https://fig.io/plugins/other/forgit" target="_blank"><img src="https://fig.io/badges/install-with-fig.svg" /></a>
|
||||
[](https://fig.io/plugins/other/forgit)
|
||||
|
||||
### 📝 Features
|
||||
|
||||
|
|
@ -137,9 +128,10 @@ For linux users `FORGIT_COPY_CMD` should be set to make copy work. Example: `FOR
|
|||
|
||||
### ⚙ Options
|
||||
|
||||
#### aliases
|
||||
Options can be set via environment variables. They have to be **exported** in
|
||||
order to be recognized by `forgit`.
|
||||
|
||||
##### shell
|
||||
#### shell aliases
|
||||
|
||||
You can change the default aliases by defining these variables below.
|
||||
(To disable all aliases, Set the `FORGIT_NO_ALIASES` flag.)
|
||||
|
|
@ -166,7 +158,7 @@ forgit_fixup=gfu
|
|||
|
||||
#### git
|
||||
|
||||
You can use git aliases by making `git-forgit` available in `$PATH`:
|
||||
You can use forgit as a subcommand of git by making `git-forgit` available in `$PATH`:
|
||||
|
||||
```sh
|
||||
# after `forgit` was loaded
|
||||
|
|
@ -177,10 +169,10 @@ export PATH="$PATH:$FORGIT_INSTALL_DIR/bin"
|
|||
|
||||
Then any forgit command will be a subcommand of git:
|
||||
|
||||
```
|
||||
$ git forgit log
|
||||
$ git forgit add
|
||||
$ git forgit diff
|
||||
```cmd
|
||||
git forgit log
|
||||
git forgit add
|
||||
git forgit diff
|
||||
```
|
||||
|
||||
Optionally you can add [aliases in git](https://git-scm.com/book/en/v2/Git-Basics-Git-Aliases):
|
||||
|
|
@ -189,7 +181,7 @@ Optionally you can add [aliases in git](https://git-scm.com/book/en/v2/Git-Basic
|
|||
git config --global alias.cf 'forgit checkout_file'
|
||||
```
|
||||
|
||||
And use the alias in git:
|
||||
And use forgit functions via a git alias:
|
||||
|
||||
```sh
|
||||
git cf
|
||||
|
|
@ -216,7 +208,7 @@ You can add default fzf options for `forgit`, including keybinds, layout, etc.
|
|||
(No need to repeat the options already defined in `FZF_DEFAULT_OPTS`)
|
||||
|
||||
``` bash
|
||||
FORGIT_FZF_DEFAULT_OPTS="
|
||||
export FORGIT_FZF_DEFAULT_OPTS="
|
||||
--exact
|
||||
--border
|
||||
--cycle
|
||||
|
|
@ -256,18 +248,21 @@ Complete loading order of fzf options is:
|
|||
Examples:
|
||||
|
||||
- `ctrl-d` to drop the selected stash but do not quit fzf (`gss` specific).
|
||||
```
|
||||
FORGIT_STASH_FZF_OPTS='
|
||||
|
||||
```sh
|
||||
export FORGIT_STASH_FZF_OPTS='
|
||||
--bind="ctrl-d:reload(git stash drop $(cut -d: -f1 <<<{}) 1>/dev/null && git stash list)"
|
||||
'
|
||||
```
|
||||
|
||||
- `ctrl-e` to view the logs in a vim buffer (`glo` specific).
|
||||
```
|
||||
FORGIT_LOG_FZF_OPTS='
|
||||
|
||||
```sh
|
||||
export FORGIT_LOG_FZF_OPTS='
|
||||
--bind="ctrl-e:execute(echo {} |grep -Eo [a-f0-9]+ |head -1 |xargs git show |vim -)"
|
||||
'
|
||||
```
|
||||
|
||||
#### other options
|
||||
|
||||
| Option | Description | Default |
|
||||
|
|
|
|||
552
bin/git-forgit
552
bin/git-forgit
|
|
@ -1,7 +1,9 @@
|
|||
#!/usr/bin/env bash
|
||||
# This file is a bridge between forgit and git itself. This makes forgit a
|
||||
# subcommand of git, and forwards all arguments on to forgit. So, all of these
|
||||
# commands will work as expected:
|
||||
# MIT (c) Wenxuan Zhang
|
||||
|
||||
# This file is meant to be executed directly. If it's available on the PATH,
|
||||
# it can also be used as a subcommand of git, which then forwards all arguments
|
||||
# on to forgit. So, all of these commands will work as expected:
|
||||
#
|
||||
# `git forgit log`
|
||||
# `git forgit checkout_file`
|
||||
|
|
@ -10,8 +12,548 @@
|
|||
# This gives users the choice to set aliases inside of their git config instead
|
||||
# of their shell config if they prefer.
|
||||
|
||||
source "$FORGIT_INSTALL_DIR/forgit.plugin.zsh"
|
||||
# Set shell for fzf preview commands
|
||||
SHELL=/bin/bash
|
||||
|
||||
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%'
|
||||
+1
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
"
|
||||
|
||||
_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; }
|
||||
# tac is not available on OSX, tail -r is not available on Linux, so we use either of them
|
||||
_forgit_reverse_lines() { tac 2> /dev/null || tail -r; }
|
||||
|
||||
_forgit_previous_commit() {
|
||||
# "SHA~" is invalid when the commit is the first commit, but we can use "--root" instead
|
||||
if [[ "$(git rev-parse "$1")" == "$(git rev-list --max-parents=0 HEAD)" ]]; then
|
||||
echo "--root"
|
||||
else
|
||||
echo "$1~"
|
||||
fi
|
||||
}
|
||||
|
||||
# optional render emoji characters (https://github.com/wfxr/emoji-cli)
|
||||
hash emojify &>/dev/null && _forgit_emojify='|emojify'
|
||||
|
||||
# extract the first git sha occurring in the input and strip trailing newline
|
||||
_forgit_extract_sha="grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]'"
|
||||
|
||||
_forgit_pager=${FORGIT_PAGER:-$(git config core.pager || echo 'cat')}
|
||||
_forgit_show_pager=${FORGIT_SHOW_PAGER:-$(git config pager.show || echo "$_forgit_pager")}
|
||||
_forgit_diff_pager=${FORGIT_DIFF_PAGER:-$(git config pager.diff || echo "$_forgit_pager")}
|
||||
_forgit_ignore_pager=${FORGIT_IGNORE_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitignore --color=always' || echo 'cat')}
|
||||
_forgit_blame_pager=${FORGIT_BLAME_PAGER:-$(git config pager.blame || echo "$_forgit_pager")}
|
||||
_forgit_enter_pager=${FORGIT_ENTER_PAGER:-"LESS='-r' less"}
|
||||
|
||||
_forgit_log_format=${FORGIT_LOG_FORMAT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset}
|
||||
_forgit_log_preview_options="--graph --pretty=format:'$_forgit_log_format' --color=always --abbrev-commit --date=relative"
|
||||
_forgit_fullscreen_context=${FORGIT_FULLSCREEN_CONTEXT:-10}
|
||||
_forgit_preview_context=${FORGIT_PREVIEW_CONTEXT:-3}
|
||||
|
||||
# git commit viewer
|
||||
_forgit_log() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
local opts graph files log_format preview_cmd enter_cmd
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview_cmd="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always -U$_forgit_preview_context % -- $files | $_forgit_show_pager"
|
||||
enter_cmd="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always -U$_forgit_fullscreen_context % -- $files | $_forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"enter:execute($enter_cmd | $_forgit_enter_pager)\"
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $_forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_LOG_FZF_OPTS
|
||||
"
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
log_format=${FORGIT_GLO_FORMAT:-$_forgit_log_format}
|
||||
eval "git log $graph --color=always --format='$log_format' $* $_forgit_emojify" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
}
|
||||
|
||||
# git diff viewer
|
||||
_forgit_diff() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
local files opts commits repo get_files preview_cmd enter_cmd
|
||||
[[ $# -ne 0 ]] && {
|
||||
if git rev-parse "$1" -- &>/dev/null ; then
|
||||
if [[ $# -gt 1 ]] && git rev-parse "$2" -- &>/dev/null; then
|
||||
commits="$1 $2" && files=("${@:3}")
|
||||
else
|
||||
commits="$1" && files=("${@:2}")
|
||||
fi
|
||||
else
|
||||
files=("$@")
|
||||
fi
|
||||
}
|
||||
repo="$(git rev-parse --show-toplevel)"
|
||||
get_files="cd '$repo' && echo {} | sed 's/.*] *//' | sed 's/ -> / /'"
|
||||
# Git stashes are named "stash@{x}", which contains the fzf placeholder "{x}".
|
||||
# In order to support passing stashes as arguments to _forgit_diff, we have to
|
||||
# prevent fzf from interpreting this substring by escaping the opening bracket.
|
||||
# The string is evaluated a few subsequent times, so we need multiple escapes.
|
||||
escaped_commits=${commits//\{/\\\\\{}
|
||||
preview_cmd="$get_files | xargs -I% git diff --color=always -U$_forgit_preview_context $escaped_commits -- % | $_forgit_diff_pager"
|
||||
enter_cmd="$get_files | xargs -I% git diff --color=always -U$_forgit_fullscreen_context $escaped_commits -- % | $_forgit_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+m -0 --bind=\"enter:execute($enter_cmd | $_forgit_enter_pager)\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_DIFF_FZF_OPTS
|
||||
--prompt=\"$commits > \"
|
||||
"
|
||||
eval "git diff --name-status $commits -- ${files[*]} | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1]\t\2/'" |
|
||||
sed 's/\t/ -> /2' | expand -t 8 |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
}
|
||||
|
||||
# git add selector
|
||||
_forgit_add() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
# Add files if passed as arguments
|
||||
[[ $# -ne 0 ]] && git add "$@" && git status -su && 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_diff_pager | sed '2 s/added:/untracked:/'
|
||||
else
|
||||
git diff --color=always -- \$file | $_forgit_diff_pager
|
||||
fi"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-0 -m --nth 2..,..
|
||||
--preview=\"$preview\"
|
||||
$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 |
|
||||
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_diff_pager "
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_RESET_HEAD_FZF_OPTS
|
||||
"
|
||||
files="$(git diff --cached --name-only --relative | FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git reset -q HEAD % && git status --short && return
|
||||
echo 'Nothing to unstage.'
|
||||
}
|
||||
|
||||
# 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_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m -0 --tiebreak=index --bind=\"enter:execute($cmd | $_forgit_enter_pager)\"
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_STASH_FZF_OPTS
|
||||
"
|
||||
git stash list | FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
}
|
||||
|
||||
# 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 -xdffn "$@"| sed 's/^Would remove //' | FZF_DEFAULT_OPTS="$opts" fzf |sed 's#/$##')
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git clean -xdff '%' && git status --short && return
|
||||
echo 'Nothing to clean.'
|
||||
}
|
||||
|
||||
_forgit_cherry_pick() {
|
||||
local base target preview opts fzf_selection fzf_exitval
|
||||
base=$(git branch --show-current)
|
||||
[[ -z $1 ]] && echo "Please specify target branch" && return 1
|
||||
target="$1"
|
||||
preview="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always % | $_forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
--preview=\"$preview\"
|
||||
-m -0 --tiebreak=index
|
||||
$FORGIT_CHERRY_PICK_FZF_OPTS
|
||||
"
|
||||
fzf_selection=$(git cherry "$base" "$target" --abbrev -v | _forgit_reverse_lines |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
fzf_exitval=$?
|
||||
[[ $fzf_exitval != 0 ]] && return $fzf_exitval
|
||||
|
||||
commits=()
|
||||
while IFS="" read -r line
|
||||
do
|
||||
commits+=("$line")
|
||||
done < <(echo "$fzf_selection" | _forgit_reverse_lines | cut -d' ' -f2)
|
||||
|
||||
git cherry-pick "${commits[@]}"
|
||||
}
|
||||
|
||||
_forgit_cherry_pick_from_branch() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout -b "$@"; return $?; }
|
||||
local cmd preview opts branch exitval
|
||||
cmd="git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
preview="git log {1} $_forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHERRY_PICK_FROM_BRANCH_FZF_OPTS
|
||||
"
|
||||
# loop until either the branch selector is closed or a commit to be cherry
|
||||
# picked has been selected from within a branch
|
||||
while true
|
||||
do
|
||||
branch="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')"
|
||||
[[ -z "$branch" ]] && return 1
|
||||
|
||||
_forgit_cherry_pick "$branch"
|
||||
|
||||
exitval=$?
|
||||
[[ $exitval != 130 ]] && return $exitval
|
||||
done
|
||||
}
|
||||
|
||||
_forgit_rebase() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
local cmd preview opts graph files target_commit prev_commit
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
cmd="git log $graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always % -- $files | $_forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $_forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_REBASE_FZF_OPTS
|
||||
"
|
||||
target_commit=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha")
|
||||
if [[ -n "$target_commit" ]]; then
|
||||
prev_commit=$(_forgit_previous_commit "$target_commit")
|
||||
|
||||
git rebase -i "$prev_commit"
|
||||
fi
|
||||
}
|
||||
|
||||
_forgit_fixup() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1
|
||||
local cmd preview opts graph files target_commit prev_commit
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
cmd="git log $graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*")
|
||||
preview="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always % -- $files | $_forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $_forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_FIXUP_FZF_OPTS
|
||||
"
|
||||
target_commit=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha")
|
||||
if [[ -n "$target_commit" ]] && git commit --fixup "$target_commit"; then
|
||||
prev_commit=$(_forgit_previous_commit "$target_commit")
|
||||
# rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them
|
||||
# GIT_SEQUENCE_EDITOR=: is needed to skip the editor
|
||||
GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit"
|
||||
fi
|
||||
|
||||
}
|
||||
|
||||
# git checkout-file selector
|
||||
_forgit_checkout_file() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout -- "$@"; return $?; }
|
||||
local cmd files opts
|
||||
cmd="git diff --color=always -- {} | $_forgit_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_CHECKOUT_FILE_FZF_OPTS
|
||||
"
|
||||
files="$(git ls-files --modified "$(git rev-parse --show-toplevel)"| FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git checkout %
|
||||
}
|
||||
|
||||
# git checkout-branch selector
|
||||
_forgit_checkout_branch() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
# if called with arguments, check if branch exists, else create a new one
|
||||
if [[ $# -ne 0 ]]; then
|
||||
if git show-branch "$@" &>/dev/null; then
|
||||
git switch "$@"
|
||||
else
|
||||
git switch -c "$@"
|
||||
fi
|
||||
checkout_status=$?
|
||||
git status --short
|
||||
return $checkout_status
|
||||
fi
|
||||
|
||||
local cmd preview opts branch
|
||||
cmd="git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
preview="git log {1} $_forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_BRANCH_FZF_OPTS
|
||||
"
|
||||
branch="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')"
|
||||
[[ -z "$branch" ]] && return 1
|
||||
|
||||
# track the remote branch if possible
|
||||
if [[ "$branch" == "remotes/origin/"* ]]; then
|
||||
if git branch | grep -qw "${branch#remotes/origin/}"; then
|
||||
# hack to force creating a new branch which tracks the remote if a local branch already exists
|
||||
git checkout -b "track/${branch#remotes/origin/}" --track "$branch"
|
||||
elif ! git checkout --track "$branch" 2>/dev/null; then
|
||||
git checkout "$branch"
|
||||
fi
|
||||
else
|
||||
git checkout "$branch"
|
||||
fi
|
||||
}
|
||||
|
||||
# git checkout-tag selector
|
||||
_forgit_checkout_tag() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout "$@"; return $?; }
|
||||
local cmd opts preview
|
||||
cmd="git tag -l --sort=-v:refname"
|
||||
preview="git log {1} $_forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_TAG_FZF_OPTS
|
||||
"
|
||||
tag="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -z "$tag" ]] && return 1
|
||||
git checkout "$tag"
|
||||
}
|
||||
|
||||
# git checkout-commit selector
|
||||
_forgit_checkout_commit() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout "$@"; return $?; }
|
||||
local cmd opts graph
|
||||
cmd="echo {} | $_forgit_extract_sha |xargs -I% git show --color=always % | $_forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $_forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_COMMIT_FZF_OPTS
|
||||
"
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
eval "git log $graph --color=always --format='$_forgit_log_format' $_forgit_emojify" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf | eval "$_forgit_extract_sha" | xargs -I% git checkout % --
|
||||
}
|
||||
|
||||
_forgit_branch_delete() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
local preview opts cmd branches
|
||||
preview="git log {1} $_forgit_log_preview_options"
|
||||
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --multi --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_BRANCH_DELETE_FZF_OPTS
|
||||
"
|
||||
|
||||
cmd="git branch --color=always | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
branches=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
|
||||
echo -n "$branches" | tr '\n' '\0' | xargs -I{} -0 git branch -D {}
|
||||
}
|
||||
|
||||
# git revert-commit selector
|
||||
_forgit_revert_commit() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git revert "$@"; return $?; }
|
||||
local cmd opts files preview commits IFS
|
||||
cmd="git log --graph --color=always --format='$_forgit_log_format' $* $_forgit_emojify"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --tiebreak=index
|
||||
$FORGIT_REVERT_COMMIT_OPTS
|
||||
"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview="echo {} | $_forgit_extract_sha | xargs -I% git show --color=always % -- $files | $_forgit_show_pager"
|
||||
# shellcheck disable=2207
|
||||
IFS=$'\n' commits=($(eval "$cmd" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview" -m |
|
||||
sed 's/^[^a-f^0-9]*\([a-f0-9]*\).*/\1/'))
|
||||
[ ${#commits[@]} -eq 0 ] && return 1
|
||||
for commit in "${commits[@]}"; do
|
||||
git revert "$commit"
|
||||
done
|
||||
}
|
||||
|
||||
# git blame viewer
|
||||
_forgit_blame() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && git blame "$@" && return 0
|
||||
local opts flags preview file
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
$FORGIT_BLAME_FZF_OPTS
|
||||
"
|
||||
flags=$(git rev-parse --flags "$@")
|
||||
preview="
|
||||
if (git ls-files {} --error-unmatch) &>/dev/null; then
|
||||
git blame {} --date=short $flags | $_forgit_blame_pager
|
||||
else
|
||||
echo File not tracked
|
||||
fi
|
||||
"
|
||||
file=$(FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview")
|
||||
[[ -z "$file" ]] && return 1
|
||||
eval git blame "$file" "$flags"
|
||||
}
|
||||
|
||||
# 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:-${XDG_CACHE_HOME:-$HOME/.cache}/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 opts
|
||||
cmd="$_forgit_ignore_pager $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m --preview-window='right:70%'
|
||||
--preview=\"eval $cmd\"
|
||||
$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 | awk '{print $2}'))
|
||||
[ ${#args[@]} -eq 0 ] && return 1
|
||||
# shellcheck disable=SC2068
|
||||
_forgit_ignore_get ${args[@]}
|
||||
}
|
||||
_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"
|
||||
}
|
||||
|
||||
valid_commands=(
|
||||
"add"
|
||||
"blame"
|
||||
"branch_delete"
|
||||
"checkout_branch"
|
||||
"checkout_commit"
|
||||
"checkout_file"
|
||||
"checkout_tag"
|
||||
"cherry_pick"
|
||||
"cherry_pick_from_branch"
|
||||
"clean"
|
||||
"diff"
|
||||
"ignore"
|
||||
"log"
|
||||
"rebase fixup"
|
||||
"reset_head"
|
||||
"revert_commit"
|
||||
"stash_show"
|
||||
)
|
||||
|
||||
cmd="$1"
|
||||
shift
|
||||
forgit::${cmd//_/::} "$@"
|
||||
|
||||
# shellcheck disable=SC2076
|
||||
if [[ ! " ${valid_commands[*]} " =~ " ${cmd} " ]]; then
|
||||
if [[ -z "$cmd" ]]; then
|
||||
printf "forgit: missing command\n\n"
|
||||
else
|
||||
printf "forgit: '%s' is not a valid forgit command.\n\n" "$cmd"
|
||||
fi
|
||||
printf "The following commands are supported:\n"
|
||||
printf "\t%s\n" "${valid_commands[@]}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_forgit_"${cmd}" "$@"
|
||||
|
|
|
|||
1
conf.d/bin/git-forgit
Symbolic link
1
conf.d/bin/git-forgit
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../bin/git-forgit
|
||||
|
|
@ -1,640 +1,115 @@
|
|||
# MIT (c) Chris Apple
|
||||
|
||||
set INSTALL_DIR (dirname (dirname (status -f)))
|
||||
set FORGIT "$INSTALL_DIR/conf.d/bin/git-forgit"
|
||||
|
||||
function forgit::warn
|
||||
printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$argv" >&2;
|
||||
end
|
||||
|
||||
function forgit::info
|
||||
printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$argv" >&2;
|
||||
end
|
||||
|
||||
function forgit::inside_work_tree
|
||||
git rev-parse --is-inside-work-tree >/dev/null;
|
||||
end
|
||||
|
||||
function forgit::reverse_lines
|
||||
# tac is not available on OSX, tail -r is not available on Linux, so we use either of them
|
||||
if command -v tac &> /dev/null
|
||||
tac
|
||||
else
|
||||
tail -r
|
||||
# backwards compatibility:
|
||||
# export all user-defined FORGIT variables to make them available in git-forgit
|
||||
set unexported_vars 0
|
||||
set | awk -F ' ' '{ print $1 }' | grep FORGIT_ | while read var
|
||||
if ! set -x | grep -q "^$var "
|
||||
if test $unexported_vars = 0
|
||||
forgit::warn "Config options have to be exported in future versions of forgit."
|
||||
forgit::warn "Please update your config accordingly:"
|
||||
end
|
||||
forgit::warn " set -x $var \"$$var\""
|
||||
set unexported_vars (math $unexported_vars + 1)
|
||||
set -x $var $$var
|
||||
end
|
||||
end
|
||||
|
||||
function forgit::previous_commit
|
||||
# "SHA~" is invalid when the commit is the first commit, but we can use "--root" instead
|
||||
if test (git rev-parse $argv) = (git rev-list --max-parents=0 HEAD)
|
||||
echo "--root"
|
||||
else
|
||||
echo "$argv~"
|
||||
end
|
||||
end
|
||||
|
||||
# extract the first git sha occurring in the input and strip trailing newline
|
||||
set -g forgit_extract_sha "grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]'"
|
||||
|
||||
set -g forgit_pager "$FORGIT_PAGER"
|
||||
set -g forgit_show_pager "$FORGIT_SHOW_PAGER"
|
||||
set -g forgit_diff_pager "$FORGIT_DIFF_PAGER"
|
||||
set -g forgit_ignore_pager "$FORGIT_IGNORE_PAGER"
|
||||
set -g forgit_enter_pager "$FORGIT_ENTER_PAGER"
|
||||
set -g forgit_log_format "$FORGIT_LOG_FORMAT"
|
||||
|
||||
set -x FORGIT_INSTALL_DIR (dirname (dirname (status -f)))
|
||||
|
||||
test -z "$forgit_pager"; and set -g forgit_pager (git config core.pager || echo 'cat')
|
||||
test -z "$forgit_show_pager"; and set -g forgit_show_pager (git config pager.show || echo "$forgit_pager")
|
||||
test -z "$forgit_diff_pager"; and set -g forgit_diff_pager (git config pager.diff || echo "$forgit_pager")
|
||||
test -z "$forgit_ignore_pager"; and set -g forgit_ignore_pager (type -q bat >/dev/null 2>&1 && echo 'bat -l gitignore --color=always' || echo 'cat')
|
||||
test -z "$forgit_enter_pager"; and set -g forgit_enter_pager "env LESS='-r' less"
|
||||
test -z "$forgit_log_format"; and set -g forgit_log_format "-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset"
|
||||
test -z "$forgit_fullscreen_context"; and set -g forgit_fullscreen_context "10"
|
||||
test -z "$forgit_preview_context"; and set -g forgit_preview_context "3"
|
||||
|
||||
set -g forgit_log_preview_options "--graph --pretty=format:'$forgit_log_format' --color=always --abbrev-commit --date=relative"
|
||||
|
||||
# optional render emoji characters (https://github.com/wfxr/emoji-cli)
|
||||
type -q emojify >/dev/null 2>&1 && set -g forgit_emojify '|emojify'
|
||||
|
||||
# git commit viewer
|
||||
function forgit::log -d "git commit viewer"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
set files (echo $argv | sed -nE 's/.* -- (.*)/\1/p')
|
||||
set preview_cmd "echo {} | $forgit_extract_sha | xargs -I% git show --color=always -U$forgit_preview_context % -- $files | $forgit_show_pager"
|
||||
set enter_cmd "echo {} | $forgit_extract_sha | xargs -I% git show --color=always -U$forgit_fullscreen_context % -- $files | $forgit_show_pager"
|
||||
|
||||
if test -n "$FORGIT_COPY_CMD"
|
||||
set copy_cmd $FORGIT_COPY_CMD
|
||||
else
|
||||
set copy_cmd pbcopy
|
||||
end
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"enter:execute($enter_cmd | $forgit_enter_pager)\"
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | $copy_cmd)\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_LOG_FZF_OPTS
|
||||
"
|
||||
|
||||
if test -n "$FORGIT_GLO_FORMAT"
|
||||
set log_format "$FORGIT_GLO_FORMAT"
|
||||
else
|
||||
set log_format "$forgit_log_format"
|
||||
end
|
||||
|
||||
if set -q FORGIT_LOG_GRAPH_ENABLE
|
||||
set graph "--graph"
|
||||
else
|
||||
set graph ""
|
||||
end
|
||||
|
||||
eval "git log $graph --color=always --format='$log_format' $argv $forgit_emojify" |
|
||||
env FZF_DEFAULT_OPTS="$opts" fzf
|
||||
|
||||
set fzf_exit_code $status
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[ $fzf_exit_code = 130 ] && return 0
|
||||
return $fzf_exit_code
|
||||
"$FORGIT" log $argv
|
||||
end
|
||||
|
||||
function forgit::extract_file --argument-names 'path'
|
||||
set no_leading_whitespace (echo $path | sed 's/^[[:space:]]*//')
|
||||
set no_m_or_double_question (echo $no_leading_whitespace | cut -d ' ' -f 2-)
|
||||
set if_renamed (echo $no_m_or_double_question | sed 's/.* -> //')
|
||||
set no_quotes (echo $if_renamed | tr -d "\"")
|
||||
set no_leading_whitespace_again (echo $no_quotes | sed 's/^[[:space:]]*//')
|
||||
set no_spaces (echo $no_leading_whitespace_again | string escape --no-quoted)
|
||||
echo $no_spaces
|
||||
end
|
||||
|
||||
## git diff viewer
|
||||
function forgit::diff -d "git diff viewer" --argument-names arg1 arg2
|
||||
forgit::inside_work_tree || return 1
|
||||
if test -n "$arg1"
|
||||
# If this first arg is a commit hash
|
||||
if git rev-parse "$arg1" > /dev/null 2>&1
|
||||
if git rev-parse "$arg2" > /dev/null 2>&1
|
||||
set commits "$arg1 $arg2" && set files "$argv[3..-1]"
|
||||
else
|
||||
set commits "$arg1" && set files "$argv[2..-1]"
|
||||
end
|
||||
else
|
||||
set files "$argv"
|
||||
end
|
||||
end
|
||||
|
||||
# Git stashes are named "stash@{x}", which contains the fzf placeholder "{x}".
|
||||
# In order to support passing stashes as arguments to forgit::diff, we have to
|
||||
# prevent fzf from interpreting this substring by escaping the opening bracket.
|
||||
# The string is evaluated a few subsequent times, so we need multiple escapes.
|
||||
set escaped_commits (echo $commits | sed 's/{/\\\\\\\\{/g')
|
||||
|
||||
set preview_cmd "forgit::extract_file {} | xargs -I% git diff --color=always -U$forgit_preview_context $escaped_commits -- % | $forgit_diff_pager"
|
||||
set enter_cmd "forgit::extract_file {} | xargs -I% git diff --color=always -U$forgit_fullscreen_context $escaped_commits -- % | $forgit_diff_pager"
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+m -0 --bind=\"enter:execute($enter_cmd | $forgit_enter_pager)\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_DIFF_FZF_OPTS
|
||||
--prompt=\"$commits > \"
|
||||
"
|
||||
|
||||
eval git diff --name-status $commits -- $files* |
|
||||
sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)\$/[\1]\t\2/' |
|
||||
sed ' s/\t/ -> /2' |
|
||||
expand -t 8 |
|
||||
env FZF_DEFAULT_OPTS="$opts" fzf
|
||||
|
||||
set fzf_exit_code $status
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[ $fzf_exit_code = 130 ] && return 0
|
||||
return $fzf_exit_code
|
||||
"$FORGIT" diff $argv
|
||||
end
|
||||
|
||||
# git add selector
|
||||
function forgit::add -d "git add selector" --wraps "git add"
|
||||
forgit::inside_work_tree || return 1
|
||||
# Add files if passed as arguments
|
||||
count $argv >/dev/null && git add "$argv" && git status --short && return
|
||||
|
||||
set changed (git config --get-color color.status.changed red)
|
||||
set unmerged (git config --get-color color.status.unmerged red)
|
||||
set untracked (git config --get-color color.status.untracked red)
|
||||
|
||||
set preview "
|
||||
set file (forgit::extract_file {})
|
||||
# exit
|
||||
if test (git status -s -- \$file | grep '^??') # diff with /dev/null for untracked files
|
||||
git diff --color=always --no-index -- /dev/null \$file | $forgit_diff_pager | sed '2 s/added:/untracked:/'
|
||||
else
|
||||
git diff --color=always -- \$file | $forgit_diff_pager
|
||||
end
|
||||
"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-0 -m --nth 2..,..
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_ADD_FZF_OPTS
|
||||
"
|
||||
set 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/' | # deal with white spaces internal to fname
|
||||
env FZF_DEFAULT_OPTS="$opts" fzf
|
||||
)
|
||||
|
||||
if test -n "$files"
|
||||
for file in $files
|
||||
set stripped_file (forgit::extract_file $file)
|
||||
echo $stripped_file | tr '\n' '\0' | xargs -I{} -0 git add {}
|
||||
end
|
||||
git status --short
|
||||
return
|
||||
end
|
||||
echo 'Nothing to add.'
|
||||
"$FORGIT" add $argv
|
||||
end
|
||||
|
||||
## git reset HEAD (unstage) selector
|
||||
function forgit::reset::head -d "git reset HEAD (unstage) selector"
|
||||
forgit::inside_work_tree || return 1
|
||||
set preview "git diff --cached --color=always -- {} | $forgit_diff_pager"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_RESET_HEAD_FZF_OPTS
|
||||
"
|
||||
set files (git diff --cached --name-only --relative | env FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
if test -n "$files"
|
||||
for file in $files
|
||||
echo $file | tr '\n' '\0' |xargs -I{} -0 git reset -q HEAD {}
|
||||
end
|
||||
git status --short
|
||||
return
|
||||
end
|
||||
echo 'Nothing to unstage.'
|
||||
"$FORGIT" reset_head $argv
|
||||
end
|
||||
|
||||
# git checkout-restore selector
|
||||
function forgit::checkout::file -d "git checkout-file selector" --argument-names 'file_name' --wraps "git checkout --"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
if test -n "$file_name"
|
||||
git checkout -- "$file_name"
|
||||
set checkout_status $status
|
||||
git status --short
|
||||
return $checkout_status
|
||||
end
|
||||
|
||||
|
||||
set preview "git diff --color=always -- {} | $forgit_diff_pager"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_FILE_FZF_OPTS
|
||||
"
|
||||
set git_rev_parse (git rev-parse --show-toplevel)
|
||||
set files (git ls-files --modified "$git_rev_parse" | env FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
|
||||
if test -n "$files"
|
||||
for file in $files
|
||||
echo $file | tr '\n' '\0' | xargs -I{} -0 git checkout -q {}
|
||||
end
|
||||
git status --short
|
||||
return
|
||||
end
|
||||
echo 'Nothing to restore.'
|
||||
end
|
||||
|
||||
function forgit::checkout::commit -d "git checkout commit selector" --argument-names 'commit_id' --wraps "git checkout"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
if test -n "$commit_id"
|
||||
git checkout "$commit_id"
|
||||
set checkout_status $status
|
||||
git status --short
|
||||
return $checkout_status
|
||||
end
|
||||
|
||||
if test -n "$FORGIT_COPY_CMD"
|
||||
set copy_cmd $FORGIT_COPY_CMD
|
||||
else
|
||||
set copy_cmd pbcopy
|
||||
end
|
||||
|
||||
|
||||
set preview "echo {} | $forgit_extract_sha | xargs -I% git show --color=always % | $forgit_show_pager"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | $copy_cmd)\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_COMMIT_FZF_OPTS
|
||||
"
|
||||
|
||||
if set -q FORGIT_LOG_GRAPH_ENABLE
|
||||
set graph "--graph"
|
||||
else
|
||||
set graph ""
|
||||
end
|
||||
|
||||
eval "git log $graph --color=always --format='$forgit_log_format' $forgit_emojify" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha" | xargs -I% git checkout % --
|
||||
end
|
||||
|
||||
function forgit::branch::delete -d "git checkout branch deleter" --wraps "git branch --delete"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
set preview "git log {1} $forgit_log_preview_options"
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --multi --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_BRANCH_DELETE_FZF_OPTS
|
||||
"
|
||||
|
||||
set cmd "git branch --color=always | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
set branches (eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
|
||||
|
||||
if test -n "$branches"
|
||||
for branch in $branches
|
||||
echo $branch | tr '\n' '\0' | xargs -I{} -0 git branch -D {}
|
||||
end
|
||||
git status --short
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function forgit::checkout::branch -d "git checkout branch selector" --argument-names 'input_branch_name' --wraps "git branch"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
if test -n "$input_branch_name"
|
||||
if git branch --list | grep "$input_branch_name" > /dev/null
|
||||
git switch "$input_branch_name"
|
||||
else
|
||||
git switch -c "$input_branch_name"
|
||||
end
|
||||
set checkout_status $status
|
||||
git status --short
|
||||
return $checkout_status
|
||||
end
|
||||
|
||||
set preview "git log {1} $forgit_log_preview_options"
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_BRANCH_FZF_OPTS
|
||||
"
|
||||
|
||||
set cmd "git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
set branch (eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
|
||||
|
||||
test -z "$branch" && return 1
|
||||
|
||||
# track the remote branch if possible
|
||||
if not git checkout --track "$branch" 2>/dev/null
|
||||
git checkout "$branch"
|
||||
end
|
||||
end
|
||||
|
||||
# git stash viewer
|
||||
function forgit::stash::show -d "git stash viewer"
|
||||
forgit::inside_work_tree || return 1
|
||||
set preview "echo {} |cut -d: -f1 |xargs -I% git stash show --color=always --ext-diff % |$forgit_diff_pager"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m -0 --tiebreak=index --bind=\"enter:execute($preview | $forgit_enter_pager)\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_STASH_FZF_OPTS
|
||||
"
|
||||
git stash list | env FZF_DEFAULT_OPTS="$opts" fzf
|
||||
|
||||
set fzf_exit_code $status
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[ $fzf_exit_code = 130 ] && return 0
|
||||
return $fzf_exit_code
|
||||
"$FORGIT" stash_show $argv
|
||||
end
|
||||
|
||||
# git clean selector
|
||||
function forgit::clean -d "git clean selector"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
$FORGIT_CLEAN_FZF_OPTS
|
||||
"
|
||||
|
||||
set files (git clean -xdffn $argv| awk '{print $3}'| env FZF_DEFAULT_OPTS="$opts" fzf |sed 's#/$##')
|
||||
|
||||
if test -n "$files"
|
||||
for file in $files
|
||||
echo $file | tr '\n' '\0'| xargs -0 -I{} git clean -xdff {}
|
||||
end
|
||||
git status --short
|
||||
return
|
||||
end
|
||||
echo 'Nothing to clean.'
|
||||
"$FORGIT" clean $argv
|
||||
end
|
||||
|
||||
function forgit::cherry::pick -d "git cherry-picking" --argument-names 'target' --wraps "git cherry-pick"
|
||||
forgit::inside_work_tree || return 1
|
||||
set base (git branch --show-current)
|
||||
if test -z "$target"
|
||||
echo "Please specify target branch"
|
||||
return 1
|
||||
end
|
||||
set preview "echo {} | $forgit_extract_sha | xargs -I% git show --color=always % | $forgit_show_pager"
|
||||
set opts "
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0 --tiebreak=index
|
||||
$FORGIT_CHERRY_PICK_FZF_OPTS
|
||||
"
|
||||
set fzf_selection (git cherry "$base" "$target" --abbrev -v | forgit::reverse_lines |
|
||||
env FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
|
||||
set fzf_exitval $status
|
||||
test $fzf_exitval != 0 && return $fzf_exitval
|
||||
set commits (echo "$fzf_selection" | forgit::reverse_lines | cut -d' ' -f2)
|
||||
|
||||
git cherry-pick $commits
|
||||
|
||||
"$FORGIT" cherry_pick $argv
|
||||
end
|
||||
|
||||
function forgit::cherry::pick::from::branch -d "git cherry-picking with interactive branch selection"
|
||||
forgit::inside_work_tree || return 1
|
||||
"$FORGIT" cherry_pick_from_branch $argv
|
||||
end
|
||||
|
||||
set preview "git log {1} $forgit_log_preview_options"
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHERRY_PICK_FROM_BRANCH_FZF_OPTS
|
||||
"
|
||||
|
||||
set cmd "git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
|
||||
# loop until either the branch selector is closed or a commit to be cherry
|
||||
# picked has been selected from within a branch
|
||||
while true
|
||||
|
||||
set branch (eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
|
||||
test -z "$branch" && return 1
|
||||
|
||||
forgit::cherry::pick "$branch"
|
||||
|
||||
set exitval $status
|
||||
test $exitval != 130 && return $exitval
|
||||
end
|
||||
function forgit::rebase -d "git rebase"
|
||||
"$FORGIT" rebase $argv
|
||||
end
|
||||
|
||||
function forgit::fixup -d "git fixup"
|
||||
forgit::inside_work_tree || return 1
|
||||
git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1
|
||||
|
||||
if set -q FORGIT_LOG_GRAPH_ENABLE
|
||||
set graph "--graph"
|
||||
else
|
||||
set graph ""
|
||||
end
|
||||
|
||||
set files (echo $argv | sed -nE 's/.* -- (.*)/\1/p')
|
||||
set preview "echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
|
||||
if test -n "$FORGIT_COPY_CMD"
|
||||
set copy_cmd $FORGIT_COPY_CMD
|
||||
else
|
||||
set copy_cmd pbcopy
|
||||
end
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | $copy_cmd)\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_FIXUP_FZF_OPTS
|
||||
"
|
||||
|
||||
set cmd "git log $graph --color=always --format='$forgit_log_format' $argv $forgit_emojify"
|
||||
set target_commit (eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha")
|
||||
|
||||
if test -n "$target_commit" && git commit --fixup "$target_commit"
|
||||
set prev_commit (forgit::previous_commit $target_commit)
|
||||
|
||||
GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit"
|
||||
end
|
||||
|
||||
"$FORGIT" fixup $argv
|
||||
end
|
||||
|
||||
|
||||
function forgit::rebase -d "git rebase"
|
||||
forgit::inside_work_tree || return 1
|
||||
|
||||
if set -q FORGIT_LOG_GRAPH_ENABLE
|
||||
set graph "--graph"
|
||||
else
|
||||
set graph ""
|
||||
end
|
||||
set cmd "git log $graph --color=always --format='$forgit_log_format' $argv $forgit_emojify"
|
||||
|
||||
set files (echo $argv | sed -nE 's/.* -- (.*)/\1/p')
|
||||
set preview "echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
|
||||
if test -n "$FORGIT_COPY_CMD"
|
||||
set copy_cmd $FORGIT_COPY_CMD
|
||||
else
|
||||
set copy_cmd pbcopy
|
||||
end
|
||||
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | $copy_cmd)\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_REBASE_FZF_OPTS
|
||||
"
|
||||
set target_commit (eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha")
|
||||
|
||||
if test $target_commit
|
||||
set prev_commit (forgit::previous_commit $target_commit)
|
||||
|
||||
git rebase -i "$prev_commit"
|
||||
end
|
||||
function forgit::checkout::file -d "git checkout-file selector" --argument-names 'file_name' --wraps "git checkout --"
|
||||
"$FORGIT" checkout_file $argv
|
||||
end
|
||||
|
||||
# git ignore generator
|
||||
if test -z "$FORGIT_GI_REPO_REMOTE"
|
||||
set -g FORGIT_GI_REPO_REMOTE https://github.com/dvcs/gitignore
|
||||
function forgit::checkout::branch -d "git checkout branch selector" --argument-names 'input_branch_name' --wraps "git branch"
|
||||
"$FORGIT" checkout_branch $argv
|
||||
end
|
||||
|
||||
if test -z "$FORGIT_GI_REPO_LOCAL"
|
||||
if test -z "XDG_CACHE_HOME"
|
||||
set -g FORGIT_GI_REPO_LOCAL $XDG_CACHE_HOME/forgit/gi/repos/dvcs/gitignore
|
||||
else
|
||||
set -g FORGIT_GI_REPO_LOCAL $HOME/.cache/forgit/gi/repos/dvcs/gitignore
|
||||
end
|
||||
function forgit::checkout::tag -d "git checkout tag selector" --argument-names 'tag_name' --wraps "git checkout"
|
||||
"$FORGIT" checkout_tag $argv
|
||||
end
|
||||
|
||||
if test -z "$FORGIT_GI_TEMPLATES"
|
||||
set -g FORGIT_GI_TEMPLATES $FORGIT_GI_REPO_LOCAL/templates
|
||||
function forgit::checkout::commit -d "git checkout commit selector" --argument-names 'commit_id' --wraps "git checkout"
|
||||
"$FORGIT" checkout_commit $argv
|
||||
end
|
||||
|
||||
function forgit::ignore -d "git ignore generator"
|
||||
if not test -d "$FORGIT_GI_REPO_LOCAL"
|
||||
forgit::ignore::update
|
||||
end
|
||||
|
||||
set preview "$forgit_ignore_pager $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m --preview-window='right:70%'
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_IGNORE_FZF_OPTS
|
||||
"
|
||||
set IFS '\n'
|
||||
|
||||
set args $argv
|
||||
if not count $argv > /dev/null
|
||||
set args (forgit::ignore::list | nl -nrn -w4 -s' ' |
|
||||
env FZF_DEFAULT_OPTS="$opts" fzf |awk '{print $2}')
|
||||
end
|
||||
|
||||
if not count $args > /dev/null
|
||||
return 1
|
||||
end
|
||||
|
||||
forgit::ignore::get $args
|
||||
function forgit::branch::delete -d "git checkout branch deleter" --wraps "git branch --delete"
|
||||
"$FORGIT" branch_delete $argv
|
||||
end
|
||||
|
||||
function forgit::revert::commit --argument-names 'commit_hash' --wraps "git revert --"
|
||||
if test -n "$commit_hash"
|
||||
git revert -- "$commit_hash"
|
||||
set revert_status $status
|
||||
git status --short
|
||||
return $revert_status
|
||||
end
|
||||
"$FORGIT" revert_commit $argv
|
||||
end
|
||||
|
||||
set preview "echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
set opts "
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --tiebreak=index
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_REVERT_COMMIT_OPTS
|
||||
"
|
||||
function forgit::blame
|
||||
"$FORGIT" blame $argv
|
||||
end
|
||||
|
||||
set files (echo $argv | sed -nE 's/.* -- (.*)/\1/p') # extract files parameters for `git show` command
|
||||
|
||||
set cmd "git log --graph --color=always --format='$forgit_log_format' $argv $forgit_emojify"
|
||||
|
||||
set commits (eval $cmd |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf -m |
|
||||
string match -r "[a-f0-9]+")
|
||||
|
||||
if test -z "$commits"
|
||||
return 1
|
||||
end
|
||||
|
||||
git revert $commits
|
||||
function forgit::ignore -d "git ignore generator"
|
||||
"$FORGIT" ignore $argv
|
||||
end
|
||||
|
||||
function forgit::ignore::update
|
||||
if test -d "$FORGIT_GI_REPO_LOCAL"
|
||||
forgit::info 'Updating gitignore repo...'
|
||||
set pull_result (git -C "$FORGIT_GI_REPO_LOCAL" pull --no-rebase --ff)
|
||||
test -n "$pull_result" || return 1
|
||||
else
|
||||
forgit::info 'Initializing gitignore repo...'
|
||||
git clone --depth=1 "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL"
|
||||
end
|
||||
"$FORGIT" ignore_update $argv
|
||||
end
|
||||
|
||||
function forgit::ignore::get
|
||||
for item in $argv
|
||||
set filename (find -L "$FORGIT_GI_TEMPLATES" -type f \( -iname "$item.gitignore" -o -iname "$item}" \) -print -quit)
|
||||
if test -n "$filename"
|
||||
set header $filename && set header (echo $filename | sed 's/.*\.//')
|
||||
echo "### $header" && cat "$filename" && echo
|
||||
else
|
||||
forgit::warn "No gitignore template found for '$item'." && continue
|
||||
end
|
||||
end
|
||||
"$FORGIT" ignore_get $argv
|
||||
end
|
||||
|
||||
function forgit::ignore::list
|
||||
find "$FORGIT_GI_TEMPLATES" -print | sed -e 's#.gitignore$##' -e 's#.*/##' | sort -fu
|
||||
"$FORGIT" ignore_list $argv
|
||||
end
|
||||
|
||||
function forgit::ignore::clean
|
||||
if test -d "$FORGIT_GI_REPO_LOCAL"
|
||||
rm -rf "$FORGIT_GI_REPO_LOCAL"
|
||||
end
|
||||
"$FORGIT" ignore_clean $argv
|
||||
end
|
||||
|
||||
set -g 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%'
|
||||
+1
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
"
|
||||
|
||||
# register aliases
|
||||
if test -z "$FORGIT_NO_ALIASES"
|
||||
if test -n "$forgit_add"
|
||||
|
|
|
|||
|
|
@ -1,510 +1,127 @@
|
|||
#!/usr/bin/env bash
|
||||
# MIT (c) Wenxuan Zhang
|
||||
|
||||
forgit::error() { printf "%b[Error]%b %s\n" '\e[0;31m' '\e[0m' "$@" >&2; return 1; }
|
||||
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; }
|
||||
# tac is not available on OSX, tail -r is not available on Linux, so we use either of them
|
||||
forgit::reverse_lines() { tac 2> /dev/null || tail -r; }
|
||||
|
||||
forgit::previous_commit() {
|
||||
# "SHA~" is invalid when the commit is the first commit, but we can use "--root" instead
|
||||
if [[ "$(git rev-parse "$1")" == "$(git rev-list --max-parents=0 HEAD)" ]]; then
|
||||
echo "--root"
|
||||
else
|
||||
echo "$1~"
|
||||
fi
|
||||
}
|
||||
# determine installation path
|
||||
if [[ -n "$ZSH_VERSION" ]]; then
|
||||
# shellcheck disable=2277,2296,2299
|
||||
0="${ZERO:-${${0:#$ZSH_ARGZERO}:-${(%):-%N}}}"
|
||||
# shellcheck disable=2277,2296,2298
|
||||
0="${${(M)0:#/*}:-$PWD/$0}"
|
||||
INSTALL_DIR="${0:h}"
|
||||
elif [[ -n "$BASH_VERSION" ]]; then
|
||||
INSTALL_DIR="$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
else
|
||||
forgit::error "Only zsh and bash are supported"
|
||||
fi
|
||||
FORGIT="$INSTALL_DIR/bin/git-forgit"
|
||||
|
||||
# optional render emoji characters (https://github.com/wfxr/emoji-cli)
|
||||
hash emojify &>/dev/null && forgit_emojify='|emojify'
|
||||
|
||||
# extract the first git sha occurring in the input and strip trailing newline
|
||||
forgit_extract_sha="grep -Eo '[a-f0-9]+' | head -1 | tr -d '[:space:]'"
|
||||
|
||||
forgit_pager=${FORGIT_PAGER:-$(git config core.pager || echo 'cat')}
|
||||
forgit_show_pager=${FORGIT_SHOW_PAGER:-$(git config pager.show || echo "$forgit_pager")}
|
||||
forgit_diff_pager=${FORGIT_DIFF_PAGER:-$(git config pager.diff || echo "$forgit_pager")}
|
||||
forgit_ignore_pager=${FORGIT_IGNORE_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitignore --color=always' || echo 'cat')}
|
||||
forgit_blame_pager=${FORGIT_BLAME_PAGER:-$(git config pager.blame || echo "$forgit_pager")}
|
||||
forgit_enter_pager=${FORGIT_ENTER_PAGER:-"LESS='-r' less"}
|
||||
|
||||
forgit_log_format=${FORGIT_LOG_FORMAT:-%C(auto)%h%d %s %C(black)%C(bold)%cr%Creset}
|
||||
forgit_log_preview_options="--graph --pretty=format:'$forgit_log_format' --color=always --abbrev-commit --date=relative"
|
||||
forgit_fullscreen_context=${FORGIT_FULLSCREEN_CONTEXT:-10}
|
||||
forgit_preview_context=${FORGIT_PREVIEW_CONTEXT:-3}
|
||||
|
||||
# git commit viewer
|
||||
forgit::log() {
|
||||
forgit::inside_work_tree || return 1
|
||||
local opts graph files log_format preview_cmd enter_cmd
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview_cmd="echo {} | $forgit_extract_sha | xargs -I% git show --color=always -U$forgit_preview_context % -- $files | $forgit_show_pager"
|
||||
enter_cmd="echo {} | $forgit_extract_sha | xargs -I% git show --color=always -U$forgit_fullscreen_context % -- $files | $forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"enter:execute($enter_cmd | $forgit_enter_pager)\"
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_LOG_FZF_OPTS
|
||||
"
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
log_format=${FORGIT_GLO_FORMAT:-$forgit_log_format}
|
||||
eval "git log $graph --color=always --format='$log_format' $* $forgit_emojify" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
}
|
||||
|
||||
# git diff viewer
|
||||
forgit::diff() {
|
||||
forgit::inside_work_tree || return 1
|
||||
local files opts commits repo get_files preview_cmd enter_cmd
|
||||
[[ $# -ne 0 ]] && {
|
||||
if git rev-parse "$1" -- &>/dev/null ; then
|
||||
if [[ $# -gt 1 ]] && git rev-parse "$2" -- &>/dev/null; then
|
||||
commits="$1 $2" && files=("${@:3}")
|
||||
else
|
||||
commits="$1" && files=("${@:2}")
|
||||
fi
|
||||
else
|
||||
files=("$@")
|
||||
# backwards compatibility:
|
||||
# export all user-defined FORGIT variables to make them available in git-forgit
|
||||
unexported_vars=0
|
||||
set | awk -F '=' '{ print $1 }' | grep FORGIT_ | while read -r var; do
|
||||
if ! export | grep -q "^$var="; then
|
||||
if [[ $unexported_vars == 0 ]]; then
|
||||
forgit::warn "Config options have to be exported in future versions of forgit."
|
||||
forgit::warn "Please update your config accordingly:"
|
||||
fi
|
||||
}
|
||||
repo="$(git rev-parse --show-toplevel)"
|
||||
get_files="cd '$repo' && echo {} | sed 's/.*] *//' | sed 's/ -> / /'"
|
||||
# Git stashes are named "stash@{x}", which contains the fzf placeholder "{x}".
|
||||
# In order to support passing stashes as arguments to forgit::diff, we have to
|
||||
# prevent fzf from interpreting this substring by escaping the opening bracket.
|
||||
# The string is evaluated a few subsequent times, so we need multiple escapes.
|
||||
escaped_commits=${commits//\{/\\\\\{}
|
||||
preview_cmd="$get_files | xargs -I% git diff --color=always -U$forgit_preview_context $escaped_commits -- % | $forgit_diff_pager"
|
||||
enter_cmd="$get_files | xargs -I% git diff --color=always -U$forgit_fullscreen_context $escaped_commits -- % | $forgit_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+m -0 --bind=\"enter:execute($enter_cmd | $forgit_enter_pager)\"
|
||||
--preview=\"$preview_cmd\"
|
||||
$FORGIT_DIFF_FZF_OPTS
|
||||
--prompt=\"$commits > \"
|
||||
"
|
||||
eval "git diff --name-status $commits -- ${files[*]} | sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1]\t\2/'" |
|
||||
sed 's/\t/ -> /2' | expand -t 8 |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
forgit::warn " export $var"
|
||||
unexported_vars=$((unexported_vars + 1))
|
||||
# shellcheck disable=SC2163
|
||||
export "$var"
|
||||
fi
|
||||
done
|
||||
|
||||
# register shell functions
|
||||
forgit::log() {
|
||||
"$FORGIT" log "$@"
|
||||
}
|
||||
|
||||
forgit::diff() {
|
||||
"$FORGIT" diff "$@"
|
||||
}
|
||||
|
||||
# git add selector
|
||||
forgit::add() {
|
||||
forgit::inside_work_tree || return 1
|
||||
# Add files if passed as arguments
|
||||
[[ $# -ne 0 ]] && git add "$@" && git status -su && 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_diff_pager | sed '2 s/added:/untracked:/'
|
||||
else
|
||||
git diff --color=always -- \$file | $forgit_diff_pager
|
||||
fi"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-0 -m --nth 2..,..
|
||||
--preview=\"$preview\"
|
||||
$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 |
|
||||
sh -c "$extract")
|
||||
[[ -n "$files" ]] && echo "$files"| tr '\n' '\0' |xargs -0 -I% git add % && git status -su && return
|
||||
echo 'Nothing to add.'
|
||||
"$FORGIT" 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_diff_pager "
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_RESET_HEAD_FZF_OPTS
|
||||
"
|
||||
files="$(git diff --cached --name-only --relative | FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git reset -q HEAD % && git status --short && return
|
||||
echo 'Nothing to unstage.'
|
||||
"$FORGIT" reset_head "$@"
|
||||
}
|
||||
|
||||
# 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_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m -0 --tiebreak=index --bind=\"enter:execute($cmd | $forgit_enter_pager)\"
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_STASH_FZF_OPTS
|
||||
"
|
||||
git stash list | FZF_DEFAULT_OPTS="$opts" fzf
|
||||
fzf_exit_code=$?
|
||||
# exit successfully on 130 (ctrl-c/esc)
|
||||
[[ $fzf_exit_code == 130 ]] && return 0
|
||||
return $fzf_exit_code
|
||||
"$FORGIT" stash_show "$@"
|
||||
}
|
||||
|
||||
# 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 -xdffn "$@"| sed 's/^Would remove //' | FZF_DEFAULT_OPTS="$opts" fzf |sed 's#/$##')
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git clean -xdff '%' && git status --short && return
|
||||
echo 'Nothing to clean.'
|
||||
"$FORGIT" clean "$@"
|
||||
}
|
||||
|
||||
forgit::cherry::pick() {
|
||||
local base target preview opts fzf_selection fzf_exitval
|
||||
base=$(git branch --show-current)
|
||||
[[ -z $1 ]] && echo "Please specify target branch" && return 1
|
||||
target="$1"
|
||||
preview="echo {} | $forgit_extract_sha | xargs -I% git show --color=always % | $forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
--preview=\"$preview\"
|
||||
-m -0 --tiebreak=index
|
||||
$FORGIT_CHERRY_PICK_FZF_OPTS
|
||||
"
|
||||
fzf_selection=$(git cherry "$base" "$target" --abbrev -v | forgit::reverse_lines |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
fzf_exitval=$?
|
||||
[[ $fzf_exitval != 0 ]] && return $fzf_exitval
|
||||
|
||||
commits=()
|
||||
while IFS="" read -r line
|
||||
do
|
||||
commits+=("$line")
|
||||
done < <(echo "$fzf_selection" | forgit::reverse_lines | cut -d' ' -f2)
|
||||
|
||||
git cherry-pick "${commits[@]}"
|
||||
"$FORGIT" cherry_pick "$@"
|
||||
}
|
||||
|
||||
forgit::cherry::pick::from::branch() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout -b "$@"; return $?; }
|
||||
local cmd preview opts branch exitval
|
||||
cmd="git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
preview="git log {1} $forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHERRY_PICK_FROM_BRANCH_FZF_OPTS
|
||||
"
|
||||
# loop until either the branch selector is closed or a commit to be cherry
|
||||
# picked has been selected from within a branch
|
||||
while true
|
||||
do
|
||||
branch="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')"
|
||||
[[ -z "$branch" ]] && return 1
|
||||
|
||||
forgit::cherry::pick "$branch"
|
||||
|
||||
exitval=$?
|
||||
[[ $exitval != 130 ]] && return $exitval
|
||||
done
|
||||
"$FORGIT" cherry_pick_from_branch "$@"
|
||||
}
|
||||
|
||||
forgit::rebase() {
|
||||
forgit::inside_work_tree || return 1
|
||||
local cmd preview opts graph files target_commit prev_commit
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
cmd="git log $graph --color=always --format='$forgit_log_format' $* $forgit_emojify"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview="echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_REBASE_FZF_OPTS
|
||||
"
|
||||
target_commit=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha")
|
||||
if [[ -n "$target_commit" ]]; then
|
||||
prev_commit=$(forgit::previous_commit "$target_commit")
|
||||
|
||||
git rebase -i "$prev_commit"
|
||||
fi
|
||||
"$FORGIT" rebase "$@"
|
||||
}
|
||||
|
||||
forgit::fixup() {
|
||||
forgit::inside_work_tree || return 1
|
||||
git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1
|
||||
local cmd preview opts graph files target_commit prev_commit
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
cmd="git log $graph --color=always --format='$forgit_log_format' $* $forgit_emojify"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*")
|
||||
preview="echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_FIXUP_FZF_OPTS
|
||||
"
|
||||
target_commit=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha")
|
||||
if [[ -n "$target_commit" ]] && git commit --fixup "$target_commit"; then
|
||||
prev_commit=$(forgit::previous_commit "$target_commit")
|
||||
# rebase will fail if there are unstaged changes so --autostash is needed to temporarily stash them
|
||||
# GIT_SEQUENCE_EDITOR=: is needed to skip the editor
|
||||
GIT_SEQUENCE_EDITOR=: git rebase --autostash -i --autosquash "$prev_commit"
|
||||
fi
|
||||
|
||||
"$FORGIT" fixup "$@"
|
||||
}
|
||||
|
||||
# git checkout-file selector
|
||||
forgit::checkout::file() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout -- "$@"; return $?; }
|
||||
local cmd files opts
|
||||
cmd="git diff --color=always -- {} | $forgit_diff_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_CHECKOUT_FILE_FZF_OPTS
|
||||
"
|
||||
files="$(git ls-files --modified "$(git rev-parse --show-toplevel)"| FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -n "$files" ]] && echo "$files" | tr '\n' '\0' | xargs -0 -I% git checkout %
|
||||
"$FORGIT" checkout_file "$@"
|
||||
}
|
||||
|
||||
# git checkout-branch selector
|
||||
forgit::checkout::branch() {
|
||||
forgit::inside_work_tree || return 1
|
||||
# if called with arguments, check if branch exists, else create a new one
|
||||
if [[ $# -ne 0 ]]; then
|
||||
if git show-branch "$@" &>/dev/null; then
|
||||
git switch "$@"
|
||||
else
|
||||
git switch -c "$@"
|
||||
fi
|
||||
checkout_status=$?
|
||||
git status --short
|
||||
return $checkout_status
|
||||
fi
|
||||
|
||||
local cmd preview opts branch
|
||||
cmd="git branch --color=always --all | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
preview="git log {1} $forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_BRANCH_FZF_OPTS
|
||||
"
|
||||
branch="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')"
|
||||
[[ -z "$branch" ]] && return 1
|
||||
|
||||
# track the remote branch if possible
|
||||
if [[ "$branch" == "remotes/origin/"* ]]; then
|
||||
if git branch | grep -qw "${branch#remotes/origin/}"; then
|
||||
# hack to force creating a new branch which tracks the remote if a local branch already exists
|
||||
git checkout -b "track/${branch#remotes/origin/}" --track "$branch"
|
||||
elif ! git checkout --track "$branch" 2>/dev/null; then
|
||||
git checkout "$branch"
|
||||
fi
|
||||
else
|
||||
git checkout "$branch"
|
||||
fi
|
||||
"$FORGIT" checkout_branch "$@"
|
||||
}
|
||||
|
||||
# git checkout-tag selector
|
||||
forgit::checkout::tag() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout "$@"; return $?; }
|
||||
local cmd opts preview
|
||||
cmd="git tag -l --sort=-v:refname"
|
||||
preview="git log {1} $forgit_log_preview_options"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_CHECKOUT_TAG_FZF_OPTS
|
||||
"
|
||||
tag="$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf)"
|
||||
[[ -z "$tag" ]] && return 1
|
||||
git checkout "$tag"
|
||||
"$FORGIT" checkout_tag "$@"
|
||||
}
|
||||
|
||||
# git checkout-commit selector
|
||||
forgit::checkout::commit() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git checkout "$@"; return $?; }
|
||||
local cmd opts graph
|
||||
cmd="echo {} | $forgit_extract_sha |xargs -I% git show --color=always % | $forgit_show_pager"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s +m --tiebreak=index
|
||||
--bind=\"ctrl-y:execute-silent(echo {} | $forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy})\"
|
||||
--preview=\"$cmd\"
|
||||
$FORGIT_COMMIT_FZF_OPTS
|
||||
"
|
||||
graph=--graph
|
||||
[[ $FORGIT_LOG_GRAPH_ENABLE == false ]] && graph=
|
||||
eval "git log $graph --color=always --format='$forgit_log_format' $forgit_emojify" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf | eval "$forgit_extract_sha" | xargs -I% git checkout % --
|
||||
"$FORGIT" checkout_commit "$@"
|
||||
}
|
||||
|
||||
forgit::branch::delete() {
|
||||
forgit::inside_work_tree || return 1
|
||||
local preview opts cmd branches
|
||||
preview="git log {1} $forgit_log_preview_options"
|
||||
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --multi --tiebreak=index --header-lines=1
|
||||
--preview=\"$preview\"
|
||||
$FORGIT_BRANCH_DELETE_FZF_OPTS
|
||||
"
|
||||
|
||||
cmd="git branch --color=always | LC_ALL=C sort -k1.1,1.1 -rs"
|
||||
branches=$(eval "$cmd" | FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')
|
||||
echo -n "$branches" | tr '\n' '\0' | xargs -I{} -0 git branch -D {}
|
||||
"$FORGIT" branch_delete "$@"
|
||||
}
|
||||
|
||||
# git revert-commit selector
|
||||
forgit::revert::commit() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && { git revert "$@"; return $?; }
|
||||
local cmd opts files preview commits IFS
|
||||
cmd="git log --graph --color=always --format='$forgit_log_format' $* $forgit_emojify"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
+s --tiebreak=index
|
||||
$FORGIT_REVERT_COMMIT_OPTS
|
||||
"
|
||||
files=$(sed -nE 's/.* -- (.*)/\1/p' <<< "$*") # extract files parameters for `git show` command
|
||||
preview="echo {} | $forgit_extract_sha | xargs -I% git show --color=always % -- $files | $forgit_show_pager"
|
||||
# shellcheck disable=2207
|
||||
IFS=$'\n' commits=($(eval "$cmd" |
|
||||
FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview" -m |
|
||||
sed 's/^[^a-f^0-9]*\([a-f0-9]*\).*/\1/'))
|
||||
[ ${#commits[@]} -eq 0 ] && return 1
|
||||
for commit in "${commits[@]}"; do
|
||||
git revert "$commit"
|
||||
done
|
||||
"$FORGIT" revert_commit "$@"
|
||||
}
|
||||
|
||||
# git blame viewer
|
||||
forgit::blame() {
|
||||
forgit::inside_work_tree || return 1
|
||||
[[ $# -ne 0 ]] && git blame "$@" && return 0
|
||||
local opts flags preview file
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
$FORGIT_BLAME_FZF_OPTS
|
||||
"
|
||||
flags=$(git rev-parse --flags "$@")
|
||||
preview="
|
||||
if (git ls-files {} --error-unmatch) &>/dev/null; then
|
||||
git blame {} --date=short $flags | $forgit_blame_pager
|
||||
else
|
||||
echo File not tracked
|
||||
fi
|
||||
"
|
||||
file=$(FZF_DEFAULT_OPTS="$opts" fzf --preview="$preview")
|
||||
[[ -z "$file" ]] && return 1
|
||||
eval git blame "$file" "$flags"
|
||||
"$FORGIT" blame "$@"
|
||||
}
|
||||
|
||||
# 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:-${XDG_CACHE_HOME:-$HOME/.cache}/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 opts
|
||||
cmd="$forgit_ignore_pager $FORGIT_GI_TEMPLATES/{2}{,.gitignore} 2>/dev/null"
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m --preview-window='right:70%'
|
||||
--preview=\"eval $cmd\"
|
||||
$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 | awk '{print $2}'))
|
||||
[ ${#args[@]} -eq 0 ] && return 1
|
||||
# shellcheck disable=SC2068
|
||||
forgit::ignore::get ${args[@]}
|
||||
}
|
||||
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" ignore "$@"
|
||||
}
|
||||
|
||||
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%'
|
||||
+1
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
"
|
||||
forgit::ignore::update() {
|
||||
"$FORGIT" ignore_update "$@"
|
||||
}
|
||||
|
||||
forgit::ignore::get() {
|
||||
"$FORGIT" ignore_get "$@"
|
||||
}
|
||||
|
||||
forgit::ignore::list() {
|
||||
"$FORGIT" ignore_list "$@"
|
||||
}
|
||||
|
||||
forgit::ignore::clean() {
|
||||
"$FORGIT" ignore_clean "$@"
|
||||
}
|
||||
|
||||
# register aliases
|
||||
# shellcheck disable=SC2139
|
||||
|
|
@ -527,15 +144,3 @@ if [[ -z "$FORGIT_NO_ALIASES" ]]; then
|
|||
alias "${forgit_fixup:-gfu}"='forgit::fixup'
|
||||
alias "${forgit_blame:-gbl}"='forgit::blame'
|
||||
fi
|
||||
|
||||
# set installation path (for use by `bin/git-forgit`)
|
||||
if [[ -n "$ZSH_VERSION" ]]; then
|
||||
FORGIT_INSTALL_DIR="$( dirname -- "$0")"
|
||||
elif [[ -n "$BASH_VERSION" ]]; then
|
||||
FORGIT_INSTALL_DIR="$(dirname -- "${BASH_SOURCE[0]}")"
|
||||
else
|
||||
forgit::warn "Only zsh and bash are fully supported"
|
||||
fi
|
||||
if [[ -n "$FORGIT_INSTALL_DIR" ]]; then
|
||||
export FORGIT_INSTALL_DIR
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in a new issue