-
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 サードパーティのホスティングオプション
- 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 置換
- 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 Visual Studio CodeでのGit
- A1.4 IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMineでのGit
- A1.5 Sublime TextでのGit
- A1.6 BashでのGit
- A1.7 ZshでのGit
- A1.8 PowerShellでのGit
- A1.9 まとめ
-
A2. 付録B: アプリケーションにGitを組み込む
- A2.1 コマンドラインGit
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
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では、あるブランチから別のブランチに変更を統合する主な方法は2つあります。それはmerge
とrebase
です。このセクションでは、リベースが何であるか、どのように行うか、なぜそれが非常に優れたツールであるか、そしてどのような場合にそれを使用すべきではないかを学びます。
基本的なリベース
基本的なマージの以前の例に戻ると、作業が分岐し、2つの異なるブランチでコミットを行ったことがわかります。

ブランチを統合する最も簡単な方法は、これまで説明してきたようにmerge
コマンドです。これは、2つの最新ブランチスナップショット(C3
とC4
)と、それら2つの最新の共通祖先(C2
)の間で3方向マージを実行し、新しいスナップショット(およびコミット)を作成します。

しかし、別の方法があります。それは、C4
で導入された変更のパッチを取得し、それをC3
の上に再適用することです。Gitでは、これをリベースと呼びます。rebase
コマンドを使用すると、あるブランチでコミットされたすべての変更を別のブランチに再適用できます。
この例では、experiment
ブランチをチェックアウトし、次のようにmaster
ブランチにリベースします。
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
この操作は、2つのブランチ(現在のブランチとリベース先のブランチ)の共通祖先に移動し、現在のブランチの各コミットによって導入された差分を取得し、それらの差分を一時ファイルに保存し、現在のブランチをリベース先のブランチと同じコミットにリセットし、最後に各変更を順番に適用することで機能します。

C4
で導入された変更をC3
にリベースするこの時点で、master
ブランチに戻り、ファストフォワードマージを実行できます。
$ git checkout master
$ git merge experiment

master
ブランチのファストフォワードこれで、C4'
が指すスナップショットは、マージの例でC5
が指していたものとまったく同じになります。統合の最終的な結果に違いはありませんが、リベースはよりきれいな履歴を作成します。リベースされたブランチのログを調べると、直線的な履歴のように見えます。つまり、すべての作業が直列に発生したように見えますが、実際には並列に発生していました。
多くの場合、コミットがリモートブランチにきれいに適用されるようにするためにこれを行います。おそらく、あなたが貢献しようとしているが、あなたがメンテナンスしているわけではないプロジェクトでです。この場合、ブランチで作業を行い、メインプロジェクトにパッチを提出する準備ができたら、その作業をorigin/master
にリベースします。そうすることで、メンテナーは統合作業を行う必要がなく、ファストフォワードまたはクリーンな適用だけで済みます。
リベース後の最後のコミット、またはマージ後の最終マージコミットが指すスナップショットは同じであることに注意してください。異なるのは履歴だけです。リベースは、ある作業ラインの変更を導入された順序で別の作業ラインに再生するのに対し、マージは終点を取り、それらを結合します。
さらに興味深いリベース
リベースのターゲットブランチ以外のものにリベースを再適用することもできます。例えば、別のトピックブランチから分岐したトピックブランチを持つ履歴のような履歴を見てみましょう。プロジェクトにサーバー側の機能を追加するためにトピックブランチ(server
)を分岐させ、コミットを行いました。次に、そこから分岐してクライアント側の変更(client
)を行い、数回コミットしました。最後に、server
ブランチに戻り、さらにいくつかのコミットを行いました。

リリースに向けてクライアント側の変更をメインラインにマージしたいが、サーバー側の変更はさらにテストするまで待機したいとします。server
にないclient
上の変更(C8
とC9
)を取得し、git rebase
の--onto
オプションを使用してmaster
ブランチにそれらを再適用できます。
$ git rebase --onto master server client
これは基本的に、「client
ブランチを取得し、それがserver
ブランチから分岐してからのパッチを特定し、それらのパッチをclient
ブランチに、まるでmaster
ブランチから直接分岐したかのように再適用する」という意味です。少し複雑ですが、結果は非常に素晴らしいものです。

これで、master
ブランチをファストフォワードできます(client
ブランチの変更を含むようにmaster
ブランチをファストフォワードするを参照)。
$ git checkout master
$ git merge client

client
ブランチの変更を含むようにmaster
ブランチをファストフォワードするserver
ブランチも取り込むことにしたとしましょう。git rebase <basebranch> <topicbranch>
を実行することで、最初にチェックアウトすることなく、server
ブランチをmaster
ブランチにリベースできます。これは、トピックブランチ(この場合はserver
)をチェックアウトし、それをベースブランチ(master
)に再適用します。
$ git rebase master server
これは、master
ブランチの上にserver
ブランチをリベースするに示されているように、master
での作業の上にserver
での作業を再適用します。

master
ブランチの上にserver
ブランチをリベースする次に、ベースブランチ(master
)をファストフォワードできます。
$ 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
を実行すると、同じ作者、日付、メッセージを持つ2つのコミットが表示され、混乱を招くでしょう。さらに、この履歴をサーバーにプッシュし直すと、リベースされたコミットがすべて中央サーバーに再導入され、人々をさらに混乱させる可能性があります。他の開発者がC4
とC6
を履歴に入れたくないと考えていると仮定するのはかなり安全です。彼らが最初にリベースした理由もそこにあります。
リベースされた際にリベースする
もしこのような状況に陥った場合、Gitにはあなたを助けるかもしれないさらなる魔法があります。チームの誰かがあなたが作業の基盤にした作業を上書きする変更を強制プッシュした場合、あなたの課題は、何があなたのもので、何が彼らによって書き換えられたかを把握することです。
コミットのSHA-1チェックサムに加えて、Gitはコミットによって導入されたパッチのみに基づいたチェックサムも計算します。これは「パッチID」と呼ばれます。
書き換えられた作業をプルダウンし、パートナーからの新しいコミットの上にリベースした場合、Gitは独自のものを見つけ出し、新しいブランチの上にそれらを再度適用することがよくできます。
例えば、前のシナリオで、誰かがリベースされたコミットをプッシュし、あなたが作業の基盤にしたコミットを破棄するの状態にあるときにマージを行う代わりにgit rebase teamone/master
を実行すると、Gitは以下を行います。
-
自分のブランチに固有の作業(
C2
、C3
、C4
、C6
、C7
)を特定する -
マージコミットではないもの(
C2
、C3
、C4
)を特定する -
ターゲットブランチに書き換えられていないもの(
C4
がC4'
と同じパッチであるため、C2
とC3
のみ)を特定する -
それらのコミットを
teamone/master
の先頭に適用する
したがって、同じ作業を新しいマージコミットに再度マージするで見る結果の代わりに、強制プッシュされたリベース作業の上にリベースするのような結果になります。

これは、パートナーが作成したC4
とC4'
がほぼ完全に同じパッチである場合にのみ機能します。そうでなければ、リベースはそれが重複であることを認識できず、別のC4
のようなパッチを追加することになります(変更がすでに多少なりとも存在するため、クリーンに適用できない可能性が高いです)。
これは、通常のgit pull
の代わりにgit pull --rebase
を実行することでも簡略化できます。または、この場合、git fetch
に続けてgit rebase teamone/master
を手動で行うことも可能です。
git pull
を使用しており、--rebase
をデフォルトにしたい場合は、git config --global pull.rebase true
のようなコマンドでpull.rebase
設定値を設定できます。
自分のコンピュータから一度も出ていないコミットだけをリベースするなら、問題ありません。プッシュされたが、他の誰もコミットの基盤にしていないコミットをリベースするなら、それも問題ありません。しかし、すでに公開されてプッシュされ、他の人がそのコミットに基づいて作業を行った可能性があるコミットをリベースすると、フラストレーションのたまる問題とチームメイトからの軽蔑に直面するかもしれません。
もしあなたやパートナーがいつかそれが必要だと感じた場合、事後の痛みを少しでも和らげるために、全員がgit pull --rebase
を実行することを知っていることを確認してください。
リベース vs. マージ
リベースとマージがどのように機能するかを見た今、どちらが良いのか疑問に思うかもしれません。これに答える前に、少し立ち戻って履歴が何を意味するのかについて話しましょう。
これに関する一つの見方は、リポジトリのコミット履歴は実際に何が起こったかの記録であるというものです。それはそれ自体で価値のある歴史的文書であり、改ざんされるべきではありません。この観点から見ると、コミット履歴を変更することはほとんど冒涜的です。あなたは実際に起こったことについて嘘をついていることになります。だから、マージコミットの乱雑な一連があったとしてもどうだというのですか?それが起こったことであり、リポジトリはそれを後世のために保存すべきです。
対立する見方は、コミット履歴はプロジェクトがどのように作成されたかの物語であるというものです。本の初稿を出版しないでしょうから、なぜ乱雑な作業を見せる必要があるのでしょうか?プロジェクトに取り組んでいるとき、すべての誤りや行き詰まった道の記録が必要かもしれませんが、自分の作業を世界に示すときには、AからBへのより一貫した物語を伝えたいと思うかもしれません。この派の人々は、メインラインブランチにマージされる前に、rebase
やfilter-branch
のようなツールを使用してコミットを書き換えます。彼らはrebase
やfilter-branch
のようなツールを使用して、将来の読者にとって最善の方法で物語を伝えます。
さて、マージとリベースのどちらが良いかという問題ですが、それがそれほど単純ではないことがお分かりいただけたでしょう。Gitは強力なツールであり、履歴に対して多くのことを行うことができますが、すべてのチーム、すべてのプロジェクトは異なります。これら両方がどのように機能するかを知った今、あなたの特定の状況にどちらが最適かを決定するのはあなた次第です。
両方の良いとこ取りができます。プッシュする前にローカルの変更をリベースして作業をきれいにしますが、すでにどこかにプッシュしたものは決してリベースしないでください。