ようこそゲストさん

mitc - 日記

2012/01/30(月) VimScriptの練習に編集中ファイルのSVNブランチ名を取得してみた

はてブ 2012/01/30 24:07 Vimmiff
Vim7.3
SVN1.6.16
 最近、EmacsとVimの両方が使えるようになりたいと思って積極的にVimを使っているんですが、たまたまSVNの管理下にあるコードをいじることがあって、編集中にブランチ名を表示したくなりました。そこで、VimScriptの勉強がてら編集中ファイルのブランチ名を取得するプラグインを作ってみました。

目次
ソースコード: https://github.com/fumiz/vim/blob/master/svn-branch-test.vim
使い方:
1. .vim/pluginに入れる
2. .vimrcでステータスラインの設定を変更
set statusline=%n\:%y%F\\|%{(&fenc!=''?&fenc:&enc).'\|'.&ff.'\|'}%m%r%=<%l/%L:%p%%>(%{g:get_current_svn_branch_name()})
3. ブランチ名がステータスラインに表示されている

ブランチの置き場所が特別な場合は.vimrcで次のような感じで正規表現を設定します。
let g:svn_branch_test_extract_regex = '\/branches\/[^\/]\+\/\([^\/]\+\)'
vim-svn-branch-name.png

原理

ブランチ名を取得する方法として、SVNの管理ファイルを見に行くやり方と、svn infoコマンドの結果から取り出すやり方の2種類がありますが、なんとなく外部コマンドを使いたくなかったのでSVNの管理ファイルを見に行くやり方を採用しました。

SVNは管理下にあるディレクトリに.svnディレクトリを作成し、次のように管理情報を格納します
.
├── .svn
│   ├── all-wcprops
│   ├── entries
│   ├── prop-base
│   ├── props
│   ├── text-base
│   └── tmp
├── LICENSE
├── Makefile
├── application
この、.svn/entriesというファイルにリポジトリのURLと現在のファイルの位置が記されています
dir
1396
http://svn.sourceforge.jp/svnroot/bathyscaphe/bathyscaphe/branches/v111-BASED-BRANCH
http://svn.sourceforge.jp/svnroot/bathyscaphe
(例はSourceForgeのBathyScaphe)

そこで、
1. 編集中のファイルの存在するディレクトリ下に.svn/entriesファイルが存在するかチェック
2. 存在するなら.svn/entriesファイルから正規表現でブランチ名を取得
という流れでブランチ名を取得することにします。

ちなみに、SVNはブランチを置く場所が慣例的に/branchesになっているものの、実際は割とフリーダムになっています。
例えば、上のBathyScapheの例では
/bathyscaphe/branches/ブランチ名
となっていますし、大人数で一つのリポジトリを共有している場合は次のように
branchesの下に階層を深く掘ってブランチを作っている場合もあります。
/branches/ユーザ名/プロジェクト名/ブランチ名
どの場合でも通用する一つの正規表現を作ることは難しいので、.vimrcで自分の環境にあわせて正規表現を編集できるようにすることにします。

スクリプト本体

github:svn-branch-test.vim
" extract branch name from .svn/entries

" 1. .vim/pluginに入れる
" 2. :ShowBranch
" 3. g:svn_branch_test_extract_regexに指定した正規表現でブランチ名を取り出し表示
"
" 正規表現の適用対象は.svn/entriesの5行目と6行目から取り出した
" SVNチェックアウト対象のルートからのパス
"
" デフォルトの正規表現は/branches/ブランチ名/*を想定
"
" .vimrcに次のように埋め込むことで編集中バッファに対応したブランチ名をステータスラインに表示する
" set statusline=%n\:%y%F\\|%{(&fenc!=''?&fenc:&enc).'\|'.&ff.'\|'}%m%r%=<%l/%L:%p%%>(%{g:get_current_svn_branch_name()})
"
" 参考にしたコード:
"   http://coderepos.org/share/browser/dotfiles/vim/kana/dot.vimrc?rev=7152
"   http://www.vim.org/scripts/script.php?script_id=1234

" 二重読み込み防止
if exists('g:loaded_svn_branch_test')
  finish
endif
let g:loaded_svn_branch_test = 1

" 設定の定義(.vimrcで変更できる)
if !exists('g:svn_branch_test_extract_regex')
  let g:svn_branch_test_extract_regex = '\/branches\/\([^\/]\+\)'
endif

" コマンドの定義
command! ShowBranch call s:show_branch()

" コマンドの本体
function! s:show_branch()
  echo g:get_current_svn_branch_name()
endfunction

" カレントディレクトリのSVNブランチ名を取得
function! g:get_current_svn_branch_name()
  return s:svn_branch_name(expand('%:p:h'))
endfunction

" 指定したディレクトリのSVNブランチ名を返す(キャッシュ対応)
let s:_svn_branch_name_cache = {}
function! s:svn_branch_name(dir)
  let cache_entry = get(s:_svn_branch_name_cache, a:dir, 0)

  if cache_entry is 0
    unlet cache_entry
    let cache_entry = s:_svn_branch_name(a:dir)
    let s:_svn_branch_name_cache[a:dir] = cache_entry
  endif

  return cache_entry
endfunction

" 指定したディレクトリのSVNブランチ名を返す
function! s:_svn_branch_name(dir)
  let head_file = s:_svn_branch_name_key_file_path(a:dir)
  return s:_svn_branch_name_extract(head_file)
endfunction

" 指定したディレクトリの.svn/entriesファイルパスを返す
function! s:_svn_branch_name_key_file_path(dir)
  return a:dir . '/.svn/entries'
endfunction

" .svn/entriesからブランチ名を抽出
function! s:_svn_branch_name_extract(entries_path)
  if !filereadable(a:entries_path)
    return ''
  endif

  let branch_line = s:_svn_branch_name_read(a:entries_path)
  let branch_name = matchlist(branch_line, g:svn_branch_test_extract_regex)[1]
  return branch_name
endfunction

" .svn/entriesからSVNのルート以下のパスを取得
function! s:_svn_branch_name_read(entries_path)
  let lines = readfile(a:entries_path, '', 6)
  if len(lines) != 6
    return ''
  endif

  " SVNのルートを取り出す。次のような文字列が格納されていることを想定している
  " branch_line->svn://svnroot/branches/mybranch/pa/th/to/module
  " root_path ->svn://svnroot
  let branch_line = lines[4]
  let root_path = lines[5]

  return branch_line[len(root_path):len(branch_line)-1]
endfunction
グローバルスコープの関数を定義して、
" カレントディレクトリのSVNブランチ名を取得
function! g:get_current_svn_branch_name()
  return s:svn_branch_name(expand('%:p:h'))
endfunction
ステータスラインから呼び出すことで、ステータスラインに情報を表示できるようです
set statusline=%n\:%y%F\\|%{(&fenc!=''?&fenc:&enc).'\|'.&ff.'\|'}%m%r%=<%l/%L:%p%%>(%{g:get_current_svn_branch_name()})

自分用Vimプラグインの作り方

1. hogehoge.vimファイルにスクリプトを書く
2. .vim/pluginに放り込む

スクリプトの中身は次のような枠組みになりそうです。

github:hello.vim
" 二重読み込み防止
if exists('g:loaded_hello')
  finish
endif
let g:loaded_hello = 1

" .vimrcで変更できる設定の定義
" この場合g:hello_stringという変数が.vimrcで定義れていない場合に
" デフォルト値として'VimScript'を使うことになっている
if !exists('g:hello_string')
  let g:hello_string = 'VimScript'
endif

" コマンドの定義(ユーザ定義のコマンドは大文字で始まる必要があるらしい)
command! ShowHello call s:show_hello()

" コマンドの本体
function! s:show_hello()
  echo "hello " . g:hello_string . "!"
endfunction

おわりに

スクリプトを配布するなら、autoloadという機構で外部から関数をロードしやすいようにしたり、ドキュメントを付けたりといった配慮があると良いようですが、私自身がまだあまり理解できていないので今回はあくまで自分用に使うなら、という前提です。

Vimはだいたいの環境で使えて軽量なので使いやすいエディタですよね。VimScriptを自分で書ければ環境固有の面倒な処理を簡単に解決できたりしそうです。凝ったことをしないのなら難しくなかったので試してみることをオススメします。

参考にしたページ


名前:  非公開コメント   

E-Mail(任意/非公開):
URL(任意):
  • TB-URL  http://mitc.s279.xrea.com/adiary.cgi/0107/tb/