如何移除一个子模块?

4473

如何删除Git子模块?为什么不能直接使用git submodule rm module_name命令?


15
那其实不是真的。那个回答没有解决从.git/config中删除子模块条目的问题。被采纳的回答展示了完全移除子模块的最新方法。这个回答也在这里更简洁地解释了:https://dev59.com/QHM_5IYBdhLWcg3wt1k0#36593218 - fvgs
我发现这篇文章在删除子模块方面非常有帮助。它包括有关删除 .gitsubmodules 和 .git/config 文件中条目的信息 链接 - Ri_
22
请直接前往适用于2017年的答案,以节省您的时间:https://dev59.com/QHM_5IYBdhLWcg3wt1k0#36593218。 - Vincenzo Pii
我为子模块问题苦斗了两天。突破口在于我找到了这个链接:https://forums.developer.apple.com/thread/13102。基本上,Xcode和其他应用程序很难扩展包含“~”的URL。一旦我将ssh://username@server.remoteHost.com/~/git/MyRepo.git更改为ssh://username@server.remoteHost.com/home/username/git/MyRepo.git(查找您服务器上的实际路径),所有奇怪的问题都在十分钟内消失了。另请参见https://dev59.com/2o_ea4cB1Zd3GeqPQJJZ#33985629。 - Elise van Looij
38个回答

45

总之,这是你应该采取的步骤:

  1. 设置 path_to_submodule 变量(没有尾随斜杠):

    path_to_submodule=path/to/submodule

  2. 从 .gitmodules 文件中删除相关行:

    git config -f .gitmodules --remove-section submodule.$path_to_submodule

  3. 从 .git/config 中删除相关部分:

    git config -f .git/config --remove-section submodule.$path_to_submodule

  4. 仅从索引中取消暂存并删除 $path_to_submodule(以防止丢失信息):

    git rm --cached $path_to_submodule

  5. 跟踪对 .gitmodules 所做的更改:

    git add .gitmodules

  6. 提交超级项目:

    git commit -m "Remove submodule submodule_name"

  7. 删除现在未被跟踪的子模块文件:

    rm -rf $path_to_submodule

    rm -rf .git/modules/$path_to_submodule


那么其他人拉取我的更改时,都必须运行rm -rf $path_to_submodule。运行rm -rf .git/modules/$path_to_submodule以删除子模块缓存? - j2emanue
我建议更新 git submodulegit submodule update。如果子模块的路径没有正确更新(git 抛出错误),请将它们删除:rm -rf .git/modules/ && rm -rf && git submodule update - luissquall

44

如果子模块是因为您添加、提交和推送包含.git的文件夹而意外添加的,那么您将没有.gitmodules文件可以编辑,或者在.git/config中找到任何东西。在这种情况下,您所需要的仅仅是:

git rm --cached subfolder
git add subfolder
git commit -m "Enter message here"
git push

FWIW,我在执行git add命令之前也删除了.git文件夹。


正是我的情况。 - zhekaus

32
  1. git submodule deinit <path to submodule>
  2. .gitmodules 文件中移除该模块的配置信息。
  3. git rm <path to submodule>
  4. 删除需要移除的子模块文件。
  5. 将被删除的文件添加到 Git,并执行 git add .gitmodules 命令。
  6. 提交并推送更改。

24
对我来说,只需要运行命令 git submodule deinit <submodule_name>git rm <path_to_submodule> 即可。最后一个命令会自动删除 .gitmodules 文件中的相关条目。此操作适用于 Git 2.17 及以上版本。 - Dmytro Ovdiienko
我需要在 git rm 命令中使用 -r 参数:git rm -r <path_to_submodule> - william_grisaitis

25

通过尝试该网站上提供的所有不同答案,我最终得出了这个解决方案:

#!/bin/sh
path="$1"
if [ ! -f "$path/.git" ]; then
  echo "$path is no valid git submodule"
  exit 1
fi
git submodule deinit -f $path &&
git rm --cached $path &&
rm -rf .git/modules/$path &&
rm -rf $path &&
git reset HEAD .gitmodules &&
git config -f .gitmodules --remove-section submodule.$path
这将恢复添加子模块之前的完全相同状态。您可以立即重新添加子模块,而这在大多数答案中都不可能实现。
git submodule add $giturl test
aboveScript test

这将使您得到一个干净的结帐,没有需要提交的更改。

此项测试使用了以下内容:

$ git --version
git version 1.9.3 (Apple Git-50)

为什么你使用 git rm --cached $path 然后 rm -rf $path 而不是 git rm -r $path - bfontaine
2
-1 如果尝试删除子模块内的子模块,则不起作用(子模块可以形成树结构!)。 另外,由于缺少引号,这是非常危险的错误!示例git submodule add https://github.com/hilbix/empty.git 'dangerous .. submodule'->当您尝试使用脚本删除'dangerous .. submodule'时,这将rm -rf ..,这很可能不是您想要的.. - Tino

20

我目前正在做的事情(截至2012年12月,结合了大部分答案):

oldPath="vendor/example"
git config -f .git/config --remove-section "submodule.${oldPath}"
git config -f .gitmodules --remove-section "submodule.${oldPath}"
git rm --cached "${oldPath}"
rm -rf "${oldPath}"              ## remove src (optional)
rm -rf ".git/modules/${oldPath}" ## cleanup gitdir (optional housekeeping)
git add .gitmodules
git commit -m "Removed ${oldPath}"

16

以下是我所做的:

1.) 从.gitmodules文件中删除相关部分,您可以使用以下命令:

git config -f .gitmodules --remove-section "submodule.submodule_name"

2.) 部署.gitmodules的更改

git add .gitmodules

3.) 从.git/config中删除相关部分,您可以使用以下命令:

git submodule deinit -f "submodule_name"

4.) 移除 Git 链接(不包含尾部斜杠):

git rm --cached path_to_submodule

五.) 清理 .git/modules 目录:

rm -rf .git/modules/path_to_submodule

6.) 提交:

git commit -m "Removed submodule <name>"

7.) 删除现在未被跟踪的子模块文件

rm -rf path_to_submodule

1
谢谢这个。对我来说,我必须重新排列前三个步骤的顺序为3),1),2)。首先执行1)会在第3步中出现“fatal:no submodule mapping found in .gitmodules for path 'submodule_name'”错误。但是这两个步骤都是必要的。(git v2.8.2) - U007D

16

所有的回答看起来都已经过时了。我正在使用 git 版本为 2.28.0。简单回答如下:

git rm path-to-submodule

然而,即使子模块已经从源代码控制中删除,.git/modules/path-to-submodule仍包含子模块仓库,.git/config也包含其URL,因此您仍需手动删除它们:

git config --remove-section submodule.path-to-submodule
rm -rf .git/modules/path-to-submodule

有时候,你必须使用-f标志:

$ git rm -f img2vec

例如,因为您可能会遇到这样的错误:

$ git rm img2vec/
error: the following file has changes staged in the index:
    img2vec
(use --cached to keep the file, or -f to force removal)

过时了吗?我的回答提到了git rm选项和相关的配置更改。 - VonC
由于这是最高和最近的答案,您应该执行以下步骤更新它:
  1. 删除 .gitmodules 文件。
  2. 通过调用 git rm --cached -r <path-to-submodule-dir> 从索引中删除相关数据。
- Jarmos

16

我最近发现了一个包含许多有用的git相关命令的git项目:https://github.com/visionmedia/git-extras

安装它并输入:

git-delete-submodule submodule

然后就完成了。子模块目录将从你的仓库中移除,但仍存在于你的文件系统中。你可以执行以下命令提交修改:git commit -am "Remove the submodule"


你可以将其称为git delete-submodule,因为需要将git-extras置于路径中才能使用。另外请注意,我建议不要使用git-extras,因为其中很多部分都是极其有缺陷和危险的。例如,git-delete-submodule可能会删除.git/modules/*下错误的路径,因为它假定模块和路径是相同的(这通常不是这种情况),并且如果您尝试删除子模块内的子模块,则无法正常工作。git-extras可能会有99%的帮助,但如果使用它时出现问题,请不要抱怨。你已经被警告了! - Tino
为节省时间,请使用以下命令:apt install git-extras && git delete-submodule submodule。@Chien-Wei Huang的帖子中额外添加了一个“-”符号。 - Machinexa

15
为了读者的方便,这里试图总结并给出一步一步的指南,以便在情况不如预期时进行操作。以下是适用于git版本2.17及以上版本摆脱子模块经过测试和安全的方法
submodule="path/to/sub"              # no trailing slash!
git submodule deinit -- "$submodule"
git rm -- "$submodule"

如果这对您没有用,请参见下面。 没有选项。 没有危险。 千万不要再考虑做更多的事情! 已在Debian Buster 2.20.1和Ubuntu 18.04 2.17.1上进行了测试。 “$ submodule”只是强调放置名称的位置,并且您必须小心空格等内容。 如果在Windows上,请忽略第一行,并将“$submodule”替换为正确指定子模块路径的Windows方式。(我不是Windows) 警告! 永远不要自己触碰.git目录的内部!编辑.git内部进入黑暗面。 不惜一切代价远离它! 是的,你可以责怪git,因为在过去的git中缺少许多方便的东西。例如,再次删除子模块的适当方法。 我认为git submodule文档中有一个非常危险的部分。 它建议自己删除$GIT_DIR/modules//。 在我看来,这不仅是错误的,而且极其危险,并会在未来引起重大头疼!请参见下文。 请注意
git module deinit

是直接的反向操作

git module init

但是{{}}。
git submodule deinit -- module
git rm -- module

也正好相反{{to}}

git submodule add -- URL module
git submodule update --init --recursive -- module

因为一些命令基本上需要做的不仅仅是一件事情:

  • git submodule deinit -- module
    • (1) 更新.git/config
  • git rm
    • (2) 删除模块的文件
    • (3) 递归删除子模块
    • (4) 更新.gitmodules
  • git submodule add
    • 将数据拉入.git/modules/NAME/
    • (1) 执行git submodule init,因此更新.git/config
    • (2) 执行git submodule update,所以非递归地检出模块
    • (4) 更新.gitmodules
  • git submodule update --init --recursive -- module
    • 如果需要,会拉取更多数据
    • (3) 递归检出子模块

这不能完全对称,因为严格保持对称并没有太多意义。只需要两个命令就足够了。而“拉取数据”是隐含的,因为你需要它,但不需要删除缓存信息,因为这根本不需要,可能会删除宝贵的数据。

这对新手来说确实很困惑,但基本上是一件好事:{{git}}只做明显的事情并且做得正确,甚至不试图去做更多。{{git}}是一个必须要可靠完成工作的工具,而不是另一个“Eierlegende Wollmilchsau”(“Eierlegende Wollmilchsau”翻译成中文是“瑞士军刀的邪恶版本”)。
因此,我理解人们的抱怨,他们说“为什么{{git}}不能为我做明显的事情”。这是因为“明显”的意义取决于观点。在每种可能的情况下保持可靠性更加重要。因此,对你来说显而易见的东西通常不是所有可能的技术情况下的正确之举。请记住:{{git}}遵循技术路径,而不是社交路径。(因此有了聪明的名字:{{git}})

如果失败了

上面的命令可能会因以下原因失败:

  • 你的git版本太旧了,需要使用更新的git版本(下面有说明)。
  • 你有未提交的数据,可能会丢失数据,最好先进行提交。
  • 你的子模块在git clean方面不干净,需要使用该命令先清理子模块(见下文)。
  • 你之前做过一些不受git支持的操作,这时会变得混乱和复杂,可以尝试在另一台机器上操作。
  • 可能还有其他我不知道的失败方式(我只是一个git高级用户)。

以下是可能的解决方法。

使用更新的git

如果你的机器太旧,git中没有submodule deinit命令。如果你不想或无法更新git,那么就使用另一台装有更新版本git的机器!git是完全分布式的,所以你可以使用另一个git完成任务:

  • workhorse:~/path/to/worktree$ git status --porcelain 绝不能 输出任何东西!如果有输出,请先清理一下!
  • workhorse:~/path/to/worktree$ ssh account@othermachine
  • othermachine:~$ git clone --recursive me@workhorse path/to/worktree/.git TMPWORK && cd TMPWORK
  • 现在进行子模块操作
  • othermachine:~/TMPWORK$ git commit . -m . && exit
  • workhorse:~/path/to/worktree$ git fetch account@othermachine:TMPWORK/.git
  • workhorse:~/path/to/worktree$ git merge --ff-only FETCH_HEAD。如果这样不行,请使用git reset --soft FETCH_HEAD
  • 现在清理一下,直到git status再次变为干净状态。你能够做到这点,因为你已经在第一步保持了干净状态。

这个othermachine可以是某个虚拟机,或者是Windows下的Ubuntu WSL,甚至是chroot(但我假设你不是root用户,因为如果你是root用户,更新到新版本的git应该更容易)。

请注意,如果您无法使用ssh登录,则有许多方法可以传输git存储库。您可以将工作树复制到某个USB驱动器上(包括.git目录),然后从该驱动器克隆。再次克隆该副本,以便以干净的方式获取内容。如果您的子模块不能直接从其他机器访问,则可能会很麻烦。但是也有解决方案:
git config --add url.NEWURLPREFIX.insteadOf ORIGINALURLPREFIX

您可以使用此乘法,它将保存在$HOME/.gitconfig中。类似于

git config --add 'url./mnt/usb/repo/.insteadof' https://github.com/

重写URL,例如

https://github.com/XXX/YYY.git

转化为

/mnt/usb/repo/XXX/YYY.git

如果你开始习惯强大的git功能,这很容易。

首先清理东西

手动清理是好的,因为这样你可能会发现一些你忘记了的东西。

  • 如果git抱怨有未保存的内容,请将其提交并将其推送到安全位置。
  • 如果git抱怨有一些剩余物,请使用git statusgit clean -ixfd
  • 尽可能避免使用rmdeinit选项。如果您是专业人士,则可以使用git的选项(如-f)。但是,由于您来到这里,可能在submodule领域没有太多经验。所以,最好保险起见。

例子:

$ git status --porcelain
 M two
$ git submodule deinit two
error: the following file has local modifications:
    two
(use --cached to keep the file, or -f to force removal)
fatal: Submodule work tree 'two' contains local modifications; use '-f' to discard them
$ cd two
$ git submodule deinit --all
error: the following file has local modifications:
    md5chk
(use --cached to keep the file, or -f to force removal)
fatal: Submodule work tree 'md5chk' contains local modifications; use '-f' to discard them
$ cd md5chk
$ git submodule deinit --all
error: the following file has local modifications:
    tino
(use --cached to keep the file, or -f to force removal)
fatal: Submodule work tree 'tino' contains local modifications; use '-f' to discard them
$ cd tino
$ git status --porcelain
?? NEW
$ git clean -i -f -d
Would remove the following item:
  NEW
*** Commands ***
    1: clean                2: filter by pattern    3: select by numbers    4: ask each
    5: quit                 6: help
What now> 1
Removing NEW
$ cd ../../..
$ git status --porcelain
$ git submodule deinit two
Cleared directory 'two'
Submodule 'someunusedname' (https://github.com/hilbix/src.git) unregistered for path 'two'

你看,在submodule deinit中不需要使用-f。如果事情很干净,就像git clean一样。请注意,git clean -x不需要。这意味着git submodule deinit无条件删除未跟踪的被忽略文件。通常这是您想要的,但不要忘记它。有时忽略的文件可能很珍贵,例如缓存数据,需要花费数小时到数天才能再次计算。
为什么永远不要删除$GIT_DIR/modules/<name>/
可能人们想要删除缓存的存储库,因为他们担心后面会遇到问题。这是正确的,但遇到这个“问题”是解决它的正确方式!因为修复很容易,如果做得正确,您将能够幸福地生活下去。这避免了比您自己删除数据更麻烦的麻烦。
示例:
mkdir tmptest &&
cd tmptest &&
git init &&
git submodule add https://github.com/hilbix/empty.git two &&
git commit -m . &&
git submodule deinit two &&
git rm two &&
git commit -m . &&
git submodule add https://github.com/hilbix/src.git two

最后一行输出以下错误:
A git directory for 'two' is found locally with remote(s):
  origin    https://github.com/hilbix/empty.git
If you want to reuse this local git directory instead of cloning again from
  https://github.com/hilbix/src.git
use the '--force' option. If the local git directory is not the correct repo
or you are unsure what this means choose another name with the '--name' option.

为什么会出现这个错误?因为.git/modules/two/之前是从https://github.com/hilbix/empty.git中获取的,现在需要重新从其他地方,即https://github.com/hilbix/src.git中获取。如果您从https://github.com/hilbix/empty.git中重新获取,则不会看到此错误。
现在该怎么办?好吧,就按照指示做!使用--name someunusedname
git submodule add --name someunusedname https://github.com/hilbix/src.git two

.gitmodules 看起来像这样

[submodule "someunusedname"]
    path = two
    url = https://github.com/hilbix/src.git

ls -1p .git/modules/ 给出:

someunusedname/
two/

这样,将来您可以切换分支/提交到前后,并且永远不会再次遇到任何麻烦,因为two/具有两个不同(可能不兼容)的上游存储库。最好的是:您也在本地保留了两者的缓存。

  • 这不仅适用于您。对于使用您的存储库的所有其他人也是如此。
  • 而且您不会失去历史记录。如果您忘记推送旧子模块的最新版本,则可以进入本地副本并稍后执行此操作。请注意,某人忘记推送某些子模块是很常见的情况(因为对于新手来说这是一个讨厌的事情,直到他们习惯了git)。

但是,如果您删除了缓存目录,则两个不同的检出将相互干扰,因为您不会使用--name选项,对吗?因此,每次进行检出时,您可能必须再次删除.git/modules/<module>/目录。这非常麻烦,使像git bisect这样的强大功能难以使用。

因此,保留此模块目录作为占位符有一个非常技术性的原因。建议删除.git/modules/下的某些内容的人要么不知道更好的方法,要么忘记告诉您,如果这会跨越这样的子模块不兼容性,则几乎无法使用git bisect等强大功能。

上面显示了另一个原因。看一下ls。你在那里看到了什么?

好的,模块two/的第二个变体不在.git/modules/two/下,而是在.git/modules/someunusedname/下!因此,像git rm $module; rm -f .git/module/$module这样的事情是完全错误的!你必须要么查阅module/.git要么.gitmodules来找到正确的删除内容!

所以,不仅大多数其他答案都陷入了这个危险的陷阱中,甚至很受欢迎的git扩展也有这个问题现在已经修复了)!所以,如果你不确定自己在做什么,最好不要碰.git/目录!

从哲学的角度来看,抹去历史总是错误的!除了量子力学,通常情况下,但这完全是另一回事。

你可能已经猜到了:hilbix是我的GitHub帐户。


这篇百科全书式的文章应该被分成更清晰的部分,使用更大/更清晰的子标题来指示实际答案和不同的“故障排除”等部分。 - Andrew
同意“永远不要删除$GIT_DIR/modules/<name>/” - Wayne Mao
这是一个非常好的答案,其中包含了我肯定会通过艰难的方式学到的要点。强烈推荐每个人至少快速阅读一遍。 - goldfishalpha

12

我必须进一步跟随John Douthat的步骤,并cd到子模块的目录,然后删除Git仓库:

我需要更进一步地跟随John Douthat的步骤,进入子模块目录并使用cd命令,接着删除Git仓库:

cd submodule
rm -fr .git

然后我可以将这些文件提交到父级 Git 存储库中,而无需保留之前子模块的引用。


我也不得不这样做,以便在尝试执行 git rm --cache 步骤时克服“fatal: Not a git repository:”错误。 - RickDT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接