2017年08月01日

git プロジェクトの構成

複数のプロジェクトの管理について整理する。
1つのリポジトリに、1つのプロジェクト(リリースモジュールが1つ)を格納するのはシンプルな形。リモートリポジトリを準備して、チームでローカルリポジトリを持つ構成だったとしても簡単に想像できる。
git_module01.png


1つのプロジェクトに複数のリリースモジュールがあり、それぞれを別チームで開発しているとする場合はどううしたら良いか。

1つのプロジェクトではあるが、リリースモジュールが相互に関連しておらず、参照する必要がなければ各チームでリポジトリを準備したほうが良い。それぞれの履歴を持つので、履歴を確認するのが簡単。

作業メンバーはそれぞれに必要なリモートリポジトリからクローンすれば良いので、チームに必要のないデータを持たなくてよい。
git_module02.png


逆に、複数モジュールがあるが最終的にまとめて結合してリリースするような場合、リポジトリが複数あると、採取的にまとめるときに、複数のリポジトリそれぞれからクローンする必要があり、このよう場合はリポジトリがまとまってくれていたほうが良かったのにと思うときがある。
git_module03.png

どのような形であれ、チーム単位で見たらそれぞれのリポジトリを単独で使って作業をする限りは管理はさほど難しくならない。
もし、他のリポジトリを自分のチームのリポジトリへ取り込む必要が出てきたらどうすれば良いか?






別のリポジトリを追跡する


例えば、ライブラリを組み込んで使うときのように、異なるモジュールを組み込んで使う場合はどうすべきか?
gitには、外部のリポジトリを取り込むことができるサブモジュールという機能がある。


例として、まずは取り込む元となるリモートリポジトリを準備する。
$ mkdir submodule.git && cd submodule.git
$ git init --bare
Initialized empty Git repository in **********/submodule.git/

このリモートリポジトリをクローンして作業する。簡単な作業として、ファイルを追加してコミットし、プッシュまでしておく。
$ git clone **********/submodule.git/
Cloning into 'submodule'...
warning: You appear to have cloned an empty repository.
done.
$ cd submodule/
$ echo "submodeule" > submodule.txt

$ git add submodule.txt

$ git commit -m "Add new file : submodule.txt"
[master (root-commit) e609385] Add new file : submodule.txt
1 file changed, 1 insertion(+)
create mode 100644 submodule.txt

$ git push origin
Counting objects: 3, done.
Writing objects: 100% (3/3), 234 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To **********/submodule.git/
* [new branch] master -> master

コミットID:e609385 でファイルを追加した。


このリモートリポジトリの取り込み先としてのプロジェクトを同様に準備する。
$ mkdir project1.git && cd project1.git
$ git init --bare
Initialized empty Git repository in **********/project1.git/

$ git clone **********/project1.git/
Cloning into 'project1'...
warning: You appear to have cloned an empty repository.
done.
$ cd project1/
$ echo "project1" > project1.txt

$ git add project1.txt

$ git commit -m "Add new file : project1.txt"
[master (root-commit) 14e6026] Add new file : project1.txt
1 file changed, 1 insertion(+)
create mode 100644 project1.txt

$ git push origin
Counting objects: 3, done.
Writing objects: 100% (3/3), 231 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To **********/project1.git/
* [new branch] master -> master

これにサブモジュール機能を適用していく。




プロジェクトにサブモジュールを追加する


"sub" ディレクトリを作成して、そこに追加してみる。
$ mkdir sub && cd sub

サブモジュールとして追加するリポジトリ(先に作ったリモートリポジトリ)を指定する。

$ git remote add **********/submodule.git/

Cloning into 'sub/submodule'...
done.

ステータスを確認してみる。
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
(use "git reset HEAD ..." to unstage)

new file: .gitmodules
new file: sub/submodule
取り込みたいファイルと ".gitmodules" が追加され、ステージされている。

diff も見てみる。
$ git diff --cached
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..fdda908
--- /dev/null
+++ b/.gitmodules

@@ -0,0 +1,3 @@
+[submodule "sub/submodule"]
+ path = sub/submodule
+ url = **********/submodule.git/

diff --git a/sub/submodule b/sub/submodule
new file mode 160000
index 0000000..e609385
--- /dev/null
+++ b/sub/submodule

@@ -0,0 +1 @@
+Subproject commit e60938592340493b0a9ba52682c8c0f41c1aa0c2

"+Subproject commit e60938592340493b0a9ba52682c8c0f41c1aa0c2" は、登録したサブモジュールの取り込み元となるコミットを示している。

".git/config" にも情報が書き込まれている。
[submodule "sub/submodule"]
url = **********//submodule.git/




これをコミットすれば、サブモジュールの追加が完了。プッシュもしておく。
$ git commit -m "Add submodule : sub/submodule/"
[master 1df63d2] Add submodule : sub/submodule/
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 sub/submodule

$ git push origin
Counting objects: 4, done.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 417 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To **********/project1.git/
14e6026..1df63d2 master -> master

git_module04.png





サブモジュールを持つプロジェクトをクローンする


リモートリポジトリにプッシュしたので、これを他のメンバーにも展開したい。
他のメンバーはまずは普通にプロジェクトをクローンし、クローンしたファイルの中身を見てみる。
$ git clone  **********/project1.git/
Cloning into 'project1'...
done.


├ .git
└ ....
├ .gitmodules
├ project1.txt
└ sub
└ submodule

"sub/submodule" ディレクトリはあるが、肝心のサブモジュールとしてのファイルが存在していない。それに ".git/config" にもサブモジュールの情報が書き込まれていない。
git_module05.png


サブモジュールのデータを引っ張ってくるためには、以下のようにする。

$ git submodule init
$ git submodule update

または

$ git submodule update --init


init で、".gitmodules" の情報を ".git/config" に書き込む。
updateで、データを引っ張ってきた状態にする。update --init はこれらを一気に行う操作。

さらに、この2つの操作を --recursive オプションでクローンと同時に実行することもできる。

$ git clone --recursive **********/project1.git/




ここで間違えてはいけないことは update 操作。
これは取り込み元のリポジトリの最新をもってくるのではなく、取り込み先のリポジトリ(ここでは "project1.git")に登録されたサブモジュールの情報から、取り込むデータを引っ張ってくる操作。このため取り込み元のリポジトリが更新されていたとしても関係なく、取り込み先にサブモジュールとして登録されているコミットIDからデータを引っ張ってくる。
git_module06.png




サブモジュールを最新に更新する


サブモジュールの取り込み元のリポジトリが更新され、そして取り込み先でもその最新リポジトリ内容を使うことになったとする。そうなるとサブモジュール自体を最新に更新する操作が必要となる。

取り込み元のリポジトリの最新コミットで更新するのであれば以下のコマンドを使う。

$ git submodule foreach git pull <remote> <branch>


もし "master" ブランチだけであれば、省略して、

$ git submodule foreach git pull


この操作後、変化点が出てくるので、ステージして、コミットし、プッシュすれば更新完了。

$ git add.
$ git commit -m "Update submodule"
$ git push --all


サブモジュールの参照内容は切り替わっている。



では最新ではなく、取り込み元リポジトリ内の特定コミット状態に更新したい場合はどうするのか?

その前に、サブモジュールはどのような状態か調べてみる。
サブモジュールがあるディレクトリに移動し、ステータスとリモートリポジトリを見てみる。
$ cd sub/submodule/
$ git status
HEAD detached at e609385
nothing to commit, working directory clean

$ git remote -v
origin **********/submodule.git/ (fetch)
origin **********/submodule.git/ (push)

取り込み先(ここでは "project1.git")が参照するリモートリポジトリを指しておらず、取り込み元のリモートリポジトリを指している。
実は、サブモジュールがあるディレクトリ以下は、取り込み元のリポジトリの管理下にあり、さらにサブモジュールは "detached HEAD" の状態と同じだとわかる。
git_module07.png
サブモジュールは "detached HEAD" で直接コミットIDを参照しているので、取り込み元のリポジトリが変化しても影響を受けずに存在している。
そして取り込み元のgit管理下に置かれているので、取り込み元のリポジトリの内容でブランチの切替もチェックアウト操作も可能。


話を戻して、特定のコミットで更新する場合はどうすれば良いのかというと、
まずは更新するためのコミットIDを知ることから行う。サブモジュールの管理下に移動して、リモートリポジトリからプルして最新情報を取得する。後はブランチを切り替え、ログを確認するなどして必要なコミットIDを取得する。
$ git checkout new_branch
Previous HEAD position was e609385... Add new file : submodule.txt
Branch new_branch set up to track remote branch new_branch from origin.
Switched to a new branch 'new_branch'

$ git log --oneline
83386e4 modified new_branch submodule1
dba71a8 modified submodule3
62dfb39 modified submodule2
e609385 Add new file : submodule.txt

次に、そのコミットIDを使い、サブモジュールを "detached HEAD" 状態に切り替える。
例として、コミットID:dba71a8 に切り替えてみる。
$ git checkout dba71a8
Previous HEAD position was 83386e4... modified new_branch submodule1
HEAD is now at dba71a8... modified submodule3

これで更新できているので、取り込み先のgit管理下に戻して、
この状態をステージし、コミットし、プッシュすれば更新が完了する。
$ git add .
$ git commit -m "Update submodule"
[master f1b5a0b] Update submodule
1 file changed, 1 insertion(+), 1 deletion(-)

$ git push --all
Counting objects: 3, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 286 bytes | 0 bytes/s, done.
Total 3 (delta 1), reused 0 (delta 0)
To **********/project1.git/
1df63d2..f1b5a0b master -> master




他のメンバーは、通常通りにリモートリポジトリから最新状態に取得しローカルリポジトリを更新したら、サブモジュールをリモートリポジトリの管理状態に更新するだけ。

$ git submodule update






サブモジュールを削除する


サブモジュールが不要になれば、サブモジュールを追加したディレクトリを指定して操作を行う。

$ git submodule deinit sub/submodule/

Cleared directory 'sub/submodule'
Submodule 'sub/submodule' (**********/submodule.git/) unregistered for path 'sub/submodule'
これでサブモジュールを削除できる。




まとめ


サブモジュールは外部のリポジトリを参照し、うまく取り込むことができるので、プロジェクトの構成内容の管理を分散できてパッと見は便利に思える。開発を前に進めて行くときは便利だと思うが、過去の内容を確認したくて過去のリビジョンをチェックアウトするときには注意が必要。サブモジュールはリポジトリの管理が異なるので、同時にはは過去の状態に戻ってくれない。サブモジュールを戻すにはそのリビジョン当時のコミットIDを指定して、別途チェックアウトしてあげないといけない。このような動作の癖を理解して、構成管理をしたうえでプロジェクトを進めないと、過去のリビジョンを急ぎ確認したいときに、間違った構成のまま作業を進めてしまって、要らない苦労をするかもしれない。
サブモジュールを取りこんでそのあとの管理の仕方をメンバー内で共有し、リポジトリが別管理であることを意識して作業することができれば便利だと思う。





gitをインストール
gitサーバーのセットアップ
git ブランチについて
git 変更を一時的に退避 stash
git ブランチを合流するマージ
git ブランチを付け替える
git コミット履歴を変更する
git 変更をリセットする
git リモートでの操作
git ファイルの追跡
git リリース準備
git リモートブランチを追加
git チェックアウトをもっと便利に使う
git 変更をpatchファイルにする
git コンフリクトに対処する
git 失敗したときの復元
posted by Zorinos at 20:00| Comment(0) | Linux | 更新情報をチェックする