Hatena::ブログ(Diary)

unpushの日記

2011-05-22

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

| 14:15

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はとりあえず気にしない
    • マージ後もそのまま作業を続けたいので、現在のインデックスの内容はそのまま残す

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

$ 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.txtblobを見るとさっきの変更が反映されているのが分かる

$ 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.txtblobを見ると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してチェックアウトすればいいかなーと思う。。。

2011-02-16

Gitにパッチを送るメモ

| 00:22

コードを書く

起点をどこにするか

バグ修正の場合
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

2011-01-25

GIT - The information manager from hell

| 12:51

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さんは、面白いなぁ。

2011-01-17

2011-01-07

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

| 23:50

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

× git reset --hard HEAD^

ではなく、

○ git reset --hard ORIG_HEAD

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

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

続きを読む