使用git init在子文件夹中创建新的git仓库并在主git仓库中忽略

4
我尝试理解git子模块。它似乎很复杂(我没有任何子模块的经验)。值得花些时间去了解。但是,现在我有一些关于git子模块的问题。
实际上我正在尝试:
- 有一个主存储库,在主存储库中我想创建一个新的(子)存储库。所以我做了以下操作: - 第一步)创建文件夹(ABC)。 - 第二步)在主存储库中忽略文件夹(ABC)。 - 第三步)在文件夹(ABC)中,运行git init(创建一个新的存储库)
这是创建子存储库的正确方法(或者可能是正确的方向)吗?
git子模块和我上面所做的类似吗?
提前感谢。

你为什么想要通过将文件夹 ABC 添加到主代码库的 .gitignore 中来忽略它?你想达到什么样的目的? - Flimm
1个回答

5

Git并没有定义"子仓库",所以您可以按照自己的喜好进行定义。

Git确实定义了在.gitignore中列出目录的含义,这个特定模式可以正常工作。您所做的是设置了两个完全不相关的仓库。

在典型的设置中,这两个仓库会并排存在:

$HOME/
      [various dot files, personal files, etc]
      src/
          project-A/
                    .git/
                         [all of Git's files]
                    .gitignore
                    README.md
                    [various files]
          project-B/
                    .git/
                         [all of Git's files]
                    .gitignore
                    README.md
                    [various files]

你所做的只是将project-B移动到project-A工作树中。请记住,任何标准的存储库都有这三个部分:
  • .git存储库本身,包含Git用于自己目的的文件。它包含主要的Git存储库数据库之一,其中保存了所有提交和其他Git对象。它具有Git需要完成自己任务的所有文件。(您可以随时查看这些文件,但通常情况下,除非您知道自己在做什么,否则不应直接编辑它们。但有些文件是明显的,这种情况下,直接编辑也往往可以正常工作。例如,您可能想查看文件.git/config,它具有相当简单的格式,类似于Windows INI文件。查看文件.git/HEAD的内容也很有教育意义。)

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

  • 索引,这是Git保存下一个要提交的文件的位置。索引具有每个文件的副本,从当前提交中解冻,准备好进入下一个提交。但是,这些副本采用了一种特殊的、仅适用于Git的格式,类似于提交内部的文件。它们不像工作树中的文件那样普通。

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

文件在索引中的存在就是使该文件成为已跟踪的文件,因此在工作树中存在但不在索引中的文件是一个未跟踪的文件。正如我们刚才所说的那样,Git会抱怨它,除非您通过.gitignore条目告诉Git不要抱怨这个文件

您可以使用选项(例如-a)或参数(例如.)运行git add命令,以表明要添加所有内容或添加多个文件。在这种情况下,git add会检查任何当前未跟踪的文件-即目前不在索引中的任何文件-并根据.gitignore设置,不会将该文件添加,以使其保持未跟踪状态。
因此,.gitignore的含义不是忽略此文件,而是如果此文件未被跟踪,则不要对它未被跟踪进行投诉,并且在我批量添加或更新大量文件到索引中以便准备进入下一个提交时,不自动将其复制到索引中。 索引本身实际上是一个或多个文件,存在于.git中,但由于两个原因,它值得单独列出。第一点是它非常重要且显眼,尽管您无法看到它。第二点是Git现在支持git worktree add来创建其他工作树。您添加的每个工作树都有自己的索引。也就是说,索引和工作树是成对出现的:只有一个存储库,并且在该存储库中,您可以免费获取一个索引和工作树。然后您可以git worktree add第二个索引和工作树,然后是第三个索引和工作树,以此类推。显然,索引至少逻辑上与存储库本身不同:它与工作树相关联,而不是与存储库相关联。
无论如何,以上内容的要点是,通过将project-B放在project-A中,您所做的就是让项目A有大量被忽略的文件。
$HOME/
      [various dot files, personal files, etc]
      src/
          project-A/
                    .git/
                         [all of Git's files]
                    .gitignore                           <-- lists `/project-B/`
                    project-B/
                              .git/
                                   [all of Git's files]  <-- these files are untracked in A
                              .gitignore                 <-- and this is untracked in A
                              README.md                  <-- and so is this
                              [various files]            <-- and all of these
                    README.md
                    [various files]

git子模块和我上面所做的操作有点类似吗?
不完全相同,它要复杂得多。但是,它确实产生了类似的工作树结构。
使用子模块时,您实际上会将两个存储库进行链接。尽管该链接是单向的,但这种关系实际上是一种联系。
通常,您首先会创建两个独立的存储库,并填充它们以进行各种提交。这就像上面并排设置项目A和B,而不是一个嵌套在另一个中。事实上,很多时候,您根本不需要创建项目B:其他人已经将项目B创建为一个高级库,您希望在新项目A中使用它来完成一些工作。让我们以此为例,因为这不仅更常见,而且也是git子模块期望使用的方式。如果您自己制作B项目,可以在开始制作A项目之前,先开始制作B项目,并将其设置在GitHub或任何其他您用于普遍访问的地方。
因此,在此时,您拥有一个B项目,假设其主要的、公共访问的存储库存储在GitHub的URL git://github.com/someuser/B / ssh://git@github.com/someuser/B / https://github.com/someuser/B。
现在,您将创建A项目。您可以使用GitHub上的“创建存储库”按钮来先在那里创建它,然后将其克隆到您的笔记本电脑或任何其他您工作的地方。
<click some buttons in browser>

git clone <url> A      # so now you have A/README.md and A/.git and so on
cd A

或者,如果你愿意的话,在GitHub上可以将其创建为空,或者甚至根本不在GitHub上创建。
mkdir A
cd A
git init

无论哪种方法,你现在都在你的A/目录下,其中有一个.git/子目录,其中保存着存储库数据库、索引和工作树。在这个工作树中,你可以创建和编辑各种文件,使用git add将它们复制到索引中,以便它们进入下一次提交,然后运行git commit将索引的内容冻结成一个新的快照。
现在你已经准备好将仍然是独立Git存储库的存储库B链接到存储库A中。为此,你选择其中一个存储库B主要版本所在的URL。你的Git将运行git clone将存储库B的新克隆放在你的工作树中,因此你还必须选择项目B的路径——它将进入当前工作树中的目录。在这里,我们选择ssh://github.com/someuser/B作为URL,project-B作为目录。
git submodule add ssh://github.com/someuser/B project-B

您的Git现在运行git clone来创建project-B,作为ssh://github.com/someuser/B的克隆。这个克隆在几乎所有方面都是普通的克隆。例如,它有一个.git1我们只是将其称为“子模块”,因为它被另一个Git存储库使用。就克隆B而言,它不需要知道或关心这一点,它只是一个普通的克隆。
与此同时,A现在使用B的克隆使A成为Git所称的“超级项目”。在您的A工作树中,git submodule add命令将创建一个名为.gitmodules的文件,并将子模块B的URL和路径放入该文件。它将git add此文件到A的索引中,以备下一次提交。最后,它将使用git add project-B向A的索引添加一种特殊类型的gitlink条目,即子模块B的路径。
因此,现在A中的索引有两个全新的条目:一个用于.gitmodules,如果您查看该文件的工作树副本,则现在列出了子模块,还有一个用于名为project-B的“文件”(实际上是gitlink)。如果您现在在项目A的工作树中运行git commit,则会在存储库数据库中得到一个新的提交。这个新提交包含了已经在索引中的所有文件(例如README.md等),加上这个新文件.gitmodules和这个新的gitlink条目。

1在旧版本的Git中,子模块克隆中的此.git是一个普通目录,它保存着该克隆的存储库数据库,就像任何普通的Git存储库工作树一样,其中数据库位于其.git内部。在现代Git中,它是一个文件,告诉Git在项目A的.git目录中隐藏子模块B的.git存储库数据库。这种隐藏子模块B存储库的方式使得A的附加工作树可以共享子模块B存储库,而不仅仅是复制它。


子模块的操作

请记住,子模块不需要知道它是一个子模块:它只是一个常规的 Git 存储库。如果你现在使用 cd project-B 命令进入项目 B 克隆的工作树并运行 git loggit status 和其他各种 Git 命令,它们都可以正常工作并告诉你在项目 B 的此克隆中正在发生什么。
如果愿意,你可以在这里进行工作!但有一个问题。此时,管理项目 A 工作树的“超级项目” Git 命令了子模块 Git(位于 project-B 目录的工作树中)进入“分离 HEAD”模式。如果你不熟悉分离 HEAD 模式,则现在需要学习一下。如果您已经熟悉了它,或者在学习了它之后回到这里,那么子模块项目 B 工作树的 HEAD 分离的“一个特定提交”是记录在超级项目中的 gitlink 中的哈希 ID。
换句话说,在项目 A 中工作并告诉它“去操作项目 B 目录中的另一个 Git 存储库”的方式是查看存储在项目 A 的索引中的 gitlink,以了解要使用哪个提交。假设为了说明,它是 0123456...
如果你确实进入了 project-B 目录,那么你就在 B 的克隆版中,并且可以 git checkout 任何其他提交,甚至是在 B 中检出一个分支。这会更改分离的 HEAD,甚至将其附加到一个分支,以便现在存储库 B 有不同的提交被检出。因此,假设你这样做了:
cd project-B
git checkout develop
... do some work ...
git add ... some files ...
git commit -m 'do work on B to make it more useful for A'

您可以使用git push将新的提交推回GitHub,因为项目B毕竟是一个常规的旧存储库。但现在,在project-B(工作树目录)中的HEAD提交不再是0123456...,现在是8088dad...。如果您回到项目A的工作树并运行git submodule status,则会看到管理A的Git表示:嘿,子模块已经远离我想要的分离HEAD了,它不再是0123456... 这是真的,但如果这正是您想要的,现在是使用git add更新项目A索引中的gitlink条目的时候了。
git add project-B

例如,现在与项目A的工作树相关联的索引调用了子模块中的提交8088dad...,如果您在项目A工作树中运行git commit,您将得到一个新的提交,其中包含有关项目A的以下信息:
git commit -m 'update submodule B to 8088dad...'

(这不是最好的提交信息,最好的做法是说明您现在使用了子模块B的哪些功能,而不仅仅是“我切换到了8088dad提交”——但这只是一个示例,我甚至不知道您在使用哪些功能。)

有其他方法来更新子模块,然后在超级项目中记录一个新的提交,指示超级项目 Git 命令子模块 Git 检出这个特定提交。 git submodule 命令表达了其中许多方式。 但关键是,在超级项目存储库中存在许多提交,每个提交都说明:

  • 我使用一个从url ...获取的子模块
  • 存储在路径path ...
  • 当我使用它时,我命令子模块Git检出提交hash-ID

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

摘要

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

这意味着超级项目 Git 现在“依赖于”子模块:尽管子模块的克隆是通过超级项目 Git 命令进行控制(使子模块成为一种从属关系),但超级项目本身不再是独立的。它需要子模块 Git 的帮助,才能感觉完整。而且,由于类似从属关系的子模块是一个“克隆”,这并不能阻止管理子模块“原始版本”的人任意操作该代码库。这甚至包括从子模块克隆源中删除提交。如果源的原始提交已经消失,则存储在超级项目提交中的哈希 ID 现在是无用的。
这并不意味着“不要这样做”,而是意味着“知道当你这样做时会有什么依赖关系”。如果您希望项目 A 独立于项目 B 的存在和确切的提交哈希 ID,请不要使用子模块。如果您可以接受项目 A 依赖于项目 B,并希望方便地使用项目 B 中出现的新功能,则“git submodule”是一个很好的选择。

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