Git 工具 - 子模块 submodule

本文介绍Git子模块的概念及其使用方法,包括子模块的初始化、更新、合并等操作流程,以及如何解决子模块带来的常见问题。

7.11 Git 工具 - 子模块
子模块
有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。

我们举一个例子。 假设你正在开发一个网站然后创建了 Atom 订阅。 你决定使用一个库,而不是写自己的 Atom 生成代码。 你可能不得不通过 CPAN 安装或 Ruby gem 来包含共享库中的代码,或者将源代码直接拷贝到自己的项目中。 如果将这个库包含进来,那么无论用何种方式都很难定制它,部署则更加困难,因为你必须确保每一个客户端都包含该库。 如果将代码复制到自己的项目中,那么你做的任何自定义修改都会使合并上游的改动变得困难。

Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。

开始使用子模块
我们将要演示如何在一个被分成一个主项目与几个子项目的项目上开发。

我们首先将一个已存在的 Git 仓库添加为正在工作的仓库的子模块。 你可以通过在 git submodule add 命令后面加上想要跟踪的项目 URL 来添加新的子模块。 在本例中,我们将会添加一个名为 “DbConnector” 的库。

$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into ‘DbConnector’…
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.
默认情况下,子模块会将子项目放到一个与仓库同名的目录中,本例中是 “DbConnector”。 如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。

如果这时运行 git status,你会注意到几件事。

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

Changes to be committed:
(use “git reset HEAD …” to unstage)

new file:   .gitmodules
new file:   DbConnector

首先应当注意到新的 .gitmodules 文件。 该置文件保存了项目 URL 与已经拉取的本地目录之间的映射:

$ cat .gitmodules
[submodule “DbConnector”]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
如果有多个子模块,该文件中就会有多条记录。 要重点注意的是,该文件也像 .gitignore 文件一样受到(通过)版本控制。 它会和该项目的其他部分一同被拉取推送。 这就是克隆该项目的人知道去哪获得子模块的原因。

Note
由于 .gitmodules 文件中的 URL 是人们首先尝试克隆/拉取的地方,因此请尽可能确保你使用的URL 大家都能访问。 例如,若你要使用的推送 URL 与他人的拉取 URL 不同,那么请使用他人能访问到的 URL。 你也可以根据自己的需要,通过在本地执行 git config submodule.DbConnector.url <私有URL> 来覆盖这个选项的值。 如果可行的话,一个相对路径会很有帮助。

在 git status 输出中列出的另一个是项目文件夹记录。 如果你运行 git diff,会看到类似下面的信息:

$ git diff –cached DbConnector
diff –git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
— /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
虽然 DbConnector 是工作目录中的一个子目录,但 Git 还是会将它视作一个子模块。当你不在那个目录中时,Git 并不会跟踪它的内容, 而是将它看作该仓库中的一个特殊提交。

如果你想看到更漂亮的差异输出,可以给 git diff 传递 –submodule 选项。

$ git diff –cached –submodule
diff –git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
— /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule “DbConnector”]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000…c3f01dc (new submodule)
当你提交时,会看到类似下面的信息:

$ git commit -am ‘added DbConnector module’
[master fb9093c] added DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
注意 DbConnector 记录的 160000 模式。 这是 Git 中的一种特殊模式,它本质上意味着你是将一次提交记作一项目录记录的,而非将它记录成一个子目录或者一个文件。

克隆含有子模块的项目
接下来我们将会克隆一个含有子模块的项目。 当你在克隆这样的项目时,默认会包含该子模块目录,但其中还没有任何文件:

$ git clone https://github.com/chaconinc/MainProject
Cloning into ‘MainProject’…
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity… done.
cdMainProject ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r–r– 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r–r– 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
cdDbConnector/ ls
$
其中有 DbConnector 目录,不过是空的。 你必须运行两个命令:git submodule init 用来初始化本地配置文件,而 git submodule update 则从该项目中抓取所有数据并检出父项目中列出的合适的提交。

$ git submodule init
Submodule ‘DbConnector’ (https://github.com/chaconinc/DbConnector) registered for path ‘DbConnector’
$ git submodule update
Cloning into ‘DbConnector’…
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.
Submodule path ‘DbConnector’: checked out ‘c3f01dc8862123d317dd46284b05b6892c7b29bc’
现在 DbConnector 子目录是处在和之前提交时相同的状态了。

不过还有更简单一点的方式。 如果给 git clone 命令传递 –recursive 选项,它就会自动初始化并更新仓库中的每一个子模块。

$ git clone –recursive https://github.com/chaconinc/MainProject
Cloning into ‘MainProject’…
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity… done.
Submodule ‘DbConnector’ (https://github.com/chaconinc/DbConnector) registered for path ‘DbConnector’
Cloning into ‘DbConnector’…
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.
Submodule path ‘DbConnector’: checked out ‘c3f01dc8862123d317dd46284b05b6892c7b29bc’
在包含子模块的项目上工作
现在我们有一份包含子模块的项目副本,我们将会同时在主项目和子模块项目上与队员协作。

拉取上游修改
在项目中使用子模块的最简模型,就是只使用子项目并不时地获取更新,而并不在你的检出中进行任何更改。 我们来看一个简单的例子。

如果想要在子模块中查看新工作,可以进入到目录中运行 git fetch 与 git merge,合并上游分支来更新本地代码。

$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
如果你现在返回到主项目并运行 git diff –submodule,就会看到子模块被更新的同时获得了一个包含新添加提交的列表。 如果你不想每次运行 git diff 时都输入 –submodle,那么可以将 diff.submodule 设置为 “log” 来将其作为默认行为。

gitconfigglobaldiff.submodulelog git diff
Submodule DbConnector c3f01dc..d0354fc:

more efficient db routine
better connection routine
如果在此时提交,那么你会将子模块锁定为其他人更新时的新代码。

如果你不想在子目录中手动抓取与合并,那么还有种更容易的方式。 运行 git submodule update –remote,Git 将会进入子模块然后抓取并更新。

$ git submodule update –remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path ‘DbConnector’: checked out ‘d0354fc054692d3906c85c3af05ddce39a1c0644’
此命令默认会假定你想要更新并检出子模块仓库的 master 分支。 不过你也可以设置为想要的其他分支。 例如,你想要 DbConnector 子模块跟踪仓库的 “stable” 分支,那么既可以在 .gitmodules 文件中设置(这样其他人也可以跟踪它),也可以只在本地的 .git/config 文件中设置。 让我们在 .gitmodules 文件中设置它:

$ git config -f .gitmodules submodule.DbConnector.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)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path ‘DbConnector’: checked out ‘c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687’
如果不用 -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 …” to update what will be committed)
(use “git checkout – …” to discard changes in working directory)

modified: .gitmodules
modified: DbConnector (new commits)

no changes added to commit (use “git add” and/or “git commit -a”)
如果你设置了配置选项 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 …” to update what will be committed)
(use “git checkout – …” to discard changes in working directory)

modified:   .gitmodules
modified:   DbConnector (new commits)

Submodules changed but not updated:

  • DbConnector 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 “DbConnector”]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector 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: Scott Chacon schacon@gmail.com Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff –git a/.gitmodules b/.gitmodules index 6fc0b3d..fd1cc29 100644 — a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule “DbConnector”] path = DbConnector url = https://github.com/chaconinc/DbConnector + branch = stable Submodule DbConnector c3f01dc..c87d55d:

catch non-null terminated lines
more robust error handling
more efficient db routine
better connection routine
当运行 git submodule update –remote 时,Git 默认会尝试更新所有子模块,所以如果有很多子模块的话,你可以传递想要更新的子模块的名字。

在子模块上工作 你很有可能正在使用子模块,因为你确实想在子模块中编写代码的同时,还想在主项目上编写代码(或者跨子模块工作)。 否则你大概只能用简单的依赖管理系统(如 Maven 或 Rubygems)来替代了。 现在我们将通过一个例子来演示如何在子模块与主项目中同时做修改,以及如何同时提交与发布那些修改。 到目前为止,当我们运行 git submodule update 从子模块仓库中抓取修改时,Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作 “游离的 HEAD” 的状态。 这意味着没有本地工作分支(例如 “master”)跟踪改动。 所以你做的任何改动都不会被跟踪。 为了将子模块设置得更容易进入并修改,你需要做两件事。 首先,进入每个子模块并检出其相应的工作分支。 接着,若你做了更改就需要告诉 Git 它该做什么,然后运行 git submodule update –remote 来从上游拉取新工作。 你可以选择将它们合并到你的本地工作中,也可以尝试将你的工作变基到新的更改上。 首先,让我们进入子模块目录然后检出一个分支。 gitcheckoutstableSwitchedtobranchstablemergeupdatemerge 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) Unpacking objects: 100% (4/4), done. From https://github.com/chaconinc/DbConnector c87d55d..92c7337 stable -> origin/stable Updating c87d55d..92c7337 Fast-forward src/main.c | 1 + 1 file changed, 1 insertion(+) Submodule path ‘DbConnector’: merged in ‘92c7337b30ef9e0893e758dac2459d07362ab5ea’ 如果我们进入 DbConnector 目录,可以发现新的改动已经合并入本地 stable 分支。 现在让我们看看当我们对库做一些本地的改动而同时其他人推送另外一个修改到上游时会发生什么。 cdDbConnector/ vim src/db.c gitcommitamunicodesupport[stablef906e16]unicodesupport1filechanged,1insertion(+) git submodule update –remote –rebase First, rewinding head to replay your work on top of it… Applying: unicode support Submodule path ‘DbConnector’: rebased into ‘5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94’ 如果你忘记 –rebase 或 –merge,Git 会将子模块更新为服务器上的状态。并且会将项目重置为一个游离的 HEAD 状态。 gitsubmoduleupdateremoteSubmodulepathDbConnector:checkedout5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94便origin/stableGit 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/chaconinc/DbConnector 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. Aborting Unable to checkout ‘c75e92a2b3855c9e5b66f915308390d9db204aca’ in submodule path ‘DbConnector’ 如果你做了一些与上游改动冲突的改动,当运行更新时 Git 会让你知道。 gitsubmoduleupdateremotemergeAutomergingscripts/setup.shCONFLICT(content):Mergeconflictinscripts/setup.shRecordedpreimageforscripts/setup.shAutomaticmergefailed;fixconflictsandthencommittheresult.Unabletomergec75e92a2b3855c9e5b66f915308390d9db204acainsubmodulepathDbConnector git diff Submodule DbConnector c87d55d..82d2ad3:

Merge from origin/stable
updated setup script
unicode support
remove unnecessary method
add new option for conn pooling
如果我们在主项目中提交并推送但并不推送子模块上的改动,其他尝试检出我们修改的人会遇到麻烦,因为他们无法得到依赖的子模块改动。 那些改动只存在于我们本地的拷贝中。

为了确保这不会发生,你可以让 Git 在推送到主项目前检查所有子模块是否已推送。 git push 命令接受可以设置为 “check” 或 “on-demand” 的 –recurse-submodules 参数。 如果任何提交的子模块改动没有推送那么 “check” 选项会直接使 push 操作失败。 $ git push –recurse-submodules=check The following submodule paths contain changes that can not be found on any remote: DbConnector Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote. 如你所见,它也给我们了一些有用的建议,指导接下来该如何做。 最简单的选项是进入每一个子模块中然后手动推送到远程仓库,确保它们能被外部访问到,之后再次尝试这次推送。 另一个选项是使用 “on-demand” 值,它会尝试为你这样做。 gitpushrecursesubmodules=ondemandPushingsubmoduleDbConnectorCountingobjects:9,done.Deltacompressionusingupto8threads.Compressingobjects:100Writingobjects:100Total9(delta3),reused0(delta0)Tohttps://github.com/chaconinc/DbConnectorc75e92a..82d2ad3stable>stableCountingobjects:2,done.Deltacompressionusingupto8threads.Compressingobjects:100Writingobjects:100Total2(delta1),reused0(delta0)Tohttps://github.com/chaconinc/MainProject3d6d338..9a377d1master>masterGitDbConnectorGitGit 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/chaconinc/MainProject 9a377d1..eb974f8 master -> origin/master Fetching submodule DbConnector warning: Failed to merge submodule DbConnector (merge following commits not found) Auto-merging DbConnector CONFLICT (submodule): Merge conflict in DbConnector Automatic merge failed; fix conflicts and then commit the result. 所以本质上 Git 在这里指出了子模块历史中的两个分支记录点已经分叉并且需要合并。 它将其解释为 “merge following commits not found”(未找到接下来需要合并的提交),虽然这有点令人困惑,不过之后我们会解释为什么是这样。 为了解决这个问题,你需要弄清楚子模块应该处于哪种状态。 奇怪的是,Git 并不会给你多少能帮你摆脱困境的信息,甚至连两边提交历史中的 SHA-1 值都没有。 幸运的是,这很容易解决。 如果你运行 git diff,就会得到试图合并的两个分支中记录的提交的 SHA-1 值。 gitdiffdiffccDbConnectorindexeb41d76,c771610..0000000a/DbConnector+++b/DbConnectoreb41d76c771610eb41d76SHA1SHA1gitdiffSHA cd DbConnector gitrevparseHEADeb41d764bccf88be77aced643c13a7fa86714135 git branch try-merge c771610 (DbConnector) gitmergetrymergeAutomergingsrc/main.cCONFLICT(content):Mergeconflictinsrc/main.cRecordedpreimageforsrc/main.cAutomaticmergefailed;fixconflictsandthencommittheresult. vim src/main.c (1) gitaddsrc/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 DbConnector index eb41d76,c771610..0000000 — a/DbConnector +++ b/DbConnector @@@ -1,1 -1,1 +1,1 @@@ - Subproject commit eb41d764bccf88be77aced643c13a7fa86714135 -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d ++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a gitaddDbConnector(4) git commit -m “Merge Tom’s Changes” (5) [master 10d2c60] Merge Tom’s Changes 首先解决冲突 然后返回到主项目目录中 再次检查 SHA-1 值 解决冲突的子模块记录 提交我们的合并 这可能会让你有点儿困惑,但它确实不难。 有趣的是,Git 还能处理另一种情况。 如果子模块目录中存在着这样一个合并提交,它的历史中包含了的两边的提交,那么 Git 会建议你将它作为一个可行的解决方案。 它看到有人在子模块项目的某一点上合并了包含这两次提交的分支,所以你可能想要那个。 这就是为什么前面的错误信息是 “merge following commits not found”,因为它不能 这样 做。 它让人困惑是因为谁能想到它会尝试这样做? 如果它找到了一个可以接受的合并提交,你会看到类似下面的信息: gitmergeorigin/masterwarning:FailedtomergesubmoduleDbConnector(notfastforward)Foundapossiblemergeresolutionforthesubmodule:9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a:>mergedourchangesIfthisiscorrectsimplyaddittotheindexforexamplebyusing:gitupdateindexcacheinfo1600009fd905e5d7f45a0d4cbc43d1ee550f16a30e825aDbConnectorwhichwillacceptthissuggestion.AutomergingDbConnectorCONFLICT(submodule):MergeconflictinDbConnectorAutomaticmergefailed;fixconflictsandthencommittheresult.gitadd cd DbConnector/ gitmerge9fd905eUpdatingeb41d76..9fd905eFastforward cd .. gitaddDbConnector git commit -am ‘Fast forwarded to a common submodule child’ 这些命令完成了同一件事,但是通过这种方式你至少可以验证工作是否有效,以及当你在完成时可以确保子模块目录中有你的代码。 子模块技巧 你可以做几件事情来让用子模块工作轻松一点儿。 子模块遍历 有一个 foreach 子模块命令,它能在每一个子模块中运行任意命令。 如果项目中包含了大量子模块,这会非常有用。 例如,假设我们想要开始开发一项新功能或者修复一些错误,并且需要在几个子模块内工作。 我们可以轻松地保存所有子模块的工作进度。 gitsubmoduleforeachgitstashEnteringCryptoLibraryNolocalchangestosaveEnteringDbConnectorSavedworkingdirectoryandindexstateWIPonstable:82d2ad3Mergefromorigin/stableHEADisnowat82d2ad3Mergefromorigin/stable git submodule foreach ‘git checkout -b featureA’ Entering ‘CryptoLibrary’ Switched to a new branch ‘featureA’ Entering ‘DbConnector’ Switched to a new branch ‘featureA’ 你应该明白。 能够生成一个主项目与所有子项目的改动的统一差异是非常有用的。 $ git diff; git submodule foreach ‘git diff’ Submodule DbConnector 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 ‘DbConnector’
    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;
在这里,我们看到子模块中定义了一个函数并在主项目中调用了它。 这明显是个简化了的例子,但是希望它能让你明白这种方法的用处。

有用的别名
你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 我们在 Git 别名 介绍了设置 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 checkout -b add-crypto
Switched to a new branch ‘add-crypto’

$ git submodule add https://github.com/chaconinc/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 …” 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 会对你发脾气。 假设项目内有一些文件在子目录中,你想要将其转换为一个子模块。 如果删除子目录然后运行 submodule add,Git 会朝你大喊:

rmRfCryptoLibrary/ git submodule add https://github.com/chaconinc/CryptoLibrary
‘CryptoLibrary’ already exists in the index
你必须要先取消暂存 CryptoLibrary 目录。 然后才可以添加子模块:

gitrmrCryptoLibrary git submodule add https://github.com/chaconinc/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.
现在假设你在一个分支下做了这样的工作。 如果尝试切换回的分支中那些文件还在子目录而非子模块中时 - 你会得到这个错误:

$ 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
你可以通过 check -f 来强制切换,但是要小心,如果其中还有未保存的修改,这个命令会把它们覆盖掉。

$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch ‘master’
当你切换回来之后,因为某些原因你得到了一个空的 CryptoLibrary 目录,并且 git submodule update 也无法修复它。 你需要进入到子模块目录中运行 git checkout . 来找回所有的文件。 你也可以通过 submodule foreach 脚本来为多个子模块运行它。

要特别注意的是,近来子模块会将它们的所有 Git 数据保存在顶级项目的 .git 目录中,所以不像旧版本的 Git,摧毁一个子模块目录并不会丢失任何提交或分支。

拥有了这些工具,使用子模块会成为可以在几个相关但却分离的项目上同时开发的相当简单有效的方法。

<think> 我们正在解决在ARM64设备上交叉编译PyTorch时出现的两个关键错误: 1. "submodule initialization failed"(子模块初始化失败) 2. "fatal: 不是 git 仓库(或者任何父目录):.git"(表明当前目录不是Git仓库) 根据用户提供的信息,我们正在交叉编译PyTorch(版本为torch-2.8.0a0+gitUnknown),并且是在ARM64架构上进行的。 分析问题: 第一个错误(子模块初始化失败)通常是因为在编译过程中,构建系统需要初始化Git子模块(例如pybind11等),但可能由于以下原因之一: a) 没有正确克隆子模块(即缺少`--recursive`参数) b) 网络问题导致子模块下载失败 c) 缺少Git LFS(大文件存储)支持 第二个错误(不是Git仓库)表明构建过程试图在某个目录中执行Git命令,但该目录不是一个Git仓库(缺少.git目录)。这可能是因为: a) 用户下载的是源代码压缩包(而非通过Git克隆) b) 在复制源代码时丢失了.git目录 解决方案: 步骤1:确认当前目录是否为Git仓库 在PyTorch源码根目录执行: ```bash ls -a | grep .git ``` 如果没有任何输出,则说明当前目录不是Git仓库。这意味着我们需要重新以正确方式获取源代码。 步骤2:重新获取PyTorch源代码(使用完整Git仓库方式) 由于用户需要交叉编译,并且出现了.git缺失的问题,建议重新克隆整个仓库,并确保包含所有子模块: ```bash # 删除原有目录(可选,备份重要修改) cd /home/re/桌面/pytorch_env/pytorch-cross rm -rf pytorch # 使用递归克隆(确保包含子模块git clone --recursive --depth=1 https://github.com/pytorch/pytorch.git cd pytorch # 如果还需要特定的分支或标签,可以切换(注意:2.8.0a0对应分支) git checkout v2.8.0a0 # 或者具体的commit,如果标签不存在,请查找对应分支 ``` 注意:如果使用`--depth=1`(浅克隆)可能会影响某些子模块的检出,如果遇到问题,可以去掉该参数。 步骤3:处理Git LFS问题(如果存在大文件) 引用[4]中提到,如果仓库使用了Git LFS,可能会遇到smudge错误。解决方法是: ```bash # 安装Git LFS(如果未安装) sudo apt-get install git-lfs # 对于Debian/Ubuntu # 在PyTorch源码目录中初始化Git LFS并跳过smudge(跳过下载大文件,后续再拉取) git lfs install --skip-smudge # 然后重新更新子模块(如果已经克隆了完整仓库,这一步可以省略,但为了确保,可以执行) git submodule update --init --recursive ``` 步骤4:手动初始化子模块(如果步骤2中递归克隆失败) 如果由于网络问题导致子模块初始化失败,可以手动逐个初始化: ```bash # 进入PyTorch目录 cd pytorch # 初始化子模块(只初始化,不更新) git submodule init # 逐个更新子模块(如果某个子模块失败,可以单独处理) git submodule update third_party/pybind11 # 例如先更新pybind11 # 或者一次性更新所有(可能会遇到网络问题) git submodule update --recursive ``` 步骤5:如果仍然遇到子模块问题,尝试手动修复 例如,对于pybind11子模块: ```bash cd third_party rm -rf pybind11 # 删除原有(可能不完整) git clone https://github.com/pybind/pybind11.git cd .. # 注意:手动克隆后,需要确保版本与PyTorch要求一致(通过PyTorch的.gitmodules文件查看) ``` 步骤6:验证.git目录和子模块的存在 确保在PyTorch根目录存在.git目录,并且third_party/pybind11等子模块目录中都有相应的文件(如CMakeLists.txt)。 步骤7:开始交叉编译 在正确初始化仓库和子模块后,按照交叉编译的步骤进行: ```bash # 设置交叉编译环境变量(示例为ARM64) export CMAKE_TOOLCHAIN_FILE=/path/to/toolchain.cmake # 指定工具链文件 # 或者使用其他方式(如设置CC、CXX等) # 执行构建脚本(根据PyTorch官方交叉编译文档) python setup.py build --cmake-only # 先测试CMake配置 python setup.py build # 或者直接构建 ``` 注意:交叉编译PyTorch需要配置正确的工具链和依赖库(如OpenBLAS,FFTW等),请确保这些依赖已经交叉编译并安装到目标位置。 引用说明: 在重新克隆仓库时,我们参考了Git的标准做法(引用[2]中提到交叉编译PyTorch的aarch64版本,但具体步骤可能不同)以及引用[4]中关于Git LFS的解决方案。 相关问题: 1. 如何为ARM64交叉编译配置CMake工具链? 2. PyTorch交叉编译需要哪些依赖库? 3. 如何解决Git子模块更新过程中的网络问题? </think> 针对在ARM64设备上交叉编译PyTorch时出现的`submodule initialization failed`和`fatal: 不是 git 仓库`错误,以下是系统性的解决方案: ### 根本原因分析 1. **Git仓库结构损坏** `.git`目录缺失或损坏导致无法识别为Git项目,引发`fatal: not a git repository`错误[^2] 2. **子模块初始化失败** 由于仓库结构损坏,无法执行`git submodule update --init --recursive` ### 完整解决方案 #### 步骤1:重建Git仓库结构 ```bash cd /home/re/桌面/pytorch_env/pytorch-cross/pytorch # 重新初始化Git仓库 rm -rf .git # 删除损坏的Git目录 git init # 重新初始化 # 添加远程仓库 git remote add origin https://github.com/pytorch/pytorch.git # 获取完整提交历史 git fetch --depth=1 origin v2.8.0a0 # 获取指定版本 ``` #### 步骤2:恢复子模块 ```bash # 配置.gitmodules文件 cat > .gitmodules <<EOF [submodule "third_party/pybind11"] path = third_party/pybind11 url = https://github.com/pybind/pybind11.git [submodule "third_party/cub"] path = third_party/cub url = https://github.com/NVlabs/cub.git EOF # 递归初始化子模块 git submodule update --init --recursive --depth=1 ``` > **如遇网络问题**:添加Git代理 > ```bash > git config --global url."https://ghproxy.com/https://github.com".insteadOf https://github.com > ``` #### 步骤3:验证仓库状态 ```bash # 检查关键文件 ls -d .git third_party/pybind11/.git # 验证子模块 git submodule status | grep -v "^-" ``` 应有输出: ``` 4f5cf4341bcfb3a4ca47f8112d9d78b0018d4b5a third_party/pybind11 (v2.11.1) ``` #### 步骤4:ARM64交叉编译关键配置 在`setup.py`中添加ARM64特定参数: ```diff diff --git a/setup.py b/setup.py index xxxxxxx..xxxxxxx 100644 --- a/setup.py +++ b/setup.py @@ -XXX,6 +XXX,7 @@ class build_ext(setuptools.command.build_ext.build_ext): cmake_args = [ '-DCMAKE_BUILD_TYPE=' + build_type, + '-DCMAKE_SYSTEM_PROCESSOR=arm64', + '-DCMAKE_OSX_ARCHITECTURES=arm64' if IS_DARWIN else '', '-DPYTHON_EXECUTABLE=' + sys.executable, ] ``` #### 步骤5:执行交叉编译 ```bash # 设置ARM64工具链 export CC=aarch64-linux-gnu-gcc export CXX=aarch64-linux-gnu-g++ # 开始编译 python setup.py build --cmake-only python setup.py install ``` ### 关键注意事项 1. **版本锁定机制** 使用`git fetch --depth=1`避免下载全量历史,但需确保版本匹配: $$ \text{commit}_{pytorch} \mapsto \text{commit}_{submodule} $$ 可通过`.gitmodules`文件确认子模块版本要求[^1] 2. **ARM64特有依赖** 交叉编译需预先安装: ```bash sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu ``` 3. **磁盘空间要求** 编译过程需要至少15GB空闲空间,建议清理: ```bash du -sh . # 检查当前占用 git clean -xdf # 清理构建缓存 ``` > **典型成功输出**: > `Successfully installed torch-2.8.0a0+gitUnknown` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值