如何删除Git子模块?为什么我不能git子模块rm模块名称?


当前回答

为了读者的利益,本文试图对其进行总结,并给出一个循序渐进的指导,说明如果事情没有按预期进行,该如何进行。以下是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上,则忽略第一行,并用正确指定的子模块路径的Windows方式替换“$submodule”。(我不是Windows)

警告千万不要自己触摸.git目录的内部!在内部编辑.git会进入黑暗面。不惜一切代价远离!是的,你可以把这归咎于git,因为过去git中缺少了很多有用的东西。就像再次移除子模块的正确方法。我认为git子模块的文档中有一个非常危险的部分。建议您自己删除$GIT_DIR/modules/<name>/。在我的理解中,这不仅是明显错误的,而且是极其危险的,并且会在未来引发严重的头痛!见下文。

请注意

git module deinit

git module init

but

git submodule deinit -- module
git rm -- module

也与

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

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

git子模块deinit—模块(1) 更新.git/config移除文件(2) 删除模块的文件(3) 从而递归地移除子模块的子模块(4) 更新.gitmodulesgit子模块加法将数据拉入.git/modules/NAME/(1) 执行git子模块初始化,因此更新git/config(2) git子模块是否更新,因此,非递归地检查模块(4) 更新.gitmodulesgit子模块更新--init--递归--module如有需要,可提取更多数据(3) 递归地检查子模块的子模块

这不可能是完全对称的,因为保持严格对称没有多大意义。根本不需要两个以上的命令。此外,“拉入数据”是隐式的,因为您需要它,但删除缓存的信息并没有完成,因为这根本不需要,而且可能会擦除宝贵的数据。

这确实让新手感到困惑,但基本上是一件好事:git只是做了明显的事情并做了正确的事情,甚至没有尝试做更多。git是一个工具,它必须做一个可靠的工作,而不仅仅是另一个“Eierlegende Wollmilchsau”(“Eierligende Wolmilchsaw”在我看来是“瑞士军刀的邪恶版本”)。

所以我理解人们的抱怨,说“为什么不为我做显而易见的事情”。这是因为这里的“明显”取决于观点。每种情况下的可靠性都要重要得多。因此,在所有可能的技术情况下,对你来说显而易见的事情往往不是正确的。请记住:AFAICS git遵循技术路径,而不是社会路径。(因此得名:git)

如果失败

上述命令可能因以下原因而失败:

你的吉特太老了。然后使用新的git。(请参见下面的操作方法。)您有未提交的数据,可能会丢失数据。那最好先把它们交给我。你的子模块在干净的意义上是不干净的。然后,首先使用该命令清理子模块。(见下文。)您在过去做了一些git不支持的事情。然后你处于黑暗面,事情变得丑陋和复杂。(也许使用另一台机器可以解决问题。)也许还有更多我不知道的失败方法(我只是一个git超级用户)

可能的修复方法如下。

使用新的git

如果您的机器太旧,git中没有子模块deinit。如果您不想(或者可以)更新您的git,那么只需使用另一台具有更新git的机器即可!git是完全分布式的,因此您可以使用另一个git来完成任务:

workhorse:~/path/to/worktree$git-status--瓷器不能输出任何内容!如果是这样,先清理一下!workhorse:~/path/to/worktree$sshaccount@othermachine其他计算机:~$git克隆--递归me@workhorse路径/to/worktree/.git TMPWORK&&cd TMPWORK现在做子模块的事情othermachine:~/TMPWORK$gitcommit-m和&出口workhorse:~/path/to/worktree$gitfetchaccount@othermachine:TMPWORK/.gitworkhorse:~/path/to/worktree$gitmerge--ff only FETCH_HEAD。如果这不起作用,请使用git reset--soft FETCH_HEAD现在清理东西,直到git状态再次清理。你能够做到这一点,因为你之前已经清洁过了,多亏了第一步。

另一台机器可以是一些VM,或者Windows下的一些Ubuntu WSL,等等。即使是chroot(但我假设您是非root用户,因为如果您是root用户,应该更容易更新到新的git)。

请注意,如果您不能ssh登录,那么有很多方法可以传输git存储库。您可以将工作树复制到某个U盘上(包括.git目录),然后从U盘上克隆。复制副本,只是为了再次以干净的方式获取内容。这可能是一个PITA,以防您的子模块无法直接从其他机器访问。但这也有一个解决方案:

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

into

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

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

先清理东西

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

如果git抱怨未保存的东西,请提交并将其推送到安全的地方。如果git抱怨一些剩菜,gitstatus和gitclean-ixfd就是你的朋友尽可能避免使用rm和deinit选项。如果你是专业人士,git的选项(如-f)很好。但当你来到这里时,你可能在子模块领域没有那么丰富的经验。所以,安全总比抱歉好。

例子:

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

你看,子模块deinit上不需要-f。如果事情是干净的,在一个干净的意义上。还要注意,不需要gitclean-x。这意味着git子模块无条件地删除未跟踪的文件,这些文件被忽略。这通常是你想要的,但不要忘记它。有时被忽略的文件可能很珍贵,比如缓存的数据,需要数小时到数天才能重新计算。

为什么从不删除$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/gows

someunusedname/
two/

通过这种方式,将来您可以向前和向后切换分支/提交,再也不会遇到任何麻烦,因为两个/有两个不同的(可能不兼容的)上游存储库。最好的办法是:您也将两者都保存在本地。

这不仅适用于你。对于使用您的存储库的所有其他人也是如此。你不会失去历史。如果您忘记推送旧子模块的最新版本,您可以输入本地副本,稍后再这样做。请注意,很常见的情况是,有人忘记推送某些子模块(因为这是新来者的PITA,直到他们习惯了git)。

但是,如果您删除了缓存目录,两个不同的签出都会碰到对方,因为您不会使用--name选项,对吗?因此,每次执行签出操作时,您可能需要一次又一次地删除.git/modules/<module>/目录。这非常麻烦,很难使用像git平分这样的东西。

因此,保留此模块目录作为占位符是非常技术性的原因。建议删除.git/modules/以下内容的人可能不太清楚,或者忘记告诉你,如果这样做与子模块不兼容,那么像git平分这样的强大功能几乎无法使用。

上面显示了另一个原因。看看这些,你看到了什么?

模块two/的第二个变体不在.git/modules/two/下,它在.git/modules/someunusedname/下!比如git-rm$module;rm-f.git/module/$module完全错误!您必须查阅module/.git或.gitmodules才能找到要删除的正确内容!

因此,不仅大多数其他答案都落入了这个危险的陷阱,甚至非常流行的git扩展也有这个bug(现在已经修复了)!所以,如果你不知道你在做什么,最好还是掌握.git/目录!

从哲学的角度来看,抹杀历史总是错误的!除了量子力学,和往常一样,但这是完全不同的。

仅供参考,你可能猜到了:hilbix是我的GitHub帐户。

其他回答

在现代git中(我在2022年编写了这篇文章,并更新了git安装),这变得非常简单:

运行git-rm<path to submodule>并提交。

这将删除<path to submodule>处的文件树,以及.gitmodules文件中的子模块条目。即,删除存储库中子模块的所有痕迹。

然而,正如文档所指出的,子模块的.git目录(在主项目.git目录的模块/目录中)被保留了下来,“这样就可以在不需要从另一个存储库获取的情况下签出过去的提交”。如果您仍然想删除此信息,请手动删除.git/modules/中的子模块目录,并删除.git/config文件中的子模板条目

rm-rf.git/modules/<子模块路径>,以及git-config—删除节子模块<子模块路径>。



较旧的社区wiki说明:

通过Git子模块教程页面:

要删除子模块,您需要:

从.gitmodules文件中删除相关部分。暂存.gitmodules更改:git-add.gitmoduls从.git/config中删除相关部分。从工作树中删除子模块文件,并索引:git rm--cached path_to_submodule(无斜杠)。删除子模块的.git目录:rm-rf.git/modules/path_to_submodule提交更改:gitcommit-m“已删除子模块<name>”删除现在未跟踪的子模块文件:rm-rf path_to_submodule

另请参见:以下替代步骤。

如果您需要使用bash脚本在一行命令中执行此操作,如下所示:

$cd/path/to/your/rebo&&/bin/bash$HOME/remove_submodule.sh/path/to-the/submodule

在$HOME目录中创建bash脚本文件,名为remove_submodule.sh:

#!/bin/bash

git config -f .gitmodules --remove-section submodule.$1
git config -f .git/config --remove-section submodule.$1
git rm --cached $1
git add .gitmodules
git commit -m "Remove submodule in $1"
rm -rf $1
rm -rf .git/modules/$1
git push origin $(git rev-parse --abbrev-ref HEAD) --force --quiet

所有的答案看起来都过时了。我使用的是git版本2.28.0。一行回答是,

git rm path-to-submodule

然而,即使从源代码管理中删除了子模块,.git/modules/path到子模块仍然包含子模块存储库,.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-config-f.git/config--删除节子模块$submodulename git-config-f.gitmodules—删除部分子模块$子模块名称从索引中删除目录:git rm--cached$submodule路径犯罪删除未使用的文件:rm-rf$submodulepathrm-rf.git/modules/$submodulename

请注意:$submodulepath不包含前导或尾随斜杠。

出身背景

当您执行git子模块add时,它只将其添加到.gitmodules中,但是完成git子模块init后,它将添加到.git/config中。

因此,如果您希望删除模块,但能够快速恢复,那么就这样做:

git rm --cached $submodulepath
git config -f .git/config --remove-section submodule.$submodulepath

首先执行git rebase HEAD和git commit是一个好主意最后,如果你把这个写在脚本中。

还可以看看我可以取消填充Git子模块吗?的答案?。

以下是我认为必要或有用的4个步骤(首先是重要步骤):

git rm -f the_submodule
rm -rf .git/modules/the_submodule
git config -f .git/config --remove-section submodule.the_submodule
git commit -m "..."

理论上,第1步中的git rm应该处理它。希望OP问题的第二部分有一天能够得到肯定的回答(这可以在一个命令中完成)。

但从2017年7月起,第2步必须删除.git/modules/中的数据。否则,您不能在将来重新添加子模块。

正如tinlyx的回答所指出的那样,您可能可以省去上面的两个git1.8.5+步骤,因为所有的git子模块命令似乎都有效。

步骤3删除了.git/config文件中_submodule的部分。为了完整起见,应该这样做。(该条目可能会对较旧的git版本造成问题,但我没有要测试的条目)。

为此,大多数答案建议使用git子模块deinit。我发现使用git-config-f.git/config--remove部分更加明确,也不那么令人困惑。根据git子模块文档,git deinit:

注销给定的子模块。。。如果确实要删除存储库中的子模块,并使用gitrm[1]提交相反

最后但并非最不重要的是,如果您没有gitcommit,那么在执行git子模块摘要时(从git2.7开始),您可能会遇到错误:

fatal: Not a git repository: 'the_submodule/.git'
* the_submodule 73f0d1d...0000000:

这与执行步骤2或3无关。