首页 > 解决方案 > 如何一次将目录和子目录添加到 Git

问题描述

我想一次将包含子目录的目录从我的计算机推送到 GitHub。

我的文件夹结构:

parent_folder
  |_ child_folder_1
  |_ child_folder_2
  |_ etc

当我git init到父文件夹并尝试时,git add .我得到了错误:

warning: adding embedded git repository: Child_folder_1
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> Child_folder_1
hint:
hint: If you added this path by mistake, you can remove it from the
hint: index with:
hint:
hint:   git rm --cached Child_folder_1
hint:
hint: See "git help submodule" for more information.

这不仅发生在一个子文件夹上,而且发生在所有子文件夹上。是否因为子目录也包含 git 而发生?那么我该如何解决这个问题呢?提前致谢。

标签: git

解决方案


Git 不存储目录。Git 存储文件,或者更准确地说,Git 存储提交(然后存储文件——请参阅下面的挑剔区别)。但在您的情况下,问题在于这些文件已经在不同的 Git 存储库中,因此 Git 不会将它们添加到存储库中。

是否因为子目录也包含 git 而发生?

更准确地说,这是因为这些子目录Git 存储库(有点;请参阅下面的挑剔区别)。

那么我该如何解决这个问题呢?

您必须立即做出决定,一个具有许多深远影响的决定(在您真正了解所有后果之前):

  • 你想使用 Git 的子模块,还是
  • 是否要删除其他存储库以便可以将这些文件直接存储在存储库中?

你需要知道的挑剔区别

Git存储库主要是提交的集合。我们通常通过分支名称查找提交,尽管此规则有例外。 克隆存储库会复制该存储库的所有(或至少大部分)提交,但不会复制其分支名称。相反,我们的 Git——我们的软件,创建和填充我们的存储库,读取另一个存储库的分支名称并将其更改为远程跟踪名称

因此,适当的存储库主要由两个数据库组成(在此之后添加了许多辅助内容以使它们更有用)。一个,通常是最大的,持有提交——Git 有时会调用这些提交对象——以及 Git 需要的其他内部 Git 对象。这个对象数据库是一个简单的键值存储,其中的哈希 ID:大而丑陋的字母和数字字符串,它们实际上是十六进制数字,是密码校验和函数的输出。另一个数据库包含名称:分支名称、标签名称、远程跟踪名称等。每个名称都有一个哈希 ID;特别是分支名称持有提交哈希 ID,因此让我们找到那些特定的提交。

每个提交依次包含两件事:

  • 每个提交都包含每个文件的完整快照。这些文件以一种特殊的、只读的、仅限 Git 的、压缩的和去重复的形式存储。它们不是普通文件,它们的名称中嵌入了正斜杠,例如path/to/file.ext,即使在 Windows 上也是如此;它们区分大小写,即使您使用的是不区分大小写的 Windows 或 macOS 系统;等等。

  • 每个提交还包含一些元数据:有关该特定提交的信息。这包括提交人的姓名和电子邮件地址。它包括几个日期和时间戳,这有助于使每个提交都是唯一的。而且,对于 Git 的内部操作至关重要,每个提交都包含一个先前提交哈希 ID 的列表。

大多数提交中的前一个提交列表只有一个条目长,因此每个提交只记住其(单个)提交的原始哈希 ID。这将提交形成向后指向的链,其中最新的提交带有一些大而丑陋的随机散列 ID;让我们称之为H——<em>指向一些较早的提交:

            <-H

让我们调用之前的提交——它也有一些看起来很丑的随机哈希 ID 作为它的真实名称——<code>G,这样我们就可以讨论它,并将它画进去:

        <-G <-H

Commit G,作为一个提交,存储一些更早的 commit 的哈希 ID F,等等:

... <-F <-G <-H

每个提交也有每个文件的完整快照,尽管有重复数据删除,所以如果某个文件 inG与 中的文件完全匹配H,那么存储库中实际上只有一个该文件的副本。(Git 使用散列和它所谓的blob 对象来做到这一点,但您通常不需要关心这一点。)

通过比较Gin和 in的快照H,Git 可以告诉您哪些文件是相同的(可能是大多数文件)以及哪些文件是不同的。、git diff和命令都可以进行这种比较:它们将显示不同的文件,git show并且git log -p默认情况下,会显示将左侧文件更改为右侧文件的配方。所以比较GvsH告诉你哪些文件发生了变化,以及这些文件发生什么。 H不过仍然存储完整的快照:要找出发生了什么变化,Git 必须找到G. 它可以做到这一点,因为 H 指向 G.

你不能直接使用提交,因为它只读的,而且只有 Git 可以实际读取一个。您也无法完成任何新工作,因为它是只读的。为了解决这个问题,我们实际上并不直接使用提交。相反,我们使用git checkoutorgit switch提取提交。这个:

  • 从我们之前的任何提交中删除所有先前提取的文件,然后
  • 从我们要移动到的提交中提取所有文件

现在我们有了每个文件的可用副本。这些可用的副本进入 Git 所称的工作树,或简称工作树

这些工作树文件是您实际看到使用的文件。它们是普通文件,由您的计算机以普通方式存储,因此它们存在于目录中(有些人更喜欢这里的“文件夹”一词;任何一个都可以)。 这些文件不在 Git 中。 它们很可能来自Git,通过,但现在它们已经出来git checkout了,它们不再在 Git 中了。你对他们或对他们所做的任何事情都不会影响 Git。

如果你修改了这些文件,或者删除了一些文件,或者创建了新文件,你最终必须告诉 Git。Git 会将文件复制回它所谓的索引暂存区。关于这一点有很多要了解的(你不能忽略这些知识,因为它对于理解跟踪文件和未跟踪文件概念以及真正的工作原理至关重要),但我们不会在这里介绍,但你会运行,然后最终。提交动词将在文件出现在 Git 的暂存区域时从文件中进行的提交,然后更新当前分支名称以将新提交记录为该分支上的最新提交。.gitignoregit addgit commit

存储库本身通常1保存在.git工作树顶层的目录(或文件夹)中。也就是说,如果您的存储库位于 中/Users/you/work/repo2,则存在一个/Users/you/work/repo2/.git包含存储库的,然后其中的所有其他内容都是从您之前使用或签出的当前提交/Users/you/work/repo2中提取的文件。git checkoutgit switch


1形容词通常在这里,因为子模块和添加的工作树以及其他一些特殊情况可能会导致文件名为.git. 每当有一些令人信服的理由.git目录从工作树中移开时,Git 就会使用这个技巧。对于一些丑陋的情况,子模块需要这样做;见下文。


子模块

Git 的子模块在这张原本相对简单的图片上添加了一个皱纹。在很大程度上,子模块只是包含在另一个存储库中的存储库。也就是说,.git在某个 Git 存储库的工作树的子目录之一中有一个目录(或文件,根据脚注 1)。

如果 Git 存储库正在使用子模块,我们称该 Git 存储库为superproject。超级项目和子模块仍然具有 Git 存储库的所有常用属性:它们在.git目录中(或在文件中找到的路径.git)中拥有适当的存储库,以及带有签出提交的工作树。但是这个超级项目需要包含一些额外的东西,我们马上就会看到。

同时,子模块由 superproject 控制。子模块仍然是一个存储库和工作树,所以仍然有提交,你可以签出一个。除了:通常不做检查。你让超级项目做到这一点。

假设您已经克隆了一些 Git 存储库R,并且您现在检查了您将 R中使用的第一个提交,以 branch 命名B。除了与commit一起发布的文件B外,还有一条指令存储在 commit forB中,实际上是:现在克隆并使用另一个 Git 存储库。这个另一个存储库是S,子模块。 根据定义,R现在是一个超级项目。

为了克隆S,Git 需要:

  • 要克隆的存储库的 URL;
  • R中结帐的工作树中的路径名;和
  • 一旦它成为存储库S ,提交哈希 IDgit checkout --detach S

Git 从名为 gitlink 的条目中获取路径名,并从同一个gitlink中获取提交哈希 ID。gitlink 从提交中出来B并进入R的索引/暂存区域,为了克隆S,Git 在文件中查找 gitlink 的路径,.gitmodules如在 commit 中找到的那样B。这个文件有 URL,所以超级项目 Git 能够运行正确的git clone命令:

git clone <url-for-S> path/to/s

例如,或者在你的情况下:

git clone <url> Child_folder_1

要使子模块克隆工作,必须有一个.gitmodules包含正确信息的文件。git submodule命令是构建和维护此.gitmodules文件的命令。因此,要添加Child_folder_1 子模块,您必须运行:

git submodule add <repository-url> Child_folder_1

这负责创建或更新.gitmodules文件,以便克隆说明存在;你在repository-url这里提供的是git clone稍后会得到的。

如果你简单地说git add Child_folder_1,Git 会添加gitlink一半的指令集,稍后克隆将需要它。但这不会添加.gitmodules条目。这就是您收到此错误消息的原因:

warning: adding embedded git repository: Child_folder_1
hint: You've added another git repository inside your current repository.
hint: Clones of the outer repository will not contain the contents of
hint: the embedded repository and will not know how to obtain it.
hint: If you meant to add a submodule, use:
hint:
hint:   git submodule add <url> Child_folder_1
[snip]

这告诉您您错误地添加了子模块。

为什么我们需要子模块?

我们没有。

但是,我们确实需要了解 Git 存储库不允许包含另一个 Git 存储库。Git不会添加名为.git(或.Git,或.GIT,或.giT,或任何其他类似拼写)的文件或目录。更准确地说,.git禁止将任何大小写混合作为名称组件。因此,如果您在存储库中确实有存储库,git add则不会在此处添加文件。相反,它会添加 Git 调用的这个东西gitlink,它会给你子模块样式的行为。Git 的其余部分,包括git status,也不会抱怨这些文件未被跟踪(尽管它可能会提到子模块存储库本身的状态)。

如果愿意,您可以从每个子模块的工作树中删除.git文件或目录。如果你这样做,你已经使它不再相应 Git 存储库的工作树。相反,这些文件——它们仍然是计算机普通格式的普通文件——现在是存储库工作树中的普通但未跟踪的文件。我们现在可以使用存储库这一短语因为您正在使用的只有一个存储库。当您有子模块时,您正在使用两个或多个存储库,因此每次有人说“存储库”时,您必须停下来问:“等等,哪个存储库?”

但是:如果您完全删除子模块存储库,则子模块存储库中将不再有提交。 没有子模块存储库,因此没有提交。因此,您在(顶级)存储库工作树中拥有的未跟踪文件是您拥有的这些文件的唯一副本。您已经丢弃了这些文件的所有其他已提交的副本。

您现在可以将Child_folder_1文件添加并提交到包含当前工作目录(包括子目录)的(单个)存储库Child_folder_1。但是您只能获得这些文件的一个版本。作为一个子模块,你会得到一个gitlink,上面写着:克隆后签出提交 _______(用哈希 ID 填写空白),并从其他存储库git clone复制所有提交,这样你就有每个文件的多个版本。

当您确实有一个子模块时,您可以更新超级项目中的说明。你可以说:check out commit _______(这次用不同的hash ID填空)。该git submodule update命令将在超级项目中运行时切换到提交。工作树文件现在将匹配另一个提交。

您甚至可以说删除该子模块。该git submodule update命令将完全删除工作树文件。(为避免删除该.git目录,实际存储库位于.git某个超级项目目录中,因此删除该.git文件是安全的。)

所以,你必须决定

子模块非常不方便——以至于很多人称它们为“sob-modules”,因为它们会让用户哭泣。但是他们做了一些你没有他们就做不到的事情。你想做这件事吗?或者,如果您将子模块添加为子模块,您是否想要更简单的操作,即丢弃其他Git 存储库中的所有历史记录,并简单地将这些文件的这个版本放入超级项目中?

快速而简单的做法是放弃所有的历史。如果你今天不需要它,明天也不需要它,这会让你回到单个 Git 存储库的情况,没有子模块的复杂性。明天你就不会叫他们啜泣模块了。但是,如果您明天需要它,那么您将来可能会为昨天不使用这些子模块而哭泣。

你现在有尽可能多的信息来做出这个决定。要么使用git submodule add将它们制作成适当的子模块,要么删除.git并将它们添加为普通文件。


推荐阅读