-
1. 使い始める
- 1.1 バージョン管理に関して
- 1.2 Git略史
- 1.3 Gitの基本
- 1.4 コマンドライン
- 1.5 Gitのインストール
- 1.6 最初のGitの構成
- 1.7 ヘルプを見る
- 1.8 まとめ
-
2. Git の基本
- 2.1 Git リポジトリの取得
- 2.2 変更内容のリポジトリへの記録
- 2.3 コミット履歴の閲覧
- 2.4 作業のやり直し
- 2.5 リモートでの作業
- 2.6 タグ
- 2.7 Git エイリアス
- 2.8 まとめ
-
3. Git のブランチ機能
- 3.1 ブランチとは
- 3.2 ブランチとマージの基本
- 3.3 ブランチの管理
- 3.4 ブランチでの作業の流れ
- 3.5 リモートブランチ
- 3.6 リベース
- 3.7 まとめ
-
4. Gitサーバー
- 4.1 プロトコル
- 4.2 サーバー用の Git の取得
- 4.3 SSH 公開鍵の作成
- 4.4 サーバーのセットアップ
- 4.5 Git デーモン
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 サードパーティによる Git ホスティング
- 4.10 まとめ
-
5. Git での分散作業
- 5.1 分散作業の流れ
- 5.2 プロジェクトへの貢献
- 5.3 プロジェクトの運営
- 5.4 まとめ
-
6. GitHub
- 6.1 アカウントの準備と設定
- 6.2 プロジェクトへの貢献
- 6.3 プロジェクトのメンテナンス
- 6.4 組織の管理
- 6.5 スクリプトによる GitHub の操作
- 6.6 まとめ
-
7. Git のさまざまなツール
- 7.1 リビジョンの選択
- 7.2 対話的なステージング
- 7.3 作業の隠しかたと消しかた
- 7.4 作業内容への署名
- 7.5 検索
- 7.6 歴史の書き換え
- 7.7 リセットコマンド詳説
- 7.8 高度なマージ手法
- 7.9 Rerere
- 7.10 Git によるデバッグ
- 7.11 サブモジュール
- 7.12 バンドルファイルの作成
- 7.13 Git オブジェクトの置き換え
- 7.14 認証情報の保存
- 7.15 まとめ
-
8. Git のカスタマイズ
- 8.1 Git の設定
- 8.2 Git の属性
- 8.3 Git フック
- 8.4 Git ポリシーの実施例
- 8.5 まとめ
-
9. Gitとその他のシステムの連携
- 9.1 Git をクライアントとして使用する
- 9.2 Git へ移行する
- 9.3 まとめ
-
10. Gitの内側
- 10.1 配管(Plumbing)と磁器(Porcelain)
- 10.2 Gitオブジェクト
- 10.3 Gitの参照
- 10.4 Packfile
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータリカバリ
- 10.8 環境変数
- 10.9 まとめ
-
A1. 付録 A: その他の環境でのGit
- A1.1 グラフィカルインタフェース
- A1.2 Visual StudioでGitを使う
- A1.3 EclipseでGitを使う
- A1.4 BashでGitを使う
- A1.5 ZshでGitを使う
- A1.6 PowershellでGitを使う
- A1.7 まとめ
-
A2. 付録 B: Gitをあなたのアプリケーションに組み込む
- A2.1 Gitのコマンドラインツールを使う方法
- A2.2 Libgit2を使う方法
- A2.3 JGit
-
A3. 付録 C: Gitのコマンド
- A3.1 セットアップと設定
- A3.2 プロジェクトの取得と作成
- A3.3 基本的なスナップショット
- A3.4 ブランチとマージ
- A3.5 プロジェクトの共有とアップデート
- A3.6 検査と比較
- A3.7 デバッグ
- A3.8 パッチの適用
- A3.9 メール
- A3.10 外部システム
- A3.11 システム管理
- A3.12 配管コマンド
3.6 Git のブランチ機能 - リベース
リベース
Git には、あるブランチの変更を別のブランチに統合するための方法が大きく分けて二つあります。
merge
と rebase
です。
このセクションでは、リベースについて「どういう意味か」「どのように行うのか」「なぜそんなにもすばらしいのか」「どんなときに使うのか」を説明します。
リベースの基本
マージについての説明で使用した例を マージの基本 から振り返ってみましょう。 作業が二つに分岐しており、それぞれのブランチに対してコミットされていることがわかります。
このブランチを統合する最も簡単な方法は、先に説明したように merge
コマンドを使うことです。
これは、二つのブランチの最新のスナップショット (C3
と C4
) とそれらの共通の祖先 (C2
) による三方向のマージを行い、新しいスナップショットを作成 (そしてコミット) します。
しかし、別の方法もあります。
C3
で行った変更のパッチを取得し、それを C4
の先端に適用するのです。
Git では、この作業のことを リベース (rebasing) と呼んでいます。
rebase
コマンドを使用すると、一方のブランチにコミットされたすべての変更をもう一方のブランチで再現することができます。
今回の例では、次のように実行します。
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
これは、まずふたつのブランチ (現在いるブランチとリベース先のブランチ) の共通の先祖に移動し、現在のブランチ上の各コミットの diff を取得して一時ファイルに保存し、現在のブランチの指す先をリベース先のブランチと同じコミットに移動させ、そして先ほどの変更を順に適用していきます。
C4
の変更を C3
にリベースこの時点で、 master
ブランチに戻って fast-forward マージができるようになりました。
$ git checkout master
$ git merge experiment
これで、C4'
が指しているスナップショットの内容は、先ほどのマージの例で C5
が指すスナップショットと全く同じものになりました。
最終的な統合結果には差がありませんが、リベースのほうがよりすっきりした歴史になります。
リベース後のブランチのログを見ると、まるで一直線の歴史のように見えます。
元々平行稼働していたにもかかわらず、それが一連の作業として見えるようになるのです。
リモートブランチ上での自分のコミットをすっきりさせるために、よくこの作業を行います。
たとえば、自分がメンテナンスしているのではないプロジェクトに対して貢献したいと考えている場合などです。
この場合、あるブランチ上で自分の作業を行い、プロジェクトに対してパッチを送る準備ができたらそれを origin/master
にリベースすることになります。
そうすれば、メンテナは特に統合作業をしなくても単に fast-forward するだけで済ませられるのです。
あなたが最後に行ったコミットが指すスナップショットは、リベースした結果の最後のコミットであってもマージ後の最終のコミットであっても同じものとなることに注意しましょう。 違ってくるのは、そこに至る歴史だけです。 リベースは、一方のラインの作業内容をもう一方のラインに順に適用しますが、マージの場合はそれぞれの最終地点を統合します。
さらに興味深いリベース
リベース先のブランチ以外でもそのリベースを再現することができます。
たとえば トピックブランチからさらにトピックブランチを作成した歴史 のような歴史を考えてみましょう。
トピックブランチ (server
) を作成してサーバー側の機能をプロジェクトに追加し、それをコミットしました。
その後、そこからさらにクライアント側の変更用のブランチ (client
) を切って数回コミットしました。
最後に、server ブランチに戻ってさらに何度かコミットを行いました。
クライアント側の変更を本流にマージしてリリースしたいけれど、サーバー側の変更はまだそのままテストを続けたいという状況になったとします。
クライアント側の変更のうちサーバー側にはないもの (C8
と C9
) を master
ブランチで再現するには、git rebase
の --onto
オプションを使用します。
$ git rebase --onto master server client
これは「client ブランチに移動して client
ブランチと server
ブランチの共通の先祖からのパッチを取得し、master
上でそれを適用しろ」という意味になります。
ちょっと複雑ですが、その結果は非常にクールです。
これで、master
ブランチを fast-forward することができるようになりました (master ブランチを fast-forward し、client ブランチの変更を含める を参照ください)。
$ git checkout master
$ git merge client
さて、いよいよ server ブランチのほうも取り込む準備ができました。
server ブランチの内容を master
ブランチにリベースする際には、事前にチェックアウトする必要はなく git rebase [basebranch] [topicbranch]
を実行するだけでだいじょうぶです。
このコマンドは、トピックブランチ (ここでは server
) をチェックアウトしてその変更をベースブランチ (master
) 上に再現します。
$ git rebase master server
これは、server
での作業を master
の作業に続け、結果は server ブランチを master ブランチ上にリベースする のようになります。
これで、ベースブランチ (master
) を fast-forward することができます。
$ git checkout master
$ git merge server
ここで client
ブランチと server
ブランチを削除します。
すべての作業が取り込まれたので、これらのブランチはもはや不要だからです。
これらの処理を済ませた結果、最終的な歴史は 最終的なコミット履歴 のようになりました。
$ git branch -d client
$ git branch -d server
ほんとうは怖いリベース
あぁ、このすばらしいリベース機能。しかし、残念ながら欠点もあります。その欠点はほんの一行でまとめることができます。
公開リポジトリにプッシュしたコミットをリベースしてはいけない
この指針に従っている限り、すべてはうまく進みます。 もしこれを守らなければ、あなたは嫌われ者となり、友人や家族からも軽蔑されることになるでしょう。
リベースをすると、既存のコミットを破棄して新たなコミットを作成することになります。
新たに作成したコミットは破棄したものと似てはいますが別物です。
あなたがどこかにプッシュしたコミットを誰かが取得してその上で作業を始めたとしましょう。
あなたが git rebase
でそのコミットを書き換えて再度プッシュすると、相手は再びマージすることになります。
そして相手側の作業を自分の環境にプルしようとするとおかしなことになってしまいます。
いったん公開した作業をリベースするとどんな問題が発生するのか、例を見てみましょう。 中央サーバーからクローンした環境上で何らかの作業を進めたものとします。 現在のコミット履歴はこのようになっています。
さて、誰か他の人が、マージを含む作業をしてそれを中央サーバーにプッシュしました。 それを取得し、リモートブランチの内容を作業環境にマージすると、その歴史はこのような状態になります。
次に、さきほどマージした作業をプッシュした人が、気が変わったらしく新たにリベースし直したようです。
なんと git push --force
を使ってサーバー上の歴史を上書きしてしまいました。
あなたはもう一度サーバーにアクセスし、新しいコミットを手元に取得します。
さあたいへん。
ここであなたが git pull
を実行すると、両方の歴史の流れを含むマージコミットができあがり、あなたのリポジトリはこのようになります。
歴史がこんな状態になっているときに git log
を実行すると、同じ作者による同じメッセージのコミットが二重に表示されてしまいます。
さらに、あなたがその歴史をサーバにプッシュすると、リベースされたコミット群を中央サーバーに送り込むことになり、他の人たちをさらに混乱させてしまいます。
他の開発者たちは、C4
や C6
を歴史に取り込みたくないはずです。だからこそ、最初にリベースしたのでしょうからね。
リベースした場合のリベース
もしそんな状況になってしまった場合でも、Git がうまい具合に判断して助けてくれることがあります。 チームの誰かがプッシュした変更が、あなたの作業元のコミットを変更してしまった場合、どれがあなたのコミットでどれが書き換えられたコミットなのかを判断するのは大変です。
Git は、コミットの SHA-1 チェックサム以外にもうひとつのチェックサムを計算しています。これは、そのコミットで投入されたパッチから計算したものです。 これを「パッチ ID」と呼びます。
書き換えられたコミットをプルして、他のメンバーのコミットの後に新たなコミットをリベースしようとしたときに、 Git は多くの場合、どれがあなたのコミットかを自動的に判断し、そのコミットを新しいブランチの先端に適用してくれます。
たとえば先ほどの例で考えてみます。誰かがリベースしたコミットをプッシュし、あなたの作業環境の元になっているコミットが破棄された の場面で、マージする代わりに git rebase teamone/master
を実行すると、Git は次のように動きます。
-
私たちのブランチにしかない作業を特定する (C2, C3, C4, C6, C7)
-
その中から、マージコミットではないものを探す (C2, C3, C4)
-
その中から、対象のブランチにまだ書き込まれていないものを探す (C4 は C4' と同じパッチなので、ここでは C2 と C3 だけになる)
-
そのコミットを
teamone/master
の先端に適用する
その結果は 同じ作業を再びマージして新たなマージコミットを作成する の場合とは異なり、リベース後、強制的にプッシュした作業へのリベース のようになります。
これがうまくいくのは、あなたの C4 と他のメンバーの C4' がほぼ同じ内容のパッチである場合だけです。 そうでないと、これらが重複であることを見抜けません (そして、おそらくパッチの適用に失敗するでしょう。その変更は、少なくとも誰かが行っているだろうからです)。
この操作をシンプルに行うために、通常の git pull
ではなく git pull --rebase
を実行してもかまいません。
あるいは手動で行う場合は、git fetch
に続けて、たとえば今回の場合なら git rebase teamone/master
を実行します。
git pull
を行うときにデフォルトで --rebase
を指定したい場合は、
設定項目 pull.rebase
を指定します。たとえば git config --global pull.rebase true
などとすれば、指定できます。
プッシュする前の作業をきれいに整理する手段としてだけリベースを使い、まだ公開していないコミットだけをリベースすることを心がけていれば、何も問題はありません。 すでにプッシュした後で、他の人がその後の作業を続けている可能性のあるコミットをリベースした場合は、やっかいな問題を引き起こす可能性があります。 チームメイトに軽蔑されてしまうかもしれません。
どこかの時点でどうしてもそうせざるを得ないことになったら、みんなに git pull --rebase
を使わせるように気をつけましょう。
そうすれば、その後の苦しみをいくらか和らげることができます。
リベースかマージか
リベースとマージの実例を見てきました。さて、どちらを使えばいいのか気になるところです。 その答えをお知らせする前に、「歴史」とはいったい何だったのかを振り返ってみましょう。
あなたのリポジトリにおけるコミットの歴史は、実際に発生したできごとの記録 だと見ることもできます。 これは歴史文書であり、それ自体に意味がある。従って、改ざんなど許されないという観点です。 この観点に沿って考えると、コミットの歴史を変更することなどあり得ないでしょう。 実際に起こってしまったことには、ただ黙って 従う べきです。 マージコミットのせいで乱雑になってしまったら? 実際そうなってしまったのだからしょうがない。 その記録は、後世の人々に向けてそのまま残しておくべきでしょう。
別の見方もあります。コミットの歴史は、そのプロジェクトがどのように作られてきたのかを表す物語である という考えかたです。 最初の草稿の段階で本を出版したりはしないでしょう。また、自作ソフトウェア用の管理マニュアルであれば、しっかり推敲する必要があります。 この立場に立つと、リベースやブランチフィルタリングを使って、将来の読者にとってわかりやすいように、物語を再編しようという考えに至ります。
さて、元の問いに戻ります。 マージとリベースではどちらがいいのか。 お察しのとおり、単純にどちらがよいとは言い切れません。 Git は強力なツールで、歴史に対していろんな操作をすることができます。しかし、チームやプロジェクトによって、事情はそれぞれ異なります。 あなたは既に、両者の特徴を理解しています。あなたが今いる状況ではどちらがより適切なのか、それを判断するのはあなたです。
一般論として、両者のいいとこどりをしたければ、まだプッシュしていないローカルの変更だけをリベースするようにして、 歴史をきれいに保っておきましょう。プッシュ済みの変更は決してリベースしないようにすれば、問題はおきません。