-
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コマンド
7.8 Gitツール - 高度なマージ
高度なマージ
Gitでのマージは通常かなり簡単です。Gitでは他のブランチを複数回マージするのが容易なため、非常に長く存続するブランチを持っていても、シリーズの最後に一つの巨大な衝突に驚かされることなく、小さな衝突を頻繁に解決しながら最新の状態に保つことができます。
しかし、時には厄介な衝突が発生することもあります。他のいくつかのバージョン管理システムとは異なり、Gitはマージ衝突の解決に過度な賢さを発揮しようとしません。Gitの哲学は、マージ解決が曖昧でないかどうかを判断する際には賢く振る舞いますが、衝突がある場合には、自動的に解決しようとは賢く振る舞わないというものです。したがって、急速に分岐する2つのブランチをマージするのを長く待ちすぎると、いくつかの問題に遭遇する可能性があります。
このセクションでは、そのような問題のいくつかについて、そしてこれらのより厄介な状況に対処するためにGitが提供するツールについて説明します。また、実行できるさまざまな非標準的なマージの種類や、実行したマージを取り消す方法についても触れます。
マージの衝突
基本的なマージの衝突ではマージの衝突解決の基本的な内容をいくつかカバーしましたが、より複雑な衝突については、Gitは状況を把握し、衝突にうまく対処するためのいくつかのツールを提供しています。
まず、可能であれば、衝突が発生する可能性のあるマージを行う前に、ワーキングディレクトリがクリーンであることを確認してください。作業中のものがある場合は、一時ブランチにコミットするか、スタッシュしてください。これにより、ここで試すすべての操作を元に戻すことができます。マージを試す際にワーキングディレクトリに未保存の変更がある場合、これらのヒントのいくつかはその作業を維持するのに役立つかもしれません。
非常に簡単な例を見てみましょう。私たちは「hello world」と出力する非常にシンプルなRubyファイルを持っています。
#! /usr/bin/env ruby
def hello
puts 'hello world'
end
hello()
リポジトリで、whitespace
という名前の新しいブランチを作成し、すべてのUnix改行をDOS改行に変更します。これは、実質的にファイルのすべての行を変更しますが、空白文字のみです。次に、「hello world」という行を「hello mundo」に変更します。
$ git checkout -b whitespace
Switched to a new branch 'whitespace'
$ unix2dos hello.rb
unix2dos: converting file hello.rb to DOS format ...
$ git commit -am 'Convert hello.rb to DOS'
[whitespace 3270f76] Convert hello.rb to DOS
1 file changed, 7 insertions(+), 7 deletions(-)
$ vim hello.rb
$ git diff -b
diff --git a/hello.rb b/hello.rb
index ac51efd..e85207e 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,7 @@
#! /usr/bin/env ruby
def hello
- puts 'hello world'
+ puts 'hello mundo'^M
end
hello()
$ git commit -am 'Use Spanish instead of English'
[whitespace 6d338d2] Use Spanish instead of English
1 file changed, 1 insertion(+), 1 deletion(-)
次に、master
ブランチに戻り、関数にドキュメントを追加します。
$ git checkout master
Switched to branch 'master'
$ vim hello.rb
$ git diff
diff --git a/hello.rb b/hello.rb
index ac51efd..36c06c8 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello world'
end
$ git commit -am 'Add comment documenting the function'
[master bec6336] Add comment documenting the function
1 file changed, 1 insertion(+)
次に、whitespace
ブランチをマージしようとすると、空白文字の変更により衝突が発生します。
$ git merge whitespace
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
マージの停止
現在、いくつかの選択肢があります。まず、この状況から抜け出す方法について説明します。もし予期せぬ衝突に遭遇し、まだ対処したくない場合は、git merge --abort
でマージを中止するだけです。
$ git status -sb
## master
UU hello.rb
$ git merge --abort
$ git status -sb
## master
git merge --abort
オプションは、マージを実行する前の状態に戻そうとします。これが完全にできない可能性があるのは、実行時にワーキングディレクトリにスタッシュされていない、コミットされていない変更があった場合のみで、それ以外の場合は正常に動作するはずです。
何らかの理由で最初からやり直したい場合は、git reset --hard HEAD
を実行することもできます。これにより、リポジトリは最後にコミットされた状態に戻ります。コミットされていない作業は失われるため、変更が必要ないことを確認してください。
空白文字の無視
この特定のケースでは、衝突は空白文字に関連しています。ケースが単純なのでこれがわかりますが、衝突を見ると、片側ですべての行が削除され、もう片側で再度追加されているため、実際のケースでも非常に簡単に判断できます。デフォルトでは、Gitはこれらのすべての行が変更されたと見なすため、ファイルをマージできません。
デフォルトのマージ戦略は引数を取ることができ、そのうちのいくつかは空白文字の変更を適切に無視することに関するものです。マージで多くの空白文字の問題があることがわかった場合は、単に中止して、今度は-Xignore-all-space
または-Xignore-space-change
を指定して再度実行できます。最初のオプションは、行を比較する際に空白文字を**完全に**無視し、2番目のオプションは、1つ以上の空白文字のシーケンスを同等に扱います。
$ git merge -Xignore-space-change whitespace
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
このケースでは、実際のファイル変更が衝突していなかったため、空白文字の変更を無視すると、すべてが正常にマージされます。
これは、チームの誰かが時々すべてをスペースからタブへ、またはその逆に再フォーマットするのを好む場合に非常に役立ちます。
手動ファイル再マージ
Gitは空白文字の前処理をかなりうまく処理しますが、Gitが自動的に処理できない他の種類の変更も存在し、それらはスクリプトで修正可能です。例えば、Gitが空白文字の変更を処理できず、手動で処理する必要があったとしましょう。
私たちが本当に必要なのは、実際のファイルマージを試みる前に、マージしようとしているファイルをdos2unix
プログラムで実行することです。では、どうすればそれを実現できるでしょうか?
まず、マージ衝突状態になります。次に、ファイルに対する私たちのバージョン、相手のバージョン(マージ対象のブランチから)、共通のバージョン(両側が分岐した場所から)のコピーを取得します。その後、相手側または私たちの側を修正し、この単一のファイルに対してマージを再試行します。
3つのファイルバージョンを取得するのは実は非常に簡単です。Gitはこれらすべてのバージョンをインデックスの「ステージ」に保存しており、それぞれのステージには番号が関連付けられています。ステージ1は共通の祖先、ステージ2はあなたのバージョン、ステージ3はMERGE_HEAD
からのバージョン(マージ対象の「彼らの」バージョン)です。
これらの衝突したファイルの各バージョンのコピーは、git show
コマンドと特殊な構文で抽出できます。
$ git show :1:hello.rb > hello.common.rb
$ git show :2:hello.rb > hello.ours.rb
$ git show :3:hello.rb > hello.theirs.rb
より本格的に行きたい場合は、ls-files -u
プラミングコマンドを使用して、これらの各ファイルのGitブロブの実際のSHA-1を取得することもできます。
$ git ls-files -u
100755 ac51efdc3df4f4fd328d1a02ad05331d8e2c9111 1 hello.rb
100755 36c06c8752c78d2aff89571132f3bf7841a7b5c3 2 hello.rb
100755 e85207e04dfdd5eb0a1e9febbc67fd837c44a1cd 3 hello.rb
:1:hello.rb
は、そのブロブのSHA-1を検索するためのショートハンドに過ぎません。
これで、作業ディレクトリに3つのステージすべてのコンテンツが揃ったので、手動で相手の側の空白文字の問題を修正し、あまり知られていないgit merge-file
コマンドでファイルを再マージすることができます。
$ dos2unix hello.theirs.rb
dos2unix: converting file hello.theirs.rb to Unix format ...
$ git merge-file -p \
hello.ours.rb hello.common.rb hello.theirs.rb > hello.rb
$ git diff -b
diff --cc hello.rb
index 36c06c8,e85207e..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,8 -1,7 +1,8 @@@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
この時点で、ファイルをきれいにマージしました。実際、これはignore-space-change
オプションよりも優れています。なぜなら、マージ前に空白文字の変更を実際に修正するからです。ignore-space-change
マージでは、最終的にDOS改行の行がいくつか残り、混在した状態になりました。
このコミットを確定する前に、片側またはもう片側で実際に何が変更されたかを知りたい場合は、git diff
に、マージの結果としてコミットしようとしている現在の作業ディレクトリの内容と、これらのいずれかのステージを比較するように依頼できます。すべて見ていきましょう。
マージ前のブランチの変更内容、つまりマージが何を導入したかを確認するには、git diff --ours
を実行します。
$ git diff --ours
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index 36c06c8..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -2,7 +2,7 @@
# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
ここで、私たちのブランチで何が起こったか、このマージで実際にこのファイルに導入されているのは、その単一行の変更であることが簡単にわかります。
マージの結果が相手の側とどのように異なっているかを確認したい場合は、git diff --theirs
を実行します。この例と以下の例では、空白文字を取り除くために-b
を使用する必要があります。なぜなら、Gitに存在する内容と比較しているためであり、私たちのクリーンアップされたhello.theirs.rb
ファイルと比較しているわけではないからです。
$ git diff --theirs -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index e85207e..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,5 +1,6 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
puts 'hello mundo'
end
最後に、git diff --base
を使用して、両側からファイルがどのように変更されたかを確認できます。
$ git diff --base -b
* Unmerged path hello.rb
diff --git a/hello.rb b/hello.rb
index ac51efd..44d0a25 100755
--- a/hello.rb
+++ b/hello.rb
@@ -1,7 +1,8 @@
#! /usr/bin/env ruby
+# prints out a greeting
def hello
- puts 'hello world'
+ puts 'hello mundo'
end
hello()
この時点で、git clean
コマンドを使用して、手動マージのために作成したが不要になった余分なファイルを削除できます。
$ git clean -f
Removing hello.common.rb
Removing hello.ours.rb
Removing hello.theirs.rb
衝突のチェックアウト
おそらく、現時点での解決策に何らかの理由で満足していない、あるいは手動で片方または両方を編集しても上手くいかず、もっとコンテキストが必要だと感じているかもしれません。
例を少し変えてみましょう。この例では、それぞれいくつかのコミットを持つ2つの長期間のブランチがあり、マージすると正当なコンテンツ衝突が発生します。
$ git log --graph --oneline --decorate --all
* f1270f7 (HEAD, master) Update README
* 9af9d3b Create README
* 694971d Update phrase to 'hola world'
| * e3eb223 (mundo) Add more tests
| * 7cff591 Create initial testing script
| * c3ffff1 Change text to 'hello mundo'
|/
* b7dcc89 Initial hello world code
現在、master
ブランチにのみ存在する3つのユニークなコミットと、mundo
ブランチに存在する他の3つのコミットがあります。mundo
ブランチをマージしようとすると、衝突が発生します。
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.
マージの衝突が何であるかを確認したいです。ファイルを開くと、次のような表示になります。
#! /usr/bin/env ruby
def hello
<<<<<<< HEAD
puts 'hola world'
=======
puts 'hello mundo'
>>>>>>> mundo
end
hello()
マージの両側がこのファイルにコンテンツを追加しましたが、一部のコミットがこの衝突を引き起こしたのと同じ場所でファイルを変更しました。
この衝突がどのようにして発生したかを特定するために利用できるいくつかのツールを探ってみましょう。この衝突を正確にどのように修正すべきかは、おそらく明らかではありません。もっとコンテキストが必要です。
役に立つツールの1つは、--conflict
オプション付きのgit checkout
です。これはファイルを再度チェックアウトし、マージ衝突マーカーを置き換えます。マーカーをリセットして再度解決しようとしたい場合に便利です。
--conflict
にはdiff3
またはmerge
(デフォルト)を渡すことができます。diff3
を渡すと、Gitは衝突マーカーの少し異なるバージョンを使用し、「ours」と「theirs」バージョンだけでなく、「base」バージョンもインラインで提供して、より多くのコンテキストを与えます。
$ git checkout --conflict=diff3 hello.rb
それを実行すると、ファイルは次のように表示されます。
#! /usr/bin/env ruby
def hello
<<<<<<< ours
puts 'hola world'
||||||| base
puts 'hello world'
=======
puts 'hello mundo'
>>>>>>> theirs
end
hello()
このフォーマットが気に入った場合は、merge.conflictstyle
設定をdiff3
に設定することで、将来のマージ競合のデフォルトとして設定できます。
$ git config --global merge.conflictstyle diff3
git checkout
コマンドは--ours
と--theirs
オプションも受け入れます。これは、全くマージせずにどちらか一方の側を選択する非常に高速な方法となりえます。
これは、バイナリファイルの衝突で片側だけを選択できる場合や、別のブランチから特定のファイルのみをマージしたい場合に特に便利です。マージを実行した後、コミットする前に、片側またはもう片側から特定のファイルをチェックアウトできます。
マージログ
マージ衝突を解決する際に役立つもう1つのツールはgit log
です。これは、衝突の原因となった可能性のあるコンテキストを把握するのに役立ちます。開発の2つのラインがコードの同じ領域に触れていた理由を思い出すために、少し履歴をレビューすることは、時には非常に役立ちます。
このマージに関与した両方のブランチに含まれるすべてのユニークなコミットの完全なリストを取得するには、トリプルドットで学んだ「トリプルドット」構文を使用できます。
$ git log --oneline --left-right HEAD...MERGE_HEAD
< f1270f7 Update README
< 9af9d3b Create README
< 694971d Update phrase to 'hola world'
> e3eb223 Add more tests
> 7cff591 Create initial testing script
> c3ffff1 Change text to 'hello mundo'
これは、関与した合計6つのコミットと、各コミットがどの開発ラインにあったかを示す良いリストです。
ただし、これをさらに簡素化して、より具体的なコンテキストを提供できます。git log
に--merge
オプションを追加すると、現在衝突しているファイルに触れるマージのどちらかの側のコミットのみが表示されます。
$ git log --oneline --left-right --merge
< 694971d Update phrase to 'hola world'
> c3ffff1 Change text to 'hello mundo'
これを-p
オプションと一緒に実行すると、衝突に終わったファイルへの差分のみが得られます。これは、何が衝突しているのか、そしてそれをより賢く解決する方法を理解するのに必要なコンテキストを迅速に提供する上で、**非常に**役立ちます。
結合差分形式
Gitは成功したマージ結果をステージングするため、衝突したマージ状態にあるときにgit diff
を実行すると、まだ衝突しているものだけが表示されます。これは、まだ解決しなければならないものを見るのに役立ちます。
マージ衝突の直後にgit diff
を実行すると、かなりユニークな差分出力形式で情報が表示されます。
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,11 @@@
#! /usr/bin/env ruby
def hello
++<<<<<<< HEAD
+ puts 'hola world'
++=======
+ puts 'hello mundo'
++>>>>>>> mundo
end
hello()
この形式は「結合差分」と呼ばれ、各行の隣に2つのデータ列が表示されます。最初の列は、「ours」ブランチと作業ディレクトリ内のファイルの間でその行が異なる(追加または削除された)かどうかを示し、2番目の列は「theirs」ブランチと作業ディレクトリのコピーの間で同じことを行います。
その例では、<<<<<<<
と>>>>>>>
の行は作業コピーにはあるが、マージのどちらの側にもなかったことがわかります。これは理にかなっています。なぜなら、マージツールは私たちのコンテキストのためにそれらをそこに配置しましたが、私たちはそれらを削除することが期待されているからです。
衝突を解決してもう一度git diff
を実行すると、同じものが見えますが、もう少し便利です。
$ vim hello.rb
$ git diff
diff --cc hello.rb
index 0399cd5,59727f0..0000000
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
これは、「hola world」は私たちの側にはあったがワーキングコピーにはなく、「hello mundo」は相手の側にはあったがワーキングコピーにはなく、そして最後に「hola mundo」はどちらの側にもなかったが現在はワーキングコピーにあることを示しています。これは、解決策をコミットする前に確認するのに役立ちます。
これは、マージのgit log
からでも、後で何が解決されたかを確認できます。Gitは、マージコミットでgit show
を実行した場合、またはgit log -p
に--cc
オプションを追加した場合にこの形式を出力します(デフォルトでは、非マージコミットのパッチのみを表示します)。
$ git log --cc -p -1
commit 14f41939956d80b9e17bb8721354c33f8d5b5a79
Merge: f1270f7 e3eb223
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Sep 19 18:14:49 2014 +0200
Merge branch 'mundo'
Conflicts:
hello.rb
diff --cc hello.rb
index 0399cd5,59727f0..e1d0799
--- a/hello.rb
+++ b/hello.rb
@@@ -1,7 -1,7 +1,7 @@@
#! /usr/bin/env ruby
def hello
- puts 'hola world'
- puts 'hello mundo'
++ puts 'hola mundo'
end
hello()
マージの取り消し
マージコミットの作成方法がわかった今、おそらくいくつか間違いを犯すでしょう。Gitを使って作業する上で素晴らしいことの1つは、間違いを犯しても大丈夫だということです。なぜなら、それらを修正することが可能(そして多くの場合簡単)だからです。
マージコミットも例外ではありません。トピックブランチで作業を開始し、誤ってmaster
にマージしてしまったとしましょう。すると、コミット履歴は次のようになります。

この問題には、目的の結果に応じて2つのアプローチがあります。
参照を修正する
不要なマージコミットがローカルリポジトリにのみ存在する場合、最も簡単で最善の解決策は、ブランチを目的の場所を指すように移動することです。ほとんどの場合、誤ったgit merge
の後にgit reset --hard HEAD~
を実行すると、ブランチポインターが次のようにリセットされます。

git reset --hard HEAD~
後の履歴reset
については、以前リセットの謎を解くで説明しましたので、ここで何が起こっているかを理解するのはそれほど難しくないはずです。簡単な復習をしましょう。reset --hard
は通常、次の3つのステップを実行します。
-
HEADが指すブランチを移動します。この場合、
master
をマージコミット(C6
)の前にあった場所へ移動させたい。 -
インデックスをHEADと同じにします。
-
ワーキングディレクトリをインデックスと同じにします。
このアプローチの欠点は、履歴を書き換えることであり、これは共有リポジトリでは問題となる可能性があります。リベースの危険性で何が起こりうるかの詳細を確認してください。簡潔に言えば、他の人が書き換えようとしているコミットを持っている場合、おそらくreset
は避けるべきです。このアプローチは、マージ後に他のコミットが作成された場合にも機能しません。参照を移動すると、それらの変更が実質的に失われることになります。
コミットを元に戻す
ブランチポインターの移動がうまくいかない場合、Gitは既存のコミットによるすべての変更を取り消す新しいコミットを作成するオプションを提供します。Gitはこの操作を「リバート」と呼び、この特定のシナリオでは、次のように呼び出します。
$ git revert -m 1 HEAD
[master b1d8379] Revert "Merge branch 'topic'"
-m 1
フラグは、「メインライン」であり、保持すべき親を示します。HEAD
へのマージ(git merge topic
)を呼び出すと、新しいコミットには2つの親があります。最初の親はHEAD
(C6
)であり、2番目の親はマージされるブランチの先端(C4
)です。この場合、親#2(C4
)をマージすることによって導入されたすべての変更を取り消し、親#1(C6
)のすべての内容を保持したいと考えます。
リバートコミットを含む履歴は次のようになります。

git revert -m 1
後の履歴新しいコミット^M
はC6
とまったく同じ内容なので、ここから始めれば、マージは起こらなかったかのようです。ただし、現在マージされていないコミットは依然としてHEAD
の履歴に残っています。topic
をmaster
に再度マージしようとすると、Gitは混乱します。
$ git merge topic
Already up-to-date.
topic
には、すでにmaster
から到達可能なもの以外はありません。さらに悪いことに、topic
に作業を追加して再度マージすると、Gitはリバートされたマージ以降の変更のみを取り込みます。

これを回避する最善の方法は、まず元のマージを元に戻し、その後に新しいマージコミットを作成することです。なぜなら、元に戻された変更を取り込みたいからです。
$ git revert ^M
[master 09f0126] Revert "Revert "Merge branch 'topic'""
$ git merge topic

この例では、M
と^M
が打ち消し合います。^^M
は実質的にC3
とC4
からの変更をマージし、C8
はC7
からの変更をマージするので、これでtopic
は完全にマージされました。
その他のマージタイプ
これまで、2つのブランチの通常のマージについて説明しました。これは通常、「recursive」マージ戦略と呼ばれる方法で処理されます。しかし、ブランチを結合する他の方法もあります。それらのいくつかについて手短に説明しましょう。
自社または相手の好み
まず、通常のマージの「再帰的」モードでできるもう1つの便利なことがあります。-X
で渡されるignore-all-space
とignore-space-change
オプションはすでに見てきましたが、Gitに衝突が発生したときにどちらかの側を優先するように指示することもできます。
デフォルトでは、Gitがマージされる2つのブランチ間で衝突を検出すると、コードにマージ衝突マーカーを追加し、ファイルを衝突状態としてマークし、ユーザーに解決させます。Gitが手動で衝突を解決させる代わりに、特定の側を単純に選択して他方を無視することを好む場合、merge
コマンドに-Xours
または-Xtheirs
のいずれかを渡すことができます。
Gitがこれを見ると、衝突マーカーを追加しません。マージ可能な違いはマージし、衝突する違いは、バイナリファイルを含め、指定された側を丸ごと選択します。
以前使用した「hello world」の例に戻ると、ブランチをマージすると競合が発生することがわかります。
$ git merge mundo
Auto-merging hello.rb
CONFLICT (content): Merge conflict in hello.rb
Resolved 'hello.rb' using previous resolution.
Automatic merge failed; fix conflicts and then commit the result.
しかし、-Xours
または-Xtheirs
で実行すると、競合は発生しません。
$ git merge -Xours mundo
Auto-merging hello.rb
Merge made by the 'recursive' strategy.
hello.rb | 2 +-
test.sh | 2 ++
2 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 test.sh
その場合、ファイルに「hello mundo」と「hola world」が両方表示されるのではなく、「hola world」が単純に選択されます。ただし、そのブランチ上の他の非競合変更はすべて正常にマージされます。
このオプションは、個別のファイルマージに対してgit merge-file --ours
のように実行することで、以前に見たgit merge-file
コマンドにも渡すことができます。
もし、このようなことを行いたいが、Gitに相手側からの変更のマージを試みさせたくない場合は、より厳格なオプションである「ours」マージ_戦略_があります。これは、「ours」再帰マージ_オプション_とは異なります。
これは基本的に偽のマージを行います。両方のブランチを親として新しいマージコミットを記録しますが、マージするブランチを全く見ません。現在のブランチの正確なコードをマージの結果として記録するだけです。
$ git merge -s ours mundo
Merge made by the 'ours' strategy.
$ git diff HEAD HEAD~
$
現在いるブランチとマージの結果の間には差がないことがわかります。
これは、後でマージを行う際に、あるブランチがすでにマージされているとGitを欺くのに役立つことがあります。たとえば、release
ブランチから分岐し、いずれmaster
ブランチにマージしたい作業を行ったとします。その間に、master
でのバグ修正をrelease
ブランチにバックポートする必要があります。バグ修正ブランチをrelease
ブランチにマージし、同じブランチをmerge -s ours
でmaster
ブランチにもマージできます(修正はすでにそこにあるにもかかわらず)。そうすることで、後でrelease
ブランチを再度マージしても、バグ修正からの衝突は発生しません。
サブツリーマージ
サブツリーマージの考え方は、2つのプロジェクトがあり、一方のプロジェクトがもう一方のプロジェクトのサブディレクトリにマッピングされるというものです。サブツリーマージを指定すると、Gitは一方のプロジェクトがもう一方のサブツリーであると判断し、適切にマージするのに十分賢いことがよくあります。
ここでは、既存のプロジェクトに別のプロジェクトを追加し、その2番目のプロジェクトのコードを最初のプロジェクトのサブディレクトリにマージする例を説明します。
まず、Rackアプリケーションをプロジェクトに追加します。Rackプロジェクトを独自のプロジェクトのリモート参照として追加し、それを独自のブランチにチェックアウトします。
$ git remote add rack_remote https://github.com/rack/rack
$ git fetch rack_remote --no-tags
warning: no common commits
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 4 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.
From https://github.com/rack/rack
* [new branch] build -> rack_remote/build
* [new branch] master -> rack_remote/master
* [new branch] rack-0.4 -> rack_remote/rack-0.4
* [new branch] rack-0.9 -> rack_remote/rack-0.9
$ git checkout -b rack_branch rack_remote/master
Branch rack_branch set up to track remote branch refs/remotes/rack_remote/master.
Switched to a new branch "rack_branch"
これで、rack_branch
ブランチにはRackプロジェクトのルートが、master
ブランチには自身のプロジェクトのルートがあります。一方をチェックアウトしてからもう一方をチェックアウトすると、それぞれ異なるプロジェクトルートを持っていることがわかります。
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
README
これは少し奇妙な概念です。リポジトリ内のすべてのブランチが同じプロジェクトのブランチである必要はありません。これは稀にしか役立たないため一般的ではありませんが、ブランチが完全に異なる履歴を持つことはかなり簡単です。
この場合、Rackプロジェクトを私たちのmaster
プロジェクトにサブディレクトリとして取り込みたいです。これはGitでgit read-tree
を使って行えます。read-tree
とその関連コマンドについてはGitの内側で詳しく学びますが、今のところは、あるブランチのルートツリーを現在のステージングエリアとワーキングディレクトリに読み込むことだけを知っておいてください。私たちはちょうどmaster
ブランチに戻ったところで、rack_branch
ブランチをメインプロジェクトのmaster
ブランチのrack
サブディレクトリに取り込みます。
$ git read-tree --prefix=rack/ -u rack_branch
コミットすると、あたかもtarボールからコピーしたかのように、そのサブディレクトリにすべてのRackファイルがあるように見えます。面白いのは、一方のブランチからもう一方のブランチへの変更をかなり簡単にマージできることです。したがって、Rackプロジェクトが更新された場合、そのブランチに切り替えてプルすることで、上流の変更を取り込むことができます。
$ git checkout rack_branch
$ git pull
次に、それらの変更をmaster
ブランチにマージできます。変更を取り込み、コミットメッセージを事前に入力するには、--squash
オプションと、再帰マージ戦略の-Xsubtree
オプションを使用します。ここでは再帰戦略がデフォルトですが、明確にするために含めています。
$ git checkout master
$ git merge --squash -s recursive -Xsubtree=rack rack_branch
Squash commit -- not updating HEAD
Automatic merge went well; stopped before committing as requested
Rackプロジェクトからのすべての変更はマージされ、ローカルでコミットする準備ができています。また、反対のことも可能です。master
ブランチのrack
サブディレクトリで変更を行い、後でそれらをrack_branch
ブランチにマージして、メンテナーに提出したり、上流にプッシュしたりできます。
これにより、サブモジュールを使用せずにサブモジュールワークフローとある程度似たワークフローを実現できます(サブモジュールについてはサブモジュールで説明します)。リポジトリに他の関連プロジェクトを含むブランチを保持し、時折それらをプロジェクトにサブツリーマージできます。すべてのコードが1か所にコミットされるなど、いくつかの点で優れています。しかし、変更の再統合や無関係なリポジトリへのブランチの誤ったプッシュがより複雑で間違いを犯しやすくなるという他の欠点もあります。
もう一つ少し変わった点として、rack
サブディレクトリにあるものとrack_branch
ブランチにあるコードとの差分を取得するには、つまりそれらをマージする必要があるかどうかを確認するには、通常のdiff
コマンドは使用できません。代わりに、比較したいブランチを指定してgit diff-tree
を実行する必要があります。
$ git diff-tree -p rack_branch
または、rack
サブディレクトリにあるものと、最後にフェッチしたときのサーバー上のmaster
ブランチとの比較を見るには、次のように実行します。
$ git diff-tree -p rack_remote/master