Almost three years ago, I published a bash wrapper function for the svn
command on this blog. This shell function allows to use external tools when calling svn diff
, for example colordiff
, Apple’s FileMerge on OS X or vimdiff.
Since then, I improved the file by using less forks with type -fp
, added quotes where I encountered filenames with spaces, etc. However, it is basically still the same as back then. Here, I left out the parts about the other diff functions so you get an idea how this works without requiring you to read the other blog post:
function svn() { local svn=$(type -fp svn) case "$1" in diff-vim) shift; $svn diff --diff-cmd $HOME/libexec/svndiff -x vimdiff "$@" ;; *) $svn "$@" ;; esac }
This approach has one drawback for practical use of the svn diff-vim
command: sometimes, svn does not use the original file name, but creates a temporary file first. This hinders editing of such a diff using vimdiff, as edits can not be saved easily to the actual specified file.
[Side note: this happens as soon as a file has a svn:keywords
property and keywords such as $Id$
were expanded. In this case, the diff does not use the original file path but a temporary file to avoid changes introduced by keyword expansion. (details)]
This behavior became annoying for me, so I improved the original approach and present the new version below. This time, I am using svn cat
to manually retrieve the older version of the file to a temporary location and then call vimdiff with the file specified and the older version. This became more complex as I also wanted to support the -r
syntax to specify the revision to compare against:
function svn() { local svn=$(type -fp svn) case "$1" in diff-vim) shift; local file="" local rev="-rBASE" while [ $# -gt 0 ]; do case "$1" in --) break; ;; -r*) rev="$1" if [ "$1" == "-r" ]; then shift rev="-r$1" fi ;; --revision) shift rev="-r$1" ;; -*) echo "svn: invalid option: $1" return 1 ;; *) if [ -n "$file" ]; then echo "svn: diff-vim works with one single filename only" >&2 return 1 fi file="$1" ;; esac shift; done; local tmp=$(mktemp -t svn-diff-vim || return 1).${file##*.} $svn cat $rev "$file" > $tmp || return 1 chmod a-w $tmp || return 1 vimdiff "$file" $tmp || return 1 rm -f $tmp || return 1 ;; *) $svn "$@" ;; esac }
A known drawback of this approach is that you can no longer use the -c
option to view changes introduced in a specific revision, or specify a peg revision with the file@rev
syntax. For me, these aren’t showstoppers, as the most common use case for me is to revert only some parts of a file. Using the presented function above, this is quite easy with svn diff-vim
and the vim commands moving changes from one file to the other: do
for diff-obtain and dp
for diff-put.
You can also get the full file with all implemented diff commands as a download. This file is meant to be sourced from your .bashrc
. You can share and use this script as you will, it is hereby placed into Public Domain.
As always, I am happy to hear about your experiences with this script, suggestions or better solutions in the comments below!
Hi,
I’m trying to use this – I’ve source it in my console, and I then run “svn diff-vim” in a Subversion directory. I then use “:qa” to quit out of vimdiff.
However, the second time I try to run it, I get:
mktemp: cannot create temp file /tmp/svn-diff-vim: File exists
-bash: .: Is a directory
I can remove that temporary file, and use it again – however, something tells me I’m Holding It Wrong…lol.
That, or it’s not cleaning up it’s tmp files? I put an echo line before the rm tmp line, doesn’t seem to get there, and at the command-line I see:
svn diff-vim
svn: Try ‘svn help’ for more info
svn: Not enough arguments provided
2 files to edit
any ideas?
Cheers,
Victor
Hm, that sounds almost correct what you did. You are supposed to pass a single filename to the command – the file you want to diff. There is no good way to handle multiple files in vimdiff, so this function works on a single file only.
I just noticed due to the way the temporary filename is substituted, there is no way to diff a file in a subdirectory. The argument has to be in the current directory.
Use the following command for debugging:
This will print each line evaluated in the function.
Thanks for this great script. Exactly what I needed.
Maybe one day I will enhance it to support dir-diff as it works with git + meld out of the box.
I had the the same problem as Victor Hooi. The reason is, because possibly on some systems the mktemp does not work as it should. Normally it should build a standard template file name if no template is given. However on my system I have to change “mktemp -t svn-diff-vim” to “mktemp -t svn-diff-vim.XXXXXXXX”. Then it works.
Ah, I see. The function expects you have the standard mktemp from OS X. I guess you manually installed GNU mktemp from GNU coreutils and put it into your PATH first. I can confirm that the GNU mktemp does not understand the syntax I used, but the change you mentioned works around that limitation.