git subtree有效管理公共第三方lib

世有因果知因求果發表於2015-05-31

  如果你的專案中有很多第三方的lib,你希望使用它,並且也希望可能對該lib做修改並且貢獻到原始的專案中去,或者你的專案希望模組化,分為幾個repo單獨維護,那麼git subtree就是一個選擇。git subtree管理的子專案在父專案中作為一個完整的程式碼copy存在,並不包含歷史資訊。綜合考慮git subtree和git submodule的優缺點,一個可行的管理策略是:使用git subtree對父專案(在該父專案中可以臨時將子專案資料夾加入到tracking中)做split,將需要單獨存在的目錄形成一個子專案,並且push出來。隨後再父專案的開發中,將子專案目錄加入git ignore中,不做版本跟蹤,子專案目錄以另一個git repo存在(git clone子專案)。所有本地副專案中的修改如果需要分享的話,只在子專案repo中提交併且push,其他成員只需pull這個子專案repo。這個策略是一個折中。

  git subtree是附屬於git的附加工具,預設情況下並不會安裝。https://github.com/git/git/tree/master/contrib/subtree。注意新版本的git中已經預設包含了subtree這個子命令

# git clone https://github.com/git
# cd git

# make prefix=/usr/local/git install
# echo "export PATH=$PATH:/usr/local/git/bin" >> /etc/bashrc
# source /etc/bashrc
# cd contrib/subtree
# make
prefix=/usr/local/git/bin
# make prefix=/usr/local/git/bin install
# make prefix=/usr/local/git/bin install-doc
# cp git-subtree /usr/local/git/bin   :一旦將git-bustree copy到git/bin目錄下,執行git subtree 命令時,git就可以查詢並且呼叫到這個subtree子命令了!
就ok了!

  在本文中,我們假設我們工作在subtree-p這個專案中(放在github上的一個repo),而在該專案中,我們需要subtree-sub這個子模組(或者說是Lib).

  當前我們的subtree-p專案中,沒有引入任何subtree-sub這個模組,現在我們通過以下命令來實現:

 

(master)$ git remote add subtree-sub-remote https://github.com/cnweibo/subtree-sub.git                                                                                     
(master)$ git remote -v                                                                                                                                                    
origin  https://github.com/cnweibo/subtree-p.git (fetch)                                                                                                                   
origin  https://github.com/cnweibo/subtree-p.git (push)                                                                                                                    
subtree-sub-remote      https://github.com/cnweibo/subtree-sub.git (fetch)                                                                                                 
subtree-sub-remote      https://github.com/cnweibo/subtree-sub.git (push)                                                                                                  
(master)$ pwd                                                                                                                                                              
/home/cabox/workspace/subtreetest/subtree-p                                                                                                                                
(master)$ ls                                                                                                                                                               
README.md                                                                                                                                                                  
(master)$ git subtree add --prefix=mysubtree subtree-sub-remote master                                                                                                     
git fetch subtree-sub-remote master                                                                                                                                        
warning: no common commits                                                                                                                                                 
remote: Counting objects: 3, done.                                                                                                                                         
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0                                                                                                               
Unpacking objects: 100% (3/3), done.                                                                                                                                       
From https://github.com/cnweibo/subtree-sub                                                                                                                                
 * branch            master     -> FETCH_HEAD                                                                                                                              
 * [new branch]      master     -> subtree-sub-remote/master                                                                                                               
Added dir 'mysubtree'                                                                                                                                                      
(master)$ ls                                                                                                                                                               
README.md  mysubtree                                                                                                                                                       
(master)$ ls -la mysubtree/                                                                                                                                                
total 12                                                                                                                                                                   
drwxrwxr-x 2 cabox cabox 4096 May 31 02:14 .                                                                                                                               
drwxrwxr-x 4 cabox cabox 4096 May 31 02:14 ..                                                                                                                              
-rw-rw-r-- 1 cabox cabox   14 May 31 02:14 README.md     

 上面的一系列命令中,首先在subtree-p專案目錄中引入了subtree-sub這個子專案,它存放在目錄mysubtree中,和我們的subtree-sub-remote這個remote相對應。這時,subtree-p專案由於增加了新的目錄(subtree-sub子專案repo),因此需要commit和push.

注意:在mysubtree這個子目錄中是沒有任何.git檔案的,它實際上是subtree-sub這個子專案repo的純程式碼copy!! git subtree add命令本身不會像git submodule add一樣新增任何meta data@!!

(master)$ git s                                                                                                                                                            
On branch master                                                                                                                                                           
Your branch is ahead of 'origin/master' by 2 commits.                                                                                                                      
  (use "git push" to publish your local commits)                                                                                                                           
nothing to commit, working directory clean                                                                                                                                 
(master)$ git push                                                                                                                                                         
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 5, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (3/3), done.                                                                                                                                     
Writing objects: 100% (5/5), 528 bytes | 0 bytes/s, done.                                                                                                                  
Total 5 (delta 0), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-p.git                                                                                                                                
   67da25d..c1e6018  master -> master       

這時,我們檢視在github上的subtree-p repo,你就可以看到這個父專案已經引入了mysubtree(代表了subtree-sub repo!)

現在我們在parent專案中對子專案做修改並且commit, push:

(master)$ echo "kidsit add subfile1 within parent" >subfile1                                                                                                               
(master)*$ git s                                                                                                                                                           
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)                                                                                                           
                                                                                                                                                                           
        subfile1                                                                                                                                                           
                                                                                                                                                                           
nothing added to commit but untracked files present (use "git add" to track)                                                                                               
(master)*$ git a .                                                                                                                                                         
(master)*$ git ci "kidsit add subfile1 within parent"                                                                                                                      
[master ed30a68] kidsit add subfile1 within parent                                                                                                                         
 1 file changed, 1 insertion(+)                                                                                                                                            
 create mode 100644 mysubtree/subfile1                                                                                                                                     
(master)$ git s                                                                                                                                                            
On branch master                                                                                                                                                           
Your branch is ahead of 'origin/master' by 1 commit.                                                                                                                       
  (use "git push" to publish your local commits)                                                                                                                           
nothing to commit, working directory clean                                                                                                                                 
(master)$ git push                                                                                                                                                         
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 4, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (3/3), done.                                                                                                                                     
Writing objects: 100% (4/4), 398 bytes | 0 bytes/s, done.                                                                                                                  
Total 4 (delta 0), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-p.git                                                                                                                                
   c1e6018..ed30a68  master -> master                                                                                                                                      
(master)$                      

這時我們再檢視github上的subtree-p我們就能看到我們對子專案的新的改動了:

我們來繼續模擬程式碼開發過程:修改subtree-p和subtree-sub後,統一一次性做一個提交:

 

(master)$ echo "kidsit change README for p" >README.md                                                                                                                     
(master)*$ echo "kidsit add new subfileNew2 " >mysubtree/subfileNew2                                                                                                       
(master)*$ git s                                                                                                                                                           
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:   README.md                                                                                                                                              
                                                                                                                                                                           
Untracked files:                                                                                                                                                           
  (use "git add <file>..." to include in what will be committed)                                                                                                           
                                                                                                                                                                           
        mysubtree/subfileNew2                                                                                                                                              
                                                                                                                                                                           
no changes added to commit (use "git add" and/or "git commit -a")                                                                                                          
(master)*$ git a .                                                                                                                                                         
(master)*$ git ci "kidsit change p and s in ONE commit"                                                                                                                    
[master a27d335] kidsit change p and s in ONE commit                                                                                                                       
 2 files changed, 2 insertions(+), 2 deletions(-)                                                                                                                          
 create mode 100644 mysubtree/subfileNew2                                                                                                                                  
(master)$ git push                                                                                                                                                         
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 5, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (3/3), done.                                                                                                                                     
Writing objects: 100% (5/5), 461 bytes | 0 bytes/s, done.                                                                                                                  
Total 5 (delta 0), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-p.git                                                                                                                                
   ed30a68..a27d335  master -> master                                                                                                                                      
(master)$                               

這時,我們要注意,我們再subtree-p專案中對父專案和子專案分別都做了修改,但是子專案本身的repo還沒有做過任何更新,它根本不知道你曾經做過修改,這時,如果你希望將你對子專案的修改貢獻出來,可以執行git subtree push命令

 

(master)$ git push                                                                                                                                                         
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 5, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (3/3), done.                                                                                                                                     
Writing objects: 100% (5/5), 461 bytes | 0 bytes/s, done.                                                                                                                  
Total 5 (delta 0), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-p.git                                                                                                                                
   ed30a68..a27d335  master -> master                                                                                                                                      
(master)$ git subtree push --prefix=mysubtree/ subtree-sub-remote master                                                                                                   
git push using:  subtree-sub-remote master                                                                                                                                 
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 6, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (4/4), done.                                                                                                                                     
Writing objects: 100% (6/6), 563 bytes | 0 bytes/s, done.                                                                                                                  
Total 6 (delta 1), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-sub.git                                                                                                                              
   9a0ef93..001c5d8  001c5d86c4da43ab676aaeb49763d5353353159f -> master                                                                                                    

上面我們分別對subtree-p這個父專案和subtree-sub這個子專案分別作了push操作分享(一般來說對子專案push是非常少見的動作,因為我們更多的是拿別人的lib來用而不是開發),這時如果我們去看github上的兩個專案repo的commit歷史,你會發現兩個repo上都有了同一個commit!!

正因為如此,我們最好是將父專案和子專案分別做commit,不要混淆在一起!這樣做的好處是git subtree push時,git會自動將對父專案和子專案的commit區分清楚的,對父專案的修改不會出現在子專案repo修改歷史中!

現在,我們假設另外一個人對subtree-sub這個repo獨立做了新的開發,有了一個commit,但是我們的subtree-p專案實際上對此毫不知情,我們怎麼能夠獲取別人對我們用到的子專案lib來更新呢?

在父專案目錄中執行git subtree pull命令:

(master)$ git subtree pull --prefix=mysubtree -squash subtree-sub-remote master                                                                                                     
You need to run this command from the toplevel of the working tree.                                                                                                        
(master)$ kidsit                                                                                                                                                           
-bash: kidsit: command not found                                                                                                                                           
(master)$ pwd                                                                                                                                                              
/home/cabox/workspace/subtreetest/subtree-p/mysubtree                                                                                                                      
(master)$ cd ..                                                                                                                                                            
(master)$ ls                                                                                                                                                               
README.md  mysubtree                                                                                                                                                       
(master)$ pwd                                                                                                                                                              
/home/cabox/workspace/subtreetest/subtree-p                                                                                                                                
(master)$ git subtree pull --prefix=mysubtree subtree-sub-remote master                                                                                                    
remote: Counting objects: 3, done.                                                                                                                                         
remote: Compressing objects: 100% (3/3), done.                                                                                                                             
remote: Total 3 (delta 0), reused 3 (delta 0), pack-reused 0                                                                                                               
Unpacking objects: 100% (3/3), done.                                                                                                                                       
From https://github.com/cnweibo/subtree-sub                                                                                                                                
 * branch            master     -> FETCH_HEAD                                                                                                                              
   001c5d8..55689fc  master     -> subtree-sub-remote/master                                                                                                               
Merge made by the 'recursive' strategy.                                                                                                                                    
 mysubtree/standardaloneChangeInsubtreesubRepo | 1 +                                                                                                                       
 1 file changed, 1 insertion(+)                                                                                                                                            
 create mode 100644 mysubtree/standardaloneChangeInsubtreesubRepo                                                                                                          
(master)$ git lg                                                                                                                                                           
*   4d2a47a (HEAD -> master) Merge commit '55689fc679ec0eeaf0a7853cd348309dad99fb96' from cnweibo update as                                                                
 standard dev                                                                                                                                                              
|\                                                                                                                                                                         
| * 55689fc (subtree-sub-remote/master) new content as a standard repo dev env for subtree-sub by cnweibo                                                                  
| * 001c5d8 kidsit change p and s in ONE commit                                                                                                                            
| * ef33027 kidsit add subfile1 within parent                                                                                                                              
* | a27d335 (origin/master, origin/HEAD) kidsit change p and s in ONE commit                                                                                               
* | ed30a68 kidsit add subfile1 within parent                                                                                                                              
* |   c1e6018 Add 'mysubtree/' from commit '9a0ef93b82b73fee89dd3d5123d441680c2a7e40'                                                                                      
|\ \                                                                                                                                                                       
| |/                                                                                                                                                                       
| * 9a0ef93 Initial commit       
* 67da25d p 2                                                                                                                                                              
* f05da04 Create README.md                                                                                                                                                 
(master)$ ls                                                                                                                                                               
.git/      README.md  mysubtree/                                                                                                                                           
(master)$ ls mysubtree/                                                                                                                                                    
README.md                            subfile1                                                                                                                              
standardaloneChangeInsubtreesubRepo  subfileNew2                                                                                                                           
(master)$ ls mysubtree/                                                                                                                                                    
README.md  standardaloneChangeInsubtreesubRepo  subfile1  subfileNew2                                                                                                      
(master)$ git push                                                                                                                                                         
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 8, done.                                                                                                                                                 
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (8/8), done.                                                                                                                                     
Writing objects: 100% (8/8), 1.06 KiB | 0 bytes/s, done.                                                                                                                   
Total 8 (delta 1), reused 0 (delta 0)                                                                                                                                      
To https://github.com/cnweibo/subtree-p.git                                                                                                                                
   a27d335..4d2a47a  master -> master                                                                                                                                      
(master)$                                                     

上述命令主要是在subtree-p專案中pull別的獨立專案對subtree-sub子模組的貢獻,隨後git push到主專案的repo中獨立引用那一份修改。

接下來,我們來看新的專案成員進到我們的subtree-p專案中,他又該如何呢?

 他首先需要做的是git clone yourParentRep, 隨後git rm -r subModule, git add ,git commit.這時他的working directory中不再含有subModule的程式碼,他需要做的是手工建立git remote add  --prefix=submodDirectory submodRemote master/v1.0,注意:在這裡可以指定一個tag來選擇相應的歷史版本!!! git subtree add submodRemote xxxx.git master獲取subrep程式碼到你的submodDirectory目錄,這樣他自己clone的repo就有了和submodRepo的聯絡了,他也可以對父專案和子專案來做貢獻了。

請參考: https://lostechies.com/johnteague/2014/04/04/using-git-subtrees-to-split-a-repository/

git subtree方式管理的parent專案最終是包含所有子模組的完整程式碼的(可以不含子模組的歷史),同時支援對子專案的contribute。

在一個前臺可能被share,後臺可能從一個換成另外一個,這樣的場景就比較適合使用subtree來管理。

 

接下來我們再看另外一種情況: 在一個專案中,當我們完整做完穩定版本後,我們希望將public前端拿出來作為一個獨立的repo來管理,這樣前端和後端完全可以獨立開發,要實現這個,就需要使用git subtree split命令了:

 

(master)$ ls                                                                                                                                                               
README.md  mysubtree
(master)$ git subtree split --prefix=mysubtree -b splittedbranch                                                                                                           
Created branch 'splittedbranch'                                                                                                                                            
55689fc679ec0eeaf0a7853cd348309dad99fb96                                                                                                                                   
(master)$ git s                                                                                                                                                            
On branch master                                                                                                                                                           
Your branch is up-to-date with 'origin/master'.                                                                                                                            
nothing to commit, working directory clean                                                                                                                                 
(master)$ git branch -a                                                                                                                                                    
* master                                                                                                                                                                   
  splittedbranch                                                                                                                                                           
  remotes/origin/HEAD -> origin/master                                                                                                                                     
  remotes/origin/master                     
(master)$ ls                                                                                                                                                               
README.md  mysubtree                                                                                                                                                       
(master)$ git remote add splittedRemote https://github.com/cnweibo/splitted.git  
(master)$ git checkout splittedbranch                                                                                                                                      
Switched to branch 'splittedbranch'                                                                                                                                        
(splittedbranch)$ ls                                                                                                                                                       
README.md  standardaloneChangeInsubtreesubRepo  subfile1  subfileNew2                                                                                                      
(splittedbranch)$ pwd                                                                                                                                                      
/home/cabox/workspace/subtreesplit/superrepo                                                                                                                                                   
(splittedbranch)$ ls                                                                                                                                                       
README.md  standardaloneChangeInsubtreesubRepo  subfile1  subfileNew2                                                                                                      
(splittedbranch)$ git remote -v                                                                                                                                            
origin  https://github.com/cnweibo/subtree-p.git (fetch)                                                                                                                   
origin  https://github.com/cnweibo/subtree-p.git (push)                                                                                                                    
splittedRemote  https://github.com/cnweibo/splitted.git (fetch)                                                                                                            
splittedRemote  https://github.com/cnweibo/splitted.git (push) 
(splittedbranch)$ git push splittedRemote splittedbranch:master                                                                                                            
Username for 'https://github.com': kidsit                                                                                                                                  
Password for 'https://kidsit@github.com':                                                                                                                                  
Counting objects: 15, done.                                                                                                                                                
Delta compression using up to 12 threads.                                                                                                                                  
Compressing objects: 100% (11/11), done.                                                                                                                                   
Writing objects: 100% (15/15), 1.43 KiB | 0 bytes/s, done.                                                                                                                 
Total 15 (delta 2), reused 0 (delta 0)                                                                                                                                     
To https://github.com/cnweibo/splitted.git                                                                                                                                 
   2d00c85..703b820  splittedbranch -> master      

上面的一串命令將parent專案的mysubtree目錄被splitted到splittedbranch上(當你checkout這個splittedbranch時,你看到的就只有這個目錄的檔案了!),隨後將該branch上的內容push到remote中,這就形成了獨立的repo了。隨後你要做的是在父專案中,將該子資料夾刪除,通過git subtree來引用這個共享的repo

http://blog.nwcadence.com/git-subtrees/

 

注意:git filter-branch 也可以實現類似的git subtree split的功能,將一個repo的部分目錄分割為一個新的repo.

具體可參考: https://help.github.com/articles/splitting-a-subfolder-out-into-a-new-repository/

另外一篇文章: http://log.pardus.de/2012/08/modular-git-with-git-subtree.html 可以參考更好的思路

相關文章