「VC(バージョンコントロール)パッケージの基礎」(菅原泰樹)
「Emacsのトラノマキ」連載第七回「VC(バージョンコントロール)パッケージの基礎」
VC(バージョンコントロール)パッケージの基礎
Emacsのトラノマキがなんと連載7回目をむかえてしまいました.思った以上にEmacsを覚えたいという人が多いということなんでしょうか.すばらしい世の中です.さて,今回はEmacsのVC(バージョンコントロール)パッケージについて書いていきます.
※本稿はEmacs23のVCについて書いています.Emacs22の場合はキーバインドが違っていたり,機能がなかったりする場合があります.そんなときは各自describe-bindings
するなどして臨機応変に対応するようにして下さい.
VCとは
VCはEmacs上で各種バージョン管理システムを統合的に扱うパッケージです.Emacs23ではRCS, CVS, Subversion, Git, Mercurial, Bazaar, Gnu-Archに標準で対応しています.バックエンドを書けば他のバージョン管理システムに対応させることも可能です.実際vc-gitやvc-svnは本体に取り込まれるまで別パッケージとして提供されていました.
もともとがRCSあたりを対象に作られたものなので,最近のChangeSetベースのバージョン管理システムとはちょっと相性が悪いこともあります(Emacs23で対応はされてきましたが).それでもEmacsからお手軽にログを見たり差分を見たりといった機能はとても便利で魅力的です.VCの使い方を覚えるとEmacsを使うのがまたちょっと楽しくなると思います.
なお,本稿ではpcl-cvsやpsvnなどのVC以外のパッケージの説明しません.VCがChangeSetをサポートしつつある現在,これらのパッケージはそのうち廃れていくか,VCを補助する程度のパッケージになっていくのではないかと筆者は考えています.今はまだこれらのパッケージの方が便利な事も多いですし,VCと組みあわせて色々するのも楽しいし便利なのですが,まずはVCの使い方をしっかり覚えてしまいましょう.
バージョン管理システム
本編はバージョン管理システムについて一通りの知識があることを前提に書いています.念の為,まったく知らない人のためにバージョン管理システムについて簡単に説明しておきます.
Wikipedia(http://ja.wikipedia.org/wiki/バージョン管理システム)によると「主にプログラムの開発において,ソースコードやその他のデータを管理するために用いられるシステムのこと.」という事だそうです.だいたいは以下のような機能を提供してくれます.
- ソースコードの履歴の管理
- 他人とのソースコードの共有
- 他人の変更と自分の変更のマージ・衝突の検出
複数人で何かを開発するときには絶対に必要なシステムですし,一人で開発するときにも,履歴や差分の確認等に大活躍です.バージョン管理システムがない暗黒時代が昔はあったそうですが,そんな時代想像できません.死の世界です.
代表的なバージョン管理システムには以下のものがあります.最近はGitが流行してますが,仕事ではSubversionが多いでしょうね.
| CVS | http://www.cvshome.org/ |
| Subversion | http://subversion.tigris.org/ |
| Git | http://git-scm.com/ |
| Mercurial | http://www.selenic.com/mercurial/ |
VCを使う
先にも説明したようにVCは各種バージョン管理システムを統合的に同じ操作で扱います.試しにSubversionで管理されているファイルを開いてみましょう.以下のようにモードラインにSubversionで管理されていることをあらわすSVN-*
というインジケータが表示されます.
同じようにgitで管理されているファイルを開くと以下のようにGit-*
というインジケータが表示されます.
どちらでも同じ操作ができることを確認するために,履歴を表示してみましょう.履歴を表示するにはC-x v l
と入力します.Subversionの場合は以下のように履歴が表示されます.Gitの場合も同じ操作で履歴が表示できます.
各種操作の説明に入る前に,代表的なVCを操作するコマンドを以下にまとめておきます.
| キーバインド | コマンド | 説明 | Subversionのコマンド |
| C-x v = | vc-diff | 差分を表示 | diff |
| C-x v l | vc-print-log | 履歴を表示 | log |
| C-x v g | vc-annotate | 注釈を表示 | blame |
| C-x v ~ | vc-revision-other-window | 過去のバージョンを表示 | cat |
| C-x v + | vc-update | 更新 | update |
| C-x v v | vc-next-action | コミット | commit |
| C-x v i | vc-register | ファイルの追加 | add |
| C-x v u | vc-revert | 修正の破棄 | revert |
| C-x v d | vc-dir | 状態の表示 | status |
| | ediff-revision | Ediffで差分を表示 | |
参照系の操作
まずは参照系の操作からです.VCがその魅力を発揮するのはこの参照系の操作だと筆者は思っています.最近のバージョン管理システムだとファイルを個別に選んでcommitなんてあまりしませんしね.さっそく見ていきましょう.
差分の表示
差分を表示するにはC-x v = (vc-diff)
を入力します.普通は作業コピーとHEADとの差分です.svn diff
などと同じですね.リビジョンを選んで差分を表示するにはC-u C-x v =
としてからリビジョンを入力するか,後で説明する履歴表示機能を使います.C-x v =
を入力すると以下のようにWindowが分割して差分が表示されます.
差分バッファでは前後の差分への移動や,差分の該当行へのジャンプが行なえます.差分バッファでの操作を以下にまとめておきます.
| キーバインド | コマンド | 説明 |
| n | diff-hunk-next | 次の差分 |
| TAB | diff-hunk-next | 〃 |
| p | diff-hunk-prev | 前の差分 |
| S-TAB | diff-hunk-prev | 〃 |
| RET | diff-goto-source | 該当行へジャンプ |
| C-c C-c | diff-goto-source | 〃 |
| N | diff-file-next | 次のファイル |
| P | diff-file-prev | 前のファイル |
差分の表示には普通にdiffを表示する以外にもう一つ方法があります.Ediffというものです.Ediffとは差分をビジュアルに表示するためのパッケージです.EdiffはVCとは独立していて,それだけで記事が書けそうなくらいなパッケージなのですが,ここでは簡単に紹介するにとどめておきます.
VCでEdiffを使うにはM-x ediff-revision
と入力します.ファイル,最初のリビジョン,次のリビジョンを聞かれますが全部デフォルトのまま答えましょう.ファイルが今選んでいるファイル,最初のリビジョンがHEAD,次のリビジョンが作業コピーになります.そうすると以下のような画面になります.
読者の環境ではEdiff Control Panelが別のフレームになっているかもしれませんが,そこは気にしないで下さい.後で説明します.このPanelバッファでn
, p
を入力することで次前の差分を表示できます.Ediffを抜けるにはq
と入力して下さい.
EdiffはVCとは独立したパッケージなので普通のファイル同士の差分を見るのにも使えます.バッファ同士の差分も見れますし,リージョン同士の差分だって見れてしまいます.以下にediffを実行するためのコマンドをまとめておきます.とても便利なので是非使って下さい.
| コマンド | 説明 |
| ediff-files | ファイル同士の差分 |
| ediff-buffers | バッファ同士の差分 |
| ediff-regions-wordwise | リージョン同士の差分(UIが分かりにくいです) |
| ediff-directories | ディレクトリ同士の差分 |
最後にEdiffのちょっとしたカスタマイズ例を載せておきます.読者の好みに合わせて設定しておいて下さい.
;; Ediff Control Panelを同じフレーム内に表示する(筆者のスクリーンショットと同じ)
(setq ediff-window-setup-function 'ediff-setup-windows-plain)
;; 差分を横に分割して表示する
(setq ediff-split-window-function 'split-window-horizontally)
;; 余計なバッファを(確認して)削除する
(setq ediff-keep-variants nil)
注釈の表示
注釈を表示するにはC-x v g (vc-annotate)
を入力します.バージョン管理システムでの注釈とはソースの各行を最後に更新した人とそのリビジョンを表示する機能のことです.リビジョンを選んで注釈を表示するにはC-u C-x v g
としてからリビジョンを入力するか,後で説明する履歴表示機能を使います.C-x v g
を入力すると以下のようにWindowが分割して注釈が表示されます.
注釈バッファでは注釈を表示するリビジョンの変更,該当行の差分やログの表示等が行なえます.注釈バッファでの操作を以下にまとめておきます.
| キーバインド | コマンド | 説明 |
| j | vc-annotate-revision-at-line | 該当行のリビジョンの注釈を表示 |
| a | vc-annotate-revision-previous-to-line | 該当行のひとつ前のリビジョンの注釈を表示 |
| w | vc-annotate-working-revision | 作業コピーのリビジョンの注釈を表示 |
| n | vc-annotate-next-revision | 今見ている次のリビジョンの注釈を表示 |
| p | vc-annotate-prev-revision | 今見ている前のリビジョンの注釈を表示 |
| d | vc-annotate-show-diff-revision-at-line | 該当行の差分を表示(ファイル単位) |
| D | vc-annotate-show-changeset-diff-revision-at-line | 該当行の差分を表示(ChangeSet単位) |
| f | vc-annotate-find-revision-at-line | 該当行のリビジョンを取り出し |
| l | vc-annotate-show-log-revision-at-line | 該当行の履歴を表示 |
注釈機能は慣れるまでは使い道がピンとこないかもしれませんが,これを使うことで,あるリビジョン前後の変化を追うといったことが簡単になります.たとえば意味のわからない怪しいコードを見つけたとします.そうしたらC-x v g
で注釈を表示します.怪しいコードの該当行に移動してj
やa
でその修正が入った時点にジャンプします.そこからn
やp
を使ってその前後のリビジョンの修正を見ていくと時系列でコードに入った修正を見ていくことができます.何かおかしなリビジョンを見つけたら差分や履歴を表示して周辺情報を得るとよいでしょう.時系列を追ってソースを探検するときは差分や履歴より注釈の方が便利なことが多々あります.活用しましょう.
履歴の表示
履歴を表示するにはC-x v l (vc-print-log)
を入力します.するとウィンドウが分割して「VCを使う」で説明したように履歴が表示されます.履歴の表示フォーマットはVCのバックエンドによって違いますが操作方法は統一されています.履歴の取得は非同期で行なわれるのでEmacsが固まらないのが地味に嬉しいです.
履歴バッファでは差分の表示や該当リビジョンの取り出し等が行なえます.履歴バッファでの操作を以下にまとめておきます.
| キーバインド | コマンド | 説明 |
| n | log-view-msg-next | 次のメッセージ |
| TAB | log-view-msg-next | 〃 |
| p | log-view-msg-prev | 前のメッセージ |
| S-TAB | log-view-msg-prev | 〃 |
| f | log-view-find-revision | 該当リビジョンの取り出し |
| d | log-view-diff | 差分の表示(ファイル単位) |
| D | log-view-diff-changeset | 差分の表示(ChangeSet単位) |
| a | log-view-annotate-version | 該当リビジョン注釈を表示 |
履歴バッファでのコマンドはカーソルがあるリビジョンに対して行なわれます.差分表示だけは特別で,リージョンを選択してからd (log-view-diff)
やD (log-view-diff-changeset)
を行なうと選択したリビジョン間の差分が表示されます.特定リビジョンの差分を表示したい場合はC-u C-x v =
とするより断然便利なはずです.
さて,便利な履歴表示機能ですが残念な点がいくつかあります.まず標準のキーバインドがC-x v
系と異なっていることです.意味が分かりません.統一するために以下を追加しておきます.
(add-hook 'log-view-mode-hook
(lambda ()
(local-set-key (kbd "RET") 'log-view-find-revision)
(local-set-key (kbd "C-c C-c") 'log-view-find-revision)
(local-set-key (kbd "=") 'log-view-diff)
(local-set-key (kbd "g") 'log-view-annotate-version)))
もうひとつの残念なことは標準でEdiffのサポートがないことです.残念すぎるので以下の設定をしておきます.これで履歴バッファでe (log-view-ediff)
とすればediffが使えるようになります.
(add-hook 'log-view-mode-hook
(lambda ()
(local-set-key (kbd "e") 'log-view-ediff)))
(defun log-view-ediff (beg end &optional startup-hooks)
(interactive
(list (if mark-active (region-beginning) (point))
(if mark-active (region-end) (point))))
(let ((fr (log-view-current-tag beg))
(to (log-view-current-tag end)))
(when (string-equal fr to)
(save-excursion
(goto-char end)
(log-view-msg-next)
(setq to (log-view-current-tag))))
(require 'ediff-vers)
(ediff-vc-internal fr to startup-hooks)))
(defun log-view-ediff-setup ()
(set (make-local-variable 'ediff-keep-tmp-versions) t))
(defvar log-view-ediff-window-configuration nil)
(defun log-view-ediff-before-setup ()
(setq log-view-ediff-window-configuration
(if (eq this-command 'log-view-ediff)
(current-window-configuration)
nil)))
(defun log-view-ediff-cleanup ()
(when log-view-ediff-window-configuration
(ignore-errors
(set-window-configuration log-view-ediff-window-configuration)))
(setq log-view-ediff-window-configuration nil))
(require 'ediff-init)
(add-hook 'ediff-mode-hook 'log-view-ediff-setup)
(add-hook 'ediff-before-setup-hook 'log-view-ediff-before-setup)
;; add it after ediff-cleanup-mess
(add-hook 'ediff-quit-hook 'log-view-ediff-cleanup t)
更新系の操作
更新系の操作は筆者があまり使っていないので簡単に書くに留めておきます.筆者がVCで更新操作をするのは1ファイルだけとりあえずコミットしたいときぐらいでしょうか.EmacsのChangeSet対応が進めばもっと便利になりそうですが,まだまだ更新操作は弱いと感じます.
バージョン管理システムからファイルを更新するにはC-x v + (vc-update)
とします.修正してコミットするときはC-x v v (vc-next-action)
です.コミットログを入力するバッファが表示されるので,ログを入力したらC-c C-c (log-edit-done)
でコミットを完了させましょう.ログがからっぽのときに*** empty log message ***
が勝手にログとして使われるのはご愛嬌です.
ファイルを追加してそれを通知するときもC-x v v
です.もう一度C-x v v
すればコミットされます.next-actionと言うだけあってコミットだけではなく次にやりたい事をそれとなく察して実行してくれる賢い子です.修正を破棄するにはC-x v u (vc-revert)
を使います.C-x v u
とすると今の修正を差分表示して本当に破棄してよいか聞いてくるので,破棄して問題なければyesと答えましょう.
衝突の解決
更新をかけたときにコンフリクトが起きている場合,Emacsは自動的にsmerge-mode
を実行してくれます.コンフリクトが起きているファイルを開いたときに<<<<<<<
といった箇所に色が付いているのを見たことがある読者も多いのではないでしょうか.SMergeはコンフリクトの解決をサポートしてくれる便利なモードです.VCとは直接関係ありませんがここで使い方を覚えてしまいましょう.
SMergeの使い方は簡単です.まずはC-c ^ n (smerge-next)
,C-c ^ n (smerge-prev)
でコンフリクトしている箇所を探します.これらのコマンドを使って移動をすると違いのある箇所に色が付いているはずです.手で移動した場合はC-c ^ R (smerge-refine)
で目で見て分かるようにしてあげましょう.あとは自分の修正を残すならC-c ^ m (smerge-keep-mine)
を,サーバ側の修正を残すならC-c ^ o (smerge-keep-other)
を使って一つずつ解決していきます.
以下にsmerge-mode
での操作をまとめておきます.
| キーバインド | コマンド | 説明 |
| C-c ^ n | smerge-next | 次のコンフリクト |
| C-c ^ p | smerge-prev | 前のコンフリクト |
| C-c ^ R | smerge-refine | 差分に色を付ける |
| C-c ^ m | smerge-keep-mine | 自分の修正をのこす |
| C-c ^ o | smerge-keep-other | サーバの修正をのこす |
| C-c ^ a | smerge-keep-all | 両方の修正をのこす |
| C-c ^ b | smerge-keep-base | ベースリビジョンの修正に置きかえる |
| C-c ^ E | smerge-ediff | Ediffを使ってコンフリクトを解決する |
さいごに
今回は駆け足でVCの使い方を紹介してみました.まずは自分の手元で動く環境を作ってこの記事の操作を実際に試してみてください.動かすことでVCの便利さを実感できると思います.少なくとも参照系の操作に関してはEclipseなどのIDEに引けを取らないだけの機能は持っています.ちなみに手元に環境を作るなら,リポジトリを作る必要のないMercurialやgitがお手軽です.流行ってますしね.
次回ではChangeSet系のバージョン管理システムと仲良くするためのあれこれを書いていくつもりです.お楽しみに.