You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
349 lines
8.9 KiB
349 lines
8.9 KiB
#!/usr/bin/env bash |
|
|
|
|
|
# usage info |
|
usage() { |
|
cat <<EOF |
|
Usage: git recall [options] |
|
Options: |
|
-d, --date Show logs for last n days. |
|
-a, --author Author name. |
|
-f, --fetch fetch commits. |
|
-h, --help This message. |
|
-v, --version Show version. |
|
-- End of options. |
|
EOF |
|
} |
|
|
|
|
|
# Global variables |
|
SINCE="1 days ago" # show logs for last day by default |
|
AUTHOR="" |
|
FETCH=false |
|
GIT_FORMAT="" |
|
GIT_LOG="" |
|
COMMITS="" |
|
COMMITS_UNCOL=() # commits without colors |
|
LESSKEY=false |
|
SED_BIN="" # Sed option to use according OS. |
|
VERSION="1.1.10" |
|
|
|
# Are we in a git repo? |
|
if [[ ! -d ".git" ]] && ! git rev-parse --git-dir &>/dev/null; then |
|
echo "abort: not a git repository." 1>&2 |
|
exit 1 |
|
fi |
|
|
|
# Parse options |
|
while [[ "$1" =~ ^- && ! "$1" == "--" ]]; do |
|
case $1 in |
|
-v | --version ) |
|
echo "$version" |
|
exit |
|
;; |
|
-d | --date ) |
|
SINCE="$2 days ago" |
|
shift; |
|
;; |
|
-a | --author ) |
|
AUTHOR="$2" |
|
shift |
|
;; |
|
-f | --fetch ) |
|
FETCH=true |
|
;; |
|
-h | --help ) |
|
usage |
|
exit |
|
;; |
|
* ) |
|
echo "abort: unknown argument" 1>&2 |
|
exit 1 |
|
esac |
|
shift |
|
done |
|
if [[ "$1" == "--" ]]; then shift; fi |
|
|
|
|
|
# Colored output. |
|
function colored() { |
|
GREEN=$(tput setaf 4) |
|
YELLOW=$(tput setaf 3) |
|
NORMAL=$(tput sgr0) |
|
REVERSE=$(tput rev) |
|
} |
|
|
|
# Uncolored output. |
|
function uncolored() { |
|
GREEN="" |
|
YELLOW="" |
|
NORMAL="" |
|
REVERSE="" |
|
} |
|
|
|
# Enable colors if supported by terminal. |
|
if [[ -t 1 ]] && [[ -n "$TERM" ]] && which tput &>/dev/null && tput colors &>/dev/null; then |
|
ncolors=$(tput colors) |
|
if [[ -n "$ncolors" ]] && [[ "$ncolors" -ge 8 ]] ; then |
|
colored |
|
else |
|
uncolored |
|
fi |
|
else |
|
uncolored |
|
fi |
|
|
|
# Check if lesskey is installed. |
|
if command -v lesskey &> /dev/null; then |
|
LESSKEY=true |
|
fi |
|
|
|
# Set AUTHOR to current user if no param passed or display for all users if param equal to "all". |
|
if [[ ! -n $AUTHOR ]]; then |
|
AUTHOR=$(git config user.name 2>/dev/null) |
|
elif [[ $AUTHOR = "all" ]]; then |
|
AUTHOR=".*" |
|
fi |
|
|
|
# Fetch changes before. |
|
if [[ $FETCH == true ]]; then |
|
echo "${GREEN}Fetching changes...${NORMAL}" |
|
git fetch --all &> /dev/null |
|
tput cuu1 |
|
tput ed # clear screen |
|
fi |
|
|
|
# Log template. |
|
GIT_FORMAT="%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset" |
|
|
|
# Log command. |
|
GIT_LOG="git log --pretty=format:'${GIT_FORMAT}' |
|
--author \"$AUTHOR\" |
|
--since \"$SINCE\" --abbrev-commit" |
|
|
|
# Change temporary the IFS to store GIT_LOG's output into an array. |
|
IFS=$'\n' |
|
COMMITS=($(eval ${GIT_LOG} 2>/dev/null)) |
|
unset IFS |
|
|
|
NI=${#COMMITS[@]} # Total number of items. |
|
SN=$(( `tput lines` - 1 )) # Screen's number of lines. |
|
CN=$(tput cols) # Screen's number of columns. |
|
TN=$(( $NI < $((SN -1)) ? $NI : $((SN -1)))) # Number of lines that we will display. |
|
OFFSET=0 #Incremented by one each time a commit's length is higher than teminal width. |
|
|
|
# If there is no items, exit. |
|
if [[ $NI = 0 ]]; then |
|
if [[ $AUTHOR = ".*" ]]; then |
|
echo "${YELLOW}All contributors did nothing during this period.${NORMAL}" && exit 0 |
|
else |
|
echo "${YELLOW}The contributor \"${AUTHOR}\" did nothing during this period.${NORMAL}" && exit 0 |
|
fi |
|
fi |
|
|
|
# Set correct sed option according OS's type |
|
case "$OSTYPE" in |
|
darwin*) SED_BIN="sed -E" ;; |
|
*) SED_BIN="sed -r" ;; |
|
esac |
|
|
|
# Create array with uncolred commits (removing escape sequences using sed) |
|
for elt in "${COMMITS[@]}" |
|
do |
|
ELT="$(echo "$elt" | $SED_BIN "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g")" # remove colors escape codes |
|
COMMITS_UNCOL+=("$ELT") |
|
done |
|
# Add +1 to OFFSET if a commit's length is bigger than the current terminal session's width. (This is to fix a redraw issue) |
|
for C in "${COMMITS_UNCOL[@]}" |
|
do |
|
if [[ ${#C} -gt $CN ]]; then |
|
OFFSET=$(( OFFSET + 1 )) |
|
fi |
|
done |
|
|
|
# Set keys. |
|
au="`echo -e '\e[A'`" # arrow up |
|
au_1="k" # arrow up |
|
ad="`echo -e '\e[B'`" # arrow down |
|
ad_1="j" # arrow down |
|
ec="`echo -e '\e'`" # escape |
|
nl="`echo -e '\n'`" # newline |
|
nl_1="e" # expand |
|
co="c" # checkout |
|
|
|
# Create a temporary lesskey file to change the keybindings so the user can use the TAB key to quit less. (more convenient) |
|
if [[ $LESSKEY = true ]]; then |
|
echo "\t quit" | lesskey -o $HOME/.lsh_less_keys_tmp -- - &> /dev/null |
|
fi |
|
|
|
## Get commit's diff. |
|
function get_diff() { |
|
ELT="$(echo "${COMMITS_UNCOL[$CP-1]}")" |
|
DIFF_TIP=${ELT:0:7} |
|
DIFF_CMD="git show $DIFF_TIP --color=always" |
|
DIFF=$(eval ${DIFF_CMD} 2>/dev/null) |
|
tmp_diff="$(echo "$DIFF" | $SED_BIN "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g")" # remove colors escape codes |
|
off=$(echo "$tmp_diff" | grep -c ".\{$CN\}") #Number of lines in the diff that are longer than terminal width. |
|
DIFF_LINES_NUMBER="$(echo "$DIFF" | wc -l)" |
|
DIFF_LINES_NUMBER=$(( DIFF_LINES_NUMBER + off )) |
|
} |
|
|
|
## This function will print the diff according the commit's tip. If the diff is too long, the result will be displayed using 'less'. |
|
function print_diff() { |
|
get_diff # get commit's diff |
|
if [[ $(( TN + DIFF_LINES_NUMBER + OFFSET )) -ge $(( `tput lines` - 1 )) ]]; then |
|
if [[ $LESSKEY = true ]]; then |
|
echo "$DIFF" | less -r -k $HOME/.lsh_less_keys_tmp |
|
else |
|
echo "$DIFF" | less -r |
|
fi |
|
tput ed # Clear screen |
|
else |
|
stop=false |
|
tput ed |
|
for i in `seq 1 $TN` |
|
do |
|
echo -n "$NORMAL" |
|
[[ $CP == "$i" ]] && echo -n "$REVERSE" |
|
echo "${COMMITS[$i - 1]}" |
|
[[ $CP == "$i" ]] && echo "$DIFF" |
|
done |
|
# Wait for user action. |
|
while ! $stop |
|
do |
|
read -sn 1 key |
|
case "$key" in |
|
"$nl" | "$nl_1") |
|
stop=true |
|
;; |
|
"q") |
|
stop=true |
|
END=true |
|
;; |
|
esac |
|
done |
|
[[ $END = false ]] && tput cuu $(( TN + DIFF_LINES_NUMBER + OFFSET )) && tput ed |
|
[[ $END = true ]] && tput cuu 1 |
|
fi |
|
} |
|
|
|
function do_checkout(){ |
|
ELT="$(echo "${COMMITS_UNCOL[$CP-1]}")" |
|
DIFF_TIP=${ELT:0:7} |
|
eval "git checkout $DIFF_TIP 2> /dev/null" |
|
} |
|
|
|
# Calculate OFFSET to avoid bad redraw. |
|
function calculate_offset { |
|
tmp=1 |
|
index=$(( SI -1 )) |
|
while [[ $tmp -lt $SN ]] |
|
do |
|
el=${COMMITS_UNCOL[$index]} |
|
if [[ ${#el} -gt $CN ]] && [[ $CP -lt $((SN -1)) ]]; then |
|
OFFSET_2=$(( OFFSET_2 + 1 )) |
|
tmp=$(( tmp + 1 )) |
|
fi |
|
tmp=$(( tmp + 1 )) |
|
index=$(( index + 1 )) |
|
done |
|
} |
|
|
|
{ # capture stdout to stderr |
|
|
|
tput civis # hide cursor. |
|
CP=1 # current position |
|
SI=1 # index |
|
END=false # end while loop |
|
EXT=0 # Used to extend the number of lines to display. |
|
|
|
## Loops, reads inputs and prints commits until user presses 'q' to exit or TAB to show the diff. |
|
while ! $END |
|
do |
|
END_INDEX=0 # Index for the last item to display |
|
# When the number of item is higher than screen's number of lines, OFFSET_2 is recalculated each time we select a new item |
|
# Set last index to print. (based on OFFSET) |
|
if [[ $TN == $NI ]]; then |
|
END_INDEX=$TN |
|
OFFSET_2=$OFFSET |
|
elif [[ $TN == $(( SN - 1 )) ]]; then |
|
# Calculate new OFFSET. |
|
if [[ $OFFSET != 0 ]]; then |
|
[[ $CP -lt $((SN -1)) ]] && OFFSET_2=0 |
|
EXT=1 |
|
calculate_offset |
|
fi |
|
END_INDEX=$(( TN + SI -1 + EXT - OFFSET_2 )) |
|
fi |
|
|
|
# Loop and echo commits |
|
for i in `seq $SI $END_INDEX` |
|
do |
|
echo -n "$NORMAL" |
|
[[ $CP == $i ]] && echo -n "$REVERSE" |
|
echo "${COMMITS[$i - 1]}" |
|
done |
|
|
|
read -sn 1 key |
|
[[ "$key" == "$ec" ]] && |
|
{ |
|
read -sn 2 k2 |
|
key="$key$k2" |
|
} |
|
|
|
case "$key" in |
|
|
|
"$au" | "$au_1") |
|
CP=$(( CP - 1 )) |
|
[[ $CP == 0 ]] && [[ $SI == 1 ]] && [[ $TN == $(( SN - 1 )) ]] && CP=$NI && SI=$(( NI - SN + 2 + OFFSET_2 )) |
|
[[ $CP == 0 ]] && [[ $SI == 1 ]] && [[ $TN == $NI ]] && CP=$TN |
|
[[ $CP == $(( SI - 1 )) ]] && [[ $SI != 1 ]] && SI=$(( SI - 1 )) |
|
|
|
[[ $TN != $(( SN - 1 )) ]] && tput cuu $(( TN + OFFSET_2 )) |
|
[[ $TN == $(( SN - 1 )) ]] && tput cuu $(( TN + EXT )) |
|
[[ $SI != 1 ]] && tput ed # clear screen |
|
;; |
|
|
|
"$ad" | "$ad_1") |
|
CP=$(( CP + 1 )) |
|
[[ $CP == $(( NI + 1 )) ]] && CP=1 && SI=1 |
|
[[ $CP == $(( SN + SI - 1 + EXT - OFFSET_2 )) ]] && [[ $TN == $(( SN - 1 )) ]] && SI=$(( SI + 1 )) |
|
|
|
[[ $TN != $(( SN - 1 )) ]] && tput cuu $(( TN + OFFSET_2 )) |
|
[[ $TN == $(( SN - 1 )) ]] && tput cuu $(( TN + EXT )) |
|
[[ $SI != 1 ]] && tput ed # clear screen |
|
[[ $SI = 1 ]] && [[ $CP = 1 ]] && tput ed # clear screen |
|
;; |
|
|
|
"$nl" | "$nl_1") |
|
[[ $TN == $NI ]] && tput cuu $(( TN + OFFSET_2 )) |
|
[[ $TN != $NI ]] && tput cuu $(( TN + EXT )) |
|
print_diff |
|
;; |
|
"$co") |
|
si=false |
|
END=true |
|
do_checkout |
|
tput cuu 1 #move cursor up one line. (remove extra line) |
|
;; |
|
|
|
"q") |
|
si=false |
|
END=true |
|
tput cuu 1 #move cursor up one line. (remove extra line) |
|
;; |
|
|
|
* ) |
|
tput cuu $(( TN + OFFSET_2 )) |
|
esac |
|
|
|
done |
|
|
|
# remove temporary less keybindings |
|
[[ $LESSKEY = true ]] && rm $HOME/.lsh_less_keys_tmp |
|
|
|
tput cnorm # unhide cursor |
|
echo "$NORMAL" # normal colors |
|
|
|
} >&2 # END capture |
|
|
|
|