チェックアウトせずにマージ

dev-xxxブランチにいるときに、masterブランチに移動しないままでdev-xxxブランチをmasterにmergeできたら

http://hibari.2ch.net/test/read.cgi/tech/1284467898/810

低レベル操作によって、ワーキングディレクトリを一切無視して任意のインデクスに対してマージしてくれるコマンドが欲しい。

http://hibari.2ch.net/test/read.cgi/tech/1284467898/813

GIT_INDEX_FILE でどうにかなりそうな気がしたので実験してみたメモ。

マージする準備

最初のコミット。a.txtb.txtを追加。

$ git init
$ echo a > a.txt
$ echo b > b.txt
$ git add a.txt b.txt
$ git commit -m 'initial'
[master (root-commit) e1f7440] initial
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 a.txt
 create mode 100644 b.txt

こうなった

$ git whatchanged --oneline
e1f7440 initial
:000000 100644 0000000... 7898192... A  a.txt
:000000 100644 0000000... 6178079... A  b.txt

topicブランチを作って、進める

$ git checkout -b topic --track master
Branch topic set up to track local branch master.
Switched to a new branch 'topic'

$ echo 'BB(topic)' >> b.txt
$ git commit -a -m 'BB(topic)'
[topic ce78e31] BB(topic)
 1 files changed, 1 insertions(+), 0 deletions(-)

1つ進んだ

$ git whatchanged --oneline topic
ce78e31 BB(topic)
:100644 100644 6178079... a86f970... M  b.txt
e1f7440 initial
:000000 100644 0000000... 7898192... A  a.txt
:000000 100644 0000000... 6178079... A  b.txt

topicmasterより1つ進んでいる

$ git branch -v
  master e1f7440 initial
* topic  ce78e31 [ahead 1] BB(topic)

masterをチェックアウトせずに、masterにtopicをマージ

topicをチェックアウトした状態のまま、そしてインデックスも保持したまま、mastertopicをマージする。

  • 方針
    • masterをインデックスに読み込んでtopicをマージし、結果のコミットを作ってmasterを進める
    • Fast-Forwardはとりあえず気にしない
    • マージ後もそのまま作業を続けたいので、現在のインデックスの内容はそのまま残す
      • GIT_INDEX_FILEで別のインデックスを使う

インデックスが保全されることを確認したいので、ちょっといじっておく

$ echo 'あばばばばwwwwwwwwwwww' >> b.txt
$ git add b.txt

編集したb.txtがインデックスに登録された

$ git status
# On branch topic
# Your branch is ahead of 'master' by 1 commit.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   b.txt
#
$ git diff --cached
diff --git a/b.txt b/b.txt
index a86f970..cb6733f 100644
--- a/b.txt
+++ b/b.txt
@@ -1,2 +1,3 @@
 b
 BB(topic)
+あばばばばwwwwwwwwwwww

この状態を維持することにする。

GIT_INDEX_FILEで別のインデックスを使う

現在のインデックスの状態を表示

$ git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f 0       b.txt

b.txtのblobを見るとさっきの変更が反映されているのが分かる

$ git cat-file blob cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f
b
BB(topic)
あばばばばwwwwwwwwwwww

.index_tmpをインデックスファイルに指定して、masterの内容を読み込んでみる

$ GIT_INDEX_FILE=.index_tmp git read-tree master

.index_tmpが出来ていた

$ ls -1a
.
..
.git
.index_tmp
a.txt
b.txt

通常のインデックスは.git/indexなので、GIT_INDEX_FILE=.git/index_altとかにすれば良かったかも。

.index_tmpインデックスの状態を表示

$ GIT_INDEX_FILE=.index_tmp git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 61780798228d17af2d34fce4cfbdf35556832472 0       b.txt

b.txtのblobを見るとmasterの内容なのが分かる

$ git cat-file blob 61780798228d17af2d34fce4cfbdf35556832472
b

別々に操作出来ていることが確認できた。

インデックス上でマージ

.index_tmpのほうで、mastertopicをマージしてみる

$ GIT_INDEX_FILE=.index_tmp git read-tree -m -i master topic

マージ後の内容

$ GIT_INDEX_FILE=.index_tmp git ls-files -s
100644 78981922613b2afb6025042ff6bd878ac1994e85 0       a.txt
100644 a86f970145550ca53098e9bed4fcdd61a935b6f4 0       b.txt

b.txtの内容をそれぞれ確認してみる。

通常のインデックスのb.txt

$ git cat-file blob cb6733f4cdef9366bbbaeef9c8a5af40a3f03b5f
b
BB(topic)
あばばばばwwwwwwwwwwww

保持したい内容のままになっている

マージ後のインデックスのb.txt

$ git cat-file blob a86f970145550ca53098e9bed4fcdd61a935b6f4
b
BB(topic)

topicブランチの内容になっている。マージできたっぽい。

Low-levelコマンドでマージコミットを作る。

$ echo 'Merge! branch topic into master!' | git commit-tree `GIT_INDEX_FILE=.index_tmp git write-tree` -p master -p topic
f59b2bb25340f8677c3b2842db81c2e9a8250781

コミットオブジェクトできた

$ git log --oneline --graph f59b2bb25340f8677c3b2842db81c2e9a8250781
*   f59b2bb Merge! branch topic into master!
|\
| * ce78e31 BB(topic)
|/
* e1f7440 initial

Low-levelコマンドでmasterを更新

$ git update-ref refs/heads/master f59b2bb25340f8677c3b2842db81c2e9a8250781

できたー

$ git branch -v
  master f59b2bb Merge! branch topic into master!
* topic  ce78e31 [behind 1] BB(topic)

通常のインデックスはそのまま変化なし

$ git status
# On branch topic
# Your branch is behind 'master' by 1 commit, and can be fast-forwarded.
#
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#       modified:   b.txt
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#       .index_tmp
$ git diff --cached
diff --git a/b.txt b/b.txt
index a86f970..cb6733f 100644
--- a/b.txt
+++ b/b.txt
@@ -1,2 +1,3 @@
 b
 BB(topic)
+あばばばばwwwwwwwwwwww

問題点

ファイル単位で更新がぶつかるとアウト

git merge-baseで3-Wayにしたりとか、Low-levelコマンドをくししてがんばればどうにかなるけど…

もっと本格的にコンフリクトするとアウト

人間が関与しないとダメなやつは無理ですねこれ。

ツール化したらどうなるか

  • コンフリクトしない自信がある場合は良いかも知れない
  • コンフリクトしたらしょうがなくチェックアウトしてマージするようにする方向なら良いかもしれない

個人的にはマージする時はstashしてチェックアウトすればいいかなーと思う。。。

Gitにパッチを送るメモ

コードを書く

起点をどこにするか

バグ修正の場合
maint(無い場合はmaster)
新機能の場合
master(puに依存する場合はpu)

コミットを作る

Documentation/SubmittingPatches を読む。

  • パッチは論理的な単位に分ける。
  • git diff --check で無駄な空白が無いかチェック。
  • コメントアウトしたコードや不要なファイルが無いかチェック。
  • コミットメッセージ
    • 最初の一行は、50文字程度の短い要約にする。最後にピリオドは付けない。
    • 二行目は、空白行にする。
    • 三行目以降に、コミットメッセージの本文を書く。
    • 命令形、現在時制で書く。
    • コミット以前と以後で何が変わるのかを、明確に。
    • コードを書いた理由、何がしたかったのか、目的を明確に。
    • 最後の行に、サインオフ(原作者の証明書に同意する署名)を書く。実名のみ。
Signed-off-by: Your Name <you@example.com>
  • コミット後
    • バグを修正した場合、それが本当に直っているか、チェック
    • テストツールがパスするか、チェック

テストツール

t/READMEを読む。

  • makeすると全テストを実行(けっこう時間かかる)
  • 最新版のproveなら並列実行できるらしい
prove --timer --jobs 15 ./t[0-9]*.sh
  • 個別にテストするには、シェルで実行する
sh ./t3010-ls-files-killed-modified.sh

全て(?)のテストスクリプトでは test-lib.sh がsourceされている。これにより"t/trash directory.テスト名"というようなディレクトリが作られて、そこに cd して git init されるので、各テストスクリプトはその砂場でテストを実行する。この砂場はテスト成功時には削除されるが、スクリプトデバッグオプション(--debug、-d)を付けると消されずに残る。

パッチを送る

電子メールの形式でパッチを作る

現在のブランチにあるコミットのうち、origin/masterに存在しないものを指定ディレクトリに出力

mkdir outgoing
git format-patch -M origin/master -o outgoing

outgoingディレクトリにそれぞれのコミットがパッチとして出力されるので、確認し、必要があれば編集する。
出力されたファイルは電子メールの形式で、Subjectはコミットメッセージの一行目、bodyは以降のコミットメッセージになっている。
コミットメッセージの最後の行(Signed-off-byのはず)の後には、三本ダッシュだけの行があり、以降にdiffstat、続いてパッチ本体がある。
コミットメッセージには含めないけどメールには書きたい内容は、この三本ダッシュ以降パッチ本体以前に書く。diffstatの手前など。

メーリングリストにパッチを送る

git-send-emailを使う。

gmailSMTPから送る方法でやってみる
~/.gitconfig か .git/configに以下を追加

[sendemail]
    from = Your Name <you@example.com>
    smtpencryption = tls
    smtpserver = smtp.gmail.com
    smtpuser = you@gmail.com
    smtpserverport = 587

まずは自分に送ってテスト

git send-email outgoing/0001-hoge.patch

送り先、In-Reply-To(誰かに返信する場合は入れたほうが良い)、SMTPのパスワード等を聞かれるので入力する。

自分宛に届いたら、そのメールをmbox形式でファイルに保存して、実際にパッチを当ててみる。

git checkout origin/master
git am hoge.mbox

この結果は、git format-patchしたブランチの内容と同じになるはず

git diff HEAD..master

大丈夫そうなら、メーリングリストとメンテナ(あるいは自分の変更点を知るべき人が分かるならその人)宛に送る。

気をつけること

パッチを添付しない

パッチについてメーリングリストでディスカッションする際に、引用できなくなるので。

英語を頑張る orz

「以前はこう、そしてパッチ後はこう。〜がしたくて、やった」という説明を明確に書かないと、レビュアを混乱させる結果になる。
英語苦手でも、そこはちゃんと書くことorz

入門Git

今回、やはり入門Gitがとても参考になった。

入門Git

入門Git

版管理とは何か、ということ(新しいコミットを作るというのはどういう意味を持つ行為なのか、など)から、オープンソースのプロジェクトにおける役割分担、分散型システムでのコミットアクセス(コミット権限)がCVSSubversionとどう違うのかなど、ワークフローの部分、そして特にすごいのが「Chapter 10.9 オープンな開発プロセス」で語られる、メーリングリスト上での議論の進め方。大半がネイティブスピーカーではない中で、どう議論を組み立てていくべきか、どうやったら無用な意地の張り合いを避けてプロジェクトを発展させられるのか、パッチレビューで反対意見を書く際に誤解されないための文章の組み立て方まで、ものすごいノウハウと、思想が詰まっている。

そうした互いの知識と経験を活かして、プロジェクト全体をより良くするための共同作業をしているのだ、という気持ちから始めれば、「譲れるところ」と「譲れないところ」という対立が出てくる余地はありません。あるのは、「こうするとプロジェクト全体に良い」か、「これだけではダメで別のやり方をしないとプロジェクト全体には役に立たない」かの違いだけです。

ー濱野 純 『入門Git』 秀和システム、 p.179

GIT - The information manager from hell

Gitのソースコードを見てたら、どうもそんなことが書いてあるwww
面白いなと思っていろいろ探してるうちに、初期コミットに行き着いた。

commit e83c5163316f89bfbde7d9ab23ca2e25604af290
Author: Linus Torvalds
Date:   Thu Apr 7 15:13:13 2005 -0700

    Initial revision of "git", the information manager from hell

なんともこうLinusさんは、面白いなぁ。

マージ後のreset HEAD^は危険だった

直前のマージを取り消す場合は、

× git reset --hard HEAD^

ではなく、

○ git reset --hard ORIG_HEAD

としないと危ない、という話。

「マージ後にgit reset --hard HEAD^で取り消し」は去年の日記でもけっこう使ってるけど、たまたま上手くいっていたからよかったが、ORIG_HEADが正しい指定方法だった。場合によってはちょっと危ない。

続きを読む

普通のキーボードとHHKを同時に使う

なぜそんなことをするのかというと。卓上の据置型キーボードはきちんと座る用、HHK Proは膝上とか(腹上とか)に置いてダラっとした姿勢用。

両方とも同じようなレイアウトなら普通にいける(Ubuntu10.04)んだけど、HHKはA横がCtrl、もう1台のキーボードはA横がCapsLock…。単純にctrl:swapcapsだとHHKのA横がCapsLockになってしまう…。

卓上キーボードはCtrlとCapsLockを入れ替え、HHKはそのままにしたい。

続きを読む

git rebaseのメモ

ときどき間違うので。

大雑把に言うと、git rebase は「git reset + git cherry-pick × n回 を自動化したもの」と考えられる(適用するコミット群が少なければ、手動でreset & cherry-pickしても良いが、たくさんあるとそうもいかない)
好きな場所にresetして、好きな位置から好きな位置までのコミットを順次適用できる。
つまりコミットを並べ替えたり除外したり、「積み木を積み直す」ようなことが出来る。

続きを読む