Subversion diff with vimdiff improved

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!

5 thoughts on “Subversion diff with vimdiff improved

  1. Victor Hooi

    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

  2. Rainer Müller Post author

    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:

    set -x; svn diff-vim filename; set +x

    This will print each line evaluated in the function.

  3. Maik

    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.

  4. Maik

    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.

  5. Rainer Müller Post author

    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.

Leave a Reply

Your email address will not be published.

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

This site uses Akismet to reduce spam. Learn how your comment data is processed.