如何在添加Git子模块时指定分支/标签?

git submodule add -b是如何工作的?

添加具有特定分支的子模块后,新的克隆存储库(在git submodule update --init之后)将位于特定提交处,而不是分支本身(子模块上的git status显示“当前不在任何分支上”)。

我在.gitmodules.git/config上找不到任何关于子模块分支或任何特定提交的信息,那么Git是如何解决的呢?

此外,是否可以指定一个标签而不是一个分支?

我用的是1.6.5.2版本。

842577 次浏览

Git子模块有点奇怪——它们总是处于“分离头”模式——它们不会像你期望的那样更新到分支上的最新提交。

不过,当您考虑它时,这确实有一些意义。假设我使用子模块酒吧创建了存储库foo。我推送我的更改并告诉您从存储库foo中签出提交a7402be。

然后想象一下,在您进行克隆之前,有人提交了对存储库酒吧的更改。

当你从存储库foo中签出提交a7402be时,你希望得到我推送的相同代码。这就是为什么子模块在你明确告诉它们然后进行新的提交之前不会更新。

就我个人而言,我认为子模块是Git中最令人困惑的部分。有很多地方可以比我更好地解释子模块。我推荐Scott Chacon的progit

注意:Git 1.8.2添加了跟踪分支的可能性。请看下面的一些答案。


习惯这个有点令人困惑,但是子模块并不在一个分支上。就像你说的,它们只是指向子模块存储库的特定提交的指针。

这意味着,当其他人签出您的存储库或提取您的代码并进行git子模块更新时,该子模块将签出到该特定提交。

这对于不经常更改的子模块来说非常棒,因为这样项目中的每个人都可以在同一次提交中拥有子模块。

如果要将子模块移动到特定标记:

cd submodule_directorygit checkout v1.0cd ..git add submodule_directorygit commit -m "moved submodule to v1.0"git push

然后,另一个希望submodule_directory更改为该标签的开发人员这样做

git pullgit submodule update --init

git pull更改提交其子模块目录指向。git submodule update实际上合并在新代码中。

我如何使用Git子模块的一个例子。

  1. 创建一个新的存储库
  2. 然后克隆另一个仓库作为子模块
  3. 然后我们让该子模块使用名为V3.1.2的标签
  4. 然后我们承诺。

这看起来有点像这样:

git initvi READMEgit add READMEgit commitgit submodule add git://github.com/XXXXX/xxx.yyyy.git stm32_std_libgit status
git submodule initgit submodule update
cd stm32_std_lib/git reset --hard V3.1.2cd ..git commit -a
git submodule status

也许它有帮助(即使我使用标签而不是分支)?

Git 1.8.2增加了跟踪分支的可能性。

# add submodule to track branch_name branchgit submodule add -b branch_name URL_to_Git_repo optional_directory_rename
# update your submodulegit submodule update --remote

另见git子模块

我想在这里补充一个答案,它实际上只是其他答案的集合,但我认为它可能更完整。

当你有这两件事时,你就知道你有一个Git子模块。

  1. 你的.gitmodules有一个这样的条目:

     [submodule "SubmoduleTestRepo"]path = SubmoduleTestRepourl = https://github.com/jzaccone/SubmoduleTestRepo.git
  2. 您在Git存储库中有一个子模块对象(在本例中称为SubmoduleTestRepo)。github将这些显示为“子模块”对象。或者从命令行执行git submodule status。Git子模块对象是Git对象的特殊类型,它们保存特定提交的SHA信息。

    每当您执行git submodule update时,它都会用提交的内容填充您的子模块。由于.gitmodules中的信息,它知道在哪里找到提交。

    现在,-b所做的就是在.gitmodules文件中添加一行。因此,遵循相同的示例,它看起来像这样:

     [submodule "SubmoduleTestRepo"]path = SubmoduleTestRepourl = https://github.com/jzaccone/SubmoduleTestRepo.gitbranch = master

    备注:.gitmodules文件只支持分支名称,但是不支持SHA和TAG!(相反,每个模块的分支提交可以使用“git add .”跟踪和更新,例如git add ./SubmoduleTestRepo,并且您不需要每次更改.gitmodules文件)

    子模块对象仍然指向特定的提交。-b选项给你带来的唯一好处是能够根据Vogella的回答向你的更新添加--remote标志:

     git submodule update --remote

    它不是将子模块的内容填充到子模块指向的提交中,而是将该提交替换为master分支上的最新提交,然后用该提交填充子模块。这可以通过djakob7回答分两步完成。由于您现在已经更新了子模块对象指向的提交,因此您必须将更改的子模块对象提交到您的Git存储库中。

    git submodule add -b并不是让分支的所有内容都保持最新的神奇方法。它只是在.gitmodules文件中添加有关分支的信息,并为您提供在填充分支之前将子模块对象更新为指定分支的最新提交的选项。

(Git 2.22,Q2 2019,引入了git submodule set-branch --branch aBranch -- <submodule_path>

注意如果你有一个现有的子模块,还没有跟踪分支,然后(如果你有git 1.8.2+):

  • 确保父repo知道它的子模块现在跟踪分支:

      cd /path/to/your/parent/repogit config -f .gitmodules submodule.<path>.branch <branch>
  • 确保您的子模块实际上位于该分支的最新位置:

      cd path/to/your/submodulegit checkout -b branch --track origin/branch# if the master branch already exist:git branch -u origin/master master

        (其中“起源”是上游远程仓库的名称,子模块是从其中克隆的。
        子模块中的git remote -v将显示它。通常,它是“起源”)

  • 不要忘记在父仓库中记录子模块的新状态:

      cd /path/to/your/parent/repogit add path/to/your/submodulegit commit -m "Make submodule tracking a branch"
  • 该子模块的后续更新必须使用--remote选项:

      # update your submodule# --remote will also fetch and ensure that# the latest commit from the branch is usedgit submodule update --remote
    # to avoid fetching usegit submodule update --remote --no-fetch

请注意,对于git 2.10+(2016年第三季度),您可以使用“.”作为分支名称:

分支的名称在update --remote.gitmodules中记录为submodule.<name>.branch
.的特殊值用于指示子模块中分支的名称应与当前存储库中的当前分支的名称相同。

但是,如评论 byLubosD

对于git checkout,如果要跟随的分支名称是“.”,它会杀死您未提交的工作!
使用#0代替。

这意味着Git 2.23(2019年8月)或更高版本。

见“困惑#0


如果您想更新分支后的所有子模块:

    git submodule update --recursive --remote

请注意,对于每个更新的子模块,结果将几乎总是分离的HEAD,如他的回答中的丹·卡梅隆注释。

Clintm指出在评论,如果您运行git submodule update --remote并且生成的sha1与子模块当前所在的分支相同,则它不会做任何事情并使子模块仍然“在该分支上”而不是处于分离的头部状态。

为了确保分支实际上被签出(并且不会修改表示父存储库子模块的特殊条目的SHA1),他建议:

git submodule foreach -q --recursive 'branch="$(git config -f $toplevel/.gitmodules submodule.$name.branch)"; git switch $branch'

每个子模块仍将引用相同的SHA1,但如果您确实进行了新的提交,您将能够推送它们,因为它们将被您希望子模块跟踪的分支引用。
在子模块内推送之后,不要忘记返回父存储库,为这些修改的子模块添加、提交和推送新的SHA1。

注意$toplevel的使用,亚历山大·波格雷布尼亚克推荐在评论
$toplevel于2010年5月在git1.7.2中引入:提交f030c96

它包含顶级目录的绝对路径(其中.gitmodules是)。

dtmland添加在评论

Foreach脚本将无法检出未跟随分支的子模块。
但是,此命令为您提供:

 git submodule foreach -q --recursive 'branch="$(git config -f $toplevel/.gitmodules submodule.$name.branch)"; [ "$branch" = "" ] && git checkout master || git switch $branch' –

相同的命令,但更容易阅读:

git submodule foreach -q --recursive \'branch="$(git config -f $toplevel/.gitmodules submodule.$name.branch)"; \[ "$branch" = "" ] && \git checkout master || git switch $branch' –  

umläute使用简化版本在评论改进了dtmland的命令:

git submodule foreach -q --recursive 'git switch $(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'

多行:

git submodule foreach -q --recursive \'git switch \$(git config -f $toplevel/.gitmodules submodule.$name.branch || echo master)'

在Git 2.26(Q1 2020)之前,被告知在子模块中递归获取更新的获取不可避免地会产生大量输出,并且很难发现错误消息。

该命令已被教导枚举在操作结束时出错的子模块

提交0222540(2020年1月16日)by艾米丽·谢弗(#0)
(由Junio C Hamano----#0----合并于提交b5c71cc,05 Feb 2020)

fetch:在获取子模块时强调失败

署名:Emily Shaffer

在有许多子模块的情况下,当一个子模块提取失败时,如果多个提取回退到fetch-by-oid,则唯一失败的子模块提取的错误被埋在其他子模块的活动下。
晚些时候调用失败,这样用户就会意识到出了问题,以及在哪里。

因为fetch_finish()只被run_processes_parallel,同步调用,所以在submodules_with_errors周围不需要互斥。


请注意,在Git 2.28(2020年第三季度)中,继续重写脚本化的“git子模块”瓷器命令的部分内容;这次轮到“git submodule set-branch”子命令了。

提交2964d6e(2020年6月2日)byShourya Shukla(#0)
(由提交1046282中的Junio C Hamano----#0----合并,2020年6月25日)

submodule:从shell到C的端口子命令'set-分支'

导师:Christian Couder
导师:Kaartic Sivaraam
帮助者:Denton Liu
帮助者:Eric阳光
帮助者:Doàn Tr n Cng Danh
签字人:舒尔亚·舒克拉

将子模块子命令set-分支转换为内置命令并通过git submodule.sh调用它。

根据我的经验,在超级项目或未来的签出中切换分支仍然会导致子模块的分离HEAD,无论子模块是否被正确添加和跟踪(即@djakobs7和@Johnny Z回答)。

而不是手动检查出正确的分支手动或通过脚本git子模块Foreach可以使用。

这将检查分支属性的子模块配置文件并签出集合分支。

git submodule foreach -q --recursive 'branch="$(git config -f $toplevel.gitmodules submodule.$name.branch)"; git checkout $branch'

我在我的.gitconfig文件中有这个。它仍然是一个草稿,但到目前为止被证明是有用的。它帮助我始终将子模块重新连接到它们的分支。

[alias]
######################## Submodules aliases#######################
# git sm-trackbranch: places all submodules on their respective branch specified in .gitmodules# This works if submodules are configured to track a branch, i.e if .gitmodules looks like:# [submodule "my-submodule"]#   path = my-submodule#   url = git@wherever.you.like/my-submodule.git#   branch = my-branchsm-trackbranch = "! git submodule foreach -q --recursive 'branch=\"$(git config -f $toplevel/.gitmodules submodule.$name.branch)\"; git checkout $branch'"
# sm-pullrebase:# - pull --rebase on the master repo# - sm-trackbranch on every submodule# - pull --rebase on each submodule## Important note:# - have a clean master repo and subrepos before doing this!# - this is *not* equivalent to getting the last committed#   master repo + its submodules: if some submodules are tracking branches#   that have evolved since the last commit in the master repo,#   they will be using those more recent commits!##   (Note: On the contrary, git submodule update will stick#   to the last committed SHA1 in the master repo)sm-pullrebase = "! git pull --rebase; git submodule update; git sm-trackbranch ; git submodule foreach 'git pull --rebase' "
# git sm-diff will diff the master repo *and* its submodulessm-diff = "! git diff && git submodule foreach 'git diff' "
# git sm-push will ask to push also submodulessm-push = push --recurse-submodules=on-demand
# git alias: list all aliases# useful in order to learn git syntaxalias = "!git config -l | grep alias | cut -c 7-"

我们使用嘎嘎从另一个Git存储库中提取特定模块。我们需要在没有提供存储库的整个代码库的情况下提取代码-我们需要来自那个巨大存储库的非常特定的模块/文件,并且应该在每次运行更新时更新。

我们是这样实现的:

创建配置

name: Project Name
modules:local/path:repository: https://github.com/<username>/<repo>.gitpath: repo/pathbranch: devother/local/path/filename.txt:repository: https://github.com/<username>/<repo>.githexsha: 9e3e9642cfea36f4ae216d27df100134920143b9path: repo/path/filename.txt
profiles:init:tasks: ['modules']

使用上述配置,它从提供的GitHub存储库创建一个目录,如第一个模块配置中指定的,另一个是从给定的存储库提取并创建文件。

其他开发人员只需要运行

$ quack

它从上面的配置中提取代码。

要为子模块切换分支(假设您已经将子模块作为存储库的一部分):

  • cd到包含子模块的存储库的根
  • 打开.gitmodules进行编辑
  • 为每个子模块在path = ...url = ...下方添加branch = your-branch行;保存文件.gitmodules
  • 然后不改变目录do$ git submodule update --remote

…对于这样修改的每个子模块,这应该在指定分支上提取最新的提交。

为子模块选择分支的唯一效果是,每当您在git submodule update命令行中传递--remote选项时,Git将在分离头模式下签出所选远程分支的最新提交(如果选择了默认的--checkout行为)。

如果您使用子模块的浅层克隆,则在为Git子模块使用此远程分支跟踪功能时必须特别小心。您在子模块设置不是中为此目的选择的分支将在git submodule update --remote期间克隆。如果您还在git submodule update命令行中传递--depth参数和您不指示Git您要克隆哪个分支-而实际上你不能!-,当显式的--branch参数丢失时,它将隐式地表现得像git-clone(1)留档git clone --single-branch中解释的那样,因此它只会克隆主分支

毫不奇怪,在git submodule update命令执行克隆阶段之后,它将最终尝试检查您之前为子模块设置的远程分支的最新提交,并且,如果这不是主要的,它不是本地浅克隆的一部分,因此它将失败

致命的:需要一个单一的修订

无法在子模块路径“mySubmodle”中找到当前的原始/不是主要分支版本

git子模块add-b开发--name分支名称--https://branch.git

现有的答案缺少第二步,并且细节过多。

要切换现有子模块以跟踪新的远程URL和/或新分支:

  1. 编辑.gitmodules中的真相来源。

例如,从

[submodule "api"]path = apiurl = https://github.com/<original_repo>/api.git

[submodule "api"]path = apiurl = https://github.com/<another_repo>/api.gitbranch = work-in-progress

您也可以使用hexsha作为提交哈希。或tag,但请参阅3。

  1. git submodule sync:从.gitmodules中指定的刚刚编辑的真实源更新.git/modules中git缓存的子模块的描述。

  2. 只有当您指定标签:#0才能获取标签。

  3. git submodule update --init --recursive --remote:更新工作副本中的签出子模块。

  4. 提交更改。