-
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 スマート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 内部コマンドと外部コマンド
- 10.2 Gitオブジェクト
- 10.3 Git参照
- 10.4 パックファイル
- 10.5 Refspec
- 10.6 転送プロトコル
- 10.7 メンテナンスとデータ復旧
- 10.8 環境変数
- 10.9 まとめ
-
付録A. 付録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 まとめ
-
付録B. 付録B:アプリケーションへのGitの埋め込み
- A2.1 コマンドラインGit
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
付録C. 付録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つのブランチ(現在のブランチとリベース先のブランチ)の共通祖先へ移動し、現在のブランチの各コミットによって導入された差分を取得し、それらの差分を一時ファイルに保存し、現在のブランチをリベース先のブランチと同じコミットにリセットし、最後に各変更を順番に適用することによって機能します。

この時点で、`master`ブランチに戻って高速フォワードマージを実行できます。
$ git checkout master
$ git merge experiment

これで、最終的なコミットが指すスナップショット(リベースの場合のリベースされたコミットの最後、またはマージ後の最終的なマージコミット)は、マージの例の`C5`が指していたものと全く同じです。統合の最終的な成果物には違いはありませんが、リベースの方が履歴がクリーンになります。リベースされたブランチのログを調べると、線形履歴のように見えます。元々は並行して行われたものであっても、すべての作業が連続して行われたように見えます。
多くの場合、コミットがリモートブランチにクリーンに適用されるようにするためにこれを行います。これは、貢献しようとしているが、維持管理していないプロジェクトの場合です。この場合、ブランチで作業を行い、メインプロジェクトにパッチを送信する準備ができたら、作業を`origin/master`にリベースします。このようにすると、メンテナは統合作業を行う必要がなくなり、高速フォワードまたはクリーンな適用だけで済みます。
リベースまたはマージのどちらの結果でも、最終的なコミットが指すスナップショットは同じであることに注意してください。異なるのは履歴だけです。リベースは、ある作業ラインからの変更を導入された順序で別の作業ラインに再適用しますが、マージはエンドポイントを取り、それらをマージします。
より高度なリベース
リベースの再適用先をリベースターゲットブランチ以外にすることもできます。別のトピックブランチから分岐した履歴を例にとってみましょう。サーバーサイドの機能をプロジェクトに追加するためにトピックブランチ(`server`)を分岐し、コミットを行いました。その後、クライアントサイドの変更を行うためにそこから分岐し(`client`)、数回コミットしました。最後に、`server`ブランチに戻ってさらにいくつかのコミットを行いました。

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

これで、master
ブランチをfast-forwardできます(master
ブランチをfast-forwardしてclient
ブランチの変更を含めるを参照)。
$ git checkout master
$ git merge client

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

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
を実行すると、同じ作成者、日付、メッセージを持つ2つのコミットが表示され、混乱します。さらに、この履歴をサーバーにプッシュすると、リベースされたコミットがすべて中央サーバーに再導入され、さらに混乱を招く可能性があります。他の開発者が履歴にC4
とC6
を含めたくないことはほぼ確実に推測できます。それが、彼らが最初にリベースした理由です。
リベース時のリベース
このような状況に陥った場合、Gitにはさらに役立つ魔法があります。チームの誰かが作業のベースとしていた作業を上書きする変更を強制的にプッシュした場合、課題は自分のものと書き換えられたものを区別することです。
コミットのSHA-1チェックサムに加えて、Gitはコミットで導入されたパッチに基づいてチェックサムも計算します。これは「patch-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
を実行することを知らせましょう。
リベースとマージ
リベースとマージが実際にどのように機能するかを見たので、どちらが良いのか疑問に思うかもしれません。これについて答える前に、少し立ち戻って、履歴の意味について説明しましょう。
このことについての一つの見方は、リポジトリのコミット履歴は実際に起こったことの記録であるということです。それはそれ自体貴重な歴史文書であり、改ざんされるべきではありません。この角度から見ると、コミット履歴を変更することはほとんど冒涜的です。あなたは嘘をついているのです。散らかった一連のマージコミットがあったとしたらどうでしょうか?それが実際に起こったことであり、リポジトリはそのことを後世のために保存する必要があります。
反対の見方は、コミット履歴はプロジェクトの作成方法に関する物語であるということです。本の最初の草稿を公開しないのと同じように、散らかった作業を見せるのはなぜでしょうか?プロジェクトに取り組んでいるときは、すべての誤りや行き詰まった道の記録が必要になる場合がありますが、作業を世間に公開する準備ができたときは、AからBに到達する方法について、より一貫した物語を伝えたいと思うかもしれません。この陣営の人々は、rebase
やfilter-branch
などのツールを使用して、メインラインブランチにマージする前にコミットを書き換えます。彼らはrebase
とfilter-branch
などのツールを使用して、将来の読者にとって最適な方法で物語を伝えます。
では、マージとリベースのどちらが良いかという質問についてですが、それほど単純ではないことがわかるでしょう。Gitは強力なツールであり、履歴に対して多くのことができますが、すべてのチームとすべてのプロジェクトが異なるものです。これらの両方がどのように機能するかを知ったので、状況に応じてどちらが最適かを決定するのはあなた次第です。
両方の利点を活かすことができます。作業を整理するために、プッシュする前にローカルの変更をリベースしますが、どこかにプッシュしたものは決してリベースしません。