更新Git子模块到最新的提交

我有一个带有Git子模块的项目。它来自ssh://… URL,并且在提交A上。Commit B已被推送到该URL,我希望子模块检索提交并更改它。

现在,我的理解是git submodule update应该这样做,但它没有。它什么都不做(没有输出,成功退出代码)。这是一个例子:

$ mkdir foo$ cd foo$ git init .Initialized empty Git repository in /.../foo/.git/$ git submodule add ssh://user@host/git/mod modCloning into mod...user@host's password: hunter2remote: Counting objects: 131, done.remote: Compressing objects: 100% (115/115), done.remote: Total 131 (delta 54), reused 0 (delta 0)Receiving objects: 100% (131/131), 16.16 KiB, done.Resolving deltas: 100% (54/54), done.$ git commit -m "Hello world."[master (root-commit) 565b235] Hello world.2 files changed, 4 insertions(+), 0 deletions(-)create mode 100644 .gitmodulescreate mode 160000 mod# At this point, ssh://user@host/git/mod changes; submodule needs to change too.$ git submodule initSubmodule 'mod' (ssh://user@host/git/mod) registered for path 'mod'$ git submodule update$ git submodule syncSynchronizing submodule url for 'mod'$ git submodule update$ man git-submodule$ git submodule update --rebase$ git submodule update$ echo $?0$ git status# On branch masternothing to commit (working directory clean)$ git submodule update mod$ ...

我还尝试了git fetch mod,它似乎做了一个获取(但不可能,因为它没有提示输入密码!),但是git loggit show否认存在新的提交。到目前为止,我刚刚rm-ing模块并重新添加它,但这在原则上是错误的,在实践中也是乏味的。

832235 次浏览

#0命令实际上告诉Git,您希望您的子模块每个都签出已在超级项目索引中指定的提交。如果您想将您的子模块更新到远程可用的最新提交,您需要直接在子模块中执行此操作。

所以总结一下:

# Get the submodule initiallygit submodule add ssh://bla submodule_dirgit submodule init
# Time passes, submodule upstream is updated# and you now want to update
# Change to the submodule directorycd submodule_dir
# Checkout desired branchgit checkout master
# Updategit pull
# Get back to your project rootcd ..
# Now the submodules are in the state you want, sogit commit -am "Pulled down update to submodule_dir"

如果你是一个忙碌的人:

git submodule foreach git pull origin master

@杰森在某种程度上是正确的,但不完全正确。

更新

更新注册的子模块,即克隆缺失的子模块和签出指定的提交包含存储库的索引。这将使子模块HEAD成为分离,除非--rebase或--合并是指定或密钥$name.update设置为重新定位或合并。

所以,git submodule update确实签出,但它是对包含存储库索引中的提交。它根本不知道上游的新提交。所以去你的子模块,获取你想要的提交,并在主存储库中提交更新的子模块状态,然后做git submodule update

你的主项目指向子模块应该在的一个特定提交。git submodule update尝试在每个已经初始化的子模块中检查该提交。子模块实际上是一个独立的存储库——仅仅在子模块中创建一个新的提交并推送是不够的。你还需要在主项目中显式添加子模块的新版本。

所以,在你的情况下,你应该在子模块中找到正确的提交-让我们假设这是master的提示:

cd modgit checkout mastergit pull origin master

现在回到主项目,阶段子模块并提交:

cd ..git add modgit commit -m "Updating the submodule 'mod' to the latest version"

现在推送您的主项目的新版本:

git push origin master

从现在开始,如果其他人更新了他们的主项目,那么他们的git submodule update将更新子模块,假设它已经初始化。

在您的项目父目录中,运行:

git submodule update --init

或者,如果您运行递归子模块:

git submodule update --init --recursive

有时这仍然不起作用,因为在更新子模块时,您在本地子模块目录中进行了本地更改。

大多数情况下,本地更改可能不是您要提交的更改。它可能是由于子模块中的文件删除等原因发生的。如果是这样,请在本地子模块目录和项目父目录中重置,再次运行:

git submodule update --init --recursive

在这个讨论中,似乎有两种不同的场景混合在一起:

情景1

使用我的父存储库指向子模块的指针,我想检查父存储库指向的每个子模块中的提交,可能是在第一次迭代所有子模块并从远程更新/拉取它们之后。

这是,正如所指出的,完成

git submodule foreach git pull origin BRANCHgit submodule update

场景2,我认为这是OP的目标

在一个或多个子模块中发生了新的东西,我想1)拉取这些更改,2)更新父存储库以指向这个/这些子模块的HEAD(最新)提交。

这将由

git submodule foreach git pull origin BRANCHgit add module_1_namegit add module_2_name......git add module_n_namegit push origin BRANCH

不太实用,因为您必须对所有n个子模块的n条路径进行硬编码,例如脚本以更新父存储库的提交指针。

通过每个子模块进行自动迭代会很酷,更新父存储库指针(使用git add)以指向子模块的头部。

为此,我制作了这个小Bash脚本:

git-update-submodules.sh

#!/bin/bash
APP_PATH=$1shift
if [ -z $APP_PATH ]; thenecho "Missing 1st argument: should be path to folder of a git repo";exit 1;fi
BRANCH=$1shift
if [ -z $BRANCH ]; thenecho "Missing 2nd argument (branch name)";exit 1;fi
echo "Working in: $APP_PATH"cd $APP_PATH
git checkout $BRANCH && git pull --ff origin $BRANCH
git submodule syncgit submodule initgit submodule updategit submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"
for i in $(git submodule foreach --quiet 'echo $path')doecho "Adding $i to root repo"git add "$i"done
git commit -m "Updated $BRANCH branch of deployment repo to point to latest head of submodules"git push origin $BRANCH

要运行它,执行

git-update-submodules.sh /path/to/base/repo BRANCH_NAME

拟订

首先,我假设名称为$BRANCH(第二个参数)的分支存在于所有存储库中。请随意使其更加复杂。

前几节是检查参数是否存在。然后我提取父存储库的最新内容(每当我只是做拉取时,我更喜欢使用--ff(快进)。顺便说一句,我有rebase off)。

git checkout $BRANCH && git pull --ff origin $BRANCH

然后一些子模块初始化,可能是必要的,如果新的子模块已经添加或尚未初始化:

git submodule syncgit submodule initgit submodule update

然后我更新/拉取所有子模块:

git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

注意以下几点:首先,我使用&&链接了一些Git命令——这意味着以前的命令必须无错误地执行。

在可能成功的拉取之后(如果在远程上找到了新的东西),我做了一个推送以确保可能的合并提交不会留在客户端上。同样,它只发生如果拉取实际带来了新的东西。

最后,最后的|| true是确保脚本在错误时继续运行。要做到这一点,迭代中的所有内容都必须用双引号包装,Git命令用括号包装(运算符优先)。

我最喜欢的部分:

for i in $(git submodule foreach --quiet 'echo $path')doecho "Adding $i to root repo"git add "$i"done

迭代所有子模块-使用--quiet,它删除了“输入MODULE_PATH”输出。使用'echo $path'(必须用单引号),子模块的路径被写入输出。

这个相对子模块路径的列表被捕获在一个数组中($(...))-最后迭代这个并执行git add $i来更新父存储库。

最后,一个带有一些消息的提交,说明父存储库已更新。默认情况下,如果什么都没做,这个提交将被忽略。将其推送到源,你就完成了。

我有一个脚本在Jenkins作业中运行这个,然后链接到预定的自动部署,它的工作原理就像一个魅力。

我希望这会对某人有所帮助。

Git 1.8.2提供了一个新选项--remote,它将完全启用此行为。运行

git submodule update --remote --merge

将从每个子模块的上游获取最新更改,合并它们,并查看子模块的最新版本。正如留档所说:

#远程

此选项仅对更新命令有效。使用子模块的远程跟踪分支的状态,而不是使用超级项目记录的SHA-1来更新子模块。

这相当于在每个子模块中运行git pull <remote> <default_branch>(通常是git pull origin mastergit pull origin main),这通常正是您想要的。

这里有一个很棒的一行程序,可以将所有内容更新到master上的最新内容:

git submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive

感谢Mark Jaquith

简单明了,获取子模块:

git submodule update --init --recursive

现在继续将它们更新到最新的master分支(例如):

git submodule foreach git pull origin master

在我的例子中,我希望git更新到最新的,同时重新填充任何丢失的文件。

以下恢复了丢失的文件(感谢--force,这里似乎没有提到),但它没有提取任何新的提交:

git submodule update --init --recursive --force

这确实:

git submodule update --recursive --remote --merge --force

git pull --recurse-submodules

这将拉取所有最新的提交。

如果您不知道主机分支,请这样做:

git submodule foreach git pull origin $(git rev-parse --abbrev-ref HEAD)

它将获取主Git存储库的一个分支,然后对每个子模块进行相同分支的拉取。

注意,更新子模块提交的现代形式是:

git submodule update --recursive --remote --force

请参阅 加布里埃尔 · 斯台普斯的答案,以获得另一个选择,使用 --merge --force没有

即使包含存储库的索引中指定的提交已经与子模块中检出的提交匹配,--force选项也允许执行检出。

在这种情况下,--merge选项似乎没有必要: “超级项目中记录的提交将合并到子模块中的当前分支中。”


较早的形式是:

git submodule foreach --quiet git pull --quiet origin

除了... 第二种形式不是真正的“安静”。

犯下282f5a(2019年4月12日) by 易懂的泰语(pclouds)
(由 朱尼奥 · C · 哈马诺 gitster于2019年4月25日在 提交 f1c9f6c合并)

解决“ <command> --quiet”不被尊重的问题

罗宾报告说

git submodule foreach --quiet git pull --quiet origin

不再那么安静了。
Fc1b924之前应该保持安静(submodule: port submodule子命令‘ foreach’from shell to C,2018-05-10,Git v2.19.0-rc0) ,因为 parseopt不能意外地吃掉选项。

git pull”表现得好像没有给出 --quiet

这是因为 submodule--helper中的 parseopt将尝试解析 这两个 --quiet选项就好像它们是 foreach 的选项,而不是 git-pull的选项。
解析后的选项将从命令行中删除 稍后拉,我们执行这个

git pull origin

当调用子模块助手时,在“ git pull”前面添加“ --”将 停止 parseopt,以便解析实际上不属于 submodule--helper foreach.

作为一项安全措施,PARSE_OPT_KEEP_UNKNOWN被移除 永远不会看到未知的选择或出现问题 我正在看一些用法字符串更新。

同时,我还将“ --”添加到其他子命令中,这些子命令将“ $@”传递给 “ $@”在这些情况下是路径,不太可能是 --something-like-this.
但是这个观点仍然站得住脚,git-submodule已经解析和分类了什么是选项,什么是路径。
submodule--helper不应该将 git-submodule传递的路径视为选项,即使它们看起来像一个选项。


Git 2.23(Q32019)修复了另一个问题: 当使用“ --recursive”选项时,“ git submodule foreach”没有保护传递给每个子模块中要正确运行的命令的命令行选项。

提交30db18b(2019年6月24日) by 莫里安十四行诗(momoson)
(由 朱尼奥 · C · 哈马诺 gitster于2019年7月9日在 投入968eecb合并)

submodule foreach: 修正选项的递归

来电:

git submodule foreach --recursive <subcommand> --<option>

导致一个错误,说明选项 --<option>不知道 submodule--helper.
当然,这只是在 <option>不是 git submodule foreach的有效选项的情况下。

这样做的原因是,上面的调用在内部被转换为 调用 submodule —— helper:

git submodule--helper foreach --recursive \
-- <subcommand> --<option>

此调用首先执行子命令,其选项位于 第一级子模块,并继续调用 submodule foreach电话

git --super-prefix <submodulepath> submodule--helper \
foreach --recursive <subcommand> --<option>

在第一级子模块内。注意,前面的双破折号 子命令不见了。

这个问题最近才开始出现,因为在提交 A282f5a时,git submodule foreach参数解析的 PARSE_OPT_KEEP_UNKNOWN标志被移除了。
因此,现在有人抱怨未知选项,因为参数解析没有以双破折号正确地结束。

这个提交通过在递归过程中在子命令前面添加双破折号来修复问题。


注意,在 Git 2.29(Q42020)之前,“ git submodule update --quiet(< a href = “ https://git-scm.com/docs/git-submodule # Document/git-submodule.txt-——static”rel = “ nofollow noReferrer”> man )没有压制底层的“ rebase”和“ pull”命令。

提交3ad0401(2020年9月30日) by 西奥多 · 杜布瓦(tbodt)
(由 朱尼奥 · C · 哈马诺 gitster提交300cd14合并,2020年10月5日)

submodule update : 使用“ --quiet”进行 merge/rebase 时保持沉默

签名: Theodore Dubois

诸如

$ git pull --rebase --recurse-submodules --quiet

从 merge 或 rebase 产生非安静输出。
在调用“ rebase”和“ merge”时向下传递 --quiet选项。

还要修复 git submodule update(< a href = “ https://git-scm.com/docs/git-submodule # Document/git-submodule.txt-update-init-remote-N% 7C-no-fetch-no-fetch-no-椒盐-f% 7C-force-checkout% 7C-rebase% 7C-merge-reference enceltrepositorygt-depthltdepthgt-recsive-jobsltngt-no-single-Branch-[ tpathgt...”rel = “ nofollow noReferrer”> man )-v 的解析。

E84c3cf3(“ git-submodule.sh: 接受 cmd_update中的详细标志为非静音”,2018-08-14,Git v2.19.0-rc0—— 合并)教授“ git submodule update”“ (< a href = “ https://git-scm.com/docs/git-submodule # Document/git-submodule.txt-update-init-remote-N% 7C-no-fetch-no-fetch-no-椒盐-f% 7C-force-checkout% 7C-rebase% 7C-merge-reference enceltrepositorygt-depthltdepthgt-recsive-jobsltngt-no-single-Branch-[ tpathgt...”rel = “ nofollow noReferrer”> man )采用“ --quiet”时,它显然不知道 ${GIT_QUIET: + —— static }是如何工作的,并且审阅人员似乎错过了将变量设置为“0”而不是取消设置,仍然导致“ --quiet”被传递给底层命令。


使用 Git 2.38(Q32022) ,git-submodule.sh准备转变为内置的,这意味着有上述问题的 submodule--helper正在淡出。

提交5b893f7提交2eec463提交8f12108提交36d4516第六季,第5集,完第六季,第7集提交 d9c7f69承认吧提交757d092提交5b893f70,提交5b893f71(2022年6月28日) by 提交5b893f72。
提交 b788fc6(2022年6月28日) by Glen Choo (chooglen)
(由 朱尼奥 · C · 哈马诺 gitster提交361cbe6合并,2022年7月14日)

使用「 $quiet」 ,而不是「 $GIT_QUIET"

埃瓦尔 · 阿恩菲尔 · 比亚尔马森

自从 B3c5f5c(“ submodule: 将核心 cmd_update()逻辑移动到 C”,2022-03-15,Git v2.36.0-rc0—— 合并)以来,我们在 git-sh-setup.sh中没有使用“ say”函数,这是唯一受到使用“ GIT_QUIET"”影响的函数。

我们仍然希望支持 --quiet,但是让我们使用我们自己的变量。
现在很明显,我们只关心将“ --quiet”传递给 git submodule--helper,而不更改任何“ say”调用的输出。

如果希望为每个子模块签出 master分支,可以使用以下命令:

git submodule foreach git checkout master

处理包含 子模组的 git 项目的最简单方法是始终添加

--recurse-submodules

在每个 git 命令的末尾 例如:

git fetch --recurse-submodules

另一个

git pull --update --recurse-submodules

等等。

这对我来说可以更新到最新的提交

git submodule update --recursive --remote --init

对我来说,git submodule没有都有效,但是这个有效:

cd <path/to/submodule>
git pull

它下载并更新第三方回购协议。 然后

cd <path/to/repo>
git commit -m "update latest version" <relative_path/to/submodule>
git push

它更新您的远程回购(与最后提交 repo@xxxxxx的链接)。

如何更新所有的 git 子模块在一个回购(两种方法做两个 非常不同的事情!)

快速总结

# Option 1: as a **user** of the outer repo, pull the latest changes of the
# sub-repos as previously specified (pointed to as commit hashes) by developers
# of this outer repo.
# - This recursively updates all git submodules to their commit hash pointers as
#   currently committed in the outer repo.
git submodule update --init --recursive


# Option 2. As a **developer** of the outer repo, update all subrepos to force
# them each to pull the latest changes from their respective upstreams (ex: via
# `git pull origin main` or `git pull origin master`, or similar, for each
# sub-repo).
git submodule update --init --recursive --remote


# now add and commit these subrepo changes
git add -A
git commit -m "Update all subrepos to their latest upstream changes"

细节

  1. 备选案文1: 作为外部回购协议的 使用者,努力使所有子模块进入外部回购协议开发者所希望的状态:
    git submodule update --init --recursive
    
  2. 选项2: 作为外部回购的 开发商,尝试将所有子模块更新为最新的提交推送到每个远程回购的默认分支(即: 将所有子回购更新为每个子回购的开发者所希望的最新状态) :
    git submodule update --init --recursive --remote
    
    代替使用 git submodule foreach --recursive git pull origin mastergit submodule foreach --recursive git pull origin main

在我看来,上述两个选项的最佳答案是 没有使用我在其他答案中看到的 --merge--force选项。

对上述备选办法的解释:

  • 上面的 --init部分初始化子模块,以防您刚刚克隆了回购,但还没有做到这一点
  • --recursive对子模块中的子模块执行这种操作,递归地永远停止
  • --remote指示将子模块更新为子模块默认远程的默认分支上的最新提交。在大多数情况下,这类似于对每个子模块执行 git pull origin mastergit pull origin main。如果您想要更新到最外层的回购(超级回购)指定的提交,请关闭 --remote

git submodule foreach --recursive git pull(不要使用这个——它经常失败)与 git submodule update --recursive --remote(使用这个! ——它总是有效的)

我留下了以下的评论 在这个答案之下。我认为他们是重要的,所以我把他们放在我的答案了。

基本上,在某些情况下,git submodule foreach --recursive git pull可能会起作用。对于其他人来说,git submodule foreach --recursive git pull origin master可能是您需要的。对于其他人来说,git submodule foreach --recursive git pull origin main可能就是您所需要的。而对于其他人来说,没有的这些可能工作!例如,您可能需要 git submodule foreach --recursive git pull upstream develop。或者,更糟糕的是,可能没有适用于外部回购的 任何 git submodule foreach命令,因为每个子模块可能需要一个不同的命令来从其默认的远程和默认分支更新自己。在 所有的情况下,我可以找到,但是,这个 是的的工作,包括对 所有的情况下,你可能使用的几个 git submodule foreach命令之一,我刚才介绍了上面。所以,用这个代替:

git submodule update --recursive --remote

无论如何,下面是我对 在这个答案之下的几点看法:

(1/4)@DavidZ,很多人认为 git submodule foreach git pullgit submodule update --remote是一回事,后者只是更新的命令。然而,他们不是也是同样的道理。git submodule foreach git pull在多种情况下失败,其中 git submodule update --remote工作正常!如果您的子模块指向一个提交散列,而这个散列没有指向它的分支,这种情况在现实开发中经常出现,您希望为您的外部回购提供子模块的特定版本,那么该子模块..。

(2/4) ... 处于分离的 HEAD 状态。在这种情况下,git submodule foreach git pull无法在该子模块上运行 git pull,因为分离的 HEAD 不能有上游分支。但是,如果 origin是默认远程,而 main是该默认远程的默认分支,或者 git pull origin master,例如,如果 origin是默认远程,而 master是默认分支,那么它似乎在该子模块上调用 git pull origin main

(3/4)此外,在许多情况下,git submodule foreach git pull origin master甚至会失败,因为许多子模块使用 master作为默认分支,而许多其他子模块使用 main作为默认分支,因为 GitHub 最近从 master改为 main,以摆脱与美国奴隶制度(“主”和“从”)有关的术语。

(4/4)因此,我添加了明确的远程和分支,以便更清楚地表明它们是经常需要的,并提醒人们,git pull通常是不够的,git pull origin master可能不工作,当前者不工作时,git pull origin main可能工作,甚至可能不工作,而且它们的 没有本身与 git submodule update --remote相同,因为后一个命令足够聪明,只需为每个子模块执行 git pull <default_remote> <default_branch>,显然调整远程和分支,以满足每个子模块的需要。

相关及其他研究

  1. 如何找到回购的主要分支: https://stackoverflow.com/a/49384283/4561887
  2. 如何通过 git submodule foreach <cmd>: https://stackoverflow.com/a/45744725/4561887运行一个自定义命令来更新每个子报表
  3. 然后搜索 foreach--remote等。