如何使 git 存储库中的现有目录成为 git 子模块

How to make an existing directory within a git repository a git submodule

我对 git-子模块很困惑。

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


我对 git 子模块有很好的经验:
在我的 dotfiles repository 中,我在 ~/dotfiles-repo 中创建了 .gitmodules 文件,并在其中添加了路径和 url。从那时起,如果我对子模块和 运行 git status 中的文件进行更改,我会得到类似这样的内容:.vim/bundle/auto-complete (new commits) # in red

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

我读过 this question which led me to this answer 但我不确定我是否需要 git-subtree。我不想做那些可能会造成难以恢复的变化的事情。

Edit: This suggested duplicate-solution didn't work either, I recieved an error that Updates were rejected because the remote contains work that you do not have locally. It seems that @GabLeRoux practically told me to push <repo-A> to the url of <repo-B>.

使用git submodule absorbgitdirs

这就是 docs 声明此命令的作用:

If a git directory of a submodule is inside the submodule, move the git directory of the submodule into its superprojects $GIT_DIR/modules path and then connect the git directory and its working directory by setting the core.worktree and adding a .git file pointing to the git directory embedded in the superprojects git directory.

因此,不必像@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 (see commit) 中引入。

解决方法很简单。它是从 here.

中提取的
  1. git rm submodule-dir
    这将删除 git 在 submodule-dir
  2. 中跟踪的所有文件
  3. rm -rf submoduledir
    这将删除可能留在 submodule-dir 中的所有其他文件,因为 git 忽略了它们。
  4. 现在,我们必须提交才能从索引中删除文件:
    git commit
    提交后,我们清理了 git 跟进和 submodul-dir 没有跟进的文件。 现在是时候了:
  5. git submodule add <remote-path-to-submodule>
    这将重新添加子模块,但作为真正的子模块。
  6. 此时检查 .gitmodules 并查看子模块是否已成功添加可能是个好主意。在我的例子中,我已经有一个 .gitmodules 文件,所以我必须修改它。

基本上没有比假装重新开始更好的方法了:

  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) manual page (or even the relevant chapter from the Git book). I found some more in-depth explanations on this blog post 中收集到什么,但由于 post 有点长,我冒昧地在这里总结一下。

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

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

提交对象包含(或更准确地说,由 SHA1 引用)在父树对象中。这是不寻常的,因为 usually happen the other way round, but this explains why you see a directory appear in the main repository's git status after you have performed a commit in the submodule. You can also make some experimentsgit ls-tree 可以更详细地观察这个提交对象。

.git/modules中的子目录代表子模块中的.git子目录;事实上,子模块中有一个 .git file ,它使用 gitdir: 行指向前者。这是默认行为 since version 1.7.8 of Git。不知道为什么如果你只是继续拥有一个单独的 .git 目录,为什么一切都不会正常工作,除非发行说明中指出你可能 运行 在具有子模块的分支之间切换时遇到麻烦还有一个没有。

.gitmodules 文件提供 git submodule update --remote 和朋友应该从中提取的 URL;这显然不同于主存储库的一组遥控器。另请注意,.gitmodulesgit submodule sync 命令和在幕后调用它的其他命令部分复制到 .git/config

虽然手动为 .gitmodules + .git/config.git/modules + mysubmodule/.git 进行必要的更改相当容易(事实上,有甚至 git submodule absorbgitdirs 对于后者), 并没有真正的瓷器来创建树内提交对象 。因此,通过移动 + 重做上面提出的更改提出的解决方案。

按顺序回答您的问题:

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

您提到的 answer 也可能更正您的提交历史记录:

  1. 优点:

该文件夹将被视为所有提交历史记录中的子模块,而不仅仅是所有未来的提交。如果您检出到将其视为文件夹的先前版本,这可以避免任何并发症。这是一个复杂的问题,因为当你 return 到分支的顶端时,你可能还必须输入你的子模块并检出到最新的提交以恢复所有文件(可能从你的工作目录中删除)。这可以通过对您的最新提交进行某种递归检查来避免。

  1. 缺点:

如果提交历史被修改,所有其他贡献者也必须重新克隆项目,因为他们会遇到合并冲突或更糟;将问题提交重新引入项目。

None 这些解决方案似乎对我有用,所以我想出我自己的解决方案:

  1. 确保一个新的 git 存储库已经存在,它将保存内容 例如,我们将使用“git@github.com:/newemptyrepo

  2. 导航到您正在模块化的目录:

cd myproject/submodule-dir
  1. 从父索引中删除 to-be 子模块:
git rm -r --cached .
  1. 在 to-be 子模块中初始化一个新的 git 仓库:
git init
  1. 为 to-be 子模块设置源并进行第一次提交:
git remote add origin git@github.com:/newemptyrepo
git add . && git commit && git push --set-upstream origin master
  1. 现在您必须导航到父存储库的 top-level 路径:
cd .. && cd `git rev-parse --show-toplevel`
  1. 最后,像往常一样添加子模块:
git submodule add git@github.com:/newemptyrepo ./myproject/submodule-dir
  1. 现在提交并推送上述命令所做的更改,一切就绪!