#!/bin/bash # # See $desc, below, for program description # # Copyright (c) 2013 Red Hat, Inc. # # Author(s): # Jeff Cody # # This program is free software; you can redistribute it and/or modify it under # the terms of the GNU General Public License as published by the Free Software # Foundation; under version 2 of the license # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more # details. # # You should have received a copy of the GNU General Public License along with # this program; if not, see . # set -C -u -e set -o pipefail desc=" $0 compares the commits in a specified git range, with the corresponding upstream commit. This assumes that you have both the downstream and upstream repositories added as remotes in your git repo. Example usage: $0 -r qemu-kvm-0.12.1.2-2.370.el6..my_feature_branch -u v1.5.0 Ouput key: [----] Indicates the patches upstream and downstream are identical [####] Indicates the number of differences (#### is substituted) [down] Indicates the patch only exists downstream" def_upstream="v1.7.0" def_diffprog=meld def_range="HEAD^!" def_color='y' def_pause='y' def_sensitivity=0 upstream=`git config backport-diff.upstream || true` diffprog=`git config backport-diff.diffprog || true` range=`git config backport-diff.range || true` color=`git config backport-diff.color || true` pause=`git config backport-diff.pause || true` sensitivity=`git config backport-diff.sensitivity || true` if [[ -z "$upstream" ]] then upstream=$def_upstream git config backport-diff.upstream $upstream || true fi if [[ -z "$diffprog" ]] then diffprog=$def_diffprog git config backport-diff.diffprog $diffprog || true fi if [[ -z "$range" ]] then range=$def_range git config backport-diff.range $range || true fi if [[ -z "$color" ]] then color=$def_color git config backport-diff.color $color || true fi if [[ -z "$pause" ]] then pause=$def_pause git config backport-diff.pause $pause || true fi if [[ -z "$sensitivity" ]] then sensitivity=$def_sensitivity git config backport-diff.sensitivity $sensitivity || true fi usage() { echo "" echo "$0 [OPTIONS]" echo "$desc" echo "" echo "OPTIONS:" echo " -r git range optional; default is '$range' " echo " -u upstream git tag / branch / id optional; default is '$upstream' " echo " -n do not use colors " echo " -d diff program to use optional; default is '$diffprog' " echo " -p do not pause when viewing diffs " echo " -s sensitivity level of diff compares 0: only show functional code differences 1: show (0) + contextual differences 2+: offer to compare all patches, regardless of any differences " echo " -h help" echo "" echo "You can set each of the default options using git-config:" echo " git config backport-diff.upstream" echo " git config backport-diff.diffprog" echo " git config backport-diff.range" echo " git config backport-diff.color" echo " git config backport-diff.pause" } while getopts ":r:u:nd:phs:" opt do case $opt in r) range=$OPTARG ;; u) upstream=$OPTARG ;; n) color='n' ;; d) diffprog=$OPTARG ;; p) pause='n' ;; s) sensitivity=$OPTARG if ! [[ "$sensitivity" =~ ^[0-9]+$ ]] then echo "Invalid argument for -s" >&2 usage exit 1 fi ;; h) usage exit ;; \?) echo "Unknown option: -$OPTARG" >&2 usage exit 1 ;; esac done if [[ $color == 'y' ]] then bold=$(tput bold) color1=$(tput setaf 1) color2=$(tput setaf 2) color3=$(tput setaf 3) color4=$(tput setaf 4) color5=$(tput setaf 5) color6=$(tput setaf 6) color7=$(tput setaf 7) reset=$(tput sgr0) else bold= color1= color2= color3= color4= color5= color6= color7= reset= fi trap cleanup EXIT cleanup() { echo $reset } total=`git rev-list "$range" |wc -l` # verify the upstream exists upstream_valid='n' # branch if git show-ref --quiet --verify refs/heads/${upstream} then upstream_valid='y' # tag elif git show-ref --quiet --verify refs/tags/${upstream} then upstream_valid='y' # remote branch elif git show-ref --quiet --verify refs/remotes/${upstream} then upstream_valid='y' # commit id elif git rev-list --max-count=1 --quiet $upstream >/dev/null 2>&1 then upstream_valid='y' fi numdiff=0 label= subjlist= cnt=0 compare_git() { echo "Key:" printf "[----] : patches are identical\n" printf "[${bold}####${reset}] : number of functional differences between upstream/downstream patch\n" printf "[${bold}${color1}down${reset}] : patch is downstream-only\n" printf "The flags [${bold}FC${reset}] indicate (F)unctional and (C)ontextual differences, respectively\n\n" # don't pipe the git job into read, to avoid subshells while read hashsubj do let cnt=$cnt+1; subj=${hashsubj:40} downhash=${hashsubj:0:40} # A little bit hackish, but find the match by looking at upstream # subject lines, and using the last one. Not all backports contain # the phrase "cherry-pick", so we can't really try and find the # upstream hash from that... uphash=`git log $upstream --pretty=format:"%H" --perl-regexp --grep="^\\Q${subj}\\E$"|tail -n 1` if [[ -n "$uphash" ]] then numdiff=`diff -u <(git diff $uphash^! |egrep ^[-+])\ <(git diff $downhash^! |egrep ^[-+])\ | egrep '^[-+]' | egrep -v '^[-+]{3}' |wc -l || true` # for contextual diff checking, we will ignore hashes and line number offsets condiff=`diff -u <(git diff $uphash^\! |sed -e s/^@@.*@@//g |egrep -v ^index |egrep -v ^diff)\ <(git diff $downhash^\!|sed -e s/^@@.*@@//g |egrep -v ^index |egrep -v ^diff)\ | egrep '^[-+]' | egrep -v '^[-+]{3}'|wc -l || true` f="-" c="-" if [[ $sensitivity -gt 1 ]] then showdiff=1 else showdiff=0 fi if [[ $condiff -ne 0 ]] then c=${bold}C${reset} if [[ $sensitivity -gt 0 ]] then showdiff=1 fi fi if [[ $numdiff -ne 0 ]] then f=${bold}F${reset} showdiff=1 printf "%03d/${total}:[${bold}%04d${reset}] [${f}${c}] ${bold}${color4}'${subj}'${reset}\n" $cnt $numdiff else printf "%03d/$total:[----] [${f}${c}] '$subj'\n" $cnt fi if [[ $showdiff -eq 1 ]] then if [[ diffprog == "meld" ]] then label="--label=\"#$cnt: $subj ( <-upstream, downstream-> )\"" fi subjlist[$cnt]=$subj exe[$cnt]="${label} <(git show $uphash^!) <(git show $downhash^!) 2>/dev/null" shortexe[$cnt]="<(git show ${uphash:0:7}^\!) <(git show ${downhash:0:7}^\!)" fi else printf "%03d/$total:[${bold}${color1}down${reset}] ${bold}${color4}'$subj'${reset}\n" $cnt fi done < <(git log --pretty=tformat:"%H%s" --reverse $range) } if [[ $upstream_valid != 'y' ]] then echo "Upstream $upstream is not valid (does not exist)!" echo "Do you need to add the remote upstream repo?" exit 2 fi >&2 compare_git echo "Do you want to view the diffs using ${bold}${diffprog}${reset}? y/[n]: " read -n 1 view echo "" if [[ "$view" == "y" ]] then for idx in ${!exe[*]} do if [[ $pause == 'y' ]] then echo "Press [Enter] to view diff(diff) of ${idx}/${total}: ${bold}${color4}${subjlist[$idx]}${reset}" read else echo "Viewing diff(diff) of ${idx}/${total}: ${bold}${color4}${subjlist[$idx]}${reset}" fi eval ${diffprog} ${exe[$idx]} || true done fi echo "To view diffs later, you may run:" for idx in ${!shortexe[*]} do printf "%03d/$total: '${diffprog} ${shortexe[$idx]}'\n" $idx done