Created
February 9, 2020 07:28
-
-
Save Hashbrown777/0b2b535d582cb8a3aa817c832062dcb5 to your computer and use it in GitHub Desktop.
performs an `svn diff` but includes `svn annotate` metadata
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function blameDiff() { | |
file="$1" | |
rev1="$2" | |
rev2="$3" | |
#default to HEAD if omitted | |
if [ -n "$rev1" ] | |
then | |
title1="(revision $rev1)" | |
else | |
title1="(working copy)" | |
rev1='HEAD' | |
fi | |
if [ -n "$rev2" ] | |
then | |
title2="(revision $rev2)" | |
else | |
title2="(working copy)" | |
rev2='HEAD' | |
fi | |
#check that the svn urls are the same | |
tmp1="$(svn info -r $rev1 "$file" |\ | |
grep '^Relative URL' |\ | |
sed 's/Relative URL: //' \ | |
)" | |
tmp2="$(svn info -r $rev2 "$file" |\ | |
grep '^Relative URL' |\ | |
sed 's/Relative URL: //' \ | |
)" | |
if [ "$tmp1" != "$tmp2" ] | |
then | |
#if not, then one of these revisions is in another branch | |
#lets have this in the output | |
title1="($tmp1) $title1" | |
title2="($tmp2) $title2" | |
fi | |
#can just print this but you wont get deleted revision/blame | |
# diff -u \ | |
# <(svn blame -r "$rev1" "$file") \ | |
# <(svn blame -r "$rev2" "$file") \ | |
# | sed "s|^--- .*$|--- $file $title1|" \ | |
# | sed "s|^+++ .*$|+++ $file $title2|" | |
# return 0 | |
#an array of commitNumber|committer pairs for the file | |
history=() | |
#a map between elements in `history` and a list of line numbers changed. | |
#each item in the list is a lineNumber|newLineNumber pair | |
declare -A revisions | |
#the sed match and replace expressions to pull data from the | |
#diff-line-number&cat-line-number combo and give it to the cache | |
grabData='^ *\([0-9]\+\)\t\([0-9]\+\)$' | |
formatData='\2 \1' | |
#for each revision between the ones given | |
last='' | |
while read -r line | |
do | |
#read in the revision number and submitter | |
IFS=' |' read next by tmp <<<"$line" | |
if [ -n "$last" ] | |
then | |
#save them | |
history+=("$next $by") | |
#associate and format the list | |
revisions["${history[-1]}"]="$(\ | |
diff \ | |
--unchanged-line-format="%dn%c'\012'" \ | |
--new-line-format="?%c'\012'" \ | |
--old-line-format='' \ | |
<(svn cat -r "$last" "$file") \ | |
<(svn cat -r "$next" "$file") \ | |
| cat -n \ | |
| grep -v '?$' \ | |
| sed "s/$grabData/$formatData/" \ | |
)" | |
fi | |
#remember the last revision looked at | |
last="$next" | |
done <<<"$( | |
svn log -r "$rev1:$rev2" "$file" \ | |
| grep '^r[0-9]\+ | ' \ | |
| sed 's/^r//' \ | |
)" | |
#pull the full diff | |
diff \ | |
--new-line-format='+%L' \ | |
--old-line-format='-%L' \ | |
--unchanged-line-format='=%L' \ | |
<(svn blame -r "$rev1" "$file") \ | |
<(svn blame -r "$rev2" "$file") \ | |
| { | |
#header stuff | |
echo "Index: $file" | |
echo '===================================================================' | |
echo "--- $file $title1" | |
echo "+++ $file $title2" | |
#count the line number we're up to for the original file | |
origLine=0 | |
#count the line number we're up to for the new file | |
newLine=0 | |
#keep a few of the output lines, and their line number contexts | |
buffer=() | |
origContext=() | |
newContext=() | |
#tells the script to print the buffer if <3; | |
#the context lines around real differences | |
printing=4 | |
#whether or not the next print needs to show line numbers | |
needsContext=true | |
#the sed match and replace expressions to pull data from diff | |
#and give it to read | |
grabData='^\([+=-]\)\( *[0-9]\+\)\( *[^ ]\+\)\(.*\)$' | |
formatData='\1\v\2\v\3\v\4' | |
#for each line in the full diff | |
while read -r data | |
do | |
IFS=$'\v' read flag committed who line <<<"$(\ | |
sed $'s/\t/ /g' \ | |
<<<"$data" \ | |
| sed "s/$grabData/$formatData/" \ | |
)" | |
#the last surviving revision of the line | |
edited="$rev2" | |
#who killed this line | |
by='' | |
case "$flag" in | |
+) | |
#a new line was introduced | |
((++newLine)) | |
printing=0 | |
;; | |
-) | |
#an old line was removed | |
((++origLine)) | |
printing=0 | |
#the line number that changes throughout history | |
number="$origLine" | |
#for each commit | |
for revision in "${history[@]}" | |
do | |
#read in the two line numbers from the matching change | |
number="$(grep "^$number " <<<"${revisions["$revision"]}")" | |
IFS=' ' read edited by <<<"$revision" | |
#not present; this was the revision where it was destroyed | |
if [ -z "$number" ] | |
then | |
break | |
fi | |
#pull the new line number for the next revision | |
IFS=' ' read tmp number <<<"$number" | |
done | |
;; | |
=) | |
#an old line continues to exist in the new file | |
((++newLine)) | |
((++origLine)) | |
flag=' ' | |
((++printing)) | |
;; | |
esac | |
#format the line to print | |
buffer+=("$(printf "%s %s:%-${#committed}s%s:%-${#who}s%s" \ | |
"$flag" \ | |
"$committed" \ | |
"$edited" \ | |
"$who" \ | |
"$by" \ | |
"$line" \ | |
)") | |
#can just end it here, but it will print the whole file/s | |
# echo "${buffer[-1]}" | |
# buffer=() | |
# continue | |
#and add the context | |
origContext+=("$origLine") | |
newContext+=("$newLine") | |
if ((printing < 4)) | |
then | |
if $needsContext | |
then | |
echo "@@ -${origContext[0]} +${newContext[0]} @@" | |
needsContext=false | |
fi | |
#print all lines in the buffer | |
for line in "${buffer[@]}" | |
do | |
echo "$line" | |
done | |
#and reset it | |
origContext=() | |
newContext=() | |
buffer=() | |
fi | |
#if there are too many lines in the buffer | |
if ((${#buffer[@]} > 3)) | |
then | |
#remove the overflow | |
origContext=("${origContext[@]:1}") | |
newContext=("${newContext[@]:1}") | |
buffer=("${buffer[@]:1}") | |
#and note that we now need to show the context because of this | |
needsContext=true | |
fi | |
done | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Usage;
blameDiff <path> [rev1] [rev2]
(unwrap the function in its own script, or source it/put it in your bashrc to call)It basically reimplements the svn diff using just svn cat and regular diff to take into account the revision and line numbers, tracking exactly where in the history a given line was removed.
Even takes into account whether the files are in different branches and displays it as svn would.
Here's screenshots of the two following commands, with the code redacted for work-reasons.
As you can see a bunch of extra info is given in the new format on the right; the first columns show ashley added a couple lines back in r6631, and deleted a whole bunch in r6639 originally committed by zes long ago @R6466&6483.