自從看了蔣鑫的《Git權威指南》之後就開始使用Git Submodule功能,團隊也都熟悉了怎麼使用,多個子系統(模塊)都能及時更新到最新的公共資源,把使用的過程以及經驗和容易遇到的問題分享給大家。
Git權威指南 PDF高清中文版 http://www.linuxidc.com/Linux/2013-10/91053.htm
Git Submodule功能剛剛開始學習可能覺得有點怪異,所以本教程把每一步的操作的命令和結果都用代碼的形式展現給大家,以便更好的理解。
每個公司的系統都會有一套統一的系統風格,或者針對某一個大客戶的多個系統風格保持統一,而且如果風格改動後要同步到多個系統中;這樣的需求幾乎每個開發人員都遇到,下面看看各個層次的程序員怎麼處理:
假如對於系統的風格需要幾個目錄:css、images、js。
普通程序員,把最新版本的代碼逐個復制到每個項目中,如果有N個項目,那就是要復制N x 3次;如果漏掉了某個文件夾沒有復制…@(&#@#。
文藝程序員,使用Git Submodule功能,執行:git submodule update,然後沖一杯咖啡悠哉的享受著。
引用一段《Git權威指南》的話: 項目的版本庫在某些情況蝦需要引用其他版本庫中的文件,例如公司積累了一套常用的函數庫,被多個項目調用,顯然這個函數庫的代碼不能直接放到某個項目的代碼中,而是要獨立為一個代碼庫,那麼其他項目要調用公共函數庫該如何處理呢?分別把公共函數庫的文件拷貝到各自的項目中會造成冗余,丟棄了公共函數庫的維護歷史,這顯然不是好的方法。
“工欲善其事,必先利其器”!
既然文藝程序員那麼輕松就搞定了,那我們就把過程一一道來。
說明:本例采用兩個項目以及兩個公共類庫演示對submodule的操作。因為在一寫資料或者書上的例子都是一個項目對應1~N個lib,但是實際應用往往並不是這麼簡單。
➜ henryyan@hy-hp ~
pwd
/home/henryyan
mkdir
-p submd
/repos
創建需要的本地倉庫:
1 2 3 4 5cd
~
/submd/repos
git --git-
dir
=lib1.git init --bare
git --git-
dir
=lib2.git init --bare
git --git-
dir
=project1.git init --bare
git --git-
dir
=project2.git init --bare
初始化工作區:
1 2mkdir
~
/submd/ws
cd
~
/submd/ws
初始化project1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33➜ henryyan@hy-hp ~
/submd/ws
git clone ..
/repos/project1
.git
Cloning into project1...
done
.
warning: You appear to have cloned an empty repository.
➜ henryyan@hy-hp ~
/submd/ws
cd
project1
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master)
echo
"project1"
> project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗
ls
project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git add project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: project-infos.txt
#
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git commit -m
"init project1"
[master (root-commit) 473a2e2] init project1
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) git push origin master
Counting objects: 3,
done
.
Writing objects: 100% (3
/3
), 232 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/project1
.git
* [new branch] master -> master
<
/file
>
初始化project2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34➜ henryyan@hy-hp ~
/submd/ws/project1
cd
..
➜ henryyan@hy-hp ~
/submd/ws
git clone ..
/repos/project2
.git
Cloning into project2...
done
.
warning: You appear to have cloned an empty repository.
➜ henryyan@hy-hp ~
/submd/ws
cd
project2
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master)
echo
"project2"
> project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master) ✗
ls
project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master) ✗ git add project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master) ✗ git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
# (use "git rm --cached <file>..." to unstage)
#
# new file: project-infos.txt
#
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master) ✗ git commit -m
"init project2"
[master (root-commit) 473a2e2] init project2
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project2
git:(master) git push origin master
Counting objects: 3,
done
.
Writing objects: 100% (3
/3
), 232 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/project2
.git
* [new branch] master -> master
<
/file
>
初始化公共類庫lib1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18➜ henryyan@hy-hp ~
/submd/ws
git clone ..
/repos/lib1
.git
Cloning into lib1...
done
.
warning: You appear to have cloned an empty repository.
➜ henryyan@hy-hp ~
/submd/ws
cd
lib1
➜ henryyan@hy-hp ~
/submd/ws/lib1
git:(master)
echo
"I'm lib1."
> lib1-features
➜ henryyan@hy-hp ~
/submd/ws/lib1
git:(master) ✗ git add lib1-features
➜ henryyan@hy-hp ~
/submd/ws/lib1
git:(master) ✗ git commit -m
"init lib1"
[master (root-commit) c22aff8] init lib1
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 lib1-features
➜ henryyan@hy-hp ~
/submd/ws/lib1
git:(master) git push origin master
Counting objects: 3,
done
.
Writing objects: 100% (3
/3
), 227 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/lib1
.git
* [new branch] master -> master
初始化公共類庫lib2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19➜ henryyan@hy-hp ~
/submd/ws/lib1
git:(master)
cd
..
➜ henryyan@hy-hp ~
/submd/ws
git clone ..
/repos/lib2
.git
Cloning into lib2...
done
.
warning: You appear to have cloned an empty repository.
➜ henryyan@hy-hp ~
/submd/ws
cd
lib2
➜ henryyan@hy-hp ~
/submd/ws/lib2
git:(master)
echo
"I'm lib2."
> lib2-features
➜ henryyan@hy-hp ~
/submd/ws/lib2
git:(master) ✗ git add lib2-features
➜ henryyan@hy-hp ~
/submd/ws/lib2
git:(master) ✗ git commit -m
"init lib2"
[master (root-commit) c22aff8] init lib2
1 files changed, 1 insertions(+), 0 deletions(-)
create mode 100644 lib2-features
➜ henryyan@hy-hp ~
/submd/ws/lib2
git:(master) git push origin master
Counting objects: 3,
done
.
Writing objects: 100% (3
/3
), 227 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/lib2
.git
* [new branch] master -> master
➜ henryyan@hy-hp ~
/submd/ws/lib2
git:(master)
cd
..
/project1
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master)
ls
project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) git submodule add ~
/submd/repos/lib1
.git libs
/lib1
Cloning into libs
/lib1
...
done
.
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git submodule add ~
/submd/repos/lib2
.git libs
/lib2
Cloning into libs
/lib2
...
done
.
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗
ls
libs project-infos.txt
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗
ls
libs
lib1 lib2
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: .gitmodules
# new file: libs/lib1
# new file: libs/lib2
#
# 查看一下公共類庫的內容
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master)
cat
libs
/lib1/lib1-features
I'm lib1.
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master)
cat
libs
/lib2/lib2-features
I'm lib2.
<
/file
>
好了,到目前為止我們已經使用git submodule add命令為project1成功添加了兩個公共類庫(lib1、lib2),查看了當前的狀態發現添加了一個新文件(.gitmodules)和兩個文件夾(libs/lib1、libs/lib2);那麼新增的.gitmodules文件是做什麼用的呢?我們查看一下文件內容便知曉了:
1 2 3 4 5 6 7n@hy-hp ~
/submd/ws/project1
git:(master) ✗
cat
.gitmodules
[submodule
"libs/lib1"
]
path = libs
/lib1
url =
/home/henryyan/submd/repos/lib1
.git
[submodule
"libs/lib2"
]
path = libs
/lib2
url =
/home/henryyan/submd/repos/lib2
.git
原來如此,.gitmodules記錄了每個submodule的引用信息,知道在當前項目的位置以及倉庫的所在。
好的,我們現在把更改提交到倉庫。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) ✗ git commit -a -m
"add submodules[lib1,lib2] to project1"
[master 7157977] add submodules[lib1,lib2] to project1
3 files changed, 8 insertions(+), 0 deletions(-)
create mode 100644 .gitmodules
create mode 160000 libs
/lib1
create mode 160000 libs
/lib2
➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master) git push
Counting objects: 5,
done
.
Delta compression using up to 2 threads.
Compressing objects: 100% (4
/4
),
done
.
Writing objects: 100% (4
/4
), 491 bytes,
done
.
Total 4 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (4
/4
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/project1
.git
45cbbcb..7157977 master -> master
假如你是第一次引入公共類庫的開發人員,那麼項目組的其他成員怎麼Clone帶有Submodule的項目呢,下面我們再clone一個項目講解如何操作。
模擬開發人員B……
1 2 3 4 5 6 7 8➜ henryyan@hy-hp ~
/submd/ws/project1
git:(master)
cd
~
/submd/ws
➜ henryyan@hy-hp ~
/submd/ws
git clone ..
/repos/project1
.git project1-b
Cloning into project1-b...
done
.
➜ henryyan@hy-hp ~
/submd/ws
cd
project1-b
➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) git submodule
-c22aff85be91eca442734dcb07115ffe526b13a1 libs
/lib1
-7290dce0062bd77df1d83b27dd3fa3f25a836b54 libs
/lib2
看到submodules的狀態是hash碼和文件目錄,但是注意前面有一個減號:-,含義是該子模塊還沒有檢出。
OK,檢出project1-b的submodules……
1 2 3 4 5 6 7 8 9 10➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) git submodule init
Submodule
'libs/lib1'
(
/home/henryyan/submd/repos/lib1
.git) registered
for
path
'libs/lib1'
Submodule
'libs/lib2'
(
/home/henryyan/submd/repos/lib2
.git) registered
for
path
'libs/lib2'
➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) git submodule update
Cloning into libs
/lib1
...
done
.
Submodule path
'libs/lib1'
: checked out
'c22aff85be91eca442734dcb07115ffe526b13a1'
Cloning into libs
/lib2
...
done
.
Submodule path
'libs/lib2'
: checked out
'7290dce0062bd77df1d83b27dd3fa3f25a836b54'
讀者可以查看:.git/config文件的內容,最下面有submodule的注冊信息!
驗證一下類庫的文件是否存在:
1 2 3➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master)
cat
libs
/lib1/lib1-features
libs
/lib2/lib2-features
I'm lib1.
I'm lib2.
上面的兩個命令(git submodule init & update)其實可以簡化,後面會講到!
我們在開發人員B的項目上修改Submodule的內容。
先看一下當前Submodule的狀態:
1 2 3 4➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master)
cd
libs
/lib1
➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git status
# Not currently on any branch.
nothing to commit (working directory clean)
為什麼是Not currently on any branch呢?不是應該默認在master分支嗎?別急,一一解答!
Git對於Submodule有特殊的處理方式,在一個主項目中引入了Submodule其實Git做了3件事情:
記錄引用的倉庫
記錄主項目中Submodules的目錄位置
記錄引用Submodule的commit id
在project1中push之後其實就是更新了引用的commit id,然後project1-b在clone的時候獲取到了submodule的commit id,然後當執行git submodule update的時候git就根據gitlink獲取submodule的commit id,最後獲取submodule的文件,所以clone之後不在任何分支上;但是master分支的commit id和HEAD保持一致。
查看~/submd/ws/project1-b/libs/lib1的引用信息:
1 2 3 4➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
cat
.git
/HEAD
c22aff85be91eca442734dcb07115ffe526b13a1
➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
cat
.git
/refs/heads/master
c22aff85be91eca442734dcb07115ffe526b13a1
現在我們要修改lib1的文件需要先切換到master分支:
1 2 3 4 5 6➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git checkout master
Switched to branch
'master'
➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git:(master)
echo
"add by developer B"
>> lib1-features
➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git:(master) ✗ git commit -a -m
"update lib1-features by developer B"
[master 36ad12d] update lib1-features by developer B
1 files changed, 1 insertions(+), 0 deletions(-)
在主項目中修改Submodule提交到倉庫稍微繁瑣一點,在git push之前我們先看看project1-b狀態:
1 2 3 4 5 6 7 8 9 10➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) ✗ git status
# On branch 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: libs/lib1 (new commits)
#
no changes added to commit (use
"git add"
and
/or
"git commit -a"
)
<
/file
><
/file
>
libs/lib1 (new commits)狀態表示libs/lib1有新的提交,這個比較特殊,看看project1-b的狀態:
1 2 3 4 5 6 7 8➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) ✗ git
diff
diff
--git a
/libs/lib1
b
/libs/lib1
index c22aff8..36ad12d 160000
--- a
/libs/lib1
+++ b
/libs/lib1
@@ -1 +1 @@
-Subproject commit c22aff85be91eca442734dcb07115ffe526b13a1
+Subproject commit 36ad12d40d8a41a4a88a64add27bd57cf56c9de2
從狀態中可以看出libs/lib1的commit id由原來的c22aff85be91eca442734dcb07115ffe526b13a1更改為36ad12d40d8a41a4a88a64add27bd57cf56c9de2
注意:如果現在執行了git submodule update操作那麼libs/lib1的commit id又會還原到c22aff85be91eca442734dcb07115ffe526b13a1, 這樣的話剛剛的修改是不是就丟死了呢?不會,因為修改已經提交到了master分支,只要再git checkout master就可以了。
現在可以把libs/lib1的修改提交到倉庫了:
1 2 3 4 5 6 7 8➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) ✗
cd
libs
/lib1
➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git:(master) git push
Counting objects: 5,
done
.
Writing objects: 100% (3
/3
), 300 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/repos/lib1
.git
c22aff8..36ad12d master -> master
現在僅僅只完成了一步,下一步要提交project1-b引用submodule的commit id:
1 2 3 4 5 6 7 8 9 10 11 12 13 14➜ henryyan@hy-hp ~
/submd/ws/project1-b/libs/lib1
git:(master)
cd
~
/submd/ws/project1-b
➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) ✗ git add -u
➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) ✗ git commit -m
"update libs/lib1 to lastest commit id"
[master c96838a] update libs
/lib1
to lastest commit
id
1 files changed, 1 insertions(+), 1 deletions(-)
➜ henryyan@hy-hp ~
/submd/ws/project1-b
git:(master) git push
Counting objects: 5,
done
.
Delta compression using up to 2 threads.
Compressing objects: 100% (3
/3
),
done
.
Writing objects: 100% (3
/3
), 395 bytes,
done
.
Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3
/3
),
done
.
To
/home/henryyan/submd/ws/
..
/repos/project1
.git
7157977..c96838a master -> master
OK,大功高成,我們完成了Submodule的修改並把libs/lib1的最新commit id提交到了倉庫。
接下來要看看project1怎麼獲取submodule了。
Git 的詳細介紹:請點這裡
Git 的下載地址:請點這裡
推薦閱讀:
Fedora通過Http Proxy下載Git http://www.linuxidc.com/Linux/2009-12/23170.htm
在Ubuntu Server上安裝Git http://www.linuxidc.com/Linux/2009-06/20421.htm
服務器端Git倉庫的創建(Ubuntu) http://www.linuxidc.com/Linux/2011-02/32542.htm
Linux下Git簡單使用教程(以Android為例) http://www.linuxidc.com/Linux/2010-11/29883.htm
Git權威指南 PDF高清中文版 http://www.linuxidc.com/Linux/2013-10/91053.htm