-
-
Save junegunn/f4fca918e937e6bf5bad to your computer and use it in GitHub Desktop.
# fshow - git commit browser (enter for show, ctrl-d for diff, ` toggles sort) | |
fshow() { | |
local out shas sha q k | |
while out=$( | |
git log --graph --color=always \ | |
--format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" | | |
fzf --ansi --multi --no-sort --reverse --query="$q" \ | |
--print-query --expect=ctrl-d --toggle-sort=\`); do | |
q=$(head -1 <<< "$out") | |
k=$(head -2 <<< "$out" | tail -1) | |
shas=$(sed '1,2d;s/^[^a-z0-9]*//;/^$/d' <<< "$out" | awk '{print $1}') | |
[ -z "$shas" ] && continue | |
if [ "$k" = ctrl-d ]; then | |
git diff --color=always $shas | less -R | |
else | |
for sha in $shas; do | |
git show --color=always $sha | less -R | |
done | |
fi | |
done | |
} |
@Frederick888 thanks for sharing this, so useful—just bumped my git efficiency up a few notches!
Thanks everybody for sharing your versions. All of this was so helpful!
After spending some hours of optimizing, I'd like to throw my version on the table as well. It adds the diff stats in the preview window, using a dimmed presentation:
When entering a commit, another fzf instance opens up containing a list of files changed in that commit, with the preview window showing the diff for that file:
Entering a file shows the diff on that file with its complete context.
The git-fuzzy-diff
function detects whether diff-so-fancy
is installed and, if so, uses it.
Furthermore I tried to split and structure the code a bit in order to not have just one huge intransparent command. I started off from @victorbrca's version and now it looks like this:
GIT_FZF_DEFAULT_OPTS="
$FZF_DEFAULT_OPTS
--ansi
--reverse
--height=100%
--bind shift-down:preview-down
--bind shift-up:preview-up
--bind pgdn:preview-page-down
--bind pgup:preview-page-up
--bind q:abort
$GIT_FZF_DEFAULT_OPTS
"
git-fuzzy-diff ()
{
PREVIEW_PAGER="less --tabs=4 -Rc"
ENTER_PAGER=${PREVIEW_PAGER}
if [ -x "$(command -v diff-so-fancy)" ]; then
PREVIEW_PAGER="diff-so-fancy | ${PREVIEW_PAGER}"
ENTER_PAGER="diff-so-fancy | sed -e '1,4d' | ${ENTER_PAGER}"
fi
# Don't just diff the selected file alone, get related files first using
# '--name-status -R' in order to include moves and renames in the diff.
# See for reference: https://stackoverflow.com/q/71268388/3018229
PREVIEW_COMMAND='git diff --color=always '$@' -- \
$(echo $(git diff --name-status -R '$@' | grep {}) | cut -d" " -f 2-) \
| '$PREVIEW_PAGER
# Show additional context compared to preview
ENTER_COMMAND='git diff --color=always '$@' -U10000 -- \
$(echo $(git diff --name-status -R '$@' | grep {}) | cut -d" " -f 2-) \
| '$ENTER_PAGER
git diff --name-only $@ | \
fzf ${GIT_FZF_DEFAULT_OPTS} --exit-0 --preview "${PREVIEW_COMMAND}" \
--preview-window=top:85% --bind "enter:execute:${ENTER_COMMAND}"
}
git-fuzzy-log ()
{
PREVIEW_COMMAND='f() {
set -- $(echo -- "$@" | grep -o "[a-f0-9]\{7\}")
[ $# -eq 0 ] || (
git show --no-patch --color=always $1
echo
git show --stat --format="" --color=always $1 |
while read line; do
tput dim
echo " $line" | sed "s/\x1B\[m/\x1B\[2m/g"
tput sgr0
done |
tac | sed "1 a \ " | tac
)
}; f {}'
ENTER_COMMAND='(grep -o "[a-f0-9]\{7\}" | head -1 |
xargs -I % bash -ic "git-fuzzy-diff %^1 %") <<- "FZF-EOF"
{}
FZF-EOF'
git log --graph --color=always --format="%C(auto)%h %s%d " | \
fzf ${GIT_FZF_DEFAULT_OPTS} --no-sort --tiebreak=index \
--preview "${PREVIEW_COMMAND}" --preview-window=top:15 \
--bind "enter:execute:${ENTER_COMMAND}"
}
In my gitconfig
I have set up aliases for it:
[alias]
fd = !bash -ic 'git-fuzzy-diff \"$@\"' x
fl = !bash -ic 'git-fuzzy-log \"$@\"' x
Perhaps someone should turn this into a zsh plugin?
@melkster Actually there is a project called forgit, which does something similar based on fzf and is available as a zsh plugin.
@carlfriedrich Interesting, I'll check it out!
@melkster FYI I edited my post above with an updated version, adding a git-fuzzy-diff
command which shows a diff for each changed file separately.
I think one could plug this into forgit, if it would allow for customizing the preview command, which is not possible at the moment. I created an issue for that. If you are interested in that as well, maybe you could add a comment there to show that I'm not the only person who might want this. :-)
@Frederick888 I'll check it out later. My use-case is actually different , I'm looking to use fzf to find commit to fixup :).
git fixup <command>
I would then go to fzf and look for commit based on the title, enter would return hash.
How did you hook it up at the end? a fuzzy-find for fixup sounds awesome!
@ultrox @slerer
This article gives you a git alias that is exactly what you are after
Thanks for this... and another modification. This shows preview of the file, while allowing you to still go into it.
Key binds:
- q = quit
- j = down
- k = up
- alt-k = preview up
- alt-j = preview down
- ctrl-f = preview page down
- ctrl-b = preview page up
git-commit-show () { git log --graph --color=always --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" | \ fzf --ansi --no-sort --reverse --tiebreak=index --preview \ 'f() { set -- $(echo -- "$@" | grep -o "[a-f0-9]\{7\}"); [ $# -eq 0 ] || git show --color=always $1 ; }; f {}' \ --bind "j:down,k:up,alt-j:preview-down,alt-k:preview-up,ctrl-f:preview-page-down,ctrl-b:preview-page-up,q:abort,ctrl-m:execute: (grep -o '[a-f0-9]\{7\}' | head -1 | xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF' {} FZF-EOF" --preview-window=right:60% }
Add open commit link
version:
#!/usr/bin/env python3
import subprocess
import re
from typing import Optional
import argparse
import os
GITLOG_PATH = os.path.abspath(__file__)
USAGE_DOC = """
Usage:
gitlog [--opencommitlink <shorthash>] [<git-log-options>...]
Examples:
gitlog --all
gitlog --since="2 weeks ago" --author="John Doe"
gitlog --opencommitlink abc1234
"""
def gitlog(*args: str) -> None:
git_cmd = [
'git', 'log', '--graph', '--color=always', '--format=%C(auto)%h%d %s %C(bold green)%ar %C(dim white)%an'
] + list(args or [])
# Usage:
# - ctrl-m: page the commit
# - ctrl-l: open the commit link
# See [ref](https://gist.github.com/junegunn/f4fca918e937e6bf5bad?permalink_comment_id=2731105#gistcomment-2731105)
fzf_cmd = [
'fzf', '--ansi', '--no-sort', '--reverse', '--tiebreak=index', '--no-multi',
'--preview', 'f() { set -- $(echo -- "$@" | grep -o "[a-f0-9]\\{7\\}"); [ $# -eq 0 ] || git show --color=always $1 ; }; f {}',
'--preview-window=right:60%:hidden',
'--bind', 'ctrl-j:down,ctrl-k:up',
'--bind', 'ctrl-f:page-down,ctrl-b:page-up',
'--bind', 'alt-j:preview-down,alt-k:preview-up',
'--bind', 'alt-f:preview-page-down,alt-b:preview-page-up',
'--bind', 'ctrl-/:toggle-preview',
'--bind', 'ctrl-u:track+clear-query',
'--bind', 'q:abort',
'--bind', 'ctrl-m:execute:(echo {} | grep -o "[a-f0-9]\\{7\\}" | head -1 | xargs -I @ sh -c \'git show --color=always @ | command bat --number\')',
'--bind', 'ctrl-l:execute:(echo {} | grep -o "[a-f0-9]\\{7\\}" | head -1 | xargs -I @ ' + GITLOG_PATH + ' --opencommitlink @)'
]
# Run git log command and pipe to fzf
process = subprocess.Popen(git_cmd, stdout=subprocess.PIPE)
subprocess.run(fzf_cmd, stdin=process.stdout)
process.wait()
def commit_link(shorthash: str) -> Optional[str]:
# get fullhash
fullhash = subprocess.run(['git', 'rev-parse', shorthash], stdout=subprocess.PIPE).stdout.decode().strip()
if not fullhash:
print(f"Invalid commit hash: {shorthash}")
return None
# get the remote URL
remote_url = subprocess.run(['git', 'config', '--get', 'remote.origin.url'], stdout=subprocess.PIPE).stdout.decode().strip()
if not remote_url:
print("Could not determine remote URL")
return None
# parse the remote URL
match = re.search(r'(?:://|@)([^/:]+)[:/](.+)\.git', remote_url)
if not match:
print("Could not determine repository or host")
return None
host = match.group(1)
repo = match.group(2)
return f"https://{host}/{repo}/commit/{fullhash}"
def open_commit_link(shorthash: str) -> None:
link = commit_link(shorthash)
if link:
subprocess.run(['xdg-open', link], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
def main() -> None:
parser = argparse.ArgumentParser(
description="A script to display git logs and open commit links.",
formatter_class=argparse.RawTextHelpFormatter,
epilog=USAGE_DOC
)
parser.add_argument('--opencommitlink', type=str, help="Open the commit link for the given short hash.")
known_args, other_args = parser.parse_known_args()
if known_args.opencommitlink:
open_commit_link(known_args.opencommitlink)
else:
gitlog(*other_args)
if __name__ == "__main__":
main()
@Frederick888 I'll check it out later. My use-case is actually different , I'm looking to use fzf to find commit to fixup :).
git fixup <command>
I would then go to fzf and look for commit based on the title, enter would return hash.