“Git:子模块”的版本间差异
(→推送) |
(→删除) |
||
第276行: | 第276行: | ||
# 提交我们的合并 | # 提交我们的合并 | ||
== | == 删除项目的子模块 == | ||
git没有直接删除子模块的命令,所以只能逐步删除相关文件。 | git没有直接删除子模块的命令,所以只能逐步删除相关文件。 | ||
# 在版本控制中删除子模块: | # 在版本控制中删除子模块: |
2020年10月14日 (三) 20:06的版本
关于submodule
有种情况我们经常会遇到:某个工作中的项目需要包含并使用另一个项目。 也许是第三方库,或者你独立开发的,用于多个父项目的库。 现在问题来了:你想要把它们当做两个独立的项目,同时又想在一个项目中使用另一个。
Git 通过子模块来解决这个问题。 子模块允许你将一个 Git 仓库作为另一个 Git 仓库的子目录。 它能让你将另一个仓库克隆到自己的项目中,同时还保持提交的独立。
可以使用 git submodule --help
查看所有相关命令。
git clone <repository> --recursive //递归的方式克隆整个项目
git submodule add <repository> <path> //添加子模块
git submodule init //初始化子模块
git submodule update //更新子模块
git submodule foreach git pull //拉取所有子模块
开始使用子模块
关联项目子模块:
# 添加子模块
git submodule add https://github.com/test/subb.git
# 添加子模块,并为子模块设置路径(/modules/subb)
git submodule add https://github.com/test/subb.git modules/subb
项目根目录下有一个.gitmodules文件,即子模块关联文件,如:
[submodule "modules/suba"]
path = modules/suba
url = https://github.com/test/suba.git
每添加一个子模块就会新增一条记录,如果是第一次添加Git子模块会自动生成。
克隆含有子模块的项目
当一个 git 项目包含子模块(submodule) 时,克隆这样的项目时,默认会包含该子模块目录,但子模块目录里面是空的。有两种方法解决:
- 如果项目已经克隆到了本地,执行下面的步骤:
- 初始化本地子模块配置文件:
git submodule init
- 更新项目,抓取子模块内容:
git submodule update
- 初始化本地子模块配置文件:
- 对于未克隆项目,使用“--recursive”参数,可以自动初始化并更新每一个子模块。
git clone --recursive 仓库地址 # 或者 git clone --recurse-submodules 仓库地址
Note:
- “--recursive”,用于的嵌套(项目中的子模块,子模块中的子模块)。
git submodule init
和git submodule update
可以合并成一步:git submodule update --init # 当有子模块嵌套时: git submodule update --init --recursive
在包含子模块的项目上工作
同时在主项目和子模块项目上与队员协作开发时:
从子模块的远端拉取上游修改
在项目中使用子模块的最简模型,就是只使用子项目并不时地获取更新,而并不在你的检出中进行任何更改。
- 如果想要在子模块中查看新工作,可以进入到目录中运行 git fetch 与 git merge,合并上游分支来更新本地代码:
# 进入子模块目录 cd modules/suba # 拉取更新 git fetch # 合并分支 git merge origin/master
- 如果不想在子目录中手动抓取与合并,那么可以采取另一种方式进行抓取和更新:
git submodule update --remote <submodule_name>
- 此命令默认更新并检出子模块仓库的 master 分支,如果需要操作其他分支:
- 需要修改分支配置【既可以在 .gitmodules 文件中设置 (这样其他人也可以跟踪它),也可以只在本地的 .git/config 文件中设置(本地有效)】。
# 在.gitmodules 文件中设置,使用DbConnector模块的stable分支 $ git config -f .gitmodules submodule.DbConnector.branch stable # 拉取子模块更新 $ git submodule update --remote
从项目远端拉取上游更改
作为主项目协作开发者来说,一般情况下会使用 git pull 来拉取项目更新,但是:默认情况下,git pull 命令会递归地抓取子模块的更改,然而,它不会更新子模块!这点可通过 git status 命令看到,它会显示子模块“已修改”,且“有新的提交”。
- 为了完成更新,需要运行 git submodule update:
# 拉取项目更新 git pull # 更新子模块代码 git submodule update --init --recursive
- “--init”:是为了防止 提交中有新的子模块,未初始化而不能更新 的情况;
- “--recursive”:是为了 子模块中有嵌套子模块 的情况;
- 如果想要自动化以上过程:
git pull --recurse-submodules
- 这会让 Git 在拉取后运行 git submodule update,将子模块置为正确的状态;
- 如果要让 Git 总是以 --recurse-submodules 拉取,可以将配置选项 submodule.recurse 设置为 true;
- 在为父级项目拉取更新时,还会出现一种特殊的情况:可能 .gitmodules 文件中记录的子模块的 URL 发生了改变(如,子模块项目改变了它的托管平台),此时,若父级项目引用的子模块提交不在仓库中本地配置的子模块远端上,那么执行
git pull --recurse-submodules
或git submodule update
就会失败。- 此时需要:
# 将新的 URL 复制到本地配置中 $ git submodule sync --recursive # 从新 URL 更新子模块 $ git submodule update --init --recursive
在子模块上工作
在开发过程中可能会出现:在子模块中编写代码的同时,还想在主项目上编写代码(或者跨子模块工作)。这就需要我们了解:
- 如何在子模块与主项目中同时做修改;
- 如何同时提交与发布那些修改。
当运行 git submodule update 从子模块仓库中抓取修改时, Git 将会获得这些改动并更新子目录中的文件,但是会将子仓库留在一个称作“'''游离的 HEAD'''”的状态(HEAD未指向任何分支或提交)。 这意味着子模块没有本地工作分支(如 “master” )跟踪改动,也就意味着即便将更改提交到了子模块,这些更改也很可能会在下次运行 git submodule update 时丢失。
所以,我们需要:
- 进入子模块目录然后检出一个分支:
# 进入子模块DbConnector $ cd DbConnector/ # 检出DbConnector的stable分支 $ git checkout stable Switched to branch 'stable'
- 更新上游代码到子模块分支,再进行本地工作;
Note:
- 更新子模块,并合并到子模块当前分支:
$ cd .. $ git submodule update --remote --merge
- 更新子模块,并变基合并到子模块当前分支:
$ cd .. $ git submodule update --remote --rebase
- 若忘记 --rebase 或 --merge,Git 会将子模块更新为服务器上的状态,并且会将项目重置为一个游离的 HEAD 状态。
$ git submodule update --remote
- 只需回到目录中再次检出分支(即包含着本地工作的分支),然后手动地合并或变基(任何需要的远程分支)即可;
- 若没有提交子模块的改动,那么运行一个子模块更新也不会出现问题,此时 Git 会只抓取更改而并不会覆盖子模块目录中未保存的工作;
- 若本地做了一些与上游改动冲突的改动,当运行更新时 Git 会有提示,进入子模块目录中后进行修复冲突即可。
发布子模块改动
现在我们的子模块目录中有一些改动。其中一部分是通过更新从上游引入的,而另一部分是本地生成的,由于还没有进行推送,所以对任何其他人都不可用。
为了确保推送主项目依赖的子模块已推送,可以让 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.
如上,同时给出了操作建议,指导接下来该如何做:
- 进入每一个子模块中然后手动推送到远程仓库,之后再次尝试推送主项目。
- 如果要对所有推送都执行检查,那么可以通过设置
git config push.recurseSubmodules check
让它成为默认行为。
- 如果要对所有推送都执行检查,那么可以通过设置
- 使用“on-demand”值:Git 进入到子模块中,并在推送主项目前推送子模块。
- 如果某个子模块因为某些原因推送失败,主项目也会推送失败。
- 也可以通过设置
git config push.recurseSubmodules on-demand
让它成为默认行为
$ git push --recurse-submodules=on-demand Pushing submodule 'DbConnector' 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/chaconinc/DbConnector 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/chaconinc/MainProject 3d6d338..9a377d1 master -> master
合并子模块改动
如果你和其他人同时改动了同一个子模块,也就是说,如果子模块的历史已经分叉并且在父项目中分别提交到了分叉的分支上,那么你需要做一些工作来修复它:
- 如果一个提交是另一个的直接祖先(一个快进式合并),那么 Git 会简单地选择之后的提交来合并;
- 但如果子模块提交已经分叉且需要合并,Git 就不能通过简单的选择commit来解决问题,如:
$ 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.
合并思路:
找到子模块的当前提交点(的 SHA-1 值),然后:直接通过 SHA-1 或 创建一个分支(推荐)来尝试合并。
合并步骤:
- 运行git diff,就会得到试图合并的两个分支中记录的提交的 SHA-1 值:
$ git diff diff --cc DbConnector index eb41d76,c771610..0000000 --- a/DbConnector +++ b/DbConnector
- 进入子模块目录,基于 git diff 的第二个 SHA-1 创建一个分支然后手动合并:
$ cd DbConnector $ git rev-parse HEAD eb41d764bccf88be77aced643c13a7fa86714135 $ git branch try-merge c771610 (DbConnector) $ 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.
- 在这儿得到了一个真正的合并冲突,所以如果想要解决并提交它,那么只需简单地通过结果来更新主项目。
$ 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 DbConnector index eb41d76,c771610..0000000 --- a/DbConnector +++ b/DbConnector @@@ -1,1 -1,1 +1,1 @@@ - Subproject commit eb41d764bccf88be77aced643c13a7fa86714135 -Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d ++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a $ git add DbConnector (4) $ git commit -m "Merge Tom's Changes" (5) [master 10d2c60] Merge Tom's Changes
合并总结:
- 首先解决冲突
- 然后返回到主项目目录中
- 再次检查 SHA-1 值
- 解决冲突的子模块记录
- 提交我们的合并
删除项目的子模块
git没有直接删除子模块的命令,所以只能逐步删除相关文件。
- 在版本控制中删除子模块:
git rm -r modules/subb
- 在编辑器中删除如下相关内容,也可以使用命令“vi .gitmodules”在vim中删除:
[submodule "modules/subb"] path = modules/subb url = https://github.com/test/subb.git branch = dev
- 在编辑器中删除如下相关内容,也可以使用命令“vim .git/config”在vim中删除:
[submodule "modules/subb"] path = modules/subb url = https://github.com/test/subb.git active = true
- 删除.git下的缓存模块:
rm -rf .git/modules/subb
- 提交修改:
git commit -am "delete subb" git push origin dev