diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 89e2795..bf3f8aa 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -41,7 +41,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 diff --git a/bin/git-forgit b/bin/git-forgit index 2bf5820..ff216d6 100755 --- a/bin/git-forgit +++ b/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,512 @@ # 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" +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" +} cmd="$1" shift -forgit::${cmd//_/::} "$@" +_forgit_"${cmd}" "$@" diff --git a/conf.d/bin/git-forgit b/conf.d/bin/git-forgit new file mode 120000 index 0000000..b25cefc --- /dev/null +++ b/conf.d/bin/git-forgit @@ -0,0 +1 @@ +../../bin/git-forgit \ No newline at end of file diff --git a/conf.d/forgit.plugin.fish b/conf.d/forgit.plugin.fish index 58ced23..4f082ba 100644 --- a/conf.d/forgit.plugin.fish +++ b/conf.d/forgit.plugin.fish @@ -1,640 +1,87 @@ # MIT (c) Chris Apple -function forgit::warn - printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$argv" >&2; -end +set -g FORGIT_INSTALL_DIR (dirname (dirname (status -f))) -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 - 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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-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.' + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" checkout_file "$argv" 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 % -- + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" checkout_commit "$argv" 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 + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" branch_delete "$argv" 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 + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" checkout_branch "$argv" 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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-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 - - 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 + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" cherry_pick_from_branch "$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_INSTALL_DIR/conf.d/bin/git-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 -end - -# git ignore generator -if test -z "$FORGIT_GI_REPO_REMOTE" - set -g FORGIT_GI_REPO_REMOTE https://github.com/dvcs/gitignore -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 -end - -if test -z "$FORGIT_GI_TEMPLATES" - set -g FORGIT_GI_TEMPLATES $FORGIT_GI_REPO_LOCAL/templates + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" rebase "$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 + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" ignore "$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 - - 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 - " - - 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 + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" revert_commit "$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_INSTALL_DIR/conf.d/bin/git-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_INSTALL_DIR/conf.d/bin/git-forgit" ignore_get "$argv" end function forgit::ignore::list - find "$FORGIT_GI_TEMPLATES" -print | sed -e 's#.gitignore$##' -e 's#.*/##' | sort -fu + "$FORGIT_INSTALL_DIR/conf.d/bin/git-forgit" ignore_list "$argv" end function forgit::ignore::clean - if test -d "$FORGIT_GI_REPO_LOCAL" - rm -rf "$FORGIT_GI_REPO_LOCAL" - end + "$FORGIT_INSTALL_DIR/conf.d/bin/git-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" diff --git a/forgit.plugin.zsh b/forgit.plugin.zsh index b4fbb15..10e9828 100755 --- a/forgit.plugin.zsh +++ b/forgit.plugin.zsh @@ -1,510 +1,107 @@ #!/usr/bin/env bash # MIT (c) Wenxuan Zhang + forgit::warn() { printf "%b[Warn]%b %s\n" '\e[0;33m' '\e[0m' "$@" >&2; } -forgit::info() { printf "%b[Info]%b %s\n" '\e[0;32m' '\e[0m' "$@" >&2; } -forgit::inside_work_tree() { git rev-parse --is-inside-work-tree >/dev/null; } -# 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 -} +# set installation path +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 -# 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 + "$FORGIT_INSTALL_DIR/bin/git-forgit" log "$@" } -# 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 + "$FORGIT_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-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_INSTALL_DIR/bin/git-forgit" ignore_update "$@" +} + +forgit::ignore::get() { + "$FORGIT_INSTALL_DIR/bin/git-forgit" ignore_get "$@" +} + +forgit::ignore::list() { + "$FORGIT_INSTALL_DIR/bin/git-forgit" ignore_list "$@" +} + +forgit::ignore::clean() { + "$FORGIT_INSTALL_DIR/bin/git-forgit" ignore_clean "$@" +} # register aliases # shellcheck disable=SC2139 @@ -527,15 +124,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