将Git子模块更新为最新的原始提交


853

我有一个带有Git子模块的项目。它来自ssh:// ... URL,位于提交A上。提交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 mod
Cloning into mod...
user@host's password: hunter2
remote: 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 .gitmodules
 create mode 160000 mod
# At this point, ssh://user@host/git/mod changes; submodule needs to change too.
$ git submodule init
Submodule 'mod' (ssh://user@host/git/mod) registered for path 'mod'
$ git submodule update
$ git submodule sync
Synchronizing submodule url for 'mod'
$ git submodule update
$ man git-submodule 
$ git submodule update --rebase
$ git submodule update
$ echo $?
0
$ git status
# On branch master
nothing to commit (working directory clean)
$ git submodule update mod
$ ...

我也试过git fetch mod,这似乎做取(但不能可能,因为它不要求输入密码!),但git loggit show否认新提交的存在。到目前为止,我一直在rm-添加模块并重新添加它,但这在原理上是错误的,在实践中既繁琐。


5
David Z的答案似乎是执行此操作的更好方法-既然Git具有通过--remote选项内置的所需功能,也许将其标记为可接受的答案而不是Jason答案中的“手动”方法会很有用?
Mark Amery

1
我非常赞同@MarkAmery。尽管Jason提供了可行的解决方案,但这并不是实现它的预期方式,因为它使子模块的提交指针停留在错误的提交标识符上。目前,新的--remote解决方案绝对是更好的解决方案,并且由于这个问题已经与Github Gist链接到有关子模块的问题,所以我认为让新读者阅读新的回答会更好。
MutantOctopus

hunter2密码不错:o)
lfarroco

Answers:


1458

git submodule update命令实际上告诉Git,您希望您的子模块每个签出已经在超级项目的索引中指定的提交。如果要将子模块更新为可从其远程获得的最新提交,则需要直接在子模块中执行此操作。

因此,总而言之:

# Get the submodule initially
git submodule add ssh://bla submodule_dir
git submodule init

# Time passes, submodule upstream is updated
# and you now want to update

# Change to the submodule directory
cd submodule_dir

# Checkout desired branch
git checkout master

# Update
git pull

# Get back to your project root
cd ..

# Now the submodules are in the state you want, so
git commit -am "Pulled down update to submodule_dir"

或者,如果您是忙碌的人:

git submodule foreach git pull origin master

335
git submodule foreach git pull
Mathias Bynens 2011年

87
@Nicklas在这种情况下,请使用git submodule foreach git pull origin master
Mathias Bynens 2011年

54
至此,在对所有这些更正进行了更正之后,我需要有人撰写说明性博客文章并将其指向我。请。
苏兹2012年

25
对'foreach'方法的细微改进-如果子模块中有子模块,则可能需要在其中添加--recursive。因此:git submodule foreach --recursive git pull origin master
Orion elenzil 2014年

4
@Abdull -a用于git commit“告诉命令自动暂存已修改和删除的文件,但您未告知Git的新文件的命令不会受到影响” 的开关。
godfrzero

473

Git 1.8.2具有一个新选项,--remote它将完全启用此行为。跑步

git submodule update --remote --merge

将在每个子模块中从上游获取最新更改,将其合并,并检出该子模块的最新版本。正如文档所述

- 远程

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

这等效于git pull在每个子模块中运行,这通常正是您想要的。


4
“等同于git pull在每个子模块中运行”要澄清,您的答案与git submodule foreach git pull?之间没有区别(从用户的角度来看)。
丹尼斯

3
@Dennis本质上是相同的,但是我不确定功能是否完全相同。我可能不知道会有一些细微的差异,例如,这两个命令对某些配置设置的响应方式。
David Z

5
我希望我能对此10,000X投票。为什么这在git的文档中没有显示?巨大的监督。
serraosays

4
对我来说,它们实际上有很大的不同。foreach git pull仅检出它们,但没有更新主存储库的指针以指向子模块的较新提交。只有这样,--remote它才指向最新的提交。
Ela782 '16

5
为什么--merge选项?有什么区别?
mFeinstein'3

126

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

git submodule update --init

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

git submodule update --init --recursive

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

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

git submodule update --init --recursive

5
这是真正的答案。我可以以某种方式将其推送到远程存储库吗?
MonsterMMORPG '16

这适用于新的子模块!我可以更新所有其他模块,但是新子模块的文件夹将保持为空,直到运行此命令。
亚历克西斯威尔克

1
它不会拉动现有子模块的更改
Sergey G.

73

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

因此,根据您的情况,您应该在子模块中找到正确的提交-假设这是技巧master

cd mod
git checkout master
git pull origin master

现在回到主项目,暂存子模块并提交:

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

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

git push origin master

从这一点开始,如果其他任何人更新了他们的主项目,那么git submodule update他们将在假设子模块已初始化的情况下更新子模块。


24

在此讨论中,似乎将两种不同的情况混合在一起:

场景1

使用父存储库指向子模块的指针,我想检查父存储库指向的每个子模块中的提交,可能是在首先遍历所有子模块并从远程更新/拉出它们之后。

如前所述,这是通过

git submodule foreach git pull origin BRANCH
git submodule update

方案2,我认为这是OP的目标

一个或多个子模块中发生了新的事情,我想1)进行这些更改并2)更新父存储库,以指向此/这些子模块的HEAD(最新)提交。

这将通过

git submodule foreach git pull origin BRANCH
git add module_1_name
git add module_2_name
......
git add module_n_name
git push origin BRANCH

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

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

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

git-update-submodules.sh

#!/bin/bash

APP_PATH=$1
shift

if [ -z $APP_PATH ]; then
  echo "Missing 1st argument: should be path to folder of a git repo";
  exit 1;
fi

BRANCH=$1
shift

if [ -z $BRANCH ]; then
  echo "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 sync
git submodule init
git submodule update
git submodule foreach "(git checkout $BRANCH && git pull --ff origin $BRANCH && git push origin $BRANCH) || true"

for i in $(git submodule foreach --quiet 'echo $path')
do
  echo "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(快速转发)。我已经将基准重新设置了,顺便说一句)。

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

然后,如果已添加新子模块或尚未对其进行初始化,则可能需要一些子模块初始化:

git submodule sync
git submodule init
git 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')
do
  echo "Adding $i to root repo"
  git add "$i"
done

使用--quiet遍历所有子模块- 删除“ Entering MODULE_PATH”输出。使用'echo $path'(必须用单引号引起来)子模块的路径写入输出。

相对子模块路径的此列表捕获在数组($(...))中-最后对其进行迭代,并git add $i进行更新以更新父存储库。

最后,提交带有一些消息的提交,说明父存储库已更新。如果未执行任何操作,则默认情况下将忽略此提交。将其推入原点,即可完成。

我在Jenkins作业中有一个运行此脚本的脚本,此脚本随后链接到计划的自动部署,并且它的工作原理很吸引人。

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


2
!@#$%因此,我们正在使用与您相似的脚本;一个注意事项:我们在for循环内使用git子模块foreach --recursive --quiet pwd而不是git子模块foreach --quiet'echo $ path'。该pwd命令为存在的每个子模块打印正确的“绝对路径”。--recursive确保我们访问所有子模块,包括大型项目中可能存在的submodules-within-submodules-...。两种方法/c/Users/Ger/Project\ Files/...都会给包含空格的目录造成麻烦,例如,因此政策是永远不要在我们项目的任何地方使用空格。
Ger Hobbelt 2014年

2
很好,您是对的,甚至对问题的答案都存在误解,但正如David Z的出色答案所指出的那样,您的脚本是不必要的,因为该功能自2013年中期以来已内置到Git中他们添加了该--remote选项。git submodule update --remote行为大致与脚本相同。
马克·阿默里

@GerHobbelt谢谢。没错,我们只有1个级别的子模块,所以我从没想过要递归。在我有机会验证它是否可以按预期运行之前,我不会更新脚本,但是绝对可以,我的脚本会包含子子模块。至于文件夹中的空格,这绝对听起来像是要避免的东西!:S
FrederikStruck-Schøning2015年

@MarkAmery感谢您的反馈。但是,我看到了1个问题:无法通过参数指定子模块的分支。从git手册:The remote branch used defaults to master, but the branch name may be overridden by setting the submodule.<name>.branch option in either .gitmodules or .git/config (with .git/config taking precedence).我不想每次都想对master以外的另一个分支进行.gitmodules或.git / config编辑。但是也许我错过了什么?而且,该方法似乎强制执行递归合并(因此缺少快速前进的可能性)。
FrederikStruck-Schøning15年

最后一件事:我尝试了@DavidZ的方法,但它似乎并没有做确切的事情,我打算去做(以及哪个op在问):将子模块的HEAD提交添加到父对象(即“更新指针”) )。但是,它似乎确实很好(而且更快)地完成了所有子模块中最新更改的获取和合并。las,默认情况下仅来自master分支(除非您编辑.gitmodules文件(请参见上文))。
FrederikStruck-Schøning15年

19

简单而简单,要获取子模块:

git submodule update --init --recursive

现在,将其更新到最新的master分支(例如):

git submodule foreach git pull origin master

12

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

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

较旧的形式是:

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

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

看看提交a282f5a(2019年4月12日)由阮泰玉维战(pclouds
(由Junio C gitsterHamano合并--commit f1c9f6c中,2019年4月25日)

submodule foreach:修正“ <command> --quiet”不被尊重

罗宾报道

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

真的不再安静了
fc1b924submodule:从shell到C的port submodule子命令' foreach',2018-05-10,Git v2.19.0-rc0)之前应该保持安静,因为parseopt那时不能偶然吃掉选项。

git pull”的行为就像--quiet没有给出。

发生这种情况是因为parseoptin submodule--helper将尝试解析这两个--quiet选项,就好像它们是foreach的选项一样,而不是foreach的git-pull
解析的选项从命令行中删除。所以当我们稍后再拉时,我们执行

git pull origin

调用子模块帮助程序时,--在“ ”前面添加“ ” git pull将停止parseopt解析不真正属于的选项 submodule--helper foreach

PARSE_OPT_KEEP_UNKNOWN为了安全起见,已将其删除。parseopt永远不要看到未知的选项或出现问题。当我查看它们时,还有一些用法字符串更新。

虽然它,我还加“ --”来表示通过其他的子命令“ $@”来 submodule--helper$@在这些情况下,“ ”是路径,可能性较小 --something-like-this
但是重点仍然存在,git-submodule已经解析并分类了什么是选项,什么是路径。即使它们看起来像一个,
submodule--helper也永远不应将传递的路径视为git-submodule选项。


Git 2.23(2019年第三季度)修复了另一个问题:“ git submodule foreach”没有保护传递给命令的命令行选项,--recursive而在使用该选项时,该选项无法在每个子模块中正确运行。

参见Morian Sonnet(momoson提交30db18b(2019年6月24日
(通过合并JUNIOÇ滨野- gitster-提交968eecb,2019年7月9日)

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中删除了PARSE_OPT_KEEP_UNKNOWN用于参数解析的标志。 因此,现在抱怨未知的选项,因为参数解析没有以双破折号正确结束。git submodule foreach

此提交通过在递归过程中在子命令前面添加双破折号来解决此问题。


7
git pull --recurse-submodules

这将提取所有最新提交。


4

就我而言,我想git更新到最新版本,同时重新填充所有丢失的文件。

以下内容恢复了丢失的文件(感谢--force此处未提及的文件),但没有拉出任何新的提交:

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

这样做:

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


3

@Jason在某种程度上是正确的,但并非完全正确。

更新

更新注册的子模块,即克隆缺少的子模块并检出包含存储库的索引中指定的提交。除非指定了--rebase或--merge或关键字submodule。$ name.update设置为rebase或merge,否则这将使子模块HEAD分离。

因此,进行git submodule update结帐,但这是对包含存储库的索引中的提交。它根本不知道上游的新提交。因此,转到您的子模块,获取所需的提交,并在主存储库中提交更新的子模块状态,然后执行git submodule update


1
看来,如果我将子模块移至其他提交,然后运行git submodule update,update会将子模块移至超级项目的当前HEAD中指定的提交。(无论超级项目中最新提交的内容是说子项目应该位于–在Jason帖子中的解释之后,这种行为对我来说都是合乎逻辑的)它也似乎可以获取,但仅在子项目执行错误的情况下才可以。 ,这让我更加困惑。
Thanatos

2

这是一个很棒的一线工具,可将所有内容更新为最新的master:

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

感谢Mark Jaquith


2

如果您不知道主机分支,请执行以下操作:

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

它将获得主Git存储库的一个分支,然后为每个子模块提取同一分支。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.