チェックアウトせずにマージ
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.txt
とb.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
topic
はmaster
より1つ進んでいる
$ git branch -v master e1f7440 initial * topic ce78e31 [ahead 1] BB(topic)
masterをチェックアウトせずに、masterにtopicをマージ
topic
をチェックアウトした状態のまま、そしてインデックスも保持したまま、master
にtopic
をマージする。
- 方針
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
のほうで、master
とtopic
をマージしてみる
$ 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を使う。
gmailのSMTPから送る方法でやってみる
~/.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がとても参考になった。
- 作者: 濱野純(Junio C Hamano)
- 出版社/メーカー: 秀和システム
- 発売日: 2009/09/24
- メディア: 単行本
- 購入: 31人 クリック: 736回
- この商品を含むブログ (155件) を見る
そうした互いの知識と経験を活かして、プロジェクト全体をより良くするための共同作業をしているのだ、という気持ちから始めれば、「譲れるところ」と「譲れないところ」という対立が出てくる余地はありません。あるのは、「こうするとプロジェクト全体に良い」か、「これだけではダメで別のやり方をしないとプロジェクト全体には役に立たない」かの違いだけです。
ー濱野 純 『入門Git』 秀和システム、 p.179
マージ後のreset HEAD^は危険だった
直前のマージを取り消す場合は、
× git reset --hard HEAD^
ではなく、
○ git reset --hard ORIG_HEAD
としないと危ない、という話。
「マージ後にgit reset --hard HEAD^で取り消し」は去年の日記でもけっこう使ってるけど、たまたま上手くいっていたからよかったが、ORIG_HEADが正しい指定方法だった。場合によってはちょっと危ない。
続きを読むgit rebaseのメモ
ときどき間違うので。
大雑把に言うと、git rebase は「git reset + git cherry-pick × n回 を自動化したもの」と考えられる(適用するコミット群が少なければ、手動でreset & cherry-pickしても良いが、たくさんあるとそうもいかない)
好きな場所にresetして、好きな位置から好きな位置までのコミットを順次適用できる。
つまりコミットを並べ替えたり除外したり、「積み木を積み直す」ようなことが出来る。