Git Submodule 的認識與正確使用!

此篇文章最近更新時間為2010-11-14 04:50:52 目前共有21篇留言

關於作者 - JosephJ

任職於 Faria。喜好戶外運動、2008 年 5 月完成「跑步環島」。對於新技術跟程式碼有著強烈的偏執狂。

已經用了 git submodule 好一陣子了,今天看到了 Git submodules in N easy steps 才覺得比較搞懂一些之前碰到的問題。趁機來整理、釐清之前常碰到的小問題吧~~

什麼是 Git Submodule

剛剛從 SVN 或 CVS 等 Client-Server 架構的版本控制系統切換到 Git 時,可能會有這個想法:「能不能只取得一部分的程式碼、而非整個 Repository?」因為在 SVN/CVS 可以針對 Repository 中的某個目錄 checkout,不需要是整個 Repository、甚至還可以用 SVN Externals 達到不同角色 (視覺、前端、後端)checkout 不同 File Layout(之前在無名小站時,超喜歡 svn:externals 的概念)。

但 Git 是分散式的版本控制系統,每個人都是一個完整的 Repository,沒辦法像 SVN/CVS 指定到某個資料夾。例如你要取得 YUI 3 的 Git,只能 git clone https://github.com/yui/yui3.git、而不能指定到底下的目錄。

SVN/CVS 你可以用目錄區隔大小專案、都在同一個大的 Repository。而 Git 的想法必須修正為每個小專案就是一個 Repository、或不同團隊開發是一個 Repository、甚至功能獨立也可以是一個 Repository。若說 SVN 是包容百川、Git 就是各自獨立的小河流

軟體開發團隊不太容易如此單純,有時需要給外包開發、有時需要分工、有時需要用 Open Source,光用以上的切分方式是沒辦法達成所有需求的、還是得將各自獨立的小河流連接起來。例如我先前在 WebRebuild 與 COSCUP 分享的 JavaScript Platform,為了分享把原始碼放了一份到 Github : http://github.com/josephj/javascript-platform-yui,但我的工作及部落格都有使用的需求,該怎麼做呢?如果每次都得 git clone 再 copy 檔案到兩個地方、這樣手工做真的是個很遜的解決方案。好在有 Git Submodule 可以幫忙解決!

簡單來說,Git Submodule 可以輕易地將別人的 git 掛入到你目前 git 的任何位置

新增一個 Git Submodule

例如我有目前本機有一個 josephj.git、在 /home/josephj/www 下,而我需要將 javascript platform 放到 /home/josephj/www/static/ 可以用以下幾行快速達成。

  • 切換到我的 repository 目錄:
    $ cd /home/josephj/www
  • 使用 git submodule add [repository 位置] [欲放置的位置] 增加一個新的 submodule:
    $ git submodule add git@github.com:josephj/javascript-platform-yui.git static/platform 需要注意 [欲放置的位置] 不能以 / 結尾(會造成修改不生效)、也不能已經是現有的路徑喔(不能順利 Clone)。
  • 按下去就會看到以下結果:
    $ git submodule add git://github.com/josephj/javascript-platform-yui.git static/platform
    Initialized empty Git repository in /home/josephj/www/static/platform/.git/
    remote: Counting objects: 31, done.
    remote: Compressing objects: 100% (31/31), done.
    remote: Total 31 (delta 14), reused 0 (delta 0)
    Receiving objects: 100% (31/31), 6.06 KiB, done.
    Resolving deltas: 100% (14/14), done.
    
    這時在 /home/josephj/www/ 會產生一個 .gitmodules 記錄你的 Submodule 資訊。該 git 的相關檔案也都會在此時被拉下來
  • 用 git status 看一下:
    $ git status
    # On branch master
    # Changes to be committed:
    #   (use "git reset HEAD ..." to unstage)
    #
    #       new file:   .gitmodules
    #       new file:   static/platform
    #
    
    會發現它只列出 submodule 目錄而非所有底下檔案,parent git 實際上也只會記錄 submodule 的 commit id 以供未來做比對用。這裡一個很重要的點是大家必須理解的:parent git 與 submodule git 的關連性(被掛入的目錄、repository 位置)記錄在 .gitmodules 中,而版本控制則是靠 parent git 記住 submodule git 的 commit id。
  • 先 commit 一下:
    $ git add .gitmodules static/platform
    git commit -m "Add submodule into version control";
  • 但是你還必須做 init 的動作,你的 .git/config 才會有對應 submodule 的資訊。
    $ git submodule init
    

更新已安裝的 Submodule

當初我第一次新增一個 Submodule 後,以為未來它都會像 SVN External 一樣、在我 git pull 的時候自動更新。但實際情況是你必須手動處理才能更新 Submodule

  1. 進入該目錄 Subomdule 目錄: $ cd static/platform
  2. 向來源的 master branch 做 git pull 的動作(這裡的 git pull 不會更新你 parent git 的檔案) $ git pull origin master
  3. 若 submodule 有更新的檔案,你可以到 parent git 觀看一下情況:
    $ cd ../../ 
    $ git status
    # Not currently on any branch.
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #       modified:   static/platform (new commits)
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    與第一次 git submodule add 相同,submodule 更新的檔案並不會在 git status 中要求你 commit 喔!
  4. 我們前面提到,submodule 的版本控制在於 submodule git 的 commit id,上面看到 static/platform 有 new commit。表示你既然把新的內容 pull 回來、應該要更新 submodule 的 commit id 到你的 git 中:
    $ git add static/platform
    $ git commit -m "static/platform submodule updated"
    如此一來,新的 submodule commit id 就被你的 repositiory 給記錄下來囉!

團隊使用 Submodule

在一個多人的軟體開發團隊中,通常還是會有 Centralized Git Repositiory,像我們公司就採用了 gitosis 的解決方案。而像上述更新 Submodule 的情形,通常只有一兩個負責架構的人來做(大多是一開始把東西掛進來的人)、其他人只是單純使用者的角色,並不需要負責更新的工作

  1. 像上面我增加了一個 Submodule,對於團隊其他人來說,他們在下一次的 git pull 會看到以下的狀況:
    $ git status
    # On branch develop
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #       modified:   static/platform (new commits)
    #
    no changes added to commit (use "git add" and/or "git commit -a")
    這表示其他人也會拿到 .gitmodules 的設定,但他必須使用 git submodule init 將新的 Submodule 註冊到自己的 .git/config、未來才能使用。
    $ git submodule init
    Submodule 'static/platform' (http://github.com/josephj/javascript-platform-yui.git) registered for path 'static/platform'
    
  2. 接著其他人使用 git submodule update 把該 Submodule 的內容全部拉下來!
    $ git submodule update
    Cloning into static/platform...
    remote: Counting objects: 34, done.
    remote: Compressing objects: 100% (34/34), done.
    remote: Total 34 (delta 15), reused 0 (delta 0)
    Unpacking objects: 100% (34/34), done.
    Submodule path 'static/platform': checked out '117c5b3c5a195deac2e53aa118b78ef3f01ae371'
    

使用時機

簡單整理一下:
  • git submodule init: 在 .gitmodules 第一次被其他人建立或有新增內容的時候,用 git submodule init 更新你的 .git/config、設定目錄與增加 submodule 的 remote URL
  • git submodule update: 在 init 完有新的 submodule commit id 後就可以做了,會把所有相關檔案拉下來。若其他人更新 submodule 造成你拿到新的 commit id 時,你可以直接用 git submodule update 做更新即可、不需要做任何 add 或 commit 的動作!

可以想見,其他成員使用 git submodule update 的情況會遠比 git submodule init 多很多。

修改 Submodule 的內容

有時自己也是 Submodule 的 Owner,碰到要改 Code 時,要我切回原本的此 Git 開發位置有點麻煩... 不如就直接改被當成 Submodule 掛進來的原始碼吧

  1. 到 submodule 目錄去做些修改:
    $ cd static/platform
    $ vim README # 做些修改
    
  2. 接著就是常見的 git add , git commit, git push
    $ git add README
    $ git commit -m "Add comments"
    $ git push
    
  3. push 完回到根目錄git status 看一下!會看到
    $ git status
    # On branch master
    # Changed but not updated:
    #   (use "git add ..." to update what will be committed)
    #   (use "git checkout -- ..." to discard changes in working directory)
    #
    #       modified:   static/platform
    #
    no changes added to commit (use "git add" and/or "git commit -a")
  4. 這裡也需要再做一次 Commit 喔!
    $ git add static/platform
    $ git commit -m 'Submodule updated'
    $ git push
    

這裡有一點非常需要注意,因為 Submodule 的更新只記錄 commit id,所以你必須先在 submodule 內做 commit、push 後、再到 parent git 做 push,不然會出現版本錯亂的問題,別人跟你 submodule 的內容將會不一致。

如何移除 Submodule

這點也非常地不直覺,不是想像中 git submodule remove [欲移除的目錄] 這麼簡單...

  1. 先砍掉目錄:
    $ git rm --cached [欲移除的目錄]
    $ rm -rf [欲移除的目錄]
  2. 再修改 .gitmodules
    $ vim .gitmodules
    將相關內容移除
  3. 再修改 .git/config
    $ vim .git/config
    將相關內容移除
  4. 最後再 commit,改變整個 Repository。
    $ git add .gitmodules
    $ git commit -m "Remove a submodule"
    
  5. 安全起見再做個 sync:
    $ git submodule sync

結語

我們公司目前主要將 Submodule 運用在與外包公司的合作上,因為彼此 Engineering 團隊負責的專案項目雖不同,但有部分的開發會需要在我們的結構下開目錄,我們也不希望他們改到我們的程式,此時 Git Submodule 提供了非常好的分工效果:把他們開發好的東西掛進來、更新即可。另有一點很重要的是, Git Submodule 內還可以將其他的 Submodule 給掛進來,形成一個巢狀式的結構,彈性非常地大。我們只要抓他們的大 Git 當 Submodule,下面怎麼掛就由外包公司決定。

整篇文章看下來,會發現 git submodule 的操作有許多需要注意的地方,像是更新、修改、刪除都要遵循一定的程序,不然你 PUSH 回 Central Repository 時,別人 PULL 下來的 Submodule 可能並不會更新,就會產生混亂了 Orz...

暇不掩瑜,Git Submodule 還是一個強大且團隊開發上非常重要的功能,就盡量使用前先搞懂、小心使用囉 ;)



Comments

  1. yi 2014-08-21 11:01:52
    Very useful, sounds great!
  2. 凍仁翔 2014-04-22 17:42:17
    小弟在使用 git submodule 時想更改 submoudle 的設定時遇到「You are on a branch yet to be born」的錯誤訊息,爬完文後動了一下 .git/modules/ 裡的設定即可,來此補一下心得。

    資料來源:
    - “You are on a branch yet to be born” when adding git submodule http://stackoverflow.com/questions/11887203/you-are-on-a-branch-yet-to-be-born-when-adding-git-submodule
  3. Yi 2013-06-18 23:42:41
    嗨,請問刪除submodule的部份是不是要加上.git/modules/呢?
    我試了很久一直都怪怪的,後來發現這裡好像也要刪掉。
  4. スーツ burberry 2013-05-20 16:21:45
    スーツ burberry
    バーバリー コート
    burberry メンズ
    スーツ バーバリー
    正規代理店,品質保証!


    ★2013人気製品
    、新材料有限公司
    2013年最も人気のあるハイエンド帽子ストック★大
    ハイエンド安い帽子スタイル
    良い品質、
    低価格
    ┓┏┓┏┓
    ┻┗┛┗┛%品質保証
     満足保障。
    ありがとう。 (^ 0 ^)
    私の顧客大きな買い物

    U R L:http://www.mgpvvt.com
    リンクス:http://www.mgsyrk.com
  5. MeaCulpa 2012-08-05 08:07:20
    若真的要集中管理,还是stick with SVN吧。 或者试试更简单的Bzr之类
  6. wo_is神仙 2012-03-05 12:09:30
    很好的教程,通俗易懂~
  7. Dongsheng Cai 2011-05-15 22:26:46
    刚刚把几个外部模块弄成了submodule,满怀希望git pull的时候会自动更新,结果让我失望了 :D
    解释的很详细的文章,多谢!
  8. josephj 2011-01-13 20:04:51
    bare repository 應該沒辦法直接這樣用耶
    我能想到的都是得分兩段來做的
    目前真的沒有答案了 :p
  9. Clarify 2011-01-10 16:48:56
    先感謝撥空回覆...^_^
    若 blog.git 是 bare repository, 當中 js/jquery, 有辦法 git clone ssh://git@a.com:/blog/js/jquery 嗎?
    我是用gitolite, 需要其他設定嗎?
  10. josephj 2011-01-10 13:59:02
    clarify,

    下面的範例、沒有 syntax highlight 看起來比較累些

    不過要說的是:submodule 本身也是一個 repository
    所以也可以直接 git clone 的 ;)
  11. josephj 2011-01-10 13:56:34
    # User 1 建立 blog.git 並用 git submodule 將 jquery.git 掛入
    [josephj@a.com:~]$ mkdir blog
    [josephj@a.com:~]$ cd blog
    [josephj@a.com:~/blog]$ git init .
    Initialized empty Git repository in /home/josephj/blog/.git/
    [josephj@a.com:~/blog]$ touch README
    [josephj@a.com:~/blog]$ git add .
    [josephj@a.com:~/blog]$ git commit -am 'init commit'
    [master (root-commit) 045cc70] init commit
    0 files changed, 0 insertions(+), 0 deletions(-)
    create mode 100644 README
    [josephj@a.com:~/blog]$ mkdir js
    [josephj@a.com:~/blog]$ git submodule add https://github.com/jquery/jquery.git js/jquery
    Initialized empty Git repository in /home/josephj/blog/js/jquery/.git/

    # User 2 : git clone、用 SSH 的方式將 a.com 的 jquery repository 給抓取一份回來
    [clarify@b.com:~]$ git clone ssh://clarify@a.com:/home/josephj/blog/js/jquery jquery
    Initialized empty Git repository in /home/clarify/jquery/.git/
  12. Clarify 2011-01-09 20:01:40
    我把情節說清楚一下...:)
    remote: 有 jquery.git
    user1: 有 blog.git, 並向 remote 以 submodule 掛入 jquery.git
    user2: 無法 clone 在 remote 的 jquery.git 之下, 如何能只 clone 在 user1 的 blog.git 中的 js/jquery
    是指 git clone git://domain.ip/blog.git/js/jquery.git 可行嗎?
  13. josephj 2011-01-07 22:22:47
    Clarify,

    這樣做是可以的
    就是先將 jquery 給 git clone 回來,它就是獨立的一個 repository 了
    接著再把 local jquery repository 用 git submodule 掛入你的 blog repository
    實際上你還是有一份 jquery 的 repository
  14. Clarify 2011-01-07 21:07:06
    是用 submodule 的方式掛進去, 有辦法 git clone 把此單一 repository 給拉出來嗎?
    是指 git clone git://domain.ip/blog.git/js/jquery.git 可行嗎?
  15. josephj 2011-01-06 17:07:21
    Clarify,

    如果 blog.git 的 js/jquery 當初不是用 submoudle 的方式掛進去的
    例如是下載回來、解壓縮至此的
    就沒辦法用 git clone 把此單一目錄給拉出來

    SVN 跟 Git 的架構完全不同
    git clone 是整個 repository、是 repository 對 repository
    svn checkout 只是某個目錄、是 client 對 server repository
    要達到 svn checkout 是不可能的,類似效果只能用 submodule 來達成
  16. Clarify 2011-01-06 16:53:42
    簡單說若 SVN 可以只 checkout 子目錄而不是整個 repository
    Git 如何能只 clone 在 blog.git 中的 js/jquery
    這樣的推論是否成立?
  17. josephj 2011-01-05 18:41:20
    不知道有沒有回答到 Clarify 的問題
    想要強調一下,submodule 一定是一個功能完整、獨立的 Git
  18. josephj 2011-01-05 18:38:21
    比如說我已經有一個 blog 的 local Git repository
    位於 /var/www/blog
    我想要將 jQuery 直接用 submodule 的方式納入我的 blog.git 中

    就這樣做...
    $ cd /var/www/blog
    $ mkdir js
    $ git submodule add js/jquery https://github.com/jquery/jquery.git

    未來你如果看到 jQuery 在 GitHub 有新的版本
    你可以這樣抓到最新的原始碼:
    $ cd /var/www/blog/js/jquery
    $ git pull

    這樣會改變 js/jquery submodule 的 commit id
    所以你還要這樣做,將變動寫到你的 blog.git repository 中:

    $ cd /var/www/blog
    $ git add js/jquery
    $ git commit -m 'jquery submodule updated'

    簡單來說就是把別人一直在開發的東西變成是你程式的一部分啦 ;)
  19. Clarify 2011-01-05 10:21:58
    文中提到...
    在 SVN/CVS 可以針對 Repository 中的某個目錄 checkout,不需要是整個 Repository..
    但對照 Git 的話
    Git 能只 clone 在整個 Repository 內的某個 submodule 嗎? if possible, how?
  20. Awoo~ 2010-11-15 10:34:24
    同意宗豪大所講的,大部分的情況要手動升級比較好 <(_ _)>

    不過有些只是單純分工
    如果 Git Submodule 可以針對這樣做設計
    其實也可以讓操作起來更方便
  21. Tsung 2010-11-15 10:11:59
    比較不直覺的一點:即使你加入了 git submodule,但每次 git pull 它並不會自動偵測並更新,你還是必須手動地進入該目錄,利用以下指令取得更新的檔案

    這點倒不是不直覺, 而是這種作法是最安全的, 舉個比較誇張點的範例來說:
    你現在的是 YUI2, 但是, git pull 後, 卻升級到 YUI3 ..
    外掛模組 是否要升級, 通常會是人工確認, 是否 stable, 或者是否需要.. 等等, 所以, 通常都會另外手動升級.
暱稱: 必填。
Email: 非必填。若填寫為不公開欄位,僅供站長參考聯繫。
內容: 必填。限 255 個字元以內。
驗證碼:
送出

Facebook Comment