mirror of
https://github.com/wfxr/forgit.git
synced 2026-09-09 23:06:25 -04:00
feat: add interactive git restore selector (#537)
Co-authored-by: Javier Tia <floss@jetm.me>
This commit is contained in:
parent
e789afed64
commit
3dbcacd3f2
|
|
@ -53,6 +53,7 @@ It's **lightweight** and **easy to use**.
|
|||
| `gat` | Interactive `.gitattributes` generator |
|
||||
| `gso` | Interactive `git show` viewer |
|
||||
| `grh` | Interactive `git reset HEAD <file>` selector |
|
||||
| `grs` | Interactive `git restore <file>` selector |
|
||||
| `gcf` | Interactive `git checkout <file>` selector |
|
||||
| `gcff` | Interactive `git checkout <file> from <commit>` selector |
|
||||
| `gcb` | Interactive `git checkout <branch>` selector |
|
||||
|
|
@ -198,6 +199,7 @@ forgit_diff=gd
|
|||
forgit_show=gso
|
||||
forgit_add=ga
|
||||
forgit_reset_head=grh
|
||||
forgit_restore=grs
|
||||
forgit_ignore=gi
|
||||
forgit_attributes=gat
|
||||
forgit_checkout_file=gcf
|
||||
|
|
@ -279,6 +281,7 @@ Each forgit command can be customized with dedicated environment variables for g
|
|||
| `gd` | `FORGIT_DIFF_GIT_OPTS` | `FORGIT_DIFF_FZF_OPTS` |
|
||||
| `gso` | `FORGIT_SHOW_GIT_OPTS` | `FORGIT_SHOW_FZF_OPTS` |
|
||||
| `grh` | `FORGIT_RESET_HEAD_GIT_OPTS` | `FORGIT_RESET_HEAD_FZF_OPTS` |
|
||||
| `grs` | `FORGIT_RESTORE_GIT_OPTS` | `FORGIT_RESTORE_FZF_OPTS` |
|
||||
| `gcf` | `FORGIT_CHECKOUT_FILE_GIT_OPTS` | `FORGIT_CHECKOUT_FILE_FZF_OPTS` |
|
||||
| `gcff` | `FORGIT_SHOW_GIT_OPTS`<br>`FORGIT_CHECKOUT_FILE_GIT_OPTS` | `FORGIT_CHECKOUT_FILE_FROM_COMMIT_LOG_FZF_OPTS`<br>`FORGIT_CHECKOUT_FILE_FROM_COMMIT_SHOW_FZF_OPTS` |
|
||||
| `gcb` | `FORGIT_CHECKOUT_BRANCH_GIT_OPTS`<br>`FORGIT_CHECKOUT_BRANCH_BRANCH_GIT_OPTS` | `FORGIT_CHECKOUT_BRANCH_FZF_OPTS` |
|
||||
|
|
|
|||
|
|
@ -219,6 +219,12 @@ _forgit_list_files() {
|
|||
git ls-files -z "$@" "$rootdir" | tr '\0' '\n' | uniq
|
||||
}
|
||||
|
||||
_forgit_list_staged_files() {
|
||||
local up
|
||||
up="$(git rev-parse --show-cdup)"
|
||||
git diff --name-only --cached -z | tr '\0' '\n' | awk -v up="$up" '{ print up $0 }'
|
||||
}
|
||||
|
||||
# Print changed files in the worktree
|
||||
#
|
||||
# Always includes modified and unmerged files. Includes untracked files when
|
||||
|
|
@ -674,6 +680,74 @@ _forgit_reset_head() {
|
|||
git status --short
|
||||
}
|
||||
|
||||
_forgit_restore_preview() {
|
||||
git diff --color=always "$@" | _forgit_pager diff
|
||||
}
|
||||
|
||||
_forgit_git_restore() {
|
||||
_forgit_restore_git_opts=()
|
||||
_forgit_parse_array _forgit_restore_git_opts "$FORGIT_RESTORE_GIT_OPTS"
|
||||
git restore "${_forgit_restore_git_opts[@]}" "$@"
|
||||
}
|
||||
|
||||
_forgit_parse_restore_flags() {
|
||||
local staged worktree preview_arg
|
||||
staged=false
|
||||
worktree=false
|
||||
|
||||
for arg in "$@"; do
|
||||
case "$arg" in
|
||||
-S | --staged) staged=true ;;
|
||||
-W | --worktree) worktree=true ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $staged == true && $worktree != true ]]; then
|
||||
preview_arg=--staged
|
||||
elif [[ $staged == true && $worktree == true ]]; then
|
||||
preview_arg=HEAD
|
||||
fi
|
||||
|
||||
printf '%s\t%s\t%s' "$staged" "$worktree" "$preview_arg"
|
||||
}
|
||||
|
||||
# git restore selector
|
||||
_forgit_restore() {
|
||||
_forgit_inside_work_tree || return 1
|
||||
_forgit_contains_non_flags "$@" && {
|
||||
_forgit_git_restore "$@"
|
||||
return $?
|
||||
}
|
||||
local files opts staged worktree candidates preview_arg
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags "$@")
|
||||
opts="
|
||||
$FORGIT_FZF_DEFAULT_OPTS
|
||||
-m -0
|
||||
--preview=\"$FORGIT preview restore_preview $preview_arg -- {}\"
|
||||
$FORGIT_RESTORE_FZF_OPTS
|
||||
"
|
||||
|
||||
candidates=()
|
||||
if [[ $staged == true ]]; then
|
||||
while IFS='' read -r file; do
|
||||
candidates+=("$file")
|
||||
done < <(_forgit_list_staged_files)
|
||||
fi
|
||||
if [[ $worktree == true || $staged != true ]]; then
|
||||
while IFS='' read -r file; do
|
||||
candidates+=("$file")
|
||||
done < <(_forgit_list_files --modified)
|
||||
fi
|
||||
|
||||
[[ ${#candidates[@]} -eq 0 ]] && echo "Nothing to restore." && return 1
|
||||
|
||||
files=()
|
||||
while IFS='' read -r file; do
|
||||
files+=("$file")
|
||||
done < <(printf '%s\n' "${candidates[@]}" | sort -u | FZF_DEFAULT_OPTS="$opts" fzf)
|
||||
[[ ${#files[@]} -gt 0 ]] && _forgit_git_restore "$@" -- "${files[@]}"
|
||||
}
|
||||
|
||||
_forgit_stash_show_preview() {
|
||||
local stash
|
||||
stash=$(echo "$1" | _forgit_extract_stash_name)
|
||||
|
|
@ -1737,6 +1811,7 @@ PUBLIC_COMMANDS=(
|
|||
"reflog"
|
||||
"rebase"
|
||||
"reset_head"
|
||||
"restore"
|
||||
"revert_commit"
|
||||
"show"
|
||||
"stash_show"
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ _git-forgit() {
|
|||
'reflog:git reflog viewer'
|
||||
'rebase:git rebase'
|
||||
'reset_head:git reset HEAD (unstage) selector'
|
||||
'restore:git restore file selector'
|
||||
'revert_commit:git revert commit selector'
|
||||
'reword:git fixup=reword'
|
||||
'show:git show viewer'
|
||||
|
|
@ -117,6 +118,7 @@ _git-forgit() {
|
|||
reflog) _git-forgit-reflog ;;
|
||||
rebase) _git-rebase ;;
|
||||
reset_head) _git-staged ;;
|
||||
restore) _git-restore ;;
|
||||
revert_commit) __git_recent_commits ;;
|
||||
reword) _git-log ;;
|
||||
squash) _git-log ;;
|
||||
|
|
@ -151,6 +153,7 @@ compdef _git-log forgit::log
|
|||
compdef _git-reflog forgit::reflog
|
||||
compdef _git-rebase forgit::rebase
|
||||
compdef _git-staged forgit::reset::head
|
||||
compdef _git-restore forgit::restore
|
||||
compdef __git_recent_commits forgit::revert::commit
|
||||
compdef _git-log forgit::reword
|
||||
compdef _git-log forgit::squash
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ _git_forgit() {
|
|||
reflog
|
||||
rebase
|
||||
reset_head
|
||||
restore
|
||||
revert_commit
|
||||
reword
|
||||
show
|
||||
|
|
@ -114,6 +115,7 @@ _git_forgit() {
|
|||
reflog) _git_reflog ;;
|
||||
rebase) _git_rebase ;;
|
||||
reset_head) _git_reset ;;
|
||||
restore) _git_restore ;;
|
||||
revert_commit) _git_revert ;;
|
||||
reword) _git_log ;;
|
||||
show) _git_show ;;
|
||||
|
|
@ -156,6 +158,7 @@ if [[ $(type -t forgit::add) == function ]]; then
|
|||
__git_complete forgit::reflog _git_reflog
|
||||
__git_complete forgit::rebase _git_rebase
|
||||
__git_complete forgit::reset::head _git_reset
|
||||
__git_complete forgit::restore _git_restore
|
||||
__git_complete forgit::revert::commit _git_revert
|
||||
__git_complete forgit::reword _git_log
|
||||
__git_complete forgit::show _git_show
|
||||
|
|
@ -183,6 +186,7 @@ if [[ $(type -t forgit::add) == function ]]; then
|
|||
__git_complete "${forgit_reflog}" _git_reflog
|
||||
__git_complete "${forgit_rebase}" _git_rebase
|
||||
__git_complete "${forgit_reset_head}" _git_reset
|
||||
__git_complete "${forgit_restore}" _git_restore
|
||||
__git_complete "${forgit_revert_commit}" _git_revert
|
||||
__git_complete "${forgit_reword}" _git_log
|
||||
__git_complete "${forgit_show}" _git_show
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
function __fish_forgit_needs_subcommand
|
||||
for subcmd in add blame branch_delete checkout_branch checkout_commit checkout_file checkout_file_from_commit \
|
||||
checkout_tag cherry_pick cherry_pick_from_branch clean diff fixup ignore log reflog rebase reset_head \
|
||||
revert_commit reword squash stash_show stash_push switch_branch worktree worktree_add worktree_delete
|
||||
restore revert_commit reword squash stash_show stash_push switch_branch worktree worktree_add worktree_delete
|
||||
if contains -- $subcmd (commandline -opc)
|
||||
return 1
|
||||
end
|
||||
|
|
@ -44,6 +44,7 @@ complete -c git-forgit -n __fish_forgit_needs_subcommand -a log -d 'git commit v
|
|||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a reflog -d 'git reflog viewer'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a rebase -d 'git rebase'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a reset_head -d 'git reset HEAD (unstage) selector'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a restore -d 'git restore file selector'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a revert_commit -d 'git revert commit selector'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a reword -d 'git fixup=reword'
|
||||
complete -c git-forgit -n __fish_forgit_needs_subcommand -a show -d 'git show viewer'
|
||||
|
|
@ -70,6 +71,7 @@ complete -c git-forgit -n '__fish_seen_subcommand_from log' -a "(complete -C 'gi
|
|||
complete -c git-forgit -n '__fish_seen_subcommand_from reflog' -a "(complete -C 'git reflog ')"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from rebase' -a "(complete -C 'git rebase ')"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from reset_head' -a "(__fish_git_files all-staged)"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from restore' -a "(complete -C 'git restore ')"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from revert_commit' -a "(__fish_git_commits)"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from reword' -a "(complete -C 'git log ')"
|
||||
complete -c git-forgit -n '__fish_seen_subcommand_from show' -a "(complete -C 'git show ')"
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ end
|
|||
if test -z "$FORGIT_NO_ALIASES"
|
||||
abbr -a -- (string collect $forgit_add; or string collect "ga") git-forgit add
|
||||
abbr -a -- (string collect $forgit_reset_head; or string collect "grh") git-forgit reset_head
|
||||
abbr -a -- (string collect $forgit_restore; or string collect "grs") git-forgit restore
|
||||
abbr -a -- (string collect $forgit_log; or string collect "glo") git-forgit log
|
||||
abbr -a -- (string collect $forgit_reflog; or string collect "grl") git-forgit reflog
|
||||
abbr -a -- (string collect $forgit_diff; or string collect "gd") git-forgit diff
|
||||
|
|
|
|||
|
|
@ -72,6 +72,10 @@ forgit::reset::head() {
|
|||
"$FORGIT" reset_head "$@"
|
||||
}
|
||||
|
||||
forgit::restore() {
|
||||
"$FORGIT" restore "$@"
|
||||
}
|
||||
|
||||
forgit::stash::show() {
|
||||
"$FORGIT" stash_show "$@"
|
||||
}
|
||||
|
|
@ -195,6 +199,7 @@ if [[ -z $FORGIT_NO_ALIASES ]]; then
|
|||
|
||||
builtin export forgit_add="${forgit_add:-ga}"
|
||||
builtin export forgit_reset_head="${forgit_reset_head:-grh}"
|
||||
builtin export forgit_restore="${forgit_restore:-grs}"
|
||||
builtin export forgit_log="${forgit_log:-glo}"
|
||||
builtin export forgit_reflog="${forgit_reflog:-grl}"
|
||||
builtin export forgit_diff="${forgit_diff:-gd}"
|
||||
|
|
@ -224,6 +229,7 @@ if [[ -z $FORGIT_NO_ALIASES ]]; then
|
|||
|
||||
builtin alias "${forgit_add}"='forgit::add'
|
||||
builtin alias "${forgit_reset_head}"='forgit::reset::head'
|
||||
builtin alias "${forgit_restore}"='forgit::restore'
|
||||
builtin alias "${forgit_log}"='forgit::log'
|
||||
builtin alias "${forgit_reflog}"='forgit::reflog'
|
||||
builtin alias "${forgit_diff}"='forgit::diff'
|
||||
|
|
|
|||
35
tests/list-staged-files.test.sh
Normal file
35
tests/list-staged-files.test.sh
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function set_up_before_script() {
|
||||
source bin/git-forgit
|
||||
|
||||
# Ignore global git config files
|
||||
export GIT_CONFIG_SYSTEM=/dev/null
|
||||
export GIT_CONFIG_GLOBAL=/dev/null
|
||||
|
||||
# Create a temporary git repository for testing
|
||||
cd "$(bashunit::temp_dir)" || return 1
|
||||
git init -q
|
||||
git config user.email "test@example.com"
|
||||
git config user.name "Test User"
|
||||
|
||||
touch untracked.txt modified.txt staged.txt
|
||||
git add modified.txt
|
||||
git commit -q -m "modified"
|
||||
echo modified >>modified.txt
|
||||
git add staged.txt
|
||||
}
|
||||
|
||||
function test_list_staged_files_only_lists_staged() {
|
||||
output=$(_forgit_list_staged_files)
|
||||
|
||||
assert_same "$output" "staged.txt"
|
||||
}
|
||||
|
||||
function test_list_staged_files_shows_relative_paths() {
|
||||
mkdir subdirectory
|
||||
cd subdirectory || return 1
|
||||
|
||||
output=$(_forgit_list_staged_files)
|
||||
assert_same "$output" "../staged.txt"
|
||||
}
|
||||
50
tests/restore.test.sh
Normal file
50
tests/restore.test.sh
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
function set_up_before_script() {
|
||||
source bin/git-forgit
|
||||
|
||||
# Ignore global git config files
|
||||
export GIT_CONFIG_SYSTEM=/dev/null
|
||||
export GIT_CONFIG_GLOBAL=/dev/null
|
||||
|
||||
# Create a temporary git repository for testing
|
||||
cd "$(bashunit::temp_dir)" || return 1
|
||||
git init -q
|
||||
git config user.email "test@example.com"
|
||||
git config user.name "Test User"
|
||||
}
|
||||
|
||||
function test_restore_shows_message_when_no_modified_files() {
|
||||
output=$(_forgit_restore 2>&1)
|
||||
assert_general_error
|
||||
assert_same "Nothing to restore." "$output"
|
||||
}
|
||||
|
||||
function test_parse_restore_args() {
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags --staged)
|
||||
|
||||
assert_same "$staged" true
|
||||
assert_same "$worktree" false
|
||||
assert_same "$preview_arg" --staged
|
||||
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags --worktree)
|
||||
assert_same "$staged" false
|
||||
assert_same "$worktree" true
|
||||
assert_same "$preview_arg" ''
|
||||
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags --worktree --staged)
|
||||
assert_same "$staged" true
|
||||
assert_same "$worktree" true
|
||||
assert_same "$preview_arg" HEAD
|
||||
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags -W -S)
|
||||
assert_same "$staged" true
|
||||
assert_same "$worktree" true
|
||||
assert_same "$preview_arg" HEAD
|
||||
|
||||
# other flags do not change the outcome
|
||||
IFS=$'\t' read -r staged worktree preview_arg < <(_forgit_parse_restore_flags -W -S -U --unknown-flag)
|
||||
assert_same "$staged" true
|
||||
assert_same "$worktree" true
|
||||
assert_same "$preview_arg" HEAD
|
||||
}
|
||||
Loading…
Reference in a new issue