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

$ 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 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

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

git submodule update --remote --merge

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

- 远程

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

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

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

git submodule update --init

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

git submodule update --init --recursive

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

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

git submodule update --init --recursive

您的主要项目指向子模块应该位于的特定提交。 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将更新子模块。

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

场景 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(快速转发)。BTW 我已经变基了)。

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确保脚本在错误时继续运行是|| 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作业中有一个运行此脚本的脚本,此脚本随后链接到预定的自动部署,并且它的工作原理很吸引人。

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

简单明了,要获取子模块:

git submodule update --init --recursive

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

git submodule foreach git pull origin master

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

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

较旧的形式是:

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

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

请参阅NguyễnTháiNgọcDuy( pclouds 提交的 commit a282f5a (2019 年 4 月 12 日
(由Junio C gitster - gitstercommit f1c9f6c 中合并 ,2019 年 4 月 25 日)

submodule foreach :修复了不遵守 “ <command> --quiet ” 的问题

罗宾报道

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

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

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

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

git pull origin

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

作为PARSE_OPT_KEEP_UNKNOWN已删除PARSE_OPT_KEEP_UNKNOWNparseopt应该永远不会看到未知的选项或出现问题。在查看它们时,还有一些用法字符串更新。

同时,我还将 “ -- ” 添加到其他将 “ $@ ” 传递给submodule--helper子命令中。在这些情况下,“ $@ ” 是路径,不太可能--something-like-this
但是要指出的是, git-submodule已经解析并分类了什么是选项,什么是路径。
submodule--helper永远不要将git-submodule传递的路径视为选项,即使它们看起来像一个。


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

参见Morian Sonnet( momoson )的 commit 30db18b (2019 年 6 月 24 日
(由Junio C gitster - gitstercommit 968eecb 中合并 ,2019 年 7 月 9 日)

submodule foreach :修复选项的递归

致电:

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

导致错误,指出submodule--helper不知道选项--<option>
当然,仅当<option>不是git submodule foreach的有效选项时。

原因是上述调用在内部转换为对子模块 --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 pull --recurse-submodules

这将提取所有最新提交。

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

更新

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

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

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

以下内容恢复了丢失的文件(由于--force似乎在这里没有提到),但是它没有提取任何新的提交:

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

这样做:

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