201607211349Git multiple repositories management
一個專案往往會由許多 sub projects 組成才能運作,而這些 sub projects 可能被許多其他專案所共用而單獨形成一個 repository 由專人負責維護,這時候我們該怎麼管理這些 sub projects 呢?這裡列出三種管理方式,每種方式各有優缺點。
Git submodule
Git submodule 是三種方法裡面最早提出的作法,基本指令如下
初始化 submodule 專案
初始化 submodule 需要使用 submodule add 指令來告訴 main project 去建立一個 submodule
[autosun@:~/demo/Test-git]$ git submodule add https://github.com/autosun/autosun-toolset.git toolset
這時候我們打開產生的 .gitmodule 檔案就會看到 submodule 的資訊了。此外也可以看到 submodule 的 source code 被 sync 下來了。
[autosun@:~/demo/Test-git]$ cat .gitmodules [submodule "toolset"] path = toolset url = https://github.com/autosun/autosun-toolset.git [submodule "uva"] path = uva url = https://github.com/autosun/Uva-code.git
更新 submodule
當 submodule 有更新版的時候,我們需要到各個 submodule 目錄下 git pull 去拉最新的版本,或者使用 submodule foreach。
$ git submodule foreach --recursive git pull origin master
Clone 帶有 submodule 的 repository
當要去 Clone 一個帶有 submodule 的 repository 的時候,你會發現下了 git clone 之後雖然有 submodule 的目錄但是是空的,這時候還需要 submodule init 指令與 submodule update 指令去 sync。
[autosun@:~/demo]$ git clone git@github.com:autosun/Submodule-test.git [autosun@:~/demo/Submodule-test]$ git submodule init [autosun@:~/demo/Submodule-test]$ git submodule update --recursive
或是在 clone 的時候多帶 --recursive 參數才會一併 clone submodule。
[autosun@:~/demo]$ git clone --recursive git@github.com:autosun/Submodule-test.git
缺點
- Clone 時不方便
- 不便於切換 submodule 的分支
Git subtree
Subtree 是比較新的作法,有個文章寫的滿詳細的這邊借用了他的例子,你可以在這裡 Christophe Porteneuve's blog - Mastering GIt subtrees. 找到範例並下載
首先為了方便日後操作,我加入遠端 repository
[git-subtree/main]$ git remote add plugin ../remotes/plugin
初始化 subtree
初始化 subtree 跟 submodule 一樣簡單,透過 submodule add 指令即可
[git-subtree/main]$ git subtree add --prefix=vendor/plugins/demo plugin HEAD git fetch plugin master warning: no common commits remote: Counting objects: 11, done. remote: Compressing objects: 100% (9/9), done. remote: Total 11 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (11/11), done. From ../remotes/plugin * branch master -> FETCH_HEAD * [new branch] master -> plugin/master Added dir 'vendor/plugins/demo'
然後就可以從 git log 看到 sub project 的原始碼變成 main project 的一部分
[git-subtree/main]$ git log --oneline --graph --decorate * 32e539d (HEAD, master) Add 'vendor/plugins/demo/' from… |\ | * fe64799 (plugin/master) Fix repo name for main project… | * 89d24ad Main files (incl. subdir) for plugin, to populate its… | * cc88751 Initial commit * b90985a (origin/master) Main files for the project, to populate… * e052943 Initial import
如果你不想保留原有 sub project 的 commit 紀錄,可以在 add 的時候加上 squash 參數
[git-subtree/main]$ git subtree add --prefix=vendor/plugins/demo --squash plugin master git fetch plugin master From ../remotes/plugin * branch master -> FETCH_HEAD Added dir 'vendor/plugins/demo'
就可以看到所有 commit 被融合成一份
[git-subtree/main]$ git log --oneline --graph --decorate * 352af7a (HEAD, master) Merge commit '03e04026fdba2ff1200a226c3… |\ | * 03e0402 Squashed 'vendor/plugins/demo/' content from commit… * b90985a (origin/master) Main files for the project, to populate… * e052943 Initial import
更新 subtree
當 remote subtree repository 有更新的時候,可以使用 subtree pull 指令來更新,其中 --squash 是可選項目
[git-subtree/main]$ git subtree pull --prefix=vendor/plugins/demo --squash plugin master
你也可以貢獻 Subtree 的專案,透過 subtree push 指令就可以達成,不過在我的經驗裡,這麼做很容易造成 subtree 的 conflict,建議回到 subtree 原有的 repository 去修改提交。
[git-subtree/main]$ git subtree push -P vendor/plugins/demo plugin master
變更 subtree 的分支
變更 subtree 的分支也相當不方便,必須刪除現有的然後重新加入
$ git rm subtree_path $ git commit $ git subtree add --prefix=subtree_path repository_url branch_name
優點
- Clone 方便
- 不需要額外的 meta data (即 .gitmodule)
- Subtree 的 commit 紀錄可以直接看到
缺點
- Commit 紀錄不易閱讀
- 容易忘記進行 subtree pull
- subtree 切換分支不便利
Google repo
Google repo 是 Google 開發的 python script,Android 開放原始碼計畫(AOSP)也是使用它進行管理,開始之前必須先下載並安裝 repo 指令
$ curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo $ chmod a+x ~/bin/repo
撰寫 repo script
Google repo 是依照 XML 檔的描述來拉取 sub project 的 repositories,範例描述如下,這裡描述了一個叫做 github 的 remote,並且使用了兩個 projects,一個是 autosun-toolset repository,拉取放到 tool 資料夾,另一個是 Uva-code repository 放到 uva 資料夾。寫好該 XML 檔需要將之放於某 repository 上,這裡我將放到 Test-git repository 裡。
初始化 project
之後我們找個地方,把所有需要的 repositoies 都 sync 下來,使用方法先下 repo init 進行初始化,要注意的是 Test-git repository 需要有前步驟寫好的 default.xml 檔
[autosun@:~/demo]$ repo init -u https://github.com/autosun/Test-git.git -m default.xml warning: gpg (GnuPG) is not available. warning: Installing it is strongly encouraged. Get https://gerrit.googlesource.com/git-repo/clone.bundle Get https://github.com/autosun/Test-git.git % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0curl: (22) The requested URL returned error: 404 Not Found Server does not provide clone.bundle; ignoring. remote: Counting objects: 22, done. remote: Compressing objects: 100% (3/3), done. remote: Total 22 (delta 0), reused 0 (delta 0), pack-reused 19 From https://github.com/autosun/Test-git * [new branch] master -> origin/master Your identity is: autosun If you want to change this, please re-run 'repo init' with --config-name repo has been initialized in /Users/autosun/demo
完成之後會看到目錄底下有個 .repo 目錄但是還沒有程式碼,需要下 repo sync 將程式碼通通拉下來。
[autosun@:~/demo]$ repo sync Fetching project autosun/Uva-code.gitFetching project autosun/autosun-toolset.git % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0 % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0curl: (22) The requested URL returned error: 404 Not Found Server does not provide clone.bundle; ignoring. 0 0 0 0 0 0 0 0 --:--:-- 0:00:01 --:--:-- 0curl: (22) The requested URL returned error: 404 Not Found Server does not provide clone.bundle; ignoring. remote: Counting objects: 87, done. remote: Counting objects: 110, done. remote: Compressing objects: 100% (5/5), done. remote: Total 110 (delta 0), reused 0 (delta 0), pack-reused 105 Receiving objects: 100% (110/110), 22.91 KiB | 0 bytes/s, done. Resolving deltas: 100% (7/7), done. From https://github.com/autosun/Uva-code * [new branch] master -> github/master Fetching projects: 50% (1/2) remote: Total 87 (delta 0), reused 0 (delta 0), pack-reused 87 From https://github.com/autosun/autosun-toolset * [new branch] master -> github/master Fetching projects: 100% (2/2), done.
更新 project
更新 project 也是直接使用 repo sync 指令即可
變更 sub project 分支
更改 XML 檔並且下 repo sync 即可
優點
- 易於更改分支
- 可搭配 gerrit 做 code review 系統
缺點
- 不能在 main project 看到 sub project 的 Commit 紀錄
Reference
Git 工具 - 子模組 (Submodules)
https://git-scm.com/book/zh-tw/v1/Git-工具-子模組-Submodules
Git Submodule 用法筆記
http://blog.chh.tw/posts/git-submodule/
The problem with Git submodules
http://ayende.com/blog/4746/the-problem-with-git-submodules
Git SubTree 共編 Library
http://yutin.logdown.com/posts/188306-git-subtree-total-addendum-library
Alternatives To Git Submodule: Git Subtree
http://blogs.atlassian.com/2013/05/alternatives-to-git-submodule-git-subtree/
Mastering Git subtrees
https://medium.com/@porteneuve/mastering-git-subtrees-943d29a798ec
Using Google's repo command in your own projects
http://www.instructables.com/id/Using-Googles-repo-command-in-your-own-projects/
Repo command reference
https://source.android.com/source/using-repo.html
by autosun