通过 git init 在子文件夹中创建新的 git 存储库,并在主 git 存储库中忽略

use*_*142 4 git git-submodules

我试图理解 git 子模块。这看起来很复杂(我没有任何子模块的经验)。投入一些时间可能是值得的。但是,现在我有一些与 git 子模块相关的问题。

其实我正在尝试的是:

  • 有一个主存储库,在主存储库内我想创建一个新的(子)存储库。所以我做了什么:
    - 首先)创建文件夹(ABC)。
    - 其次)在主存储库中忽略文件夹(ABC)。
    - 第三)在de文件夹(ABC)中,git init(创建一个新的存储库)

这是创建子存储库的正确方法(或者可能是正确的方向)吗?

git submodule 和我上面做的一样吗?

提前致谢。

tor*_*rek 5

Git 没有定义“子存储库”,因此您可以随意定义它。

\n\n

Git确实定义了在 a 中列出目录的含义.gitignore,并且这种特定模式工作得很好。您所做的是设置两个完全不相关的存储库。

\n\n

在典型的设置中,这两个存储库将并存:

\n\n
$HOME/\n      [various dot files, personal files, etc]\n      src/\n          project-A/\n                    .git/\n                         [all of Git\'s files]\n                    .gitignore\n                    README.md\n                    [various files]\n          project-B/\n                    .git/\n                         [all of Git\'s files]\n                    .gitignore\n                    README.md\n                    [various files]\n
Run Code Online (Sandbox Code Playgroud)\n\n

您所做的就是移动到工作project-B中。请记住,任何标准存储库都包含以下三个部分:project-A

\n\n
    \n
  • 存储库.git本身充满了 Git 用于 Git 自身目的的文件。其中包含主 Git 存储库数据库,其中之一保存所有提交和其他 Git 对象。它拥有 Git 完成自己的事情所需的所有文件。(您可以随时查看这些文件,但一般来说,除非您知道自己在做什么,否则不应直接编辑它们。但有些文件的目的是显而易见的,在这种情况下,直接编辑往往可以正常工作也。您可能想查看该文件.git/config,例如\xe2\x80\x94,它的格式非常简单,类似于 Windows INI 文件。查看文件内部.git/HEAD也很有启发性。)

  • \n
  • 工作,这是您进行实际工作的地方。这是一个普通的目录树,包含普通的文件。Git 会用从提交中取出的文件填充它,以便您可以处理这些文件。你还可以在这里存储 Git 不知道的文件:这些文件称为未跟踪文件。Git 会抱怨它们,除非你将它们列出\xe2\x80\x94 或它们的名称模式\xe2\x80\x94 在一个.gitignore文件中,其主要功能是让 Git 停止抱怨它们(并确保你不会不小心把它们进入索引,参见下一点)。

  • \n
  • 索引 Git 保存您将要进行的下一次提交的位置。索引具有当前提交中未冻结的每个文件的副本,准备好进入下一次提交。但这些副本采用特殊的、仅限 Git 的格式,就像提交中的文件一样。它们不是像工作树中的普通文件。

  • \n
\n\n

在处理工作树中的文件后,您可以随时使用git add将该文件从工作树重新复制回索引中。这会将其恢复为特殊的仅限 Git 的格式,以便准备好进入下一次提交。如果文件之前不在索引中,则会将其复制到索引中(将其转换为仅限 Git 的格式),因此现在它索引中。

\n\n

索引中文件的存在使得该文件被跟踪,因此位于工作树中但不在索引中的文件是未跟踪文件。正如我们刚才所说,Git 会抱怨它,除非你通过条目告诉 Git不要抱怨这个文件.gitignore

\n\n

您可以git add使用选项(如-a)或参数(如.)来运行,表示添加所有内容添加许多文件。在这种情况下,git add也会根据设置检查任何当前未跟踪的文件\xe2\x80\x94任何当前不在.gitignore索引中的文件\xe2\x80\x94 ,并且不会添加该文件,这样它将保持不被追踪。

\n\n

因此,.gitignore意思不是忽略这个文件,而是如果这个文件未被跟踪,不要抱怨它未被跟踪,并且当我批量添加或更新大量文件时不要自动将其复制索引中索引,以便他们准备好进入下一次提交。

\n\n

索引本身实际上是一个文件,或者有时是多个文件,.git但它值得单独列出,原因有两个。首先,它非常重要,而且就在你面前,尽管你不到它。第二个是 Git 现在支持git worktree add创建额外的工作树。您添加的每个工作树都有自己的索引。也就是说,索引和工作树是成对的:只有一个存储库,通过该存储库,您可以免费获得一个索引和工作树。然后,您可以创建git worktree add第二个索引和工作树,然后是第三个索引和工作树,依此类推。显然,索引至少在逻辑上与存储库本身不同,那么:它与工作树相关联,而不是与存储库相关联。

\n\n

不管怎样,上面的结果是,通过放入project-B inside project-A,你所做的就是让项目-A 有大量被忽略的文件:

\n\n
$HOME/\n      [various dot files, personal files, etc]\n      src/\n          project-A/\n                    .git/\n                         [all of Git\'s files]\n                    .gitignore                           <-- lists `/project-B/`\n                    project-B/\n                              .git/\n                                   [all of Git\'s files]  <-- these files are untracked in A\n                              .gitignore                 <-- and this is untracked in A\n                              README.md                  <-- and so is this\n                              [various files]            <-- and all of these\n                    README.md\n                    [various files]\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

git submodule 和我上面做的一样吗?

\n
\n\n

不完全是,不。它要复杂得多。不过,它确实产生了类似的工作树结构

\n\n

使用子模块时,您实际上链接了两个存储库。不过,这种联系本质上是单向的。

\n\n

一般来说,您首先创建两个单独的、完全独立的存储库,并用各种提交填充它们。这就像上面的并排设置一样,项目 A 和 B 彼此相邻,而不是嵌套,一个在另一个里面。事实上,很多时候,您根本不创建项目 B:其他人已经创建了项目 B,例如,您希望使用它来在新项目 A 中完成一些工作的精美。让我们以此为例,因为它不仅更常见,而且也是git submodule预期使用的方式\xe2\x80\x94,如果你自己制作项目 B,你可以开始当你第一次尝试项目 B 时,将其设置在 GitHub 上或任何你打算保留它以供一般访问的地方,并在开始制作项目 A 之前解决掉所有这些问题。

\n\n

因此,此时,您有一个项目 B,\xe2\x80\x94let\ 假设 \xe2\x80\x94 拥有其主要的、引人瞩目的公共访问存储库,存储在 GitHub 上的 URL //git://github.com/someuser/B上。ssh://git@github.com/someuser/Bhttps://github.com/someuser/B

\n\n

您现在要创建项目 A。您可以使用 GitHub“创建存储库”点击按钮首先在此处创建它,然后将其克隆到您的笔记本电脑或您工作的任何地方:

\n\n
<click some buttons in browser>\n\ngit clone <url> A      # so now you have A/README.md and A/.git and so on\ncd A\n
Run Code Online (Sandbox Code Playgroud)\n\n

或者,您可以在 GitHub 上将其创建为空,或者甚至根本不在GitHub 上创建它,如果您愿意的话:

\n\n
mkdir A\ncd A\ngit init\n
Run Code Online (Sandbox Code Playgroud)\n\n

无论哪种方式,您现在都位于A/目录中,该.git/目录有一个包含存储库数据库、索引和工作树的子目录。在此工作树中,您可以创建和编辑各种文件,用于git add将它们复制到索引,以便它们进入下一个提交,然后运行git commit以进行新的提交,将索引的内容冻结到新的快照中。

\n\n

现在,您已准备好将存储库 B(它仍然是一个独立的 Git 存储库)链接存储库 A。为此,您可以选择可找到存储库 B 的主要版本的 URL 之一。您的 Git 将运行git clone以将存储库 B 的新克隆放入工作树中,因此您还必须在当前工作树中选择项目 B\xe2\x80\x94 的路径(它将进入的目录)。让我们使用ssh://github.com/someuser/B这里作为 URL 和project-B目录:

\n\n
git submodule add ssh://github.com/someuser/B project-B\n
Run Code Online (Sandbox Code Playgroud)\n\n

您的 Git 现在运行以git clone创建. 这个克隆人几乎在各个方面都是一个普通的克隆人。例如,它有一个。1 我们只是将其称为“子模块”,因为它正在被另一个 Git 存储库使用。它不需要知道或关心\xe2\x80\x94,就克隆B而言,它只是一个普通的克隆。project-Bssh://github.com/someuser/B.git

\n\n

与此同时,A 现在使用 B 的克隆这一事实将 A 变成了 Git 所说的超级项目。在您的A工作树中,该git submodule add命令将创建一个名为 的文件.gitmodules,并将子模块 B 的 URL 和路径放入该文件中。它将git add这个文件添加到A的索引中,为下一次提交做好准备。最后,它将使用gitlinkgit add类型的特殊条目到 A 的索引,这是子模块 B 的路径。git add project-B

\n\n

因此,现在 中的索引A有两个全新条目:一个用于.gitmodules,其中 \xe2\x80\x94(如果您查看文件\xe2\x80\x94 的工作树副本)现在列出了子模块,另一个用于“文件” “\xe2\x80\x94 实际上,一个名为 gitlink\xe2\x80\x94 的project-B. git commit如果您现在在项目 A 的工作树中运行,您将在存储库数据库中获得新的提交。这个新的提交包含索引中已经存在的所有文件(例如,README.md等等),加上这个新文件.gitmodules,加上这个新的 gitlink 东西。

\n\n
\n\n

1在旧版本的 Git 中,.git项目 B 子模块克隆中的这个是一个普通目录,保存此克隆的存储库数据库,就像任何普通的 Git 存储库工作树(其.git. 在现代 Git 中,它是一个文件,告诉 Git 在哪里可以找到.git项目 B 子模块克隆的存储库数据库,Git 将其秘密在.git项目 A 的目录中(即在 中A/.git)。子模块 B 存储库的这种隐藏使得 A 的添加工作树能够共享子模块 B 存储库,而不是仅仅复制它。

\n\n
\n\n

子模块的操作

\n\n

再次记住,子模块不必知道它是一个子模块:它只是一个常规的、普通的 Git 存储库。如果您现在cd project-B进入项目 B 克隆的工作树并运行git loggit status以及其他各种 Git 命令,它们都可以工作,并且它们都会告诉您项目 B 的这个克隆中发生了什么。

\n\n

如果你愿意的话,可以在这里工作!然而,有一个问题。超级项目Git \xe2\x80\x94(管理项目 A\xe2\x80\x94 的工作树)此时已命令工作树目录中的子模块project-BGit进入分离 HEAD模式。如果您不熟悉分离 HEAD 模式,那么您现在需要了解一下。如果您熟悉它\xe2\x80\x94或者在您离开并了解它之后回到这里\xe2\x80\x94您的子模块项目B工作树的HEAD分离的一个特定提交是记录在超级项目的 gitlink 中的哈希 ID。

\n\n

换句话说,当您在项目 A 中工作并告诉它去操作项目 B 目录中的其他 Git 存储库时,项目 A Git 知道要使用哪个提交的方式是查看存储在项目 A 的索引。为了便于说明,我们假设这是0123456....

\n\n

如果您确实进入该project-B目录,则您位于 B 的克隆中,并且您可以进行git checkout任何其他提交,甚至可以更改 B 中的分支git checkout。这会更改分离的 HEAD,甚至将其附加到分支,因此现在存储库B 有一个不同的提交签出。假设您这样做:

\n\n
cd project-B\ngit checkout develop\n... do some work ...\ngit add ... some files ...\ngit commit -m \'do work on B to make it more useful for A\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

您可以将git push新提交回 GitHub,因为项目 B 毕竟是一个常规的旧存储库。但现在(工作树目录)HEAD中的提交不再是,现在是. 如果你爬回到项目 A 工作树并运行,你会看到管理 A 的 Git 说:嘿,子模块已经离开了我想要的分离的 HEAD,它不再存在了project-B0123456...8088dad...git submodule status0123456...

\n\n

确实如此,但如果这就是您想要的,那么现在是时候git add更新项目 A 索引中的 gitlink 条目了:

\n\n
git add project-B\n
Run Code Online (Sandbox Code Playgroud)\n\n

例如。现在,与项目 A 的工作树关联的索引要求8088dad...子模块中提交,如果您git commit在项目 A 工作树中运行,您将获得项目 A 的新提交,内容如下:

\n\n
git commit -m \'update submodule B to 8088dad...\'\n
Run Code Online (Sandbox Code Playgroud)\n\n

(这并不是真正最好的提交消息\xe2\x80\x94,最好说明你现在正在使用子模块 B 的哪些功能,而不仅仅是“我切换到提交 8088dad”\xe2\x80\x94 但这是一个例如,我什至不知道你正在使用什么功能。)

\n\n

还有其他方法可以更新子模块,然后在超级项目中的新提交中记录超级项目 Git 应该命令子模块 Git 检查特定提交。该git submodule命令表达了其中的许多内容。但重点是,随着时间的推移,超级项目存储库中存在许多提交\xe2\x80\x94 ,其中每个提交都表示:

\n\n
    \n
  • url我使用来自...的子模块
  • \n
  • 存储在路径path...
  • \n
  • 当我使用它时,我命令子模块 Git 签出 commit hash-ID
  • \n
\n\n

前两条信息记录在项目 A 的提交中名为.gitmodules. 每个提交都有自己的该文件的副本(尽管像往常一样,如果一百万次提交使用该文件的同一版本,则存储库只有该版本的一个副本)。最后一条信息直接记录在项目 A 的提交中:每个提交保存一个原始哈希 ID,给出应该git checkout在子模块路径中进行 -ed 的提交。

\n\n

概括

\n\n

的目的git submodule是允许您在超级项目 Git 中指定您依赖于其他一些 Git 存储库。您将希望新克隆使用的存储库的 URL 记录到git clone子模块中。您记录您希望超级项目 Git 使用的路径,以将克隆存储到其中。并且,对于超级项目中的每次提交,您都记录该子模块的特定子模块提交,以便克隆超级项目,然后检查该克隆中的某些特定的历史提交,还会克隆子模块并检查正确的历史提交子模块克隆。

\n\n

这意味着超级项目 Git 现在依赖于子模块:尽管子模块克隆是通过超级项目 Git 命令控制的(使子模块成为某种从属模块),但超级项目本身不再独立。它需要 Git 子模块的帮助,才能让超级项目感觉完整。而且,因为类似从属的子模块是一个克隆,所以这不会阻止管理子模块原始版本的任何人对该存储库执行任何他们想要的操作。这甚至包括将提交从子模块克隆的起源中剥离出来,如果起源的原始提交已经消失,那么存储在超级项目的提交中的哈希 ID 现在就没用了。

\n\n

这并不意味着不要这样做,它只是意味着知道当你这样做时你会陷入什么样的依赖关系。 如果您希望项目 A 独立于项目 B 的存在和确切的提交哈希 ID,请不要使用子模块。如果您同意项目 A 依赖于项目 B,并且希望能够方便地使用项目 B出现的新功能,git submodule那么这是一个不错的选择。

\n