Skip to content
Merged
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 @@ -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 |
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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` |
Expand Down
94 changes: 77 additions & 17 deletions bin/git-forgit
Original file line number Diff line number Diff line change
Expand Up @@ -231,10 +231,22 @@ _forgit_log_enter() {
"${FORGIT}" show "${sha}" "$@"
}

_forgit_git_log() {
local graph log_format
log_format=$1
shift
graph=()
[[ $_forgit_log_graph_enable == true ]] && graph=(--graph)
_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
}

# git commit viewer
_forgit_log() {
_forgit_inside_work_tree || return 1
local opts graph quoted_files log_format
local opts quoted_files log_format
quoted_files=$(_forgit_quote_files "$@")
opts="
$FORGIT_FZF_DEFAULT_OPTS
Expand All @@ -244,14 +256,8 @@ _forgit_log() {
--preview=\"$FORGIT preview 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
_forgit_git_log "$log_format" "$@" | FZF_DEFAULT_OPTS="$opts" fzf
fzf_exit_code=$?
# exit successfully on 130 (ctrl-c/esc)
[[ $fzf_exit_code == 130 ]] && return 0
Expand Down Expand Up @@ -424,6 +430,19 @@ _forgit_show_enter() {
_forgit_show_view "$file" "$_forgit_fullscreen_context" "${commit}"
}

_forgit_git_show() {
local commit=$1
shift

_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}" -- "$@" |
sed -E 's/^([[:alnum:]]+)[[:space:]]+(.*)$/[\1] \2/' |
sed 's/ / -> /2' | expand -t 8
}

# git show viewer
_forgit_show() {
_forgit_inside_work_tree || return 1
Expand Down Expand Up @@ -452,15 +471,7 @@ _forgit_show() {
$FORGIT_SHOW_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
_forgit_git_show "$commit" "${files[@]}" | FZF_DEFAULT_OPTS="$opts" fzf
fzf_exit_code=$?
# exit successfully on 130 (ctrl-c/esc)
[[ $fzf_exit_code == 130 ]] && return 0
Expand Down Expand Up @@ -909,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"
Expand Down Expand Up @@ -1532,6 +1591,7 @@ PUBLIC_COMMANDS=(
"switch_branch"
"checkout_commit"
"checkout_file"
"checkout_file_from_commit"
"checkout_tag"
"cherry_pick"
"cherry_pick_from_branch"
Expand Down
3 changes: 3 additions & 0 deletions completions/_git-forgit
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 ;;
Expand Down Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions completions/git-forgit.bash
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ _git_forgit()
checkout_branch
checkout_commit
checkout_file
checkout_file_from_commit
checkout_tag
cherry_pick
cherry_pick_from_branch
Expand Down Expand Up @@ -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 ;;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions completions/git-forgit.fish
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'
Expand Down Expand Up @@ -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)"
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 @@ -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
Expand Down
6 changes: 6 additions & 0 deletions forgit.plugin.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ forgit::reword() {
"$FORGIT" reword "$@"
}

forgit::checkout::file::from::commit() {
"$FORGIT" checkout_file_from_commit "$@"
}

forgit::checkout::file() {
"$FORGIT" checkout_file "$@"
}
Expand Down Expand Up @@ -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}"
Expand Down Expand Up @@ -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'
Expand Down
27 changes: 27 additions & 0 deletions tests/log.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash

function set_up_before_script() {
# disable log graph to remove the asterisk from the output
export FORGIT_LOG_GRAPH_ENABLE='false'

source bin/git-forgit

# ignore global git config files
export GIT_CONFIG_SYSTEM=/dev/null
export GIT_CONFIG_GLOBAL=/dev/null

# create a new git repository in a temp directory
cd "$(bashunit::temp_dir)" || return 1
git init --quiet
git config user.email "test@example.com"
git config user.name "Test User"
# create an initial commit so we have a valid repo
git commit --allow-empty -qm "Initial commit"
}

function test_forgit_git_log() {
local output
# set log format to '%s' so we don't have to match the commit hash
output=$(_forgit_git_log '%s' 2>&1 | sed 's/^[0-9a-f] //')
assert_same "Initial commit" "$output"
}
33 changes: 33 additions & 0 deletions tests/show.test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/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 new git repository in a temp directory
cd "$(bashunit::temp_dir)" || return 1
git init --quiet
git config user.email "test@example.com"
git config user.name "Test User"
# create an initial commit so we have a valid repo
touch file.txt
touch file1.txt
git add .
git commit -qm "Initial commit"
}

function test_forgit_git_show() {
local output
output=$(_forgit_git_show HEAD 2>&1)
assert_same "[A] file.txt
[A] file1.txt" "$output"
}

function test_forgit_git_show_filter() {
local output
output=$(_forgit_git_show HEAD -- file.txt 2>&1)
assert_same "[A] file.txt" "$output"
}