0


Git 子仓(Git Submodule)学习

Git 子仓学习

Git 子仓(Submodule)是 Git 提供的一种功能,用于在一个 Git 仓库(称为主仓库或 superproject)中嵌入另一个 Git 仓库(称为子仓或 submodule)。这种功能在管理大型项目或依赖关系较多的项目时非常有用。

Git 子仓的特点和用途

  1. 模块化管理:可以将项目中的某些部分独立成一个子仓库进行独立管理,这样可以保持代码的模块化和独立性。
  2. 版本一致性:每个子仓都有独立的版本控制,可以锁定在某个特定的提交或版本,从而保证主项目中使用的子仓版本的稳定性。
  3. 复用代码:子仓可以在多个项目中复用,避免代码重复和版本不一致的问题。

添加已有的子仓

使用

git submodule add

命令可以将一个已存在的 Git 仓库添加为子仓。

$ 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”。 如果你想要放到其他地方,那么可以在命令结尾添加一个不同的路径。

可以注意到新的

.gitmodules

文件。 该配置文件保存了项目 URL 与已经拉取的本地目录之间的映射:

[submodule "DbConnector"]
    path = DbConnector
    url = https://github.com/chaconinc/DbConnector

如果有多个子仓,该文件中就会有多条记录。 要重点注意的是, 该文件也像

.gitignore

文件一样受到(通过)版本控制。 它会和该项目的其他部分一同被拉取推送 。 这就是克隆该项目的人知道去哪获得子仓的原因。

克隆含有子仓的项目

当克隆一个含有子仓的项目时,默认会包含该子仓目录,但其中还没有任何文件:

$ 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.
$ cd MainProject/DbConnector/
$ 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'

其实也有更简单的方法,可以将

git submodule init

git submodule update

合并运行,可以使用命令:

  • git submodule update --init:初始化并拉取当前提交。
  • git submodule update --init --recursive: 初始化、拉取并检出任何嵌套的子仓 。

主仓克隆时自动初始化子仓

除了上述方法,还有一种更简单的方式:

  • git clone --recurse-submodules :自动初始化并同时更新仓库中的每一个子仓, 包括可能存在的嵌套子仓 。
$ git clone --recurse-submodules 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(+)

从主仓拉取子仓更新

作为协作者,当有自己的

MainProject

主仓的本地克隆, 只是执行

git pull

获取你新提交的更改还不够:

$ git pull
From https://github.com/chaconinc/MainProject
   fb9093c..0a24cfc  master     -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
   c3f01dc..c87d55d  stable     -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
 .gitmodules         |2 +-
 DbConnector         |2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

$ 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:   DbConnector (new commits)

Submodules changed but not updated:

* DbConnector c87d55d...c3f01dc (4):
  < catch non-null terminated lines
  <more robust error handling
  <more efficient db routine
  < better connection routine

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

默认情况下,

git pull

命令会递归地抓取子仓的更改,如上面第一个命令的输出所示。

然而,它不会 更新 子仓 。这点可通过

git status

命令看到,它会显示子仓“已修改”,且“有新的提交”。

此外,左边的尖括号(<)指出了新的提交, 表示这些提交已在 MainProject 中记录,但尚未在本地的

DbConnector

中检出 。

手动拉取单个子仓

由于

git pull

不能直接更新子仓,为了完成子仓的更新,还需要在

git pull

后运行

git submodule update

运行

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'
手动拉取所有子仓

如果希望一次性直接更新所有子仓,可以使用

git submodule update --init --recursive

直接递归拉取所有子仓:

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

$ git status
 On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
自动拉取所有子仓

如果想自动化前述过程,直接和主仓的拉取命令结合。那么 可以在主仓的

git pull

命令添加

--recurse-submodules

选项 (从 Git 2.14 开始)。 这会让 Git 在拉取后运行

git submodule update

,将子仓置为正确的状态。

$ git pull --recurse-submodules

如果想让 Git 总是以

--recurse-submodules

拉取,可以通过

git config submodule.recurse true

设置

submodule.recurse

选项, 告诉 Git(>=2.14)总是使用

--recurse-submodules

$ git config submodule.recurse true
$ git pull

如上所述,这也会让 Git 为每个拥有

--recurse-submodules

选项的命令(除了

git clone

) 总是递归地在子仓中执行。

这样就可以直接在运行

git pull

拉取主仓的时候,同步更新子仓。

子仓检出分支

上述

git submodule update --remote

命令默认会假定你想要更新并检出子仓仓库的

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 submodule update

从子仓仓库中抓取修改时, Git 将会获得这些改动并更新子目录中的文件,但是 会将子仓库留在一个称作“游离的 HEAD”的状态 。这意味着没有本地工作分支(例如 “master” )跟踪改动。如果没有工作分支跟踪更改,也就意味着 即便你将更改提交到了子仓,这些更改也很可能会在下次运行

git submodule update

时丢失 。

为了将子仓设置得更容易进入并修改,首先需要做两件事来更新代码:

  1. 进入每个子仓并检出其相应的工作分支。
  2. 运行 git submodule update --remote 拉取最新的代码。
$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
$ cd..
$ 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
   c87d55d..92c7337  stable     -> origin/stable
Updating c87d55d..92c7337
Fast-forward
 src/main.c |1 +
 1file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in'92c7337b30ef9e0893e758dac2459d07362ab5ea'

如果我们进入 DbConnector 目录,可以发现新的改动已经合并入本地

stable

分支。

将修改代码与远端合并/变基

接着, 可以运行

git submodule update --remote --merge

或者

git submodule update --remote --rebase

,将远端代码合并或者变基到本地提交的代码上。

$ cd DbConnector/
# 修改代码并提交
$ vim src/db.c
$ git commit -am'unicode support'[stable f906e16] unicode support
 1file changed, 1 insertion(+)
$ cd..# 更新子仓
$ 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 状态 。

即便这真的发生了也不要紧,你只需回到目录中 再次检出你的分支(即还包含着你的工作的分支),然后手动地合并或变基

origin/stable

(或任何一个你想要的远程分支) 就行了。

本地合并失败的情况

如果没有提交子仓的改动,那么更新子仓也不会成功,此时 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/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 也会让你知道:

$ 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.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca'in submodule path 'DbConnector'

发布子仓改动

如果我们在主仓中提交并推送但并不推送子仓上的改动,其他尝试检出我们修改的人会遇到麻烦, 因为他们无法得到依赖的子仓改动。那些改动只存在于我们本地的拷贝中。

为了确保这不会发生,可以让 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 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 进入到 DbConnector 模块中然后在推送主项目前推送了它。 如果那个子仓因为某些原因推送失败,主项目也会推送失败。

你也可以通过 设置

git config push.recurseSubmodules on-demand

让它成为默认行为 。

更新子仓远端仓库

.gitmodules

文件中记录的子仓的 URL 发生了改变。此时,若父级项目引用的子仓提交不在仓库中本地配置的子仓远端上,那么执行

git pull --recurse-submodules

git submodule update

就会失败。

为了补救,需要借助

git submodule sync

命令:

# 将新的 URL 复制到本地配置中
$ git submodule sync--recursive# 从新 URL 更新子仓
$ git submodule update --init--recursive

子仓技巧

子仓遍历

使用

git submodule foreach

子仓命令,它能在每一个子仓中运行任意命令。

例如,可以创建一个新分支,并将所有子仓都切换过去。

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

子仓别名

你可能想为其中一些命令设置别名,因为它们可能会非常长而你又不能设置选项作为它们的默认选项。 我们在 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

检查子仓依赖后推送。

参考文献

  1. Git - 子仓

部分图片来源网络,如有侵权请联系我删除。
如有疑问或错误,欢迎和我私信交流指正。
版权所有,未经授权,请勿转载!
Copyright © 2024.07 by Mr.Idleman. All rights reserved.


标签: git 学习 github

本文转载自: https://blog.csdn.net/qq_42059060/article/details/140712272
版权归原作者 Mr.Idleman 所有, 如有侵权,请联系我们删除。

“Git 子仓(Git Submodule)学习”的评论:

还没有评论