LoginSignup
8
4

More than 5 years have passed since last update.

Git guilt によるパッチ管理

Last updated at Posted at 2017-12-22

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 にインストールしてみました。

  1. リポジトリからクローン

    % 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.
    
  2. インストール

    % 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/
    
  3. 確認

    % 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 remoteguilt が出ないことから、 (ローカルに) guilt/master という名前のブランチがあることが分かります (origin/master はリモート originmaster ブランチです)。

% 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 appliedguilt 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 appliedguilt 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 qguilt に置換するだけです。

ブランチ毎のパッチ管理

前述の通り、Guilt ではブランチ毎にパッチが管理されてます。

MQ ではブランチの区別なくパッチ管理を管理するため、別のブランチへのパッチ適用が簡単です (そもそも Git と Mercurial ではブランチの考え方が少し異なりますが)。

パッチのバージョン管理

Guilt は MQ と違ってパッチそのものをバージョン管理することはできないようです。

MQ では、hg init --mq コマンドでパッチそのものをバージョン管理することができますが、それに相当する Guilt のコマンドは見つけられませんでした。

まとめ

Git でも MQ のようなパッチ管理できることが分かりました。

しかし、やはり Guilt でもブランチに依存しないパッチ管理やパッチのバージョン管理ができると使いやすかったと思います。

8
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4