From 8cbd52976a4477a9313a601d3452c90493e2ad57 Mon Sep 17 00:00:00 2001 From: Daniel Fichtinger Date: Fri, 2 May 2025 18:00:13 -0400 Subject: [PATCH] AutoYADM commit: 2025-05-02 18:00:13 --- .config/fish/conf.d/bin/bin/git-forgit | 1211 ------------------------ 1 file changed, 1211 deletions(-) delete mode 100755 .config/fish/conf.d/bin/bin/git-forgit diff --git a/.config/fish/conf.d/bin/bin/git-forgit b/.config/fish/conf.d/bin/bin/git-forgit deleted file mode 100755 index 27a893d1..00000000 --- a/.config/fish/conf.d/bin/bin/git-forgit +++ /dev/null @@ -1,1211 +0,0 @@ -#!/usr/bin/env bash -# 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` -# `git forgit checkout_file README.md` -# -# This gives users the choice to set aliases inside of their git config instead -# of their shell config if they prefer. - -# Check if fzf is installed -installed_fzf_version=$(fzf --version 2>/dev/null | awk '{print $1}') -if [[ -z "$installed_fzf_version" ]]; then - echo "fzf is not installed. Please install fzf first." - exit 1 -fi - -# Check fzf version -required_fzf_version="0.49.0" -higher_fzf_version=$(printf '%s\n' "$required_fzf_version" "$installed_fzf_version" | sort -V | tail -n1) -if [[ "$higher_fzf_version" != "$installed_fzf_version" ]]; then - echo "fzf version $required_fzf_version or higher is required. You have $installed_fzf_version." - exit 1 -fi - -# Set shell for fzf preview commands -# Disable shellcheck for "which", because it suggests "command -v xxx" instead, -# which is not a working replacement. -# See https://github.com/koalaman/shellcheck/issues/1162 -# shellcheck disable=2230 -SHELL="$(which bash)" -export SHELL - -# Get absolute forgit path -FORGIT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)/$(basename -- "${BASH_SOURCE[0]}") - -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 -} - -_forgit_contains_non_flags() { - while (("$#")); do - case "$1" in - -*) shift ;; - *) - return 0 - ;; - esac - done - return 1 -} - -# optional render emoji characters (https://github.com/wfxr/emoji-cli) -_forgit_emojify() { - if hash emojify &>/dev/null; then - emojify - else - cat - fi -} - -# 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:]' -} - -# extract the first git sha and copy it to the clipboard -_forgit_yank_sha() { - echo "$1" | _forgit_extract_sha | ${FORGIT_COPY_CMD:-pbcopy} -} - -# extract the first stash name in the input -_forgit_extract_stash_name() { - cut -d: -f1 | tr -d '[:space:]' -} - -# extract the first stash name and copy it to the clipboard -_forgit_yank_stash_name() { - echo "$1" | _forgit_extract_stash_name | ${FORGIT_COPY_CMD:-pbcopy} -} - -# parse a space separated string into an array -# arrays parsed with this function are global -_forgit_parse_array() { - ${IFS+"false"} && unset old_IFS || old_IFS="$IFS" - # read the value of the second argument - # into an array that has the name of the first argument - IFS=" " read -r -a "$1" <<< "$2" - ${old_IFS+"false"} && unset IFS || IFS="$old_IFS" -} - -# parse the input arguments and print only those after the "--" -# separator as a single line of quoted arguments to stdout -_forgit_quote_files() { - local files add - files=() - add=false - while (( "$#" )); do - case "$1" in - --) - add=true - shift - ;; - *) - if [ $add == true ]; then - files+=("'$1'") - fi - shift - ;; - esac - done - echo "${files[*]}" -} - -_forgit_log_graph_enable=${FORGIT_LOG_GRAPH_ENABLE:-"true"} -_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} -_forgit_dir_view=${FORGIT_DIR_VIEW:-$(hash tree &> /dev/null && echo 'tree' || echo 'find')} - -_forgit_pager() { - local pager - pager=$(_forgit_get_pager "$1") - [[ -z "${pager}" ]] && exit 1 - eval "${pager} ${*:2}" -} - -_forgit_get_pager() { - local pager - pager=${1:-core} - case "$pager" in - core) echo -n "${FORGIT_PAGER:-$(git config core.pager || echo 'cat')}" ;; - show) echo -n "${FORGIT_SHOW_PAGER:-$(git config pager.show || _forgit_get_pager)}" ;; - diff) echo -n "${FORGIT_DIFF_PAGER:-$(git config pager.diff || _forgit_get_pager)}" ;; - ignore) echo -n "${FORGIT_IGNORE_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitignore --color=always' || echo 'cat')}" ;; - attributes) echo -n "${FORGIT_ATTRIBUTES_PAGER:-$(hash bat &>/dev/null && echo 'bat -l gitattributes --color=always' || echo 'cat')}" ;; - blame) echo -n "${FORGIT_BLAME_PAGER:-$(git config pager.blame || _forgit_get_pager)}" ;; - enter) echo -n "${FORGIT_ENTER_PAGER:-"LESS='-r' less"}" ;; - *) echo "pager not found: $1" >&2 ;; - esac -} - -_forgit_is_file_tracked() { - git ls-files "$1" --error-unmatch &> /dev/null -} - -_forgit_list_files() { - local rootdir - rootdir=$(git rev-parse --show-toplevel) - # git escapes special characters in it's output when core.quotePath is - # true or unset. Git always expects unquoted file paths as input. This - # leads to issues when we consume output from git and use it to build - # input for other git commands. Use the -z flag to ensure file paths are - # unquoted. - # uniq is necessary because unmerged files are printed once for each - # merge conflict. - # With the -z flag, git also uses \0 line termination, so we - # have to replace the terminators. - git ls-files -z "$@" "$rootdir" | tr '\0' '\n' | uniq -} - -_forgit_log_preview() { - local sha - sha=$(echo "$1" | _forgit_extract_sha) - shift - echo "$sha" | xargs -I% git show --color=always -U"$_forgit_preview_context" % -- "$@" | _forgit_pager show -} - -_forgit_log_enter() { - local sha - sha=$(echo "$1" | _forgit_extract_sha) - shift - echo "$sha" | xargs -I% "${FORGIT}" show % "$@" -} - -# git commit viewer -_forgit_log() { - _forgit_inside_work_tree || return 1 - local opts graph quoted_files log_format - quoted_files=$(_forgit_quote_files "$@") - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --bind=\"enter:execute($FORGIT log_enter {} $quoted_files)\" - --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" - --preview=\"$FORGIT log_preview {} $quoted_files\" - $FORGIT_LOG_FZF_OPTS - " - graph=() - [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) - log_format=${FORGIT_GLO_FORMAT:-$_forgit_log_format} - _forgit_log_git_opts=() - _forgit_parse_array _forgit_log_git_opts "$FORGIT_LOG_GIT_OPTS" - git log "${graph[@]}" --color=always --format="$log_format" "${_forgit_log_git_opts[@]}" "$@" | - _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 reflog viewer -_forgit_reflog() { - _forgit_inside_work_tree || return 1 - _forgit_contains_non_flags "$@" && { git reflog "$@"; return $?; } - local opts reflog_format - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --bind=\"enter:execute($FORGIT log_enter {})\" - --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" - --preview=\"$FORGIT log_preview {}\" - $FORGIT_REFLOG_FZF_OPTS - " - reflog_format=${FORGIT_GRL_FORMAT:-$_forgit_log_format} - _forgit_reflog_git_opts=() - _forgit_parse_array _forgit_reflog_git_opts "$FORGIT_REFLOG_GIT_OPTS" - git reflog show --color=always --format="$reflog_format" "${_forgit_reflog_git_opts[@]}" "$@" | - _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_get_files_from_diff_line() { - # Construct a null-terminated list of the filenames - # The input looks like one of these lines: - # [R100] file -> another file - # [A] file with spaces - # [D] oldfile - # And we transform it to this representation for further usage with "xargs -0": - # file\0another file\0 - # file with spaces\0 - # oldfile\0 - # We have to do a two-step sed -> tr pipe because OSX's sed implementation does - # not support the null-character directly. - sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/ -> /\n/' | tr '\n' '\0' -} - -_forgit_get_single_file_from_diff_line() { - # Similar to the function above, but only gets a single file from a single line - # Gets the new name of renamed files - sed 's/^[[:space:]]*\[[A-Z0-9]*\][[:space:]]*//' | sed 's/.*-> //' -} - -_forgit_exec_diff() { - _forgit_diff_git_opts=() - _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" - git diff --color=always "${_forgit_diff_git_opts[@]}" "$@" -} - -_forgit_diff_view() { - local input_line=$1 - local diff_context=$2 - local repo - local commits=() - repo=$(git rev-parse --show-toplevel) - cd "$repo" || return 1 - if [ $# -gt 2 ]; then - IFS=" " read -r -a commits <<< "${*:3}" - fi - echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \ - "$FORGIT" exec_diff "${commits[@]}" -U"$diff_context" -- | _forgit_pager diff -} - -_forgit_edit_diffed_file() { - local input_line rootdir - input_line=$1 - rootdir=$(git rev-parse --show-toplevel) - filename=$(echo "$input_line" | _forgit_get_single_file_from_diff_line) - $EDITOR "$rootdir/$filename" >/dev/tty /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 - } - # 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. - for commit in "${commits[@]}"; do - escaped_commits+="'${commit//\{/\\\\\{}' " - done - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +m -0 --bind=\"enter:execute($FORGIT diff_enter {} $escaped_commits | $FORGIT pager enter)\" - --preview=\"$FORGIT diff_view {} '$_forgit_preview_context' $escaped_commits\" - --bind=\"alt-e:execute($FORGIT edit_diffed_file {})+refresh-preview\" - $FORGIT_DIFF_FZF_OPTS - --prompt=\"${commits[*]} > \" - " - _forgit_diff_git_opts=() - _forgit_parse_array _forgit_diff_git_opts "$FORGIT_DIFF_GIT_OPTS" - git diff --name-status "${_forgit_diff_git_opts[@]}" "${commits[@]}" -- "${files[@]}" | - sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' | - sed 's/ / -> /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_exec_show() { - _forgit_show_git_opts=() - _forgit_parse_array _forgit_show_git_opts "$FORGIT_SHOW_GIT_OPTS" - git show --pretty="" --diff-merges=first-parent --color=always "${_forgit_show_git_opts[@]}" "$@" -} - -_forgit_show_view() { - local input_line=$1 - local diff_context=$2 - local commit=$3 - local repo - repo=$(git rev-parse --show-toplevel) - cd "$repo" || return 1 - echo "$input_line" | _forgit_get_files_from_diff_line | xargs -0 \ - "$FORGIT" exec_show "${commit}^{commit}" -U"$diff_context" -- | _forgit_pager diff -} - -_forgit_show_preview() { - local input_line=$1 - local diff_context=$2 - local commit=$3 - if [[ "$FZF_PREVIEW_LABEL" =~ "Diff" ]]; then - _forgit_show_view "${input_line}" "${diff_context}" "${commit}" - else - git show --quiet --color=always "${FZF_PROMPT%% *}" - fi -} - -_forgit_show_enter() { - file=$1 - commit=$2 - _forgit_show_view "$file" "$_forgit_fullscreen_context" "${commit}" -} - -# git show viewer -_forgit_show() { - _forgit_inside_work_tree || return 1 - local files opts commit escaped_commit - files=() - if [[ $# -ne 0 ]]; then - if git rev-parse "$1" -- &>/dev/null ; then - commit="$1" && files=("${@:2}") - else - commit="HEAD" && files=("$@") - fi - else - commit="HEAD" - fi - # Escape opening brackets to support stashes (see comment in _forgit_diff) - escaped_commit=${commit//\{/\\\\\{} - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +m -0 --bind=\"enter:execute($FORGIT show_enter {} $escaped_commit | $FORGIT pager enter)\" - --preview=\"$FORGIT show_preview {} '$_forgit_preview_context' $escaped_commit\" - --preview-label=\" Diff \" - --bind=\"alt-e:execute($FORGIT edit_diffed_file {})+refresh-preview\" - --bind=\"alt-t:transform:[[ ! \\\"\$FZF_PREVIEW_LABEL\\\" =~ 'Diff' ]] && - echo 'change-preview-label( Diff )+refresh-preview' || - echo 'change-preview-label( Commit Message )+refresh-preview'\" - $FORGIT_DIFF_FZF_OPTS - --prompt=\"${commit} > \" - " - _forgit_show_git_opts=() - _forgit_parse_array _forgit_show_git_opts "$FORGIT_SHOW_GIT_OPTS" - # Add "^{commit}" suffix after the actual commit. This suppresses the tag information in case it is a tag. - # See: https://git-scm.com/docs/git-show#Documentation/git-show.txt-codegitshow-s--formatsv100commitcode - git show --pretty="" --name-status --diff-merges=first-parent "${_forgit_show_git_opts[@]}" "${commit}^{commit}" \ - -- "${files[@]}" | - sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' | - sed 's/ / -> /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_add_preview() { - file=$(echo "$1" | _forgit_get_single_file_from_add_line) - if (git status -s -- "$file" | grep '^??') &>/dev/null; then # diff with /dev/null for untracked files - git diff --color=always --no-index -- /dev/null "$file" | _forgit_pager diff | sed '2 s/added:/untracked:/' - else - git diff --color=always -- "$file" | _forgit_pager diff - fi -} - -_forgit_git_add() { - _forgit_add_git_opts=() - _forgit_parse_array _forgit_add_git_opts "$FORGIT_ADD_GIT_OPTS" - git add "${_forgit_add_git_opts[@]}" "$@" -} - -_forgit_get_single_file_from_add_line() { - # NOTE: paths listed by 'git status -su' mixed with quoted and unquoted style - # remove indicators | remove original path for rename case | remove surrounding quotes - sed 's/^.*] //' | - sed 's/.* -> //' | - sed -e 's/^\"//' -e 's/\"$//' -} - -_forgit_edit_add_file() { - local input_line=$1 - filename=$(echo "$input_line" | _forgit_get_single_file_from_add_line) - $EDITOR "$filename" >/dev/tty newest when you multiselect - # The instances of "cut", "nl" and "sort" all serve this purpose - # Please see https://github.com/wfxr/forgit/issues/253 for more details - - opts=" - $FORGIT_FZF_DEFAULT_OPTS - --preview=\"$FORGIT cherry_pick_preview {}\" - --multi --ansi --with-nth 2.. -0 --tiebreak=index - $FORGIT_CHERRY_PICK_FZF_OPTS - " - # Note: do not add any pipe after the fzf call here, otherwise the fzf_exitval is not propagated properly. - # Any eventual post processing can be done afterwards when the "commits" variable is assigned below. - fzf_selection=$(git log --right-only --color=always --cherry-pick --oneline "$base"..."$target" | nl | - FZF_DEFAULT_OPTS="$opts" fzf) - fzf_exitval=$? - [[ $fzf_exitval != 0 ]] && return $fzf_exitval - [[ -z "$fzf_selection" ]] && return $fzf_exitval - - commits=() - while IFS='' read -r commit; do - commits+=("$commit") - done < <(echo "$fzf_selection" | sort -n -k 1 | cut -f2 | cut -d' ' -f1 | _forgit_reverse_lines) - [ ${#commits[@]} -eq 0 ] && return 1 - - _forgit_cherry_pick_git_opts=() - _forgit_parse_array _forgit_cherry_pick_git_opts "$FORGIT_CHERRY_PICK_GIT_OPTS" - git cherry-pick "${_forgit_cherry_pick_git_opts[@]}" "${commits[@]}" -} - -_forgit_cherry_pick_from_branch_preview() { - git log --right-only --color=always --cherry-pick --oneline "$1"..."$2" -} - -_forgit_cherry_pick_from_branch() { - _forgit_inside_work_tree || return 1 - local opts branch exitval input_branch args base - - base=$(git branch --show-current) - [[ -z "$base" ]] && echo "Current commit is not on a branch." && return 1 - - args=("$@") - if [[ $# -ne 0 ]]; then - input_branch=${args[0]} - fi - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index --header-lines=1 - --preview=\"$FORGIT cherry_pick_from_branch_preview '$base' {1}\" - $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 - if [[ -z $input_branch ]]; then - branch="$(git branch --color=always --all | - LC_ALL=C sort -k1.1,1.1 -rs | - FZF_DEFAULT_OPTS="$opts" fzf | - awk '{print $1}')" - else - branch=$input_branch - fi - - unset input_branch - [[ -z "$branch" ]] && return 1 - - _forgit_cherry_pick "$branch" - - exitval=$? - [[ $exitval != 130 ]] || [[ $# -ne 0 ]] && return $exitval - done -} - -_forgit_rebase() { - _forgit_inside_work_tree || return 1 - _forgit_contains_non_flags "$@" && { git rebase "$@"; return $?; } - local opts graph target_commit prev_commit - graph=() - [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) - _forgit_rebase_git_opts=() - _forgit_parse_array _forgit_rebase_git_opts "$FORGIT_REBASE_GIT_OPTS" - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" - --preview=\"$FORGIT file_preview {}\" - $FORGIT_REBASE_FZF_OPTS - " - target_commit=$( - git log "${graph[@]}" --color=always --format="$_forgit_log_format" | - _forgit_emojify | - FZF_DEFAULT_OPTS="$opts" fzf | - _forgit_extract_sha) - if [[ -n "$target_commit" ]]; then - prev_commit=$(_forgit_previous_commit "$target_commit") - git rebase -i "${_forgit_rebase_git_opts[@]}" "$@" "$prev_commit" - fi -} - -_forgit_file_preview() { - local sha - sha=$(echo "$1" | _forgit_extract_sha) - shift - echo "$sha" | xargs -I% git show --color=always % -- "$@" | _forgit_pager show -} - -_forgit_fixup() { - _forgit_inside_work_tree || return 1 - git diff --cached --quiet && echo 'Nothing to fixup: there are no staged changes.' && return 1 - local opts graph quoted_files target_commit prev_commit - graph=() - [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) - _forgit_fixup_git_opts=() - _forgit_parse_array _forgit_fixup_git_opts "$FORGIT_FIXUP_GIT_OPTS" - quoted_files=$(_forgit_quote_files "$@") - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" - --preview=\"$FORGIT file_preview {} $quoted_files\" - $FORGIT_FIXUP_FZF_OPTS - " - target_commit=$( - git log "${graph[@]}" --color=always --format="$_forgit_log_format" "$@" | - _forgit_emojify | - FZF_DEFAULT_OPTS="$opts" fzf | - _forgit_extract_sha) - if [[ -n "$target_commit" ]] && git commit "${_forgit_fixup_git_opts[@]}" --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_checkout_file_preview() { - git diff --color=always -- "$1" | _forgit_pager diff -} - -_forgit_git_checkout_file() { - _forgit_checkout_file_git_opts=() - _forgit_parse_array _forgit_checkout_file_git_opts "$FORGIT_CHECKOUT_FILE_GIT_OPTS" - git checkout "${_forgit_checkout_file_git_opts[@]}" "$@" -} - -# git checkout-file selector -_forgit_checkout_file() { - _forgit_inside_work_tree || return 1 - local files opts - [[ $# -ne 0 ]] && { _forgit_git_checkout_file -- "$@"; return $?; } - opts=" - $FORGIT_FZF_DEFAULT_OPTS - -m -0 - --preview=\"$FORGIT checkout_file_preview {}\" - $FORGIT_CHECKOUT_FILE_FZF_OPTS - " - files=() - while IFS='' read -r file; do - files+=("$file") - done < <(_forgit_list_files --modified | - FZF_DEFAULT_OPTS="$opts" fzf) - [[ "${#files[@]}" -gt 0 ]] && _forgit_git_checkout_file "${files[@]}" -} - -_forgit_git_checkout_branch() { - _forgit_checkout_branch_git_opts=() - _forgit_parse_array _forgit_checkout_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_GIT_OPTS" - git checkout "${_forgit_checkout_branch_git_opts[@]}" "$@" -} - -# 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 opts branch - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index --header-lines=1 - --preview=\"$FORGIT branch_preview {1}\" - $FORGIT_CHECKOUT_BRANCH_FZF_OPTS - " - _forgit_checkout_branch_branch_git_opts=() - _forgit_parse_array _forgit_checkout_branch_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS" - branch="$(git branch --color=always "${_forgit_checkout_branch_branch_git_opts[@]:---all}" | LC_ALL=C sort -k1.1,1.1 -rs | - FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $1}')" - [[ -z "$branch" ]] && return 1 - - # track the remote branch if possible - if [[ "$branch" == "remotes/"* ]]; then - if git branch | grep -qw "${branch#remotes/*/}"; then - # hack to force creating a new branch which tracks the remote if a local branch already exists - _forgit_git_checkout_branch -b "track/${branch#remotes/*/}" --track "$branch" - elif ! _forgit_git_checkout_branch --track "$branch" 2>/dev/null; then - _forgit_git_checkout_branch "$branch" - fi - else - _forgit_git_checkout_branch "$branch" - fi -} - -_forgit_git_checkout_tag() { - _forgit_checkout_tag_git_opts=() - _forgit_parse_array _forgit_checkout_tag_git_opts "$FORGIT_CHECKOUT_TAG_GIT_OPTS" - git checkout "${_forgit_checkout_tag_git_opts[@]}" "$@" -} - -# git checkout-tag selector -_forgit_checkout_tag() { - _forgit_inside_work_tree || return 1 - local opts - [[ $# -ne 0 ]] && { _forgit_git_checkout_tag "$@"; return $?; } - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --preview=\"$FORGIT branch_preview {}\" - $FORGIT_CHECKOUT_TAG_FZF_OPTS - " - tag="$(git tag -l --sort=-v:refname | FZF_DEFAULT_OPTS="$opts" fzf)" - [[ -z "$tag" ]] && return 1 - _forgit_git_checkout_tag "$tag" -} - -_forgit_checkout_commit_preview() { - echo "$1" | _forgit_extract_sha | xargs -I% git show --color=always % | _forgit_pager show -} - -_forgit_git_checkout_commit() { - _forgit_checkout_commit_git_opts=() - _forgit_parse_array _forgit_checkout_commit_git_opts "$FORGIT_CHECKOUT_COMMIT_GIT_OPTS" - git checkout "${_forgit_checkout_commit_git_opts[@]}" "$@" -} - -# git checkout-commit selector -_forgit_checkout_commit() { - _forgit_inside_work_tree || return 1 - local opts graph commit - [[ $# -ne 0 ]] && { _forgit_git_checkout_commit "$@"; return $?; } - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s +m --tiebreak=index - --bind=\"ctrl-y:execute-silent($FORGIT yank_sha {})\" - --preview=\"$FORGIT checkout_commit_preview {}\" - $FORGIT_CHECKOUT_COMMIT_FZF_OPTS - " - graph=() - [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) - commit="$(git log "${graph[@]}" --color=always --format="$_forgit_log_format" | - _forgit_emojify | - FZF_DEFAULT_OPTS="$opts" fzf | _forgit_extract_sha)" - _forgit_git_checkout_commit "$commit" -} - -_forgit_branch_preview() { - # the trailing '--' ensures that this works for branches that have a name - # that is identical to a file - git log "$1" "${_forgit_log_preview_options[@]}" -- -} - -_forgit_git_branch_delete() { - _forgit_branch_delete_git_opts=() - _forgit_parse_array _forgit_branch_delete_git_opts "$FORGIT_BRANCH_DELETE_GIT_OPTS" - git branch "${_forgit_branch_delete_git_opts[@]}" -D "$@" -} - -_forgit_branch_delete() { - _forgit_inside_work_tree || return 1 - local opts - [[ $# -ne 0 ]] && { _forgit_git_branch_delete "$@"; return $?; } - - opts=" - $FORGIT_FZF_DEFAULT_OPTS - +s --multi --tiebreak=index --header-lines=1 - --preview=\"$FORGIT branch_preview {1}\" - $FORGIT_BRANCH_DELETE_FZF_OPTS - " - - for branch in $(git branch --color=always | - LC_ALL=C sort -k1.1,1.1 -rs | - FZF_DEFAULT_OPTS="$opts" fzf | - awk '{print $1}') - do - _forgit_git_branch_delete "$branch" - done -} - -_forgit_revert_preview() { - echo "$1" | - cut -f2- | - _forgit_extract_sha | - xargs -I% git show --color=always % | - _forgit_pager show -} - -_forgit_git_revert() { - _forgit_revert_commit_git_opts=() - _forgit_parse_array _forgit_revert_commit_git_opts "$FORGIT_REVERT_COMMIT_GIT_OPTS" - git revert "${_forgit_revert_commit_git_opts[@]}" "$@" -} - -# git revert-commit selector -_forgit_revert_commit() { - _forgit_inside_work_tree || return 1 - local opts commits IFS - [[ $# -ne 0 ]] && { _forgit_git_revert "$@"; return $?; } - - opts=" - $FORGIT_FZF_DEFAULT_OPTS - -m +s --tiebreak=index - --ansi --with-nth 2.. - --preview=\"$FORGIT revert_preview {}\" - $FORGIT_REVERT_COMMIT_FZF_OPTS - " - graph=() - [[ $_forgit_log_graph_enable == true ]] && graph=(--graph) - - # in this function, we do something interesting to maintain proper ordering as it's assumed - # you generally want to revert newest->oldest when you multiselect - # The instances of "cut", "nl" and "sort" all serve this purpose - # Please see https://github.com/wfxr/forgit/issues/253 for more details - - commits=() - while IFS='' read -r commit; do - commits+=("$commit") - done < <( - git log "${graph[@]}" --color=always --format="$_forgit_log_format" | - _forgit_emojify | - nl | - FZF_DEFAULT_OPTS="$opts" fzf | - sort -n -k 1 | - cut -f2- | - sed 's/^[^a-f^0-9]*\([a-f0-9]*\).*/\1/') - - [ ${#commits[@]} -eq 0 ] && return 1 - - _forgit_git_revert "${commits[@]}" -} - -_forgit_blame_preview() { - if _forgit_is_file_tracked "$1"; then - _forgit_blame_git_opts=() - _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS" - git blame --date=short "${_forgit_blame_git_opts[@]}" "$@" | _forgit_pager blame - else - echo "File not tracked" - fi -} - -_forgit_git_blame() { - _forgit_blame_git_opts=() - _forgit_parse_array _forgit_blame_git_opts "$FORGIT_BLAME_GIT_OPTS" - git blame "${_forgit_blame_git_opts[@]}" "$@" -} - -# git blame viewer -_forgit_blame() { - _forgit_inside_work_tree || return 1 - local opts flags file - _forgit_contains_non_flags "$@" && { _forgit_git_blame "$@"; return $?; } - flags=() - while IFS='' read -r flag; do - flags+=("$flag") - done < <(git rev-parse --flags "$@") - opts=" - $FORGIT_FZF_DEFAULT_OPTS - --preview=\"$FORGIT blame_preview {} ${flags[*]}\" - $FORGIT_BLAME_FZF_OPTS - " - # flags is not quoted here, which is fine given that they are retrieved - # with git rev-parse and can only contain flags - file=$(FZF_DEFAULT_OPTS="$opts" fzf) - [[ -z "$file" ]] && return 1 - _forgit_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_path_preview() { - local path name ext pager - path=$1 - name=$2 - ext=$3 - pager=$4 - quoted_files=() - while IFS='' read -r file; do - quoted_files+=("'$file'") - done < <(find -L "$path" -type f -name "$name" -o -name "$name$ext") - _forgit_pager "$pager" "${quoted_files[@]}" 2>/dev/null -} - -_forgit_ignore() { - [ -d "$FORGIT_GI_REPO_LOCAL" ] \ - || _forgit_repo_update "$FORGIT_GI_REPO_REMOTE" "$FORGIT_GI_REPO_LOCAL" - local IFS args opts - opts=" - $FORGIT_FZF_DEFAULT_OPTS - -m --preview-window='right:70%' - --preview=\"$FORGIT path_preview $FORGIT_GI_TEMPLATES {2} .gitignore ignore\" - $FORGIT_IGNORE_FZF_OPTS - " - args=("$@") - if [[ $# -eq 0 ]]; then - args=() - while IFS='' read -r arg; do - args+=("$arg") - done < <(_forgit_paths_list "$FORGIT_GI_TEMPLATES" .gitignore | - nl -w4 -s' ' | - FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $2}') - fi - [ ${#args[@]} -eq 0 ] && return 1 - _forgit_path_get "$FORGIT_GI_TEMPLATES" .gitignore "${args[@]}" -} - -# git attributes generator -export FORGIT_ATTR_REPO_REMOTE=${FORGIT_ATTR_REPO_REMOTE:-https://github.com/gitattributes/gitattributes} -export FORGIT_ATTR_REPO_LOCAL=${FORGIT_ATTR_REPO_LOCAL:-${XDG_CACHE_HOME:-$HOME/.cache}/forgit/gat/repos/gitattributes/gitattributes} -export FORGIT_ATTR_TEMPLATES=${FORGIT_ATTR_TEMPLATES:-$FORGIT_ATTR_REPO_LOCAL} - -_forgit_attributes() { - [ -d "$FORGIT_ATTR_REPO_LOCAL" ] \ - || _forgit_repo_update "$FORGIT_ATTR_REPO_REMOTE" "$FORGIT_ATTR_REPO_LOCAL" - local IFS args opts - opts=" - $FORGIT_FZF_DEFAULT_OPTS - -m --preview-window='right:70%' - --preview=\"$FORGIT path_preview $FORGIT_ATTR_TEMPLATES {2} .gitattributes attributes\" - $FORGIT_ATTRIBUTES_FZF_OPTS - " - args=("$@") - if [[ $# -eq 0 ]]; then - args=() - while IFS='' read -r arg; do - args+=("$arg") - done < <(_forgit_paths_list "$FORGIT_ATTR_TEMPLATES" .gitattributes | - nl -w4 -s' ' | - FZF_DEFAULT_OPTS="$opts" fzf | awk '{print $2}') - fi - [ ${#args[@]} -eq 0 ] && return 1 - _forgit_path_get "$FORGIT_ATTR_TEMPLATES" .gitattributes "${args[@]}" -} - -_forgit_repo_update() { - local remote path - remote=$1 - path=$2 - if [[ -d "$path" ]]; then - _forgit_info 'Updating repo...' - (cd "$path" && git pull --no-rebase --ff) || return 1 - else - _forgit_info 'Initializing repo...' - git clone --depth=1 "$remote" "$path" - fi -} - -_forgit_path_get() { - local path ext item filename header - path=$1 - ext=$2 - shift 2 - for item in "$@"; do - if filename=$(find -L "$path" -type f \( -iname "${item}$ext" -o -iname "${item}" \) -print -quit); then - [[ -z "$filename" ]] && _forgit_warn "No template found for '$item'." && continue - header="${filename##*/}" && header="${header%"$ext"}" - echo "### $header" && cat "$filename" && echo - fi - done -} - -_forgit_paths_list() { - local path ext - path=$1 - ext=$2 - find "$path" -name "*$ext" -print |sed -e "s#$ext\$##" -e 's#.*/##' -e '/^$/d' | sort -fu -} - -public_commands=( - "add" - "attributes" - "blame" - "branch_delete" - "checkout_branch" - "checkout_commit" - "checkout_file" - "checkout_tag" - "cherry_pick" - "cherry_pick_from_branch" - "clean" - "diff" - "fixup" - "ignore" - "log" - "reflog" - "rebase" - "reset_head" - "revert_commit" - "show" - "stash_show" - "stash_push" -) - -private_commands=( - "add_preview" - "blame_preview" - "branch_preview" - "checkout_commit_preview" - "checkout_file_preview" - "cherry_pick_from_branch_preview" - "cherry_pick_preview" - "clean_preview" - "diff_enter" - "exec_show" - "file_preview" - "path_preview" - "revert_preview" - "reset_head_preview" - "show_enter" - "show_preview" - "stash_push_preview" - "stash_show_preview" - "yank_sha" - "yank_stash_name" - "log_preview" - "log_enter" - "exec_diff" - "diff_view" - "edit_diffed_file" - "edit_add_file" - "pager" -) - -cmd="$1" -shift - -# shellcheck disable=SC2076 -if [[ ! " ${public_commands[*]} " =~ " ${cmd} " ]] && [[ ! " ${private_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" "${public_commands[@]}" - exit 1 -fi - -_forgit_"${cmd}" "$@"