-
1. Gitを始めるにあたって
- 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 Resetの解明
- 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 Plumbingコマンド
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
ブランチに戻って、さらにいくつかコミットを行いました。

クライアント側の変更をリリース用のメインラインにマージしたいが、サーバー側の変更はさらにテストするまで保留したいとします。client
上にあるが server
にはない変更 (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
を実行して server
ブランチを master
ブランチにリベースできます。これは、トピックブランチ (この場合は server
) をチェックアウトし、ベースブランチ (master
) の上にリプレイします。
$ git rebase master server
これは、`server` ブランチを `master` ブランチの上にリベースするに示すように、`master` の作業の上に `server` の作業をリプレイします。

server
ブランチを master
ブランチの上にリベースするその後、ベースブランチ (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. マージ
リベースとマージの動作を見てきたので、どちらが良いのか疑問に思っているかもしれません。これに答える前に、少し立ち止まって履歴が何を意味するかについて話しましょう。
これに対する1つの見方は、リポジトリのコミット履歴は**実際に起こったことの記録**であるというものです。それはそれ自体で価値のある歴史的な文書であり、改ざんされるべきではありません。この観点からすると、コミット履歴を変更することはほとんど冒涜的です。あなたは実際に起こったことについて**嘘をついている**ことになります。では、一連の汚いマージコミットがあったらどうでしょうか?それが起こった方法であり、リポジトリはそれを後世のために保存すべきです。
反対の視点は、コミット履歴は**プロジェクトがどのように作られたかという物語**であるというものです。本の初稿を公開しないように、なぜ汚い作業を見せる必要があるのでしょうか?プロジェクトに取り組んでいる間は、すべての失敗や行き止まりの道の記録が必要かもしれませんが、作業を世に発表する際には、AからBに到達するまでのより一貫した物語を語りたいと思うかもしれません。この考え方の人は、メインラインブランチにマージされる前にコミットを書き換えるために rebase
や filter-branch
のようなツールを使用します。彼らは rebase
や filter-branch
のようなツールを使用して、将来の読者にとって最適な方法で物語を語ります。
さて、マージとリベースのどちらが良いかという質問ですが、それがそれほど単純ではないことがお分かりいただけたでしょうか。Gitは強力なツールであり、履歴に対して多くのことを行うことを可能にしますが、チームやプロジェクトはそれぞれ異なります。両方の仕組みが分かったので、あなたの特定の状況にどちらが最適かを決定するのはあなた次第です。
両方の良いとこ取りができます。プッシュする前にローカルの変更をリベースして作業をクリーンアップしますが、どこかにプッシュしたものは決してリベースしないでください。