如何将git仓库中的现有目录变成子模块

94

我对git子模块非常困惑。

我的问题基本上是我无法让git理解~/main-project/submodule是一个子模块。


我对git子模块有很好的经验:
在我的dotfiles repository中,我在~/dotfiles-repo中创建了.gitmodules文件,并添加了路径和URL。从那时起,如果我更改了子模块中的文件并运行git status,我会得到类似以下内容的反馈:.vim/bundle/auto-complete (new commits) # 红色字体

我在~/main-project中创建了.gitmodules文件,但是:

  • 即使我更改了~/main-project/submodule中的内容并将更改推送,当在~/main-project中运行git status时,我也没有像<submodule> (new commits) # 红色字体这样的反馈。我只看到在这些目录中所做的更改
  • 当我在github上单击这些目录的链接时,它没有将我引导到这些目录本身的存储库,而是让我留在同一存储库中。

    1. 也许我完全不明白。子模块的主要特点是什么?
    2. 为什么git可以理解dotfiles repo中的子模块,但不能理解我的其他repo中的子模块?
    3. 是因为我已经告诉git将~/main-project/submodule中的文件添加到索引中了吗?

我看过这个问题,得到了这个回答,但我不确定是否需要使用git-subtree。我不想做可能难以恢复更改的事情。

编辑: 这个建议的重复解决方案也不起作用,我收到一个错误消息:Updates were rejected because the remote contains work that you do not have locally。看起来@GabLeRoux实际上告诉我将<repo-A>推送到<repo-B>的URL。


对于你的主要外部存储库,Git子模块只是另一个文件。因此,在外部的git status中不会看到子模块内部的更改。 - Tim Biegeleisen
可能是从文件夹创建子模块存储库并保留其git提交历史记录的重复问题。 - Devanshu Dwivedi
请参见:https://dev59.com/zWQm5IYBdhLWcg3wswpv#44699112 - oodavid
4个回答

56

使用 git submodule absorbgitdirs

这是文档中说明该命令的内容:

如果一个子模块的git目录位于子模块内部, 将子模块的git目录移动到其超级项目的 $GIT_DIR/modules路径,并通过设置 core.worktree并添加一个指向嵌入在 超级项目git目录中的git目录的.git文件来连接 git目录和其工作目录。

因此,不必像@DomQ和我之前的回答建议的那样重新开始,而只需运行以下命令:

  1. 不需要从索引中移除子模块,只需将子模块的URL添加到.gitmodules和.git/config文件中,使用命令:
    git submodule add <url> <path>
  2. (可选但建议的步骤) 将子模块的$GIT_DIR目录(在普通仓库中为.git)移动到.git/modules/<path>,使用命令:
    git submodule absorbgitdirs <path>

原始答案 - v2.12.0之前

git submodule absorbgitdirs仅在v2.12.0-rc0版本中引入(参见commit)。

解决方案非常简单。它是从这里提取的。
  1. git rm submodule-dir
    这将删除git在submodule-dir中跟踪的所有文件。
  2. rm -rf submoduledir
    这将删除可能留在submodule-dir中的其他文件,因为git忽略了它们。
  3. 现在,我们需要提交以从索引中删除这些文件:
    git commit
    提交后,我们清理了git在submodul-dir中跟踪和未跟踪的文件。 现在是时候执行以下操作:
  4. git submodule add <remote-path-to-submodule>
    这将重新添加子模块,但作为真正的子模块。
  5. 此时,检查一下.gitmodules文件,看看子模块是否成功添加。在我的情况下,我已经有一个.gitmodules文件,所以我需要修改它。

10
我会说,“rm -rf submoduledir” 感觉很危险... 我会先执行“mv submoduledir submoduledir.backup”的命令,直到我确认其他命令已经生效。 (是的,我有点多疑。) - Alexis Wilke
1
非常顺利,非常感谢!由于现在已经是v2.12以来的一段时间了,我建议将编辑移到答案的顶部。 - Qw3ry
2
rm -rf 是一个 Linux 命令,在本帖中并没有暗示我们都在使用 Linux,答案应该包含 Windows CMD 行的等效命令,或者至少澄清它只适用于 Linux。 - Sasino
关于新的方法,我仍然需要先执行 git rm -r <path>,然后才能运行 git submodule add - Roland Fredenhagen
我很困惑为什么这个答案需要这么复杂?我执行了 git submodule add $MYURL submoduledir 然后它说 "Adding existing repo" ... 这不够吗?也许问题并不是我认为的那样...? - lucidbrot
1
@lucidbrot 这个答案(截至目前的更新)解释了 Git 在幕后做了什么。absorbgitdirs 命令不是强制性的,但建议使用。实际上可以说它并非必须的。感谢您的评论! - Doron Behar

29

这些解决方案似乎都对我无效,因此我想出了自己的方法:

  1. 确保已经存在一个新的git repo来容纳新子模块的内容,例如,我们将使用“git@github.com:/newemptyrepo

  2. 进入您要模块化的目录:

cd myproject/submodule-dir

从父模块的索引中删除待删除子模块:
git rm -r --cached .

在要成为子模块的内部初始化一个新的Git仓库:
git init
  1. 为将要成为子模块的项目设置起点并进行第一次提交:
git remote add origin git@github.com:/newemptyrepo
git add . && git commit && git push --set-upstream origin master

现在,您必须导航到父存储库的顶级路径:
cd .. && cd `git rev-parse --show-toplevel`

最后,按照正常方式添加子模块即可:
git submodule add git@github.com:/newemptyrepo ./myproject/submodule-dir

现在执行上述命令进行提交和推送更改,这样你就准备好了!

6

实际上没有比假装重新开始更好的方法

  1. 确保所有内容都已提交
  2. 将子仓库移出路径
  3. git submodule add 添加子仓库的远程地址
  4. cd mysubmodule
  5. git fetch ../wherever/you/stashed/the/sub-repository/in/step-1
  6. git merge FETCH_HEAD

为了解释这个问题,我认为需要对子模块的本质有更深入的了解,而不仅仅是从git-submodule(1)手册页(甚至是Git书中的相关章节)所能获得的信息。我在这篇博客文章中找到了一些更深入的解释,但由于该文章有点冗长,因此我在此进行概括。

在低级别上,git子模块由以下元素组成:

  • 子模块树顶部的提交对象
  • (在Git的最近版本中) 用于托管子模块Git对象的.git/modules子目录,
  • .gitmodules配置文件中的条目。

提交对象包含(或更精确地说,由SHA1引用)在父树对象中。这是不寻常的,因为通常情况下事情相反发生,但这解释了为什么在子模块中执行提交后,在主存储库的git status中会出现一个目录。您还可以使用git ls-tree进行一些实验以更详细地观察此提交对象。

.git/modules中的子目录代表子模块中的.git子目录;事实上,子模块中有一个.git文件,该文件使用gitdir:行指向前者。这是自Git版本1.7.8以来的默认行为since version 1.7.8 of Git。不确定为什么如果您仍然保持单独的.git目录,为什么不会一切正常工作,除了发行说明中指出的情况,您可能会在切换具有子模块的分支和另一个没有子模块的分支之间遇到问题。

.gitmodules文件提供了git submodule update --remote等命令应从其中拉取的URL;这显然与主存储库的远程设置不同。还要注意,.gitmodules部分地复制到.git/config中,由git submodule sync命令和其他调用它的命令在后台执行。

虽然对于.gitmodules + .git/config.git/modules + mysubmodule/.git的必要更改手动完成相当容易(事实上,甚至有git submodule absorbgitdirs用于后者),但是没有一个工具可以仅创建树中提交对象。因此,上述提出了移动+重做更改的解决方案。

1

按顺序回答你的问题:

  1. GitHub中子模块的目的。就功能而言,它被设计为将其概念化为一个子仓库(几乎可以像任何其他文件一样处理),由父仓库进行版本控制,其中父仓库跟踪子模块(子仓库)的当前提交ID而不是其内容
  2. 这很可能是因为您已经将文件添加到了仓库的索引中。在这种情况下,解决方案是git rm --cached submodule-name/。然后创建一个中间提交,接着将文件夹作为一个仓库添加:git add submodule-name(请注意,在子模块的情况下,submodule-name后面没有尾随斜杠)。
  3. 是的 :)

你提到的答案也可以纠正你提交历史记录的错误:

  1. 优点

该文件夹将在所有提交历史记录中被视为子模块,而不仅仅是在所有未来的提交历史记录中。这样,如果您切换到以前被视为文件夹的版本,就不会出现任何问题。如果您返回到分支的最新状态,则可能需要进入子模块并切换到最新的提交以恢复所有文件(这些文件可能已从您的工作目录中删除)。通过进行某种递归检出到最新提交,可以避免这种情况。

  1. 缺点

如果修改提交历史记录,则所有其他贡献者也必须重新克隆项目,因为他们将得到合并冲突或更糟的情况;把问题提交再次引入项目中。


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