Vim: Automatically Add Branch Names to a Commit

Ovid on 2008-05-02T09:58:39

This is for Subversion, but I'm sure you could extend it.

We branch for every feature we add, but I sometimes forget to add branch names to a commit. No longer. Add the following two lines to your .vimrc:

filetype plugin on
au! FileType svn :call AddBranchName()

And create a file named .vim/ftplugin/svn.vim with the following:

function! AddBranchName()
    let cwd  = getcwd()
    let path = split( cwd, '/' )
    let cwd  = path[ len(path) - 1 ]
    if bufname("%") == "svn-commit.tmp" && cwd != 'trunk'
        call append(0, "Branch: " . cwd)
        call append(1, "")
        call append(2, " -  ")

        call cursor(3, 4)
    endif
endfunction

Now, when I type 'svn commit', I see something like the following, with the cursor already positioned where it needs to be:

Branch: errors_on_data_objects_with_drilldown

 -  _

--This line, and those below, will be ignored--

M    root/reports/validation_exceptions_by_period_table.tt
M    conf/log/log.conf
A    lib/Pips3/C/Reports/Imports/Validation/ObjectsByPeriod.pm
M    lib/Pips3/Report/Query/Validation.pm

This makes a few assumptions about your environment, so I would love to get some portability comments. I also wanted to leave the editor in insert mode, but couldn't figure out how to do that. Vim is one of the few tools I use regularly for which Google is almost useless.


why and the command you're looking for

jdv on 2008-05-02T13:24:44

I don't really see the point of putting the name of the branch in the message because its somewhat redundant. If you read the log with -v then you see the paths. Sounds like you want :startinsert.

Re:why and the command you're looking for

Ovid on 2008-05-02T13:42:16

I have to put the branch name in there because it's part of our coding standards. Argue with jplindstrom and others :)

And yes, startinsert is exactly what I needed. Thanks!

Re:why and the command you're looking for

jplindstrom on 2008-05-02T16:33:43

Well, I didn't make it up. But I believe it makes it easier to read the SVN Timeline in Trac. That may be the reason.

Re:why and the command you're looking for

Aristotle on 2008-05-03T00:50:24

That sort of rule is common in shops stuck on Subversion… since Subversion does so little to actually help with branches (other than making them cheap to create, as if that bought you anything), you need to put a bunch of information in your commit messages manually in order to be able to babysit Subversion later. VCSs that actually have useful merge support, such as any DVCS of your choice, make it unnecessary to do that sort of bookkeeping.

And if you like browsing Trac, wait to see the gitk browser. :-)

Re:why and the command you're looking for

jplindstrom on 2008-05-03T13:39:46

Yes, we even put revision numbers in merge messages to keep track of what we're doing. Sad, sad, sad.

The silver lining: we're looking at moving to git. We just need to find a good point in time to take the productivity hit of switching.

Branch names

mpeters on 2008-05-02T14:47:54

This seems to get the branch name from the file system and not from svn. I don't always name my checkout directories the same as my branches and I frequently use symlinks to mark which one I'm currently using the most.

Can your plugin instead use "svn info" to pull the actual branch name? It could pull the URL from "svn info" and then assume that the next path segment after "/branches" (or /branch, etc) is the actual branch name?

Re:Branch names

Smylers on 2008-05-02T16:34:12

Can your plugin instead use "svn info" to pull the actual branch name?

Turns out that's quite straightforward. Here's my version (below) modified to do that:

let branch = matchstr(system('svn info .'), '\v(branch[^/]*/)@<=[^/]*')
if branch != ''
  execute '1s//Branch: ' . branch . '\r\r -  '
endif
startinsert!

Re:Branch names

mpeters on 2008-05-02T16:43:30

forgive my vi idiocy, but I get this error message when I use that:


"svn-commit.2.tmp" 4L, 63C
Error detected while processing function AddBranchName:
line 3:
E486: Pattern not found: retired

Here's my full plugin source:
 

function! AddBranchName()
    let branch = matchstr(system('svn info .'), '\v(branch[^/]*/)@<=[^/]*')
    if branch != ''
      execute '1s//Branch: ' . branch . '\r\r -  '
    endif
    startinsert!
endfunction

Re:Branch names

Smylers on 2008-05-02T16:56:01

I get this error message

Ooops, my fault! s// repeats the most recent match; I'm guessing you previously searched for “retired” and that you have something in your Vim config which remembers the search register across sessions.

Make that s/^/ instead. Sorry about that.

my full plugin source

Note you don't need that function definition around it; what I posted was intended to be the complete plug-in. See below for the explanation.

Refactored

Smylers on 2008-05-02T16:01:53

Note that you're duplicating the filetype detection there, twice. The standard $VIMRUNTIME/filetype.vim defines an autocommand such that starting to edit a file matching svn-commit*.tmp will trigger a FileType event for svn. As you've got it set up, making a commit causes these steps to run:

  1. .vim/ftplugin/svn.vim is run (because you have filetype plug-ins enabled), which in your case defines a function.
  2. Your autocommand for that filetype runs, which runs the function that svn.vim has just defined.
  3. Your function checks that the filename is svn-commit.tmp.

If in step 1 you just run the commands immediately in svn.vim rather than defining a function then you can get rid of step 2 entirely. And the check in step 3 is unnecessary, since svn.vim only runs at all if the filename matches.

So this can be refactored quite a bit. Also:

  • fnamemodify() can do the filename manipulation.
  • You can do the insert a single go with a substitution.
  • The substitution conveniently leaves the cursor on the right line, and using the above-recommended startinsert! but with the exclamation mark conveniently puts the cursor at the end of the line, which is more robust than having to track how many lines and characters you've inserted.

Putting all that together, I think you can just have the following in .vim/ftplugin/svn.vim:

let parent_dir = fnamemodify(getcwd(), ':t')
if parent_dir != 'trunk'
  execute '1s//Branch: ' . parent_dir . '\r\r -  '
endif
startinsert!

However, neither your nor my version actually seems to do the right thing! It seems that svn commit changes to the directory of the file being committed, which messes up getcwd() for commits in subdirectories. So it looks like parsing svn info will be needed, unfortunately.