-
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 Resetの謎を解く
- 7.8 高度なマージ
- 7.9 Rerere
- 7.10 Gitでのデバッグ
- 7.11 サブモジュール
- 7.12 バンドル
- 7.13 置き換え (Replace)
- 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 Packファイル
- 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.11 Gitツール - サブモジュール
サブモジュール
あるプロジェクトで作業しているときに、その内部で別のプロジェクトを使用する必要が生じることがよくあります。それはサードパーティが開発したライブラリかもしれませんし、あなたが個別に開発して複数の親プロジェクトで使用しているものかもしれません。これらのシナリオでは共通の問題が発生します。それは、2つのプロジェクトを別々に扱いながらも、一方をもう一方の内部から使用できるようにしたいということです。
例を挙げましょう。あなたがウェブサイトを開発し、Atomフィードを作成しているとします。独自のAtom生成コードを書く代わりに、ライブラリを使用することにしました。このコードは、CPANインストールやRuby gemのような共有ライブラリから含めるか、ソースコードを自分のプロジェクトツリーにコピーするかのどちらかになるでしょう。ライブラリを含めることの問題は、ライブラリを何らかの方法でカスタマイズするのが難しく、すべてのクライアントがそのライブラリを利用できるようにする必要があるため、デプロイがより困難になることが多いことです。コードを自分のプロジェクトにコピーすることの問題は、アップストリームの変更が利用可能になったときに、あなたが行ったカスタム変更をマージするのが難しいことです。
Gitはサブモジュールを使用してこの問題に対処します。サブモジュールを使用すると、Gitリポジトリを別のGitリポジトリのサブディレクトリとして保持できます。これにより、別のリポジトリをプロジェクトにクローンし、コミットを個別に管理することができます。
サブモジュールを使い始める
メインプロジェクトといくつかのサブプロジェクトに分割された簡単なプロジェクトを開発する手順を説明します。
まず、既存のGitリポジトリを、現在作業中のリポジトリのサブモジュールとして追加してみましょう。新しいサブモジュールを追加するには、トラッキングを開始したいプロジェクトの絶対URLまたは相対URLを指定してgit submodule add
コマンドを使用します。この例では、「DbConnector」というライブラリを追加します。
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
デフォルトでは、サブモジュールはサブプロジェクトをリポジトリと同じ名前のディレクトリに、この場合は「DbConnector」に、追加します。別の場所に配置したい場合は、コマンドの最後に異なるパスを追加できます。
この時点でgit status
を実行すると、いくつかのことに気づくでしょう。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitmodules
new file: DbConnector
まず、新しい.gitmodules
ファイルに気づくはずです。これは、プロジェクトのURLと、それをプルしたローカルサブディレクトリ間のマッピングを格納する設定ファイルです。
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
複数のサブモジュールがある場合、このファイルには複数のエントリがあります。このファイルが.gitignore
ファイルのように他のファイルと一緒にバージョン管理されていることに注意することが重要です。プロジェクトの他の部分と一緒にプッシュおよびプルされます。これにより、このプロジェクトをクローンする他の人々は、サブモジュールプロジェクトをどこから取得するかを知ることができます。
注記
|
.gitmodulesファイル内のURLは、他の人が最初にクローン/フェッチしようとするものであるため、可能な場合はアクセス可能なURLを使用するようにしてください。たとえば、他の人がプルするURLとは異なるURLにプッシュする場合、他の人がアクセスできるURLを使用してください。自身の使用のために、 |
git status
出力のもう一つのリストは、プロジェクトフォルダのエントリです。それにgit diff
を実行すると、興味深いものが見られます。
$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
DbConnector
はワーキングディレクトリ内のサブディレクトリですが、Gitはそれをサブモジュールとして認識し、そのディレクトリ内にいないときはそのコンテンツを追跡しません。代わりに、Gitはそれをそのリポジトリからの特定のコミットとして認識します。
もう少し見やすい差分出力が必要な場合は、git diff
に--submodule
オプションを渡すことができます。
$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)
コミットすると、次のようなものが表示されます。
$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
DbConnector
エントリの160000
モードに注目してください。これはGitの特殊なモードで、基本的にサブディレクトリやファイルではなく、コミットをディレクトリエントリとして記録していることを意味します。
最後に、これらの変更をプッシュします。
$ git push origin master
サブモジュールを含むプロジェクトのクローン
ここでは、サブモジュールを含むプロジェクトをクローンします。このようなプロジェクトをクローンすると、デフォルトではサブモジュールを含むディレクトリは取得されますが、その内部のファイルはまだ取得されません。
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$
DbConnector
ディレクトリはありますが、空です。メインプロジェクトから2つのコマンドを実行する必要があります。ローカル設定ファイルを初期化するためのgit submodule init
と、そのプロジェクトからすべてのデータをフェッチし、スーパープロジェクトにリストされている適切なコミットをチェックアウトするためのgit submodule update
です。
$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
これで、DbConnector
サブディレクトリは、以前コミットしたときとまったく同じ状態になります。
ただし、これを行うためのもう少し簡単な方法があります。git clone
コマンドに--recurse-submodules
を渡すと、リポジトリ内の各サブモジュールが自動的に初期化および更新されます。これには、リポジトリ内のサブモジュール自体がサブモジュールを持っている場合、ネストされたサブモジュールも含まれます。
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
すでにプロジェクトをクローンしていて--recurse-submodules
を忘れてしまった場合は、git submodule update --init
を実行することでgit submodule init
とgit submodule update
のステップを組み合わせることができます。ネストされたサブモジュールも初期化、フェッチ、チェックアウトするには、確実なgit submodule update --init --recursive
を使用できます。
サブモジュールを含むプロジェクトでの作業
これで、サブモジュールを含むプロジェクトのコピーが手元にあり、メインプロジェクトとサブモジュールプロジェクトの両方でチームメイトと共同作業を行います。
サブモジュールリモートからのアップストリーム変更のプル
プロジェクトでサブモジュールを使用する最もシンプルなモデルは、サブプロジェクトを単に消費し、時折そこから更新を取得したいが、チェックアウトで実際に何も変更しない場合です。簡単な例を見てみましょう。
サブモジュールに新しい変更があるかどうかを確認したい場合は、ディレクトリに入り、git fetch
とgit merge
でアップストリームブランチをマージしてローカルコードを更新できます。
$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
ここでメインプロジェクトに戻りgit diff --submodule
を実行すると、サブモジュールが更新され、そこに追加されたコミットのリストを確認できます。毎回git diff
を実行するたびに--submodule
と入力したくない場合は、diff.submodule
設定値を「log」に設定することで、これをデフォルトの形式に設定できます。
$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
> more efficient db routine
> better connection routine
この時点でコミットすると、他の人が更新したときにサブモジュールに新しいコードが固定されます。
サブディレクトリで手動でフェッチおよびマージしたくない場合は、これを行うより簡単な方法もあります。git submodule update --remote
を実行すると、Gitがサブモジュールに入り、自動的にフェッチと更新を行います。
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
このコマンドはデフォルトで、リモートサブモジュールリポジトリのデフォルトブランチ(リモートのHEAD
が指すブランチ)にチェックアウトを更新したいと仮定します。ただし、必要に応じてこれを別のものに設定することもできます。たとえば、DbConnector
サブモジュールにそのリポジトリの「stable」ブランチを追跡させたい場合は、.gitmodules
ファイル(他の全員も追跡するように)またはローカルの.git/config
ファイルに設定できます。.gitmodules
ファイルに設定しましょう。
$ git config -f .gitmodules submodule.DbConnector.branch stable
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'
-f .gitmodules
を省略すると、あなた自身の変更のみになりますが、リポジトリとともにその情報を追跡して他の全員も同じようにするのがより理にかなっているでしょう。
この時点でgit status
を実行すると、Gitはサブモジュールに「新しいコミット」があることを示します。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
設定status.submodulesummary
を設定すると、Gitはサブモジュールの変更の短いサマリーも表示します。
$ git config status.submodulesummary 1
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: .gitmodules
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c3f01dc...c87d55d (4):
> catch non-null terminated lines
この時点でgit diff
を実行すると、.gitmodules
ファイルを変更したことと、プルダウンしてサブモジュールプロジェクトにコミットする準備ができているいくつかのコミットがあることがわかります。
$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
これは非常に便利で、サブモジュールでコミットしようとしているコミットのログを実際に見ることができます。コミット後も、git log -p
を実行するとこの情報を確認できます。
$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
git submodule update --remote
を実行すると、Gitはデフォルトで**すべての**サブモジュールを更新しようとします。多数のサブモジュールがある場合は、更新したいサブモジュールの名前だけを渡すことを検討してください。
プロジェクトリモートからのアップストリーム変更のプル
次に、MainProjectリポジトリの独自のローカルクローンを持っているコラボレーターの立場になってみましょう。コミットしたばかりの変更を取得するためにgit pull
を実行するだけでは不十分です。
$ git pull
From https://github.com/chaconinc/MainProject
fb9093c..0a24cfc master -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
c3f01dc..c87d55d stable -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
.gitmodules | 2 +-
DbConnector | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c87d55d...c3f01dc (4):
< catch non-null terminated lines
< more robust error handling
< more efficient db routine
< better connection routine
no changes added to commit (use "git add" and/or "git commit -a")
デフォルトでは、git pull
コマンドはサブモジュールの変更を再帰的にフェッチします(上記最初のコマンドの出力で確認できます)。ただし、サブモジュールを**更新**しません。これはgit status
コマンドの出力で示されており、サブモジュールが「modified」(変更済み)であり、「new commits」(新しいコミット)があると表示されます。さらに、新しいコミットを示す括弧が左向き(<)になっており、これらのコミットがMainProjectに記録されているものの、ローカルのDbConnector
チェックアウトには存在しないことを示しています。更新を完了するには、git submodule update
を実行する必要があります。
$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
念のため、プルしたばかりのMainProjectのコミットによって新しいサブモジュールが追加された場合に備えて--init
フラグを付けてgit submodule update
を実行し、また、いずれかのサブモジュールがネストされたサブモジュールを持っている場合は--recursive
フラグを付けて実行する必要があることに注意してください。
このプロセスを自動化したい場合は、git pull
コマンドに--recurse-submodules
フラグを追加できます(Git 2.14以降)。これにより、プル直後にGitがgit submodule update
を実行し、サブモジュールを正しい状態にします。さらに、Gitに常に--recurse-submodules
を使用してプルさせたい場合は、設定オプションsubmodule.recurse
をtrue
に設定できます(これはGit 2.15以降のgit pull
で機能します)。このオプションを設定すると、Gitはサポートするすべてのコマンド(clone
を除く)で--recurse-submodules
フラグを使用するようになります。
スーパープロジェクトの更新をプルする際に特別な状況が発生することがあります。それは、プルしたコミットのいずれかで、アップストリームリポジトリが.gitmodules
ファイル内のサブモジュールのURLを変更している可能性がある場合です。これは、例えばサブモジュールプロジェクトがホスティングプラットフォームを変更した場合などに起こりえます。そのような場合、スーパープロジェクトが、あなたのリポジトリでローカルに設定されたサブモジュールリモートに見つからないサブモジュールのコミットを参照していると、git pull --recurse-submodules
またはgit submodule update
が失敗する可能性があります。この状況を改善するためには、git submodule sync
コマンドが必要です。
# copy the new URL to your local config
$ git submodule sync --recursive
# update the submodule from the new URL
$ git submodule update --init --recursive
サブモジュールでの作業
サブモジュールを使用している場合、それはメインプロジェクトのコード(または複数のサブモジュールにまたがるコード)と同時にサブモジュールのコードで作業したいと考えているためである可能性が高いです。そうでなければ、おそらくもっとシンプルな依存関係管理システム(MavenやRubygemsなど)を使用しているでしょう。
それでは、メインプロジェクトと同時にサブモジュールに変更を加え、それらの変更を同時にコミットおよび公開する例を見ていきましょう。
これまで、git submodule update
コマンドを実行してサブモジュールリポジトリから変更をフェッチすると、Gitは変更を取得してサブディレクトリ内のファイルを更新しますが、サブモジュールリポジトリは「detached HEAD」状態と呼ばれる状態のままになります。これは、変更を追跡するローカル作業ブランチ(例えばmaster
など)がないことを意味します。作業ブランチが変更を追跡していないということは、サブモジュールに変更をコミットしても、次にgit submodule update
を実行したときにその変更が失われる可能性が非常に高いということです。サブモジュールの変更を追跡したい場合は、いくつかの追加の手順を実行する必要があります。
サブモジュールをより簡単に変更できるように設定するには、2つのことを行う必要があります。各サブモジュールに入り、作業するブランチをチェックアウトする必要があります。次に、変更を加えて後でgit submodule update --remote
がアップストリームから新しい作業をプルした場合にGitに何をすべきかを指示する必要があります。オプションとして、ローカルの作業にそれらをマージするか、新しい変更の上にローカルの作業をリベースしようとすることができます。
まず、サブモジュールディレクトリに入り、ブランチをチェックアウトしましょう。
$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
「merge」オプションでサブモジュールを更新してみましょう。手動で指定するには、update
呼び出しに--merge
オプションを追加するだけです。ここでは、このサブモジュールにサーバー上で変更があり、それがマージされることがわかります。
$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
c87d55d..92c7337 stable -> origin/stable
Updating c87d55d..92c7337
Fast-forward
src/main.c | 1 +
1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'
DbConnector
ディレクトリに入ると、新しい変更がすでにローカルのstable
ブランチにマージされています。次に、ライブラリに独自のローカル変更を加え、同時に他の誰かがアップストリームに別の変更をプッシュした場合に何が起こるか見てみましょう。
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'Unicode support'
[stable f906e16] Unicode support
1 file changed, 1 insertion(+)
ここでサブモジュールを更新すると、ローカルに変更を加えており、アップストリームにも取り込む必要がある変更がある場合に何が起こるかを確認できます。
$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
--rebase
または--merge
を忘れると、Gitはサブモジュールをサーバー上のものに更新し、プロジェクトをdetached HEAD状態にリセットするだけです。
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
これが起こっても心配いりません。単にディレクトリに戻って再度自分のブランチをチェックアウトし(作業はそのまま残っています)、origin/stable
(または任意の他のリモートブランチ)を手動でマージまたはリベースするだけで済みます。
サブモジュールで変更をコミットしておらず、問題を引き起こす可能性のあるsubmodule update
を実行した場合、Gitは変更をフェッチしますが、サブモジュールディレクトリ内の未保存の作業を上書きすることはありません。
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
アップストリームで変更されたものと競合する変更を行った場合、更新を実行したときにGitが通知します。
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
サブモジュールディレクトリに入り、通常どおり競合を解決できます。
サブモジュールの変更の公開
これで、サブモジュールディレクトリにいくつかの変更があります。これらの変更の一部は、更新によってアップストリームから取り込まれたものであり、その他はローカルで行われ、まだプッシュされていないため、他の誰にも利用できません。
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> Update setup script
> Unicode support
> Remove unnecessary method
> Add new option for conn pooling
メインプロジェクトでコミットして、サブモジュールの変更も一緒にプッシュせずにアップロードした場合、私たちの変更をチェックアウトしようとする他の人々は、依存しているサブモジュールの変更を取得する方法がないため、困ることになります。それらの変更は、私たちのローカルコピーにのみ存在することになります。
これが起こらないようにするには、メインプロジェクトをプッシュする前に、すべてのサブモジュールが適切にプッシュされているかGitに確認させることができます。git push
コマンドは、--recurse-submodules
引数を取り、これは「check」または「on-demand」に設定できます。「check」オプションは、コミットされたサブモジュールの変更がプッシュされていない場合、push
を単純に失敗させます。
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
ご覧のとおり、次に行うべきことについていくつかの役立つアドバイスも提供されます。簡単なオプションは、各サブモジュールに入り、手動でリモートにプッシュして外部から利用可能であることを確認し、その後、このプッシュを再度試すことです。すべてのプッシュで「check」動作をさせたい場合は、git config push.recurseSubmodules check
を実行することでこの動作をデフォルトにできます。
もう1つのオプションは、「on-demand」値を使用することです。これにより、Gitがこれを自動的に試みます。
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
ご覧のとおり、GitはDbConnector
モジュールに入り、メインプロジェクトをプッシュする前にそれをプッシュしました。もし何らかの理由でそのサブモジュールのプッシュが失敗した場合、メインプロジェクトのプッシュも失敗します。この動作をデフォルトにしたい場合は、git config push.recurseSubmodules on-demand
を実行することで設定できます。
サブモジュールの変更のマージ
もしあなたが他の誰かと同時にサブモジュールの参照を変更した場合、いくつかの問題に遭遇するかもしれません。つまり、サブモジュールの履歴が分岐し、スーパープロジェクトの異なるブランチにコミットされている場合、修正にはある程度の作業が必要になることがあります。
もしコミットのいずれかがもう一方の直接の祖先である場合(ファストフォワードマージ)、Gitは単純にマージのために後者を選択するため、問題なく機能します。
しかし、Gitは単純なマージであっても試みません。サブモジュールのコミットが分岐しており、マージする必要がある場合、次のようなものが表示されます。
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
9a377d1..eb974f8 master -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
つまり、ここで何が起こったかというと、Gitが2つのブランチがサブモジュールの履歴において分岐した点を記録しており、マージする必要があると判断したということです。それは「merge following commits not found」と説明されており、これは混乱を招きますが、その理由については後ほど説明します。
問題を解決するには、サブモジュールがどの状態であるべきかを把握する必要があります。奇妙なことに、Gitはここではあまり役立つ情報を提供してくれず、履歴の両側のコミットのSHA-1すら示してくれません。幸いにも、それは簡単に把握できます。git diff
を実行すると、マージしようとしていた両方のブランチに記録されているコミットのSHA-1を取得できます。
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
したがって、このケースでは、eb41d76
は**私たちが**持っていたサブモジュールのコミットであり、c771610
はアップストリームが持っていたコミットです。サブモジュールディレクトリに入ると、マージがそれに影響を与えていないため、すでにeb41d76
上にあるはずです。もし何らかの理由でそうでない場合、それを指すブランチを簡単に作成してチェックアウトできます。
重要なのは、もう一方のコミットのSHA-1です。これはマージして解決しなければならないものです。SHA-1を直接指定してマージを試すか、そのためのブランチを作成してからマージを試すことができます。より良いマージコミットメッセージを作成するためだけでも、後者を推奨します。
そこで、サブモジュールディレクトリに入り、git diff
から得られた2番目のSHA-1に基づいて「try-merge」という名前のブランチを作成し、手動でマージします。
$ cd DbConnector
$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135
$ git branch try-merge c771610
$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.
ここで実際のマージ競合が発生したので、それを解決してコミットすれば、その結果でメインプロジェクトを簡単に更新できます。
$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes
$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
-Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)
$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
-
まず、競合を解決します。
-
次に、メインプロジェクトディレクトリに戻ります。
-
もう一度SHA-1を確認できます。
-
競合しているサブモジュールエントリを解決します。
-
マージをコミットします。
少し混乱するかもしれませんが、実際にはそれほど難しくありません。
興味深いことに、Gitが扱う別のケースがあります。もしサブモジュールディレクトリに、履歴に**両方の**コミットを含むマージコミットが存在する場合、Gitはそれを可能な解決策として提案します。サブモジュールプロジェクトのある時点で、誰かがこれら2つのコミットを含むブランチをマージしたことをGitが認識するため、そのマージを望むかもしれません。
これが、以前のエラーメッセージが「merge following commits not found」だった理由です。Gitが**これを**実行できなかったからです。誰がGitに**これ**を実行しようとすることを期待するでしょうか?混乱を招きます。
もし単一の許容できるマージコミットが見つかった場合、次のようなものが表示されます。
$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:
git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"
which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Gitが提供する推奨コマンドは、git add
を実行したかのようにインデックスを更新し(これにより競合が解消されます)、その後コミットします。しかし、おそらくこれをすべきではありません。サブモジュールディレクトリに入り、何が違うのかを確認し、このコミットにファストフォワードし、適切にテストしてからコミットする方が簡単です。
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'
これは同じことを達成しますが、少なくともこの方法なら、機能することを検証でき、完了時にサブモジュールディレクトリにコードが残ります。
サブモジュールのヒント
サブモジュールでの作業を少し簡単にするためにできることがいくつかあります。
サブモジュールforeach
各サブモジュールで任意のコマンドを実行するforeach
サブモジュールコマンドがあります。これは、同じプロジェクトに多数のサブモジュールがある場合に非常に役立ちます。
例えば、新しい機能を開始したりバグ修正を行ったりしたい場合で、複数のサブモジュールで作業が進んでいるとします。すべてのサブモジュールでの作業を簡単にスタッシュできます。
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
その後、新しいブランチを作成し、すべてのサブモジュールでそれに切り替えることができます。
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
お分かりいただけたでしょう。非常に便利なことの1つは、メインプロジェクトとすべてのサブプロジェクトで何が変更されたかの統一された差分をきれいに生成できることです。
$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)
commit_pager_choice();
+ url = url_decode(url_orig);
+
/* build alias_argv */
alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}
+char *url_decode(const char *url)
+{
+ return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
ここでは、サブモジュールで関数を定義し、メインプロジェクトでそれを呼び出していることがわかります。これは明らかに単純化された例ですが、これがどのように役立つかのアイデアを提供できれば幸いです。
便利なエイリアス
これらのコマンドの中には非常に長いものもあり、ほとんどのコマンドにデフォルトを設定するための設定オプションがないため、いくつかのエイリアスを設定することをお勧めします。Gitエイリアスの設定についてはGitエイリアスで説明しましたが、Gitでサブモジュールを頻繁に扱う予定がある場合に設定したい例をここに示します。
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
こうすることで、サブモジュールを更新したい場合はgit supdate
を、サブモジュールの依存関係チェックとともにプッシュしたい場合はgit spush
を簡単に実行できます。
サブモジュールの問題
ただし、サブモジュールの使用には問題がないわけではありません。
ブランチの切り替え
例えば、サブモジュールを含むブランチの切り替えは、Git 2.13より古いバージョンでは難しい場合があります。新しいブランチを作成し、そこにサブモジュールを追加した後、そのサブモジュールがないブランチに切り替えると、サブモジュールディレクトリが未追跡のディレクトリとして残ります。
$ git --version
git version 2.12.2
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
(use "git add <file>..." to include in what will be committed)
CryptoLibrary/
nothing added to commit but untracked files present (use "git add" to track)
ディレクトリを削除するのは難しくありませんが、それがそこにあると少し混乱するかもしれません。もしそれを削除し、そのサブモジュールがあるブランチに切り替える場合、submodule update --init
を実行して再設定する必要があります。
$ git clean -ffdx
Removing CryptoLibrary/
$ git checkout add-crypto
Switched to branch 'add-crypto'
$ ls CryptoLibrary/
$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'
$ ls CryptoLibrary/
Makefile includes scripts src
繰り返しになりますが、それほど難しくはありませんが、少し混乱する可能性があります。
新しいGitバージョン(Git 2.13以降)では、git checkout
コマンドに--recurse-submodules
フラグが追加され、これらすべてが簡素化されました。これにより、サブモジュールを切り替えるブランチの正しい状態に配置する処理が行われます。
$ git --version
git version 2.13.3
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
git checkout
の--recurse-submodules
フラグを使用することは、スーパープロジェクト内で複数のブランチを扱い、それぞれが異なるコミットを指すサブモジュールを持っている場合にも役立ちます。実際、異なるコミットでサブモジュールを記録しているブランチ間を切り替えると、git status
を実行した際にサブモジュールが「modified」(変更済み)と表示され、「new commits」(新しいコミット)を示すことがあります。これは、ブランチを切り替える際にサブモジュールの状態がデフォルトで引き継がれないためです。
これは非常に混乱を招く可能性があるため、プロジェクトにサブモジュールがある場合は常にgit checkout --recurse-submodules
を使用することをお勧めします。--recurse-submodules
フラグがない古いGitバージョンでは、チェックアウト後にgit submodule update --init --recursive
を使用してサブモジュールを正しい状態にすることができます。
幸いなことに、Git(2.14以降)では、設定オプションsubmodule.recurse
をtrue
に設定することで、常に--recurse-submodules
フラグを使用するようにGitに指示できます(git config submodule.recurse true
)。上記で述べたように、これによりGitは、--recurse-submodules
オプションを持つすべてのコマンド(git clone
を除く)でサブモジュールに再帰的に処理を行うようになります。
サブディレクトリからサブモジュールへの切り替え
多くの人が遭遇するもう一つの主な注意点は、サブディレクトリからサブモジュールへの切り替えに関することです。プロジェクト内でファイルを追跡しており、それらをサブモジュールに移動したい場合、注意しないとGitに怒られます。プロジェクトのサブディレクトリにファイルがあり、それをサブモジュールに切り替えたいと仮定します。サブディレクトリを削除してからsubmodule add
を実行すると、Gitがエラーを出します。
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
まずCryptoLibrary
ディレクトリのステージングを解除する必要があります。その後、サブモジュールを追加できます。
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
さて、それをブランチで行ったとします。それらのファイルがサブモジュールではなく実際のツリーにまだ存在するブランチに戻ろうとすると、このエラーが発生します。
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
checkout -f
で強制的に切り替えることはできますが、未保存の変更がある場合はそのコマンドで上書きされる可能性があるため注意してください。
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
その後、切り替えると、なぜか空のCryptoLibrary
ディレクトリができ、git submodule update
でも修正されない場合があります。サブモジュールディレクトリに入り、git checkout .
を実行してすべてのファイルを元に戻す必要があるかもしれません。これをsubmodule foreach
スクリプトで実行して、複数のサブモジュールに対して実行することもできます。
今日のサブモジュールは、すべてのGitデータをトッププロジェクトの.git
ディレクトリに保持することに注意することが重要です。したがって、非常に古いGitバージョンとは異なり、サブモジュールディレクトリを削除しても、持っていたコミットやブランチが失われることはありません。
これらのツールを使用することで、サブモジュールは、関連しているが個別の複数のプロジェクトで同時に開発を行うための、かなりシンプルで効果的な方法となり得ます。