如何删除一个文件夹,但只在一个 git 分支中保留所有子文件夹和文件?

How do I remove a folder, but keep all subfodlers and files, only in one git branch?

我正在建立一个 git 分支,它需要与其他分支具有不同的目录结构。这些文件都是一样的,但是,它们基本上都必须向上移动一个级别。有很多文件,我不知道如何使用 git.

将它们全部移动到一起

目前,它看起来像这样:

Root Directory
    |
    - Main Folder
    |    |
    |     - Sub Folder 1 with a lot of subfolders and files
    |    |
    |     - Sub Folder 2 with a lot of other subfodlers and files
    |
    - A couple of random files

我想要的结果:

Root Directory
    |
    - Sub Folder 1 with a lot of subfolders and files
    |
    - Sub Folder 2 with a lot of other subfodlers and files
    |
    - A couple of random files

但是,此更改应仅在一个分支中进行。

我该怎么做?

文件夹结构特定于每个分支。所以首先你需要检查新分支

git 结帐分店 1 然后将子文件夹 1 和 2 移出主文件夹 删除 git 提交

如果您对 git 的工作原理有任何进一步的疑问,有很多很棒的教程 = 也许从 https://girliemac.com/blog/2017/12/26/git-purr/ GIT 和小猫

开始

在开始之前,这里有一些有用的背景知识:

  • Git 仅存储 文件,不存储文件夹。
  • 每个 Git 提交都会存储 所有 文件的 完整快照 — 或者更确切地说,所有 提交 个文件。这就是 Git 称为 未跟踪文件 的地方。
  • 快照是由存储 Git 调用的文件制作的,不同的是,您的 indexstaging area . (它还有一个旧名称,现在应该用于其他用途,但有时某些东西会引用 缓存。这三个名称几乎是同一个东西。)

Git 将这些文件存储在 commits 中。 Git 实际上就是关于提交。每个提交都有编号——但不是以一种很好的简单顺序 "commit #1, commit #2, ..." 方式。相反,每个提交都会获得一个唯一的 哈希 ID ,哈希 ID 完全随机出现并且与之前的提交无关。这些散列 ID 是 letters-and-numbers 的丑陋的大字符串,就像 git log 吐出的 83232e38648b51abbcbdb56c94632b6906cc85a6

由于每个文件都在每个提交中,因此Git以一种不保存它们的方式很重要不要立即用完整个磁盘驱动器。因此,保存的文件被压缩,并且 共享 跨不同的提交。 Git之所以能做到这一点,是因为它使用了一种特殊的Git-only、freeze-dried格式来存储文件。这种形式的文件无法更改,但可以共享。这意味着 无法更改现有提交 您存储库中的每个 提交都会或多或少永久存档。1 将提交视为永久提交(它们大部分 是) 且不可更改。它们存储在存储库中的历史。


1可以删除提交,但这有点困难,而且 Git 通常也不会立即这样做——所以即使你 think 一个提交不见了,不能马上找到它,它可能还在里面。


完成工作

现在,这对于归档来说一切都很好,但是这些 read-only freeze-dried 文件对于实际完成任何实际的 工作 是完全无用的。为此,Git 提供了 Git 所谓的 work-tree。那就是您工作的地方。

进入 work-tree,Git 从一些提交中提取 freeze-dried 文件,重新水化它们,使它们具有正常的日常形式。您现在可以查看和使用这些文件。您只需选择一个提交——通常是 last 对某些 分支 的提交——然后说:给我那个提交,而 Git 就是这样做的。它找到冻结的提交并枚举其中的所有文件:

  • main-folder/sub1/file1啊哈,说Git,这个work-tree没有main-folder,让我们做一个。而且我刚做的main-folder里面没有sub1,我们也做一个吧。现在我可以创建新文件 main-folder/sub1/file1.
  • main-folder/sub1/file2,说Git,已经有一个main-folder/sub1,我可以创建新文件file2在里面。

此过程根据需要重复:Git 有 文件 ,如提交中所列,它必须重新构建。完成后,如果 work-tree 在您开始时是 空的,那么,现在它具有来自该提交的每个文件的再水化版本。没有存储 个文件夹,但不需要存储它们。

如果您现在从 那个 提交切换到另一个提交,Git 将删除 它刚刚创建的所有文件对于该提交,并将它们替换为其他不同提交的文件。如果它从 main-folder/sub1 中删除 所有 个文件,它还会删除目录 main-folder/sub1。如果它最终删除了 main-folder 中的所有内容,它也会将其删除。然后它开始从你现在想要的提交中提取所有文件,创建任何directories/folders根据需要。

事实上,Git 交织了所有这些工作,创建和删除,以及优化:如果您从提交 a123456... 切换到提交 b789abc...,并且 99% 的文件在这两个提交中 相同 ,好吧,毕竟没有必要在 work-tree 中与它们混在一起,是吗?并且,对于这种特殊形式的 git checkout,Git 在切换提交之前添加了安全检查:对于我必须删除或替换的每个文件,文件是否在 work-tree "clean"? 如果文件是 "clean",删除或替换它是安全的。如果它是 "dirty"——如果你在 Git 提取它后改变了它,并且你可能想保留你的改变,因为切换会破坏它——Git 会警告你,默认情况下, 拒绝切换提交。

指数/staging-area

有一个在这个过程中巨大的喇叭皱纹。通读以上内容,您会想:好吧,我们已经提交了 freeze-dried 文件,work-tree 提交了普通文件。 但是还有第三个实体Git 将 置于 这两者之间。这是索引 / staging-area.

就像提交本身一样,索引大部分是不可见的。它实际上只是一个普通文件,.git/index 在大多数情况下 — 这最终会变得更加复杂,但它开始时只是这个普通文件。本质上,文件中的内容是您提取的提交的副本——所有 freeze-dried 文件,仅使用哈希 ID(如提交哈希 ID)来识别它们。但是,与提交中的实际冻结文件不同,索引 中的副本可以 更改。2

这就是 git add 所做的:它 freeze-dries 文件并在索引中粘贴 那个 版本。如果文件 之前没有 在索引中,那么现在是了。如果它之前在索引中是,这会踢出以前的版本。 无论哪种情况,新的 freeze-dried 文件都已准备好提交。 当您 运行 git commit、Git 只是打包所有索引中的 ready-to-go 文件,放入新的提交中。这就是 git commit 如此之快的原因:它确实没有什么工作要做。

索引中的文件根本没有存储在文件夹中。只有一个巨大的列表:文件 path/to/file1 这些 freeze-dried 内容,文件 path/to/file2 这些其他 freeze-dried 内容等等。但是无论如何,文件 索引中的存在——连同 freeze-dried ready-to-commit 内容——是 work-tree 跟踪tracked 文件是索引中的文件,因此 an untracked 文件只是 work-tree,但不在索引中。 因为 git commit 归档 index 中的内容,而不是 work-tree 中的内容,只有 已跟踪 个文件已提交。


2这里棘手的部分是将新文件放入索引实际上 freeze-dries 文件并将其存储在 存储库,如果新内容确实是新的,则创建一个新的哈希 ID,或者如果 freeze-dried 内容与任何现有文件匹配,则共享一些现有的哈希 ID。现在 supposedly-new 文件已经缩减为一个散列 ID,它适合旧文件占用的索引中的同一个槽!


有了这个,答案就很简单了

要进行新的提交,在该提交中,仅存储 某些文件,只需进行设置,以便您的 index 具有只是其中的那些文件。为此,从您的索引中删除 所有 totally-unwanted 文件,这也会删除 work-tree 个副本:

git rm ...

由于索引是通过它们相对于 work-tree 顶部的 路径来存储它们的,因此您需要保存所有要保留的文件。最简单的方法是 重命名 它们在 work-tree 索引中:

git mv main-folder/sub1 sub1

如果需要,它将在您的 work-tree 中创建(在本例中通过重命名)sub1 文件夹,然后重命名所有 tracked 文件索引——请记住,git mv 必须与索引以及 work-tree 一起工作——从它们的 main-folder/sub1/file1 等路径到 sub1/file1 等路径。 git mv 命令,就像 git rm 命令一样,然后将 work-tree 个文件一起拖动。

(方便地,或者有时可能不是,当 git mv 重命名文件夹 in-place 时,也会重命名其中的任何 未跟踪的 文件。因为其余的Git 的人对未跟踪的文件并不感兴趣,后来的 git checkout 不会将它们移回!)

因为在所有内容的基础上,Git 按内容存储文件——使用 freeze-dried 文件的哈希 ID——所有这些重命名大部分都是免费的。 Git 需要一点点 space 来存储更新后的 名称 — 提交必须存储全名以及哈希 ID,并且 names 不能在这里轻松共享,因为它们不同3—但实际内容与其他提交中具有不同名称的文件共享。

请注意,当您在具有这些 sub1/file1 类型名称的此提交与具有 main-folder/sub1/file1 类型名称的任何提交之间来回切换时,Git 可能必须在你的 work-tree 上努力搅动,首先删除所有 sub1/file1 名称,然后创建新的空 main-foldermain-folder/sub1 目录来保存(最终相同!)文件曾经在 sub1/file1 等等。如果 Git 足够聪明,可以意识到它可以 重命名 work-tree 中的那些文件,Git 可能会这样做,但是Git 通常以简单的愚蠢方式开始,就是删除和 re-create 它们。这将显示在 OS-level 文件 time-stamps 中:如果 Git 删除了一个文件并且 re-create 删除了它,它会s "now" 作为其 on-disk work-tree-文件 time-stamp.


3内部提交——但不在索引中——Git 直接回到 tree-structured 命名方案。因此,如果此新提交的顶层 sub1 与其他提交的 main-folder/sub1 中的 sub1 100% 相同,则 Git 实际上将共享基础 tree 新提交的根树的 sub1 sub-tree 对象。根树当然会有所不同,因为它将 sub1 命名为它的 sub-tree 之一,而不是命名 main-folder 作为它的 sub-tree 之一。但所有这些仅仅是实现细节:它的 none 显示在索引中,work-tree.