Git には MQ のようなパッチ管理はできないかと調べてみました。
MQ とは
分散バージョン管理システムと言えば有名な Git のほかに Mercurial SCM があります。
Mercurial には Mercurial Queue (MQ) という拡張機能があり、これを有効化すると、通常のチェンジセット (コミット) とは独立してパッチを管理することができるようになります。
例えば、以下のような場合に使えます。
-
何かオープンソースソフトウェアの実装を調査したりデバッグする際に、標準にはないログを追加するとき
-
自分や自分のいる組織特有の事情があって、ソフトウェア本体を改変する必要があるが、改変箇所をメインブランチにコミットやマージをしたくない場合
MQ でパッチを管理するようになると、メインブランチのリビジョンが進んでも、それに合わせて自分用のログ機能や改変部分のパッチを適用しなおすことができるのでとても便利です。
Git におけるパッチ管理
Git にも MQ のようなパッチ管理ツールがないかと調べたら、Guilt というのがありました。
MQ に強い影響を受けているようです。
Since guilt is heavily based on Mercurial queues (mq), some of the following is shamelessly stolen from the mq page [[1]].
Guilt のインストール
macOS にインストールしてみました。
-
リポジトリからクローン
% git clone http://repo.or.cz/guilt.git Cloning into 'guilt'... remote: Counting objects: 3844, done. remote: Total 3844 (delta 0), reused 0 (delta 0) Receiving objects: 100% (3844/3844), 624.05 KiB | 152.00 KiB/s, done. Resolving deltas: 100% (2864/2864), done.
-
インストール
% make install install -d /usr/local/bin/ install -m 755 guilt /usr/local/bin/ install -d /usr/local/lib/guilt/ install -m 755 guilt-add guilt-applied guilt-branch guilt-commit guilt-delete guilt-diff guilt-export guilt-files guilt-fold guilt-fork guilt-graph guilt-guard guilt-header guilt-help guilt-import guilt-import-commit guilt-init guilt-new guilt-next guilt-patchbomb guilt-pop guilt-prev guilt-push guilt-rebase guilt-refresh guilt-repair guilt-rm guilt-select guilt-series guilt-status guilt-top guilt-unapplied /usr/local/lib/guilt/ install -m 644 os.Darwin os.FreeBSD os.Linux os.SunOS /usr/local/lib/guilt/
-
確認
% guilt Guilt v0.36 Pick a command: add files import prev series applied fold import-commit push status branch fork init rebase top commit graph new refresh unapplied delete guard next repair diff header patchbomb rm export help pop select Example: guilt push
インストールできたようです。
Guilt を使ってみる
準備
hakimel/reveal.js: The HTML Presentation Framework を題材に Guilt を使ってみます。
まずは reveal.js をクローンします。
% git clone https://github.com/hakimel/reveal.js.git
Cloning into 'reveal.js'...
remote: Counting objects: 10252, done.
remote: Total 10252 (delta 0), reused 0 (delta 0), pack-reused 10252
Receiving objects: 100% (10252/10252), 7.68 MiB | 450.00 KiB/s, done.
Resolving deltas: 100% (5648/5648), done.
% cd reveal.js
% ls -F
.git/ README.md lib/
.gitignore bower.json package.json
.travis.yml css/ plugin/
CONTRIBUTING.md demo.html test/
Gruntfile.js index.html
LICENSE js/
.git/
の中は以下のようになっています。普通です。
% ls -F .git
HEAD description index logs/ packed-refs
config hooks/ info/ objects/ refs/
Guilt の初期化
パッチを管理できるように Guilt を初期化します。
$ guilt init
.git/
の中に patches/
が出来ていました。
% ls -F .git
HEAD hooks/ logs/ patches/
config index objects/ refs/
description info/ packed-refs
MQ を使ったことのある人は分かるかもしれませんが、チェックアウトしているブランチ名のディレクトリがあるほかは、 MQ と同じファイル構成です。
% ls -F -R .git/patches
master/
.git/patches/master:
series status
最初のパッチを作る
では、reveal.js のソースを少し弄って、 index.html
にソースコード用のフォントのスタイルを追加してみることにします。
まずはパッチを定義します。ここではパッチ名を code-font
としました。
% guilt new code-font
git status
を実行してみたら、新しいブランチになっていました。
% git status
On branch guilt/master
nothing to commit, working tree clean
少し紛らわしいのですが、git remote
で guilt
が出ないことから、 (ローカルに) guilt/master
という名前のブランチがあることが分かります (origin/master
はリモート origin
の master
ブランチです)。
% git remote
origin
ここで、index.html
を編集し、保存します。編集した内容は guilt diff
で確認できます
% guilt diff
diff --git a/index.html b/index.html
index 98accc3..394d1ca 100644
--- a/index.html
+++ b/index.html
@@ -12,6 +12,12 @@
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
+ <style type="text/css">
+ .reveal code {
+ font-family: "Source Code Pro", "Courier New", monospace;
+ }
+ </style>
+
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
git status
で作業ディレクトリの状態を見てみます。
% git status
On branch guilt/master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
この変更を guilt refresh
を使って、パッチ code-font
に反映させます。
% guilt refresh
Patch code-font refreshed
2つ目のパッチを作る
新しい変更を入れてみます。
今度は reveal.js のスライド表示で、コントロール (矢印) が表示されないようにします。
% guilt new hide-controls
再び、index.html
を編集し、保存します。ここでは以下のように編集しました。
% guilt diff
diff --git a/index.html b/index.html
index 394d1ca..331fee6 100644
--- a/index.html
+++ b/index.html
@@ -43,6 +43,7 @@
// - https://github.com/hakimel/reveal.js#configuration
// - https://github.com/hakimel/reveal.js#dependencies
Reveal.initialize({
+ controls: false,
dependencies: [
{ src: 'plugin/markdown/marked.js' },
{ src: 'plugin/markdown/markdown.js' },
やはり先程と同様に guilt refresh
を使って、パッチ hide-controls
に反映させます。
% guilt refresh
Patch hide-controls refreshed
パッチキューを確認する
コミットログを見ると、これまでの 2つのパッチがコミットされていることが分かります。
% git log master..HEAD --oneline
43af056 (HEAD -> guilt/master, refs/patches/master/hide-controls) patch hide-controls
1838f54 (refs/patches/master/code-font) patch code-font
また、guilt series
で一連のパッチ (パッチキュー、パッチスタック) を確認できます。
% guilt series
code-font
hide-controls
パッチを外す
適用したパッチを外すのは guilt pop
です。
% guilt pop
Now at code-font.
master
ブランチと作業ディレクトリを比較すると、先程の hide-control
で変更した内容がなくなっていることが分かります。
% git diff master
diff --git a/index.html b/index.html
index 98accc3..394d1ca 100644
--- a/index.html
+++ b/index.html
@@ -12,6 +12,12 @@
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
+ <style type="text/css">
+ .reveal code {
+ font-family: "Source Code Pro", "Courier New", monospace;
+ }
+ </style>
+
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
guilt applied
と guilt unapplied
で適用されているパッチと適用されていないパッチが分かります。
% guilt applied # 適用されているパッチ
code-font
% guilt unapplied # 適用されていないパッチ
hide-controls
% guilt series # 全てのパッチ
code-font
hide-controls
パッチを再更新する
この状態で、一度、作成したパッチを修正して、フォントの定義を変更するものとします。
まずは index.html
をそのまま編集します。
ここでは Source Han Code JP
を追加しました。
% git diff
diff --git a/index.html b/index.html
index 108a274..c31d574 100644
--- a/index.html
+++ b/index.html
@@ -14,7 +14,7 @@
<style type="text/css">
.reveal code {
- font-family: "Source Code Pro", "Courier New", monospace;
+ font-family: "Source Han Code JP", "Source Code Pro", "Courier New", monospace;
}
</style>
ファイルを編集したら guilt refresh
するのは同じです。
% guilt refresh
Patch code-font refreshed
差分を見ると反映されていることが分かります。
% git diff HEAD~
diff --git a/index.html b/index.html
index 98accc3..108a274 100644
--- a/index.html
+++ b/index.html
@@ -12,6 +12,12 @@
<!-- Theme used for syntax highlighting of code -->
<link rel="stylesheet" href="lib/css/zenburn.css">
+ <style type="text/css">
+ .reveal code {
+ font-family: "Source Han Code JP", "Source Code Pro", "Courier New", monospace;
+ }
+ </style>
+
<!-- Printing and PDF exports -->
<script>
var link = document.createElement( 'link' );
パッチを当て直す
外したパッチを当て直すのは guilt push
です。
% guilt push
Applying patch..hide-controls
Patch applied.
guilt applied
と guilt unapplied
で適用されているパッチを確認してみます。
% guilt applied
code-font
hide-controls
% guilt unapplied
パッチをまとめて当てたり外したり
全てのパッチをまとめて当てたり外したりするときは -a
または --all
オプションです。
% guilt pop -a
All patches popped.
% guilt push --all
Applying patch..code-font
Patch applied.
Applying patch..hide-controls
Patch applied.
別のブランチにパッチを当てる
MQ と異なり、Guilt はパッチがブランチ毎に管理されているので、他のブランチにパッチを当てるのは少し面倒です。
まずは、guilt branch
で Guilt のパッチをコピーして新しいブランチを作ります。
既に同名のローカルブランチがある場合、guilt branch
コマンドが失敗するので注意が必要です。
% guilt branch dev
Switched to branch 'dev'
パッチがコピーされていることを確認します。
% guilt series
code-font
hide-controls
リモートブランチがあるなら追跡ブランチの設定をします。
% git branch -u origin/dev
Branch 'dev' set up to track remote branch 'dev' from 'origin'.
ローカルブランチをリモートブランチにリセットします。
% git reset --hard origin/dev
HEAD is now at 0c946ae fix missing theme line-height when printing #1967
git pull
だとマージが発生するおそれがあるため、ハードリセットしました。
そして、元のパッチを全て適用します。
% guilt push -a
Applying patch..code-font
Patch applied.
Applying patch..hide-controls
Patch applied.
MQ との違い
コマンド体系
今回、Guilt の基本的な操作を試してみました。
Guilt は MQ ととてもよく似た操作で操作できることが分かります。
コマンドは MQ だと hg qrefresh
とするところを guilt refresh
とするように、大体 hg q
を guilt
に置換するだけです。
ブランチ毎のパッチ管理
前述の通り、Guilt ではブランチ毎にパッチが管理されてます。
MQ ではブランチの区別なくパッチ管理を管理するため、別のブランチへのパッチ適用が簡単です (そもそも Git と Mercurial ではブランチの考え方が少し異なりますが)。
パッチのバージョン管理
Guilt は MQ と違ってパッチそのものをバージョン管理することはできないようです。
MQ では、hg init --mq
コマンドでパッチそのものをバージョン管理することができますが、それに相当する Guilt のコマンドは見つけられませんでした。
まとめ
Git でも MQ のようなパッチ管理できることが分かりました。
しかし、やはり Guilt でもブランチに依存しないパッチ管理やパッチのバージョン管理ができると使いやすかったと思います。