【问题标题】:How do I remove a folder, but keep all subfodlers and files, only in one git branch?如何删除文件夹,但仅将所有子文件夹和文件保留在一个 git 分支中?
【发布时间】:2019-10-01 05:18:20
【问题描述】:

我正在设置一个 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 github directory mv rm


【解决方案1】:

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

  • Git 只存储文件,不存储文件夹。
  • 每个 Git 提交都会存储您的所有文件的完整快照,或者更确切地说,存储所有提交的文件。这就是 Git 所谓的未跟踪文件的来源。
  • 快照是由 Git 调用的文件创建的,不同的是,您的 index暂存区。 (它还有一个旧名称,现在应该用于其他用途,但有时有些东西会引用 cache。所有三个名称都差不多。)

Git 将这些文件存储在 commits 中。 Git 真的是关于提交的。每个提交都有编号——但不是以“commit #1, commit #2, ...”的简单顺序。相反,每个提交都会获得一个唯一的哈希 ID,哈希 ID 看起来完全随机且与之前的提交无关。这些哈希 ID 是由 83232e38648b51abbcbdb56c94632b6906cc85a6 之类的字母和数字组成的大而难看的字符串,git log 会吐出。

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


1可以删除提交,但这有点困难,而且 Git 通常也不会立即执行此操作 - 所以即使您认为提交已消失,并且无法立即找到它,它可能还在里面。


完成工作

现在,这一切都很好,很适合存档,但是这些只读的冻干文件对于真正完成任何实际的工作 完全没有用处。为此,Git 提供了 Git 所称的工作树。这就是您工作的地方。

在工作树中,Git 从某个提交中提取冻干文件,对它们进行再水化,使它们具有正常的日常形式。您现在可以查看和使用这些文件。你只需选择一个提交——通常是某个分支上的last提交——然后说:把那个提交给我,Git就会这样做。它找到冻结的提交并枚举其中的所有文件:

  • main-folder/sub1/file1啊哈,Git 说,这个工作树没有 main-folder,让我们做一个。我刚刚制作的main-folder 中没有sub1,让我们也制作它。现在我可以创建新文件main-folder/sub1/file1
  • main-folder/sub1/file2,Git 说,已经有一个主文件夹/sub1,我可以在其中创建新文件 file2

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

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

实际上,Git 交错了所有这些工作,创建、删除和优化:如果你从提交 a123456... 切换到提交 b789abc...,并且两次提交中 99% 的文件是相同的 ,好吧,毕竟没有必要在工作树中与他们胡闹,是吗?而且,通过git checkout 的这种特殊形式,Git 在切换提交之前添加了安全检查:对于我必须删除或替换的每个文件,工作树中的文件是否“干净”?该文件是“干净的”,删除或替换它是安全的。如果它是“脏的”——如果你在 Git 提取它之后更改了它,并且你可能希望保留你的更改以防切换会破坏——Git 会警告你,并且默认情况下拒绝切换提交。

索引/暂存区

在这个过程中有一个巨大的喇叭皱纹。阅读以上内容,您会想:好吧,我们有冻干文件的提交,以及普通文件的工作树。但是 Git 将第三个实体 置于之间 这两个。这是索引/暂存区。

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

这就是git add 所做的:它冻干文件并将那个 版本保存在索引中。如果文件之前没有在索引中,那么现在是。如果它之前在索引中,则会退出之前的版本。 无论哪种情况,新的冻干文件都已准备好提交。 当您运行 git commit 时,Git 只是将索引中所有准备就绪的文件打包到新提交中.这就是 为什么git commit 如此之快:它真的几乎没有什么工作要做。

索引中的文件根本不存储在文件夹中。只有一个巨大的列表:文件path/to/file1这些 冻干内容,文件path/to/file2这些其他 冻干内容,等等。但无论如何,文件索引中的存在——连同冻干的准备提交内容——是使工作树中的再水化文件被跟踪的原因已跟踪文件是索引中的文件,因此未跟踪文件只是工作树中的任何文件,但不在索引中。 由于git commit 归档索引 中的内容,而不是工作树中的内容,因此只有跟踪 文件被提交。


2这里棘手的部分是,将一个新文件放入索引实际上会冻结文件并将其存储在 repository 中,如果出现以下情况,则会创建一个新的哈希 ID新内容是真正的新内容,或者如果冻干内容与任何现有文件匹配,则共享一些现有的哈希 ID。现在,所谓的新文件已被缩减为只是一个哈希 ID,它适合旧文件占用的索引中的同一插槽!


有了这些,答案现在很容易

要进行一个新的提交,在该提交中, 只存储某些文件,只需进行设置,以便您的 index 中只有这些文件。为此,删除您的索引中所有完全不需要的文件,这也将删除工作树副本:

git rm ...

由于索引通过它们的相对于工作树顶部的路径来存储它们,因此您需要将所有要保存的文件保存在某个地方。最简单的方法是在工作树索引中重命名它们:

git mv main-folder/sub1 sub1

如果需要,它将在您的工作树中创建(在这种情况下通过重命名)sub1 文件夹,然后重命名索引中的所有 跟踪 文件——记住 git mv 有使用索引和工作树——从它们的 main-folder/sub1/file1 等路径到 sub1/file1 等路径。 git mv 命令与git rm 命令一样,然后将工作树文件一起拖动。

(很方便,或者有时不会,当git mv 就地重命名文件夹时,也会重命名其中的任何 untracked 文件。因为 Git 的其余部分对 untracked 并不真正感兴趣文件,以后的git checkout 不会将它们移回!)

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

请注意,当您在具有这些 sub1/file1 类型名称的此提交之间来回切换到具有 main-folder/sub1/file1 类型名称的任何提交时,Git 可能不得不在您的工作树上努力搅拌,删除所有sub1/file1 首先命名,然后创建新的、空的 main-foldermain-folder/sub1 目录来保存(最终相同!)曾经在 sub1/file1 中的文件,依此类推。当 Git 足够聪明地意识到它可以重命名工作树中的那些文件时,Git 可能会这样做,但是 Git 通常开始使用的简单愚蠢的方法是只是删除并重新创建它们。这将显示在操作系统级别的文件时间戳中:如果 Git 删除文件并重新创建它,它将“现在”作为其在磁盘上的工作树文件时间戳。


3在提交内部——但不在索引中——Git 直接回到树形结构的命名方案。因此,如果这个新提交的顶级sub1 与其他提交的main-folder/sub1 中的sub1 100% 相同,Git 实际上将共享底层的 tree 对象sub1 新提交的根树的子树。根树当然会有所不同,因为它将sub1 命名为其子树之一,而不是将main-folder 命名为其子树之一。但所有这些都只是实现细节:它们都没有出现在索引和工作树中。

【讨论】:

    【解决方案2】:

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

    git checkout 分支1 然后将子文件夹 1 和 2 移出主文件夹 删除 git提交

    如果您对 git 的工作原理还有任何疑问,这里有很多很棒的教程 = 可能从 https://girliemac.com/blog/2017/12/26/git-purr/ GIT with kittens 开始

    【讨论】:

    • 我知道这些基础知识(虽然谢谢你的可爱教程),我正在分行结帐。我不知道如何实际移动子文件夹当你说移动时,你的意思是实际上cd 到文件夹和move?我原以为这会在 Windows 中永久移动这些文件,而不是只在 git 中,但我可能弄错了。
    • 是的,就这样。 GIT 会为您跟踪它并根据您签出的分支更改实际的 windows 文件夹。
    猜你喜欢
    • 2020-04-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-08-03
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    相关资源
    最近更新 更多