unixorn/git-extra-commands

View on GitHub
bin/git-overwritten

Summary

Maintainability
Test Coverage
#!/usr/bin/env bash
# Usage: git overwritten [--[no-]color] [<head=HEAD>] [<base=origin>]
#
# Aggregates git blame information about original owners of lines changed or
# removed in the '<base>...<head>' diff.
#
# Each line of output represents a past commit, and consists of:
# - number of lines from the commit that this diff affects
# - commit date
# - commit sha
# - author name
# - commit subject message
#
# The commits are listed in the reverse chronological order.
#
# Author: Mislav Marohnić

set -e

unset colorize
unset head
unset base
unset diff_type

# Figure out what argument to use for date as GNU & BSD `date`s are different
[[ $(date -r123 '+%s' 2>/dev/null) -eq 123 ]] && DATEARG='-r' || DATEARG='-d@'

abort() {
  "$0" --help | head -1 >&2
  exit 1
}

while [ "$#" -gt 0 ]; do
  case "$1" in
  --color ) colorize=1 ;;
  --no-color ) colorize='';;
  --staged ) diff_type='--staged' ;;
  --unstaged ) diff_type='';;
  -h | --help )
    sed -ne '/^#/!q;s/.\{1,2\}//;1d;p' < "$0"
    exit 0
    ;;
  -* | "") abort ;;
  * )
    if [ -z "$head" ]; then head="$1"
    elif [ -z "$base" ]; then base="$1"
    else abort
    fi
    ;;
  esac
  shift 1
done

if [ "${diff_type-no}" = "no" ]; then
  head="${head:-HEAD}"
  base="${base:-origin}"
else
  head=
  base=HEAD
fi

[ -t 1 ] && colorize="${colorize-1}"

color() {
  if [ -n "$colorize" ]; then
    printf "\e[0;%dm%s\e[m" "$1" "$2"
  else
    echo -n "$2"
  fi
}

git diff ${diff_type-"${base}...${head}"} --diff-filter=DM --no-prefix -w -U0 | grep -v '^+' | awk '
  function print_range() {
    printf "-L %d,%d -- %s\n", start, stop, file
  }
  /^--- / {
    sub(/^--- /, "")
    file = $0
    next
  }
  /^-/ {
    if (!start) start = stop = diffstart
    else stop += 1
    next
  }
  start {
    print_range()
    start = 0
  }
  /^@@ / {
    sub(/^-/, "", $2)
    diffstart = int($2)
    start = 0
  }
  END {
    if (start) print_range()
  }
' | xargs -L1 git blame --line-porcelain "$base" | awk -v OFS=$'\t' '
  BEGIN { num_lines = name_length = 0 }
  function val() { sub(/^[a-z-]+ /, ""); return $0 }
  /^[0-9a-f]{40} / { sha = $1 }
  /^author / {
    author = val()
    len = length(author)
    if (len > name_length) name_length = len
  }
  /^committer-time / { time = val() }
  /^summary / { msg = val() }
  /^\t/ {
    print time, sha, author, msg
    num_lines += 1
  }
  END { print num_lines, name_length }
' | sort -n | {
  read num_lines name_length
  if [ "$num_lines" -eq 0 ]; then
    color 31 "Warning: " >&2
    echo "no changed/removed lines found in the $base...$head diff" >&2
  fi

  while IFS=$'\t' read time sha author msg; do
    date "${DATEARG}${time}" '+%F' | tr -d $'\n'
    printf ' '
    color 33 "${sha:0:7}"
    printf '  '
    color 32 "$(printf "%${name_length}s" "$author")"
    printf ': '
    echo "$msg"
  done
} | sort -r | uniq -c