Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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` |
Expand Down
75 changes: 75 additions & 0 deletions bin/git-forgit
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -674,6 +680,74 @@ _forgit_reset_head() {
git status --short
}

_forgit_restore_preview() {
git diff --color=always "$@" | _forgit_pager diff
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

_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)
Expand Down Expand Up @@ -1737,6 +1811,7 @@ PUBLIC_COMMANDS=(
"reflog"
"rebase"
"reset_head"
"restore"
"revert_commit"
"show"
"stash_show"
Expand Down
3 changes: 3 additions & 0 deletions completions/_git-forgit
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 ;;
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions completions/git-forgit.bash
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ _git_forgit() {
reflog
rebase
reset_head
restore
revert_commit
reword
show
Expand Down Expand Up @@ -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 ;;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion completions/git-forgit.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand All @@ -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 ')"
Expand Down
1 change: 1 addition & 0 deletions conf.d/forgit.plugin.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions forgit.plugin.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ forgit::reset::head() {
"$FORGIT" reset_head "$@"
}

forgit::restore() {
"$FORGIT" restore "$@"
}

forgit::stash::show() {
"$FORGIT" stash_show "$@"
}
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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'
Expand Down
35 changes: 35 additions & 0 deletions tests/list-staged-files.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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 changes: 50 additions & 0 deletions tests/restore.test.sh
Original file line number Diff line number Diff line change
@@ -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
}