我有一个带有 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 log
和 git show
否认新提交的存在。到目前为止,我只是在 rm
-ing 模块并重新添加它,但这在原则上是错误的,在实践中也是乏味的。
--remote
选项内置的功能,也许将其标记为已接受的答案而不是“手动”方法会很有用杰森的回答?
--remote
在此时绝对是一个更好的解决方案,并且由于这个问题已链接到 Github Gist 关于子模块的问题,我觉得新来的读者最好看到新的答案。
hunter2
密码感觉不错 :o)
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
将从每个子模块的上游获取最新更改,将它们合并,并检查子模块的最新版本。正如 the documentation 所说:
--remote 此选项仅对更新命令有效。不要使用超级项目记录的 SHA-1 来更新子模块,而是使用子模块的远程跟踪分支的状态。
这相当于在每个子模块中运行 git pull
,这通常正是您想要的。
git pull
” 澄清一下,您的答案和 git submodule foreach git pull
之间没有区别(从用户的角度来看)?
foreach git pull
只检查了它们,但没有更新主仓库的指针以指向子模块的较新提交。只有使用 --remote
它才使它指向最新的提交。
master
或 main
分支的 repos,git submodule foreach git pull origin master
将失败。因此git submodule update --remote
是更好的解决方案。
在您的项目父目录中,运行:
git submodule update --init
或者,如果您有递归子模块运行:
git submodule update --init --recursive
有时这仍然不起作用,因为在更新子模块时,不知何故您在本地子模块目录中进行了本地更改。
大多数情况下,本地更改可能不是您想要提交的更改。由于子模块中的文件删除等原因可能会发生这种情况。如果是这样,请在本地子模块目录和项目父目录中进行重置,然后再次运行:
git submodule update --init --recursive
git submodule update --init
后,您需要 cd
进入子模块文件夹并运行 git pull origin <branch_name>
以获取最新提交
您的主项目指向子模块应位于的特定提交。 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(快进)。顺便说一句,我已经关闭了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 作业中运行此脚本的脚本,该脚本随后链接到计划的自动部署,它的工作原理就像一个魅力。
我希望这会对某人有所帮助。
pwd
命令为存在的每个子模块打印正确的“绝对路径”; --recursive
确保我们访问 所有 子模块,包括可能存在于大型项目中的 submodules-within-submodules-...。这两种方法都会对包含空格的目录造成麻烦,例如 /c/Users/Ger/Project\ Files/...
因此政策是从不在我们项目的任何地方使用空格。
--remote
选项。 git submodule update --remote
的行为方式与您的脚本大致相同。
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。但也许我错过了什么?此外,该方法似乎强制执行递归合并(因此错过了快进的可能性)。
注意,虽然更新子模块提交的现代形式是:
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:修复“
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”被传递给底层命令。
简单明了,获取子模块:
git submodule update --init --recursive
现在继续将它们更新到最新的主分支(例如):
git submodule foreach git pull origin master
git pull --recurse-submodules
这将拉取所有最新的提交。
这对我有用,可以更新到最新的提交
git submodule update --recursive --remote --init
就我而言,我希望 git
更新到最新版本,同时重新填充所有丢失的文件。
下面恢复了丢失的文件(感谢这里似乎没有提到的 --force
),但它没有拉任何新的提交:
git submodule update --init --recursive --force
这做到了:
git submodule update --recursive --remote --merge --force
如果您不知道主机分支,请执行以下操作:
git submodule foreach git pull origin $(git rev-parse --abbrev-ref HEAD)
它将获取主 Git 存储库的一个分支,然后为每个子模块提取相同的分支。
@Jason 在某种程度上是正确的,但并不完全正确。
update 更新已注册的子模块,即克隆丢失的子模块并检查包含存储库的索引中指定的提交。这将使子模块 HEAD 分离,除非指定 --rebase 或 --merge 或键 submodule.$name.update 设置为 rebase 或 merge。
因此,git submodule update
确实签出,但它是提交到包含存储库的索引中的。它根本不知道上游的新提交。因此,转到您的子模块,获取您想要的提交并在主存储库中提交更新的子模块状态,然后执行 git submodule update
。
git submodule update
,更新会将子模块移动到超级项目的当前 HEAD 中指定的提交。 (无论超级项目中的最新提交说子项目应该是什么——这种行为,在 Jason 的帖子中解释之后,对我来说似乎是合乎逻辑的)它似乎也可以获取,但仅在子项目提交错误的情况下,这增加了我的困惑。
如果您要检查每个子模块的 master
分支 - 您可以为此目的使用以下命令:
git submodule foreach git checkout master
对我来说,所有 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
的链接)。
这是一个很棒的单线,可以将所有内容更新到 master 上的最新版本:
git submodule foreach 'git fetch origin --tags; git checkout master; git pull' && git pull && git submodule update --init --recursive
处理包含子模块的 git 项目的最简单方法是始终添加
--recurse-submodules
在每个 git 命令示例的末尾:
git fetch --recurse-submodules
其他
git pull --update --recurse-submodules
ETC...
git submodule foreach git pull
git submodule foreach git pull origin master
。git submodule foreach --recursive git pull origin master
。