349 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			349 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable file
		
	
	
	
	
| #!/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
 | |
| 
 |