ChatGPT解决这个技术问题 Extra ChatGPT

将 Git 子模块更新为源上的最新提交

我有一个带有 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-ing 模块并重新添加它,但这在原则上是错误的,在实践中也是乏味的。

大卫 Z 的答案似乎是这样做的更好方法 - 现在 Git 具有通过 --remote 选项内置的功能,也许将其标记为已接受的答案而不是“手动”方法会很有用杰森的回答?
我非常同意@MarkAmery。虽然 Jason 给出了一个可行的解决方案,但这并不是预期的方法,因为它会将子模块的提交指针留在错误的提交标识符处。新的 --remote 在此时绝对是一个更好的解决方案,并且由于这个问题已链接到 Github Gist 关于子模块的问题,我觉得新来的读者最好看到新的答案。
使用 hunter2 密码感觉不错 :o)

M
Melebius

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 submodule foreach git pull
@Nicklas 在这种情况下,请使用 git submodule foreach git pull origin master
在这一点上,有了所有这些更正的更正,我需要有人写一篇解释性的博客文章并将我指向那里。请。
对“foreach”方法的微小改进 - 您可能需要在其中添加 --recursive 以防子模块中有子模块。所以:git submodule foreach --recursive git pull origin master
如果每个 git 子模块都有不同的默认分支怎么办?
P
Peter Mortensen

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

git submodule update --remote --merge

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

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

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


“相当于在每个子模块中运行 git pull 澄清一下,您的答案和 git submodule foreach git pull 之间没有区别(从用户的角度来看)?
我希望我能投票赞成这个 10,000X。为什么这在 git 的文档中没有显示?巨大的疏忽。
对我来说,它们实际上差异很大。 foreach git pull 只检查了它们,但没有更新主仓库的指针以指向子模块的较新提交。只有使用 --remote 它才使它指向最新的提交。
为什么 --merge 选项?它有什么不同?
现在混合使用 mastermain 分支的 repos,git submodule foreach git pull origin master 将失败。因此git submodule update --remote是更好的解决方案。
P
Peter Mortensen

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

git submodule update --init

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

git submodule update --init --recursive

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

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

git submodule update --init --recursive

这是真正的答案。我可以以某种方式将它推送到我的远程存储库吗?
这适用于新的子模块!我可以更新所有其他的,但新子模块的文件夹将保持空,直到我运行这个命令。
它不会对现有子模块进行更改
这将克隆子模块,但仅限于主仓库中指定的提交。运行 git submodule update --init 后,您需要 cd 进入子模块文件夹并运行 git pull origin <branch_name> 以获取最新提交
P
Peter Mortensen

您的主项目指向子模块应位于的特定提交。 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 将更新子模块,假设它已被初始化。


P
Peter Mortensen

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

方案 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(快进)。顺便说一句,我已经关闭了rebase)。

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,这将删除“输入 MODULE_PATH”输出。使用 'echo $path'(必须是单引号),子模块的路径被写入输出。

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

最后,提交带有一些消息,说明父存储库已更新。如果什么都不做,默认情况下会忽略此提交。把它推到原点,你就完成了。

我有一个在 Jenkins 作业中运行此脚本的脚本,该脚本随后链接到计划的自动部署,它的工作原理就像一个魅力。

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


!@#$% SO 我们正在使用类似于您的脚本;注意:我们在 for 循环中使用 ``` git submodule foreach --recursive --quiet pwd ``` 而不是 ``` git submodule foreach --quiet 'echo $path' ```。 pwd 命令为存在的每个子模块打印正确的“绝对路径”; --recursive 确保我们访问 所有 子模块,包括可能存在于大型项目中的 submodules-within-submodules-...。这两种方法都会对包含空格的目录造成麻烦,例如 /c/Users/Ger/Project\ Files/... 因此政策是从不在我们项目的任何地方使用空格。
这很好,你是对的,在一些关于这个问题的答案中存在误解,但正如大卫 Z 的出色回答所指出的那样,你的脚本是不必要的,因为该功能自 2013 年中期以来已内置于 Git 中他们添加了 --remote 选项。 git submodule update --remote 的行为方式与您的脚本大致相同。
@GerHobbelt 谢谢。你是对的,我们只有 1 级子模块,所以我从没想过让它递归。在我有机会验证它是否按预期工作之前,我不会更新脚本,但我的脚本肯定会包含子子模块。至于文件夹中的空格,这听起来绝对是要避免的! :S
@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。但也许我错过了什么?此外,该方法似乎强制执行递归合并(因此错过了快进的可能性)。
最后一件事:我尝试了@DavidZ的方法,但它似乎没有做确切的事情,我开始做(以及哪个操作询问):将子模块的 HEAD 提交添加到父模块(即“更新指针” )。然而,它似乎在获取和合并所有子模块中的最新更改方面做得非常好(而且速度更快)。唉,默认情况下仅来自 master 分支(除非您编辑 .gitmodules 文件(见上文))。
V
VonC

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

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

旧形式是:

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

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

请参阅 Nguyễn Thái Ngọc Duy (pclouds)commit a282f5a(2019 年 4 月 12 日)。
(由 Junio C Hamano -- gitster --commit f1c9f6c 中合并,2019 年 4 月 25 日)

子模块 foreach:修复“ --quiet”不被尊重

Robin 报告说 git submodule foreach --quiet git pull --quiet origin 不再安静了。在 fc1b924 之前应该是安静的(子模块:port submodule subcommand 'foreach' from shell to C, 2018-05-10, Git v2.19.0-rc0)因为 parseopt 不能不小心吃掉选项。 “git pull”的行为就像没有给出 --quiet 一样。发生这种情况是因为 submodule--helper 中的 parseopt 将尝试解析这两个 --quiet 选项,就好像它们是 foreach 的选项,而不是 git-pull 的选项一样。已解析的选项将从命令行中删除。所以当我们稍后做 pull 时,我们只执行这个 git pull origin 调用 submodule helper 时,在 "git pull" 前面添加 "--" 将停止 parseopt 用于解析不真正属于 submodule--helper foreach 的选项。 PARSE_OPT_KEEP_UNKNOWN 作为安全措施被删除。 parseopt 永远不会看到未知选项或出现问题。在我查看它们时,还有一些使用字符串更新。在此期间,我还将“--”添加到将“$@”传递给子模块--helper 的其他子命令。在这些情况下,“$@”是路径,不太可能是 --something-like-this。但重点仍然存在, git-submodule 已经解析和分类了什么是选项,什么是路径。 submodule--helper 永远不应将 git-submodule 传递的路径视为选项,即使它们看起来像一个。

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

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

子模块 foreach:修复选项的递归

调用: git submodule foreach --recursive --

请注意,在 Git 2.29(2020 年第 4 季度)之前,“git submodule update --quiet(man) 并没有压制底层的“rebase”和“pull”命令。

请参阅 Theodore Dubois (tbodt)commit 3ad0401(2020 年 9 月 30 日)。
(由 Junio C Hamano -- gitster --commit 300cd14 中合并,2020 年 10 月 5 日)

子模块更新:使用“--quiet”使底层合并/变基静音签名:Theodore Dubois

诸如 $ git pull --rebase --recurse-submodules --quiet 之类的命令会从合并或变基中产生非安静的输出。在调用“rebase”和“merge”时传递 --quiet 选项。还修复了 git submodule update(man) -v 的解析。当 e84c3cf3 ("git-submodule.sh: accept verbose flag in cmd_update to be non-quiet", 2018-08-14, Git v2.19.0-rc0 -- merge) 教导 "git submodule update"(man) 取" --quiet”,它显然不知道 ${GIT_QUIET:+--quiet} 是如何工作的,而且审阅者似乎错过了将变量设置为“0”,而不是取消设置,仍然会导致“--quiet”被传递给底层命令。


P
Peter Mortensen

简单明了,获取子模块:

git submodule update --init --recursive

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

git submodule foreach git pull origin master

佚名
git pull --recurse-submodules

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


J
Jobin James

这对我有用,可以更新到最新的提交

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


这个问题已经有很多相似但不完全相同的答案。如果你能解释你的方法如何改进这里已经说过的内容,那将会有所帮助。
n
noseratio

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

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

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

这做到了:

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


P
Peter Mortensen

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

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

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


P
Peter Mortensen

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

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

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


似乎如果我将子模块移动到不同的提交,然后运行 git submodule update,更新会将子模块移动到超级项目的当前 HEAD 中指定的提交。 (无论超级项目中的最新提交说子项目应该是什么——这种行为,在 Jason 的帖子中解释之后,对我来说似乎是合乎逻辑的)它似乎也可以获取,但仅在子项目提交错误的情况下,这增加了我的困惑。
M
Mohsin Mahmood

如果您要检查每个子模块的 master 分支 - 您可以为此目的使用以下命令:

git submodule foreach git checkout master

F
Friedrich

对我来说,所有 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 的链接)。


d
dustinrwh

这是一个很棒的单线,可以将所有内容更新到 master 上的最新版本:

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

Thanks to Mark Jaquith


O
Oleg Kokorin

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

--recurse-submodules 

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

git fetch --recurse-submodules

其他

git pull --update --recurse-submodules

ETC...