feat: add interactive checkout file from commit selector

closes #476
This commit is contained in:
sandroid 2026-03-12 21:48:49 +01:00 committed by sandr01d
parent b82812007c
commit 12ec178093
7 changed files with 70 additions and 2 deletions

View file

@ -54,6 +54,7 @@ It's **lightweight** and **easy to use**.
| `gso` | Interactive `git show` viewer |
| `grh` | Interactive `git reset HEAD <file>` selector |
| `gcf` | Interactive `git checkout <file>` selector |
| `gcff` | Interactive `git checkout <file> from <commit>` selector |
| `gcb` | Interactive `git checkout <branch>` selector |
| `gsw` | Interactive `git switch <branch>` selector |
| `gbd` | Interactive `git branch -D <branch>` selector |
@ -188,6 +189,7 @@ forgit_reset_head=grh
forgit_ignore=gi
forgit_attributes=gat
forgit_checkout_file=gcf
forgit_checkout_file_from_commit=gcff
forgit_checkout_branch=gcb
forgit_switch_branch=gsw
forgit_branch_delete=gbd
@ -266,6 +268,7 @@ Each forgit command can be customized with dedicated environment variables for g
| `gso` | `FORGIT_SHOW_GIT_OPTS` | `FORGIT_SHOW_FZF_OPTS` |
| `grh` | `FORGIT_RESET_HEAD_GIT_OPTS` | `FORGIT_RESET_HEAD_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` |
| `gsw` | `FORGIT_SWITCH_BRANCH_GIT_OPTS` | `FORGIT_SWITCH_BRANCH_FZF_OPTS` |
| `gbd` | `FORGIT_BRANCH_DELETE_GIT_OPTS` | `FORGIT_BRANCH_DELETE_FZF_OPTS` |

View file

@ -920,6 +920,54 @@ _forgit_checkout_file() {
[[ "${#files[@]}" -gt 0 ]] && _forgit_git_checkout_file "$@" "${files[@]}"
}
# git checkout-file from commit selector
_forgit_checkout_file_from_commit() {
_forgit_inside_work_tree || return 1
local opts commit file branch
if [[ $# -gt 0 ]]; then
branch=$1
shift
else
# default to the current branch if none was passed
branch=$(git rev-parse --abbrev-ref HEAD)
fi
# select the commit interactively
opts="
$FORGIT_FZF_DEFAULT_OPTS
+s +m --tiebreak=index
--preview=\"$FORGIT log_preview {}\"
$FORGIT_CHECKOUT_FILE_FROM_COMMIT_LOG_FZF_OPTS
"
commit=$(_forgit_git_log "$_forgit_log_format" "$branch" "$@" |
FZF_DEFAULT_OPTS="$opts" fzf |
_forgit_extract_sha)
[[ -n "$commit" ]] || return 0
# select the file interactively
opts="
$FORGIT_FZF_DEFAULT_OPTS
+m -0
--preview=\"$FORGIT preview show_preview {} '$_forgit_preview_context' $commit\"
--preview-label=\" Diff \"
--bind=\"alt-t:transform:[[ ! \\\"\$FZF_PREVIEW_LABEL\\\" =~ 'Diff' ]] &&
echo 'change-preview-label( Diff )+refresh-preview' ||
echo 'change-preview-label( Commit Message )+refresh-preview'\"
$FORGIT_CHECKOUT_FILE_FROM_COMMIT_SHOW_FZF_OPTS
--prompt=\"${commit} > \"
"
file=$(_forgit_git_show "$commit" | FZF_DEFAULT_OPTS="$opts" fzf)
[[ -n "$file" ]] || return 0
# special case: when the file was deleted in the commit
# check out the file from the previous commit.
[[ "$file" =~ ^\[D\] ]] && commit="$commit~"
file=$(echo "$file" | _forgit_get_single_file_from_diff_line)
_forgit_git_checkout_file "$commit" -- "$file"
}
_forgit_git_checkout_branch() {
_forgit_checkout_branch_git_opts=()
_forgit_parse_array _forgit_checkout_branch_git_opts "$FORGIT_CHECKOUT_BRANCH_GIT_OPTS"
@ -1543,6 +1591,7 @@ PUBLIC_COMMANDS=(
"switch_branch"
"checkout_commit"
"checkout_file"
"checkout_file_from_commit"
"checkout_tag"
"cherry_pick"
"cherry_pick_from_branch"

View file

@ -73,6 +73,7 @@ _git-forgit() {
'checkout_branch:git checkout branch selector'
'checkout_commit:git checkout commit selector'
'checkout_file:git checkout-file selector'
'checkout_file_from_commit:git checkout-file from commit selector'
'checkout_tag:git checkout tag selector'
'cherry_pick:git cherry-picking'
'cherry_pick_from_branch:git cherry-picking with interactive branch selection'
@ -104,6 +105,7 @@ _git-forgit() {
checkout_branch) _git-branches ;;
checkout_commit) __git_recent_commits ;;
checkout_file) _git-checkout-file ;;
checkout_file_from_commit) _git-branches ;;
checkout_tag) __git_tags ;;
cherry_pick) _git-cherry-pick ;;
cherry_pick_from_branch) _git-branches ;;
@ -138,6 +140,7 @@ compdef _git-branches forgit::checkout::branch
compdef _git-switch forgit::switch::branch
compdef __git_recent_commits forgit::checkout::commit
compdef _git-checkout-file forgit::checkout::file
compdef _git-branches forgit::checkout::file::from::commit
compdef __git_tags forgit::checkout::tag
compdef _git-cherry-pick forgit::cherry::pick
compdef _git-branches forgit::cherry::pick::from::branch

View file

@ -73,6 +73,7 @@ _git_forgit()
checkout_branch
checkout_commit
checkout_file
checkout_file_from_commit
checkout_tag
cherry_pick
cherry_pick_from_branch
@ -109,6 +110,7 @@ _git_forgit()
checkout_branch) _git_checkout_branch ;;
checkout_commit) _git_checkout ;;
checkout_file) _git_checkout_file ;;
checkout_file_from_commit) _git_checkout_branch ;;
checkout_tag) _git_checkout_tag ;;
cherry_pick) _git_cherry_pick ;;
cherry_pick_from_branch) _git_checkout_branch ;;
@ -152,6 +154,7 @@ then
__git_complete forgit::switch::branch _git_switch
__git_complete forgit::checkout::commit _git_checkout
__git_complete forgit::checkout::file _git_checkout_file
__git_complete forgit::checkout::file::from::commit _git_checkout_branch
__git_complete forgit::checkout::tag _git_checkout_tag
__git_complete forgit::cherry::pick _git_cherry_pick
__git_complete forgit::cherry::pick::from::branch _git_checkout_branch
@ -179,6 +182,7 @@ then
__git_complete "${forgit_switch_branch}" _git_switch
__git_complete "${forgit_checkout_commit}" _git_checkout
__git_complete "${forgit_checkout_file}" _git_checkout_file
__git_complete "${forgit_checkout_file_from_commit}" _git_checkout_branch
__git_complete "${forgit_checkout_tag}" _git_checkout_tag
__git_complete "${forgit_cherry_pick}" _git_checkout_branch
__git_complete "${forgit_clean}" _git_clean

View file

@ -6,8 +6,8 @@
# sourced when git-forgit command or forgit subcommand of git is invoked.
function __fish_forgit_needs_subcommand
for subcmd in add 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 \
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
if contains -- $subcmd (commandline -opc)
return 1
@ -32,6 +32,7 @@ complete -c git-forgit -n __fish_forgit_needs_subcommand -a branch_delete -d 'gi
complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_branch -d 'git checkout branch selector'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_commit -d 'git checkout commit selector'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_file -d 'git checkout-file selector'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_file_from_commit -d 'git checkout-file from commit selector'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a checkout_tag -d 'git checkout tag selector'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick -d 'git cherry-picking'
complete -c git-forgit -n __fish_forgit_needs_subcommand -a cherry_pick_from_branch -d 'git cherry-picking with interactive branch selection'
@ -59,6 +60,7 @@ complete -c git-forgit -n '__fish_seen_subcommand_from branch_delete' -a "(__fis
complete -c git-forgit -n '__fish_seen_subcommand_from checkout_branch' -a "(complete -C 'git switch ')"
complete -c git-forgit -n '__fish_seen_subcommand_from checkout_commit' -a "(__fish_git_commits)"
complete -c git-forgit -n '__fish_seen_subcommand_from checkout_file' -a "(__fish_git_files modified)"
complete -c git-forgit -n '__fish_seen_subcommand_from checkout_file_from_commit' -a "(complete -C 'git switch ')"
complete -c git-forgit -n '__fish_seen_subcommand_from checkout_tag' -a "(__fish_git_tags)" -d Tag
complete -c git-forgit -n '__fish_seen_subcommand_from cherry_pick' -a "(complete -C 'git cherry-pick ')"
complete -c git-forgit -n '__fish_seen_subcommand_from clean' -a "(__fish_git_files untracked ignored)"

View file

@ -57,6 +57,7 @@ if test -z "$FORGIT_NO_ALIASES"
abbr -a -- (string collect $forgit_ignore; or string collect "gi") git-forgit ignore
abbr -a -- (string collect $forgit_attributes; or string collect "gat") git-forgit attributes
abbr -a -- (string collect $forgit_checkout_file; or string collect "gcf") git-forgit checkout_file
abbr -a -- (string collect $forgit_checkout_file_from_commit; or string collect "gcff") git-forgit checkout_file_from_commit
abbr -a -- (string collect $forgit_checkout_branch; or string collect "gcb") git-forgit checkout_branch
abbr -a -- (string collect $forgit_switch_branch; or string collect "gsw") git-forgit switch_branch
abbr -a -- (string collect $forgit_branch_delete; or string collect "gbd") git-forgit branch_delete

View file

@ -104,6 +104,10 @@ forgit::reword() {
"$FORGIT" reword "$@"
}
forgit::checkout::file::from::commit() {
"$FORGIT" checkout_file_from_commit "$@"
}
forgit::checkout::file() {
"$FORGIT" checkout_file "$@"
}
@ -191,6 +195,7 @@ if [[ -z "$FORGIT_NO_ALIASES" ]]; then
builtin export forgit_ignore="${forgit_ignore:-gi}"
builtin export forgit_attributes="${forgit_attributes:-gat}"
builtin export forgit_checkout_file="${forgit_checkout_file:-gcf}"
builtin export forgit_checkout_file_from_commit="${forgit_checkout_file_from_commit:-gcff}"
builtin export forgit_checkout_branch="${forgit_checkout_branch:-gcb}"
builtin export forgit_switch_branch="${forgit_switch_branch:-gsw}"
builtin export forgit_checkout_commit="${forgit_checkout_commit:-gco}"
@ -219,6 +224,7 @@ if [[ -z "$FORGIT_NO_ALIASES" ]]; then
builtin alias "${forgit_ignore}"='forgit::ignore'
builtin alias "${forgit_attributes}"='forgit::attributes'
builtin alias "${forgit_checkout_file}"='forgit::checkout::file'
builtin alias "${forgit_checkout_file_from_commit}"='forgit::checkout::file::from::commit'
builtin alias "${forgit_checkout_branch}"='forgit::checkout::branch'
builtin alias "${forgit_switch_branch}"='forgit::switch::branch'
builtin alias "${forgit_checkout_commit}"='forgit::checkout::commit'