Git submodules

Git子模塊

image

背景信息

子模塊(submodule)是Git為管理倉庫共用而衍生出的一個工具,通過子模塊您可以將公共倉庫作為子目錄包含到您的倉庫中,
并能夠雙向同步該公共倉庫的代碼,借助子模塊您能將公共倉庫隔離、復(fù)用,能隨時拉取最新代碼以及對它提交修復(fù),
能大大提高您的團隊效率。

有種情況我們經(jīng)常會遇到:某個工作中的項目A需要包含并使用項目B(第三方庫,或者你獨立開發(fā)的,用于多個父項目的庫),
如果想要把它們當做兩個獨立的項目,同時又想在項目A中使用項目B,可以使用Git的子模塊功能。
子模塊允許您將一個Git倉庫作為另一個Git倉庫的子目錄。 它能讓你將另一個倉庫克隆到自己的項目中,同時還保持提交的獨立。

子模塊將被記錄在一個名叫.gitmodules的文件中,其中會記錄子模塊的信息:

[submodule "module_name"]      #子模塊名稱
path = file_path               #子模塊在本倉庫(父倉)中文件的存儲路徑。
url = repo_url                 #子模塊(子倉庫)的遠程倉地址

這時,位于 file_path 目錄下的源代碼,將會來自 repo_url 。

Git操作

添加 Submodule

git submodule add <repo> [<dir>] [-b <branch>] [<path>]

示例:

# 添加子模塊倉庫,使用默認項目名為路徑名
git submodule add git@github.com:ghostxbh/uzykj-docs.git

or

# 聲明存放路徑 blog
git submodule add https://github.com/ghostxbh/uzykj-docs.git blog

查看git status當前項目git狀態(tài):

git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   .gitmodules
    new file:   uzykj-docs

.gitmodules

查看.gitmodules文件,該配置文件保存了項目 URL 與已經(jīng)拉取的本地目錄之間的映射:

[submodule "uzykj-docs"]
    path = uzykj-docs
    url = https://github.com/ghostxbh/uzykj-docs.git

如果有多個子模塊,該文件中就會有多條記錄。要重點注意的是,該文件也像 .gitignore 文件一樣受到(通過)版本控制。
它會和該項目的其他部分一同被拉取推送。這就是克隆該項目的人知道去哪獲得子模塊的原因。

注意:由于 .gitmodules 文件中的 URL 是人們首先嘗試克隆/拉取的地方,因此請盡可能確保你使用的 URL 大家都能訪問。
例如,若你要使用的推送 URL 與他人的拉取 URL 不同,那么請使用他人能訪問到的 URL。
你也可以根據(jù)自己的需要,通過在本地執(zhí)行 git config submodule.uzykj-docs.url <私有URL> 來覆蓋這個選項的值。
如果可行的話,一個相對路徑會很有幫助。

差異

使用git diff --cached uzykj-docs查看差異:

diff --git a/blog b/blog
new file mode 160000
index 0000000..337eed9
--- /dev/null
+++ b/uzykj-docs
@@ -0,0 +1 @@
+Subproject commit 337eed9b1ac06ec0cac66eadc8a99eb8bb35c987

使用git diff --cached --submodule格式化差異:

diff --git a/.gitmodules b/.gitmodules
index cf2564a..0949c46 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,8 +1,11 @@
+[submodule "uzykj-docs"]
+       path = uzykj-docs
+       url = https://github.com/ghostxbh/uzykj-docs.git
Submodule blog 0000000...337eed9 (new submodule)
image

拉取包含submodule的倉庫

方式一

如果給 git clone 命令傳遞 --recurse-submodules 選項,它就會自動初始化并更新倉庫中的每一個子模塊,包括可能存在的嵌套子模塊。

git clone <repo> [<dir>] --recursive

示例:

git clone git@github.com:ghostxbh/uzykj-docs.git --recursive

方式二

使用git clone <url>方式克隆下父模塊倉庫,再使用git submodule init初始化子模塊。

# 克隆父模塊
git clone <repo>

# 初始化子模塊
git submodule init

# 抓取所有數(shù)據(jù)并檢出父項目中列出的合適的提交
git submodule update

方式三

如果你已經(jīng)克隆了項目但忘記了 --recurse-submodules,那么可以運行 git submodule update --initgit submodule initgit submodule update 合并成一步。
如果還要初始化、抓取并檢出任何嵌套的子模塊,請使用簡明的 git submodule update --init --recursive 。

# 克隆父模塊,忘記了 --recurse-submodules
git clone <repo>

# 初始化子模塊獲取提交信息
git submodule update --init

# 初始化、抓取并檢出任何嵌套的子模塊
git submodule update --init --recursive

獲取遠端Submodule更新

git submodule update --remote

推送更新到子庫

git push --recurse-submodules=check

刪除 Submodule

刪除.gitsubmodule文件中對應(yīng)submodule的條目。
刪除.git/config中對應(yīng)submodule的條目。
執(zhí)行命令,刪除子模塊對應(yīng)的文件夾。

git rm --cached {submodule_path}    #注意更換為您的子模塊路徑

說明: 路徑不要加后面的“/”。

示例:
你的submodule保存在src/blog/目錄,則執(zhí)行命令為: git rm --cached src/blog

協(xié)作

在包含子模塊的項目上工作
現(xiàn)在我們有一份包含子模塊的項目副本,我們將會同時在主項目和子模塊項目上與隊員協(xié)作。

從子模塊的遠端拉取上游修改

在項目中使用子模塊的最簡模型,就是只使用子項目并不時地獲取更新,而并不在你的檢出中進行任何更改。我們來看一個簡單的例子。
如果想要在子模塊中查看新工作,可以進入到目錄中運行 git fetchgit merge,合并上游分支來更新本地代碼。

$ git fetch
From https://github.com/ghostxbh/uzykj-docs
c3f01dc..d0354fc  master     -> origin/master

$ git merge origin/master
Updating c3f01dc..d0354fc

如果你現(xiàn)在返回到主項目并運行 git diff --submodule ,就會看到子模塊被更新的同時獲得了一個包含新添加提交的列表。
如果你不想每次運行 git diff 時都輸入 --submodle ,那么可以將 diff.submodule 設(shè)置為 “l(fā)og” 來將其作為默認行為。

$ git config --global diff.submodule log
$ git diff
Submodule uzykj-docs c3f01dc..d0354fc:
  > more efficient db routine
  > better connection routine

如果在此時提交,那么你會將子模塊鎖定為其他人更新時的新代碼。

如果你不想在子目錄中手動抓取與合并,那么還有種更容易的方式。
運行 git submodule update --remote ,Git 將會進入子模塊然后抓取并更新。

$ git submodule update --remote uzykj-docs
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)

此命令默認會假定你想要更新并檢出子模塊倉庫的 master 分支。 不過你也可以設(shè)置為想要的其他分支。
例如,你想要 uzykj-docs 子模塊跟蹤倉庫的 “stable” 分支,那么既可以在 .gitmodules 文件中設(shè)置 (這樣其他人也可以跟蹤它),也可以只在本地的 .git/config 文件中設(shè)置。
讓我們在 .gitmodules 文件中設(shè)置它:

$ git config -f .gitmodules submodule.uzykj-docs.branch stable

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)

如果不用 -f .gitmodules 選項,那么它只會為你做修改。但是在倉庫中保留跟蹤信息更有意義一些,因為其他人也可以得到同樣的效果。

這時我們運行 git status,Git 會顯示子模塊中有“新提交”。

$ git status
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:   .gitmodules
modified:   uzykj-docs (new commits)

no changes added to commit (use "git add" and/or "git commit -a")

如果你設(shè)置了配置選項 status.submodulesummary ,Git 也會顯示你的子模塊的更改摘要:

$ git config status.submodulesummary 1

$ git status
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:   .gitmodules
    modified:   uzykj-docs (new commits)

Submodules changed but not updated:

* uzykj-docs c3f01dc...c87d55d (4):
  > catch non-null terminated lines

這時如果運行 git diff,可以看到我們修改了 .gitmodules 文件,同時還有幾個已拉取的提交需要提交到我們自己的子模塊項目中。

$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "uzykj-docs"]
    path = uzykj-docs
    url = https://github.com/ghostxbh/uzykj-docs
+       branch = stable
Submodule uzykj-docs c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine

這非常有趣,因為我們可以直接看到將要提交到子模塊中的提交日志。 提交之后,你也可以運行 git log -p 查看這個信息。

$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: ghostxbh <ghostxbh@hotmail.com>
Date:   Wed Sep 17 16:37:02 2021 +0200

    updating uzykj-docs for bug fixes

diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "uzykj-docs"]
    path = uzykj-docs
    url = https://github.com/ghostxbh/uzykj-docs
+       branch = stable
Submodule uzykj-docs c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine

當運行 git submodule update --remote 時,Git 默認會嘗試更新 所有 子模塊,
所以如果有很多子模塊的話,你可以傳遞想要更新的子模塊的名字。

從項目遠端拉取上游更改

現(xiàn)在,讓我們站在協(xié)作者的視角,他有自己的 project 倉庫的本地克隆, 只是執(zhí)行 git pull 獲取你新提交的更改還不夠:

git pull

git status

默認情況下,git pull 命令會遞歸地抓取子模塊的更改,如上面第一個命令的輸出所示。
然而,它不會 更新 子模塊。這點可通過 git status 命令看到,它會顯示子模塊“已修改”,且“有新的提交”。
此外,左邊的尖括號(<)指出了新的提交,表示這些提交已在 project 中記錄,但尚未在本地的 uzykj-docs 中檢出。
為了完成更新,你需要運行 git submodule update

$ git submodule update --init --recursive
Submodule path '/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

注意:如果 project 提交了你剛拉取的新子模塊,那么應(yīng)該在 git submodule update 后面添加 --init 選項,如果子模塊有嵌套的子模塊,則應(yīng)使用 --recursive 選項。
簡化命令git pull --recurse-submodules,(從 Git 2.14 開始)

特殊的情況:拉取的提交中, 可能 .gitmodules 文件中記錄的子模塊的 URL 發(fā)生了改變。
比如,若子模塊項目改變了它的托管平臺,就會發(fā)生這種情況。
此時,若父級項目引用的子模塊提交不在倉庫中本地配置的子模塊遠端上,那么執(zhí)行 git pull --recurse-submodulesgit submodule update 就會失敗。
為了補救,git submodule sync 命令需要:

# 將新的 URL 復(fù)制到本地配置中
git submodule sync --recursive

# 從新 URL 更新子模塊
git submodule update --init --recursive

在子模塊上工作

你很有可能正在使用子模塊,因為你確實想在子模塊中編寫代碼的同時,還想在主項目上編寫代碼(或者跨子模塊工作)。 否則你大概只能用簡單的依賴管理系統(tǒng)(如 Maven 或 Rubygems)來替代了。

$ cd uzykj-docs/
$ git checkout stable
Switched to branch 'stable'

然后嘗試用 “merge” 選項來更新子模塊。 為了手動指定它,我們只需給 update 添加 --merge 選項即可。
這時我們將會看到服務(wù)器上的這個子模塊有一個改動并且它被合并了進來。

$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)

現(xiàn)在更新子模塊,就會看到當我們在本地做了更改時上游也有一個改動,我們需要將它并入本地。

$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: unicode support
Submodule path 'uzykj-docs': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

如果你忘記 --rebase--merge,Git 會將子模塊更新為服務(wù)器上的狀態(tài)。并且會將項目重置為一個游離的 HEAD 狀態(tài)。

$ git submodule update --remote
Submodule path 'uzykj-docs': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'

即便這真的發(fā)生了也不要緊,你只需回到目錄中再次檢出你的分支(即還包含著你的工作的分支)然后手動地合并或變基 origin/stable(或任何一個你想要的遠程分支)就行了。

如果你沒有提交子模塊的改動,那么運行一個子模塊更新也不會出現(xiàn)問題,此時 Git 會只抓取更改而并不會覆蓋子模塊目錄中未保存的工作。

$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/ghostxbh/uzykj-docs
5d60ef9..c75e92a  stable     -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.

如果你做了一些與上游改動沖突的改動,當運行更新時 Git 會讓你知道。

$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.

你可以進入子模塊目錄中然后就像平時那樣修復(fù)沖突。

發(fā)布子模塊改動

現(xiàn)在我們的子模塊目錄中有一些改動。其中有一些是我們通過更新從上游引入的,而另一些是本地生成的,由于我們還沒有推送它們,所以對任何其他人都不可用。

$ git diff
Submodule uzykj-docs c87d55d..82d2ad3:
> Merge from origin/stable
> updated setup script
> unicode support
> remove unnecessary method
> add new option for conn pooling

如果我們在主項目中提交并推送但并不推送子模塊上的改動,其他嘗試檢出我們修改的人會遇到麻煩,因為他們無法得到依賴的子模塊改動。那些改動只存在于我們本地的拷貝中。

為了確保這不會發(fā)生,你可以讓 Git 在推送到主項目前檢查所有子模塊是否已推送。 git push 命令接受可以設(shè)置為 “check” 或 “on-demand” 的 --recurse-submodules 參數(shù)。 如果任何提交的子模塊改動沒有推送那么 “check” 選項會直接使 push 操作失敗。

$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
uzykj-docs

Please try

    git push --recurse-submodules=on-demand

or cd to the path and use

    git push

to push them to a remote.

如你所見,它也給我們了一些有用的建議,指導(dǎo)接下來該如何做。 最簡單的選項是進入每一個子模塊中然后手動推送到遠程倉庫,確保它們能被外部訪問到,之后再次嘗試這次推送。
如果你想要對所有推送都執(zhí)行檢查,那么可以通過設(shè)置 git config push.recurseSubmodules check 讓它成為默認行為。

另一個選項是使用 “on-demand” 值,它會嘗試為你這樣做。

$ git push --recurse-submodules=on-demand
Pushing submodule 'uzykj-docs'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/ghostxbh/uzykj-docs
c75e92a..82d2ad3  stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/ghostxbh/MainProject
3d6d338..9a377d1  master -> master

如你所見,Git 進入到 uzykj-docs 模塊中然后在推送主項目前推送了它。 如果那個子模塊因為某些原因推送失敗,主項目也會推送失敗。 你也可以通過設(shè)置 git config push.recurseSubmodules on-demand 讓它成為默認行為。

合并子模塊改動

如果你其他人同時改動了一個子模塊引用,那么可能會遇到一些問題。 也就是說,如果子模塊的歷史已經(jīng)分叉并且在父項目中分別提交到了分叉的分支上,那么你需要做一些工作來修復(fù)它。

如果一個提交是另一個的直接祖先(一個快進式合并),那么 Git 會簡單地選擇之后的提交來合并,這樣沒什么問題。

不過,Git 甚至不會嘗試去進行一次簡單的合并。 如果子模塊提交已經(jīng)分叉且需要合并,那你會得到類似下面的信息:

$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/ghostxbh/MainProject
9a377d1..eb974f8  master     -> origin/master
Fetching submodule uzykj-docs
warning: Failed to merge submodule uzykj-docs (merge following commits not found)
Auto-merging uzykj-docs
CONFLICT (submodule): Merge conflict in uzykj-docs
Automatic merge failed; fix conflicts and then commit the result.

所以本質(zhì)上 Git 在這里指出了子模塊歷史中的兩個分支記錄點已經(jīng)分叉并且需要合并。 它將其解釋為 “merge following commits not found” (未找到接下來需要合并的提交),雖然這有點令人困惑,不過之后我們會解釋為什么是這樣。

為了解決這個問題,你需要弄清楚子模塊應(yīng)該處于哪種狀態(tài)。 奇怪的是,Git 并不會給你多少能幫你擺脫困境的信息,甚至連兩邊提交歷史中的 SHA-1 值都沒有。 幸運的是,這很容易解決。 如果你運行 git diff,就會得到試圖合并的兩個分支中記錄的提交的 SHA-1 值。

$ git diff
diff --cc uzykj-docs
index eb41d76,c771610..0000000
--- a/uzykj-docs
+++ b/uzykj-docs

所以,在本例中,eb41d76 是我們的子模塊中大家共有的提交,而 c771610 是上游擁有的提交。 如果我們進入子模塊目錄中,它應(yīng)該已經(jīng)在 eb41d76 上了,因為合并沒有動過它。 如果不是的話,無論什么原因,你都可以簡單地創(chuàng)建并檢出一個指向它的分支。

來自另一邊的提交的 SHA-1 值比較重要。 它是需要你來合并解決的。 你可以嘗試直接通過 SHA-1 合并,也可以為它創(chuàng)建一個分支然后嘗試合并。 我們建議后者,哪怕只是為了一個更漂亮的合并提交信息。

所以,我們將會進入子模塊目錄,基于 git diff 的第二個 SHA-1 創(chuàng)建一個分支然后手動合并。

$ cd uzykj-docs

$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135

$ git branch try-merge c771610
(uzykj-docs) $ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.

我們在這兒得到了一個真正的合并沖突,所以如果想要解決并提交它,那么只需簡單地通過結(jié)果來更新主項目。

$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes

$ cd .. (2)
$ git diff (3)
diff --cc uzykj-docs
index eb41d76,c771610..0000000
--- a/uzykj-docs
+++ b/uzykj-docs
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
  -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
  ++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
  $ git add uzykj-docs (4)

$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
  • 首先解決沖突
  • 然后返回到主項目目錄中
  • 再次檢查 SHA-1 值
  • 解決沖突的子模塊記錄
  • 提交我們的合并

有趣的是,Git 還能處理另一種情況。 如果子模塊目錄中存在著這樣一個合并提交,它的歷史中包含了的兩邊的提交,那么 Git 會建議你將它作為一個可行的解決方案。 它看到有人在子模塊項目的某一點上合并了包含這兩次提交的分支,所以你可能想要那個。

這就是為什么前面的錯誤信息是 “merge following commits not found”,因為它不能 這樣 做。 它讓人困惑是因為誰能想到它會嘗試這樣做?

如果它找到了一個可以接受的合并提交,你會看到類似下面的信息:

$ git merge origin/master
warning: Failed to merge submodule uzykj-docs (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:

git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "uzykj-docs"

which will accept this suggestion.
Auto-merging uzykj-docs
CONFLICT (submodule): Merge conflict in uzykj-docs
Automatic merge failed; fix conflicts and then commit the result.

Git 建議的命令是更新索引,就像你運行了 git add 那樣,這樣會清除沖突然后提交。
不過你可能不應(yīng)該這樣做。你可以輕松地進入子模塊目錄,查看差異是什么,快進到這次提交,恰當?shù)販y試,然后提交它。

$ cd uzykj-docs/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward

$ cd ..
$ git add uzykj-docs
$ git commit -am 'Fast forwarded to a common submodule child'

這些命令完成了同一件事,但是通過這種方式你至少可以驗證工作是否有效,以及當你在完成時可以確保子模塊目錄中有你的代碼。

技巧

你可以做幾件事情來讓用子模塊工作輕松一點兒。

子模塊遍歷

有一個 foreach 子模塊命令,它能在每一個子模塊中運行任意命令。 如果項目中包含了大量子模塊,這會非常有用。

例如,假設(shè)我們想要開始開發(fā)一項新功能或者修復(fù)一些錯誤,并且需要在幾個子模塊內(nèi)工作。 我們可以輕松地保存所有子模塊的工作進度。

$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'uzykj-docs'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable

然后我們可以創(chuàng)建一個新分支,并將所有子模塊都切換過去。

$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'uzykj-docs'
Switched to a new branch 'featureA'

你應(yīng)該明白。 能夠生成一個主項目與所有子項目的改動的統(tǒng)一差異是非常有用的。

$ git diff; git submodule foreach 'git diff'
Submodule uzykj-docs contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)

      commit_pager_choice();

+     url = url_decode(url_orig);
+
    /* build alias_argv */
    alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
    alias_argv[0] = alias_string + 1;
Entering 'uzykj-docs'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}

+char *url_decode(const char *url)
+{
+       return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;

在這里,我們看到子模塊中定義了一個函數(shù)并在主項目中調(diào)用了它。 這明顯是個簡化了的例子,但是希望它能讓你明白這種方法的用處。

有用的別名

你可能想為其中一些命令設(shè)置別名,因為它們可能會非常長而你又不能設(shè)置選項作為它們的默認選項。 我們在 Git 別名 介紹了設(shè)置 Git 別名, 但是如果你計劃在 Git 中大量使用子模塊的話,這里有一些例子。

$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'

這樣當你想要更新子模塊時可以簡單地運行 git supdate ,或 git spush 檢查子模塊依賴后推送。

問題

然而使用子模塊還是有一些小問題。

切換分支

例如,使用 Git 2.13 以前的版本時,在有子模塊的項目中切換分支可能會造成麻煩。 如果你創(chuàng)建一個新分支,在其中添加一個子模塊,之后切換到?jīng)]有該子模塊的分支上時,你仍然會有一個還未跟蹤的子模塊目錄。

$ git --version
git version 2.12.2

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/ghostxbh/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'adding crypto library'
[add-crypto 4445836] adding crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary

$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
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)

    CryptoLibrary/

nothing added to commit but untracked files present (use "git add" to track)

移除那個目錄并不困難,但是有一個目錄在那兒會讓人有一點困惑。 如果你移除它然后切換回有那個子模塊的分支,需要運行 submodule update --init 來重新建立和填充。

$ git clean -fdx
Removing CryptoLibrary/

$ git checkout add-crypto
Switched to branch 'add-crypto'

$ ls CryptoLibrary/

$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'

$ ls CryptoLibrary/
Makefile    includes    scripts     src

新版的 Git(>= 2.13)通過為 git checkout 命令添加 --recurse-submodules 選項簡化了所有這些步驟, 它能為了我們要切換到的分支讓子模塊處于的正確狀態(tài)。

$ git --version
git version 2.13.3

$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'

$ git submodule add https://github.com/ghostxbh/CryptoLibrary
Cloning into 'CryptoLibrary'...
...

$ git commit -am 'adding crypto library'
[add-crypto 4445836] adding crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary

$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working tree clean

當你在父級項目的幾個分支上工作時,對 git checkout 使用 --recurse-submodules 選項也很有用, 它能讓你的子模塊處于不同的提交上。確實,如果你在記錄了子模塊的不同提交的分支上切換, 那么在執(zhí)行 git status 后子模塊會顯示為“已修改”并指出“新的提交”。 這是因為子模塊的狀態(tài)默認不會在切換分支時保留。

這點非常讓人困惑,因此當你的項目中擁有子模塊時,可以總是使用 git checkout --recurse-submodules。 (對于沒有 --recurse-submodules 選項的舊版 Git,在檢出之后可使用 git submodule update --init --recursive 來讓子模塊處于正確的狀態(tài))。

幸運的是,你可以通過 git config submodule.recurse true 設(shè)置 submodule.recurse 選項, 告訴 Git(>=2.14)總是使用 --recurse-submodules。 如上所述,這也會讓 Git 為每個擁有 --recurse-submodules 選項的命令(除了 git clone) 總是遞歸地在子模塊中執(zhí)行。

從子目錄切換到子模塊

另一個主要的告誡是許多人遇到了將子目錄轉(zhuǎn)換為子模塊的問題。 如果你在項目中已經(jīng)跟蹤了一些文件,然后想要將它們移動到一個子模塊中,那么請務(wù)必小心,否則 Git 會對你發(fā)脾氣。 假設(shè)項目內(nèi)有一些文件在子目錄中,你想要將其轉(zhuǎn)換為一個子模塊。 如果刪除子目錄然后運行 submodule add,Git 會朝你大喊:

$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/ghostxbh/CryptoLibrary
'CryptoLibrary' already exists in the index

你必須要先取消暫存 CryptoLibrary 目錄。 然后才可以添加子模塊:

$ git rm -r CryptoLibrary
$ git submodule add https://github.com/ghostxbh/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.

現(xiàn)在假設(shè)你在一個分支下做了這樣的工作。 如果嘗試切換回的分支中那些文件還在子目錄而非子模塊中時——你會得到這個錯誤:

$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting

你可以通過 checkout -f 來強制切換,但是要小心,如果其中還有未保存的修改,這個命令會把它們覆蓋掉。

$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'

當你切換回來之后,因為某些原因你得到了一個空的 CryptoLibrary 目錄,并且 git submodule update 也無法修復(fù)它。 你需要進入到子模塊目錄中運行 git checkout . 來找回所有的文件。 你也可以通過 submodule foreach 腳本來為多個子模塊運行它。

要特別注意的是,近來子模塊會將它們的所有 Git 數(shù)據(jù)保存在頂級項目的 .git 目錄中,所以不像舊版本的 Git,摧毀一個子模塊目錄并不會丟失任何提交或分支。

擁有了這些工具,使用子模塊會成為可以在幾個相關(guān)但卻分離的項目上同時開發(fā)的相當簡單有效的方法

資源


收錄時間: 2021-09-29

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容