首页 > 解决方案 > Git 在一个遥控器中取消跟踪文件,但将其保存在另一个遥控器中

问题描述

我有一个名为的存储库xyz,它有两个遥控器AB. A归我所有,但我没有推送访问权限B(因此我无法更改 remote 中的任何内容B)。B有一堆我不想要的文件,所以我在我的遥控器上取消跟踪它们A。现在,当我从中拉出时B,git 给了我这个警告:

The following untracked working tree files would be overwritten by merge

我知道我可以在本地删除它们,但我想将其中一些文件保留在本地。

有没有可以做到这一点的命令/方法?

标签: git

解决方案


简短的回答是否定的,但事实上,问题本身并没有意义:

B有一堆我不想要的文件,所以我在遥控器上取消了它们的跟踪A...

远程是一个短名称,例如origin,存在于某个 Git 存储库中,用于保存某个其他Git 存储库的 URL。只是一个名称,遥控器实际上根本不保存任何文件。大概您的意思是在这里使用“远程”一词来代表特定的 Git 存储库。

不幸的是,即使我们进行了这些调整,我们仍然会留下一些与 Git 的工作方式不太匹配的东西。我们得到一个形式为我有存储库 R1 和 R2 的声明。在 R1 中,有一些跟踪的文件,而在 R2 中,我将它们设为不跟踪。 这可以用来表示某些东西,但这不是我认为您的意思。1 这里的问题是短语untracked file的定义,它实际上不是存储库的属性。要了解我的意思,我们必须深入了解存储库是什么和做什么,以及提交是如何工作的,以及在 Git 中是如何构造和解构的。


1强制性参考


Git 存储库是提交的集合,以及一些附带项目

那些 Git 新手和 Git 的普通用户倾向于认为 Git 是关于文件的,但事实并非如此。这实际上都是关于commits的。大致而言,Git 存储库只是一个内部 Git 对象的大型数据库,我们使用和关心的对象是提交。

  • 数据库中的每个提交都有编号。这些数字不是简单的计数数字——它们不是提交 #1,然后是 #2,然后是 #3,等等——而是大的、丑陋的、看起来随机的哈希 ID。每个提交都有自己唯一的哈希 ID,并且宇宙中的每个 Git 都同意提交获得哈希 ID。(这是真正神奇的部分,也是 Git 如何作为分布式版本控制系统——DVCS——而不仅仅是一个普通的 VCS。)

  • 提交是 100% 只读的。就此而言,所有内部 Git 对象都共享此功能,因为它们的哈希 ID 是其内容的加密校验和。数据库是一个简单的键值存储,其中的值必须散列回它们的键;这就是 Git 通过检查用于查找数据的校验和是否与数据的校验和匹配来提供数据完整性的方式。

  • 提交存储两件事:

    • 提交中的主要数据是 Git 知道的每个文件的完整快照,您或任何人何时进行了该提交。(从技术上讲,这是提交元数据中的一个对象。)

    • 除了主要数据之外,每个提交还存储一些元数据,包括提交人的姓名和电子邮件地址、一些日期和时间戳等。对于 Git 至关重要(但在此答案中未探索),每个提交都存储一些先前提交的哈希 ID,这意味着 Git 可以使用任何提交来查找更早的提交。

因为提交中的所有内容都是只读的,所以提交中的文件也是只读的。而且,因为每次提交都会存储每个文件,Git 会以去重格式保存文件(这还涉及压缩和散列文件的内容,使用更多的 Git 内部对象)。这样,无论您进行多少次提交,如果他们一遍又一遍地重复使用相同的文件,存储库几乎不会变得更大:唯一需要的空间是提交本身,而不是它的文件,它们都是重复的。

但是因为提交中的文件是以这种只读的、仅限 Git 的内部格式存储的,所以它们实际上对于完成任何工作都毫无用处。这就是我们了解 Git 所谓的存储库与我们工作的存储库类型之间的区别的地方。(没有专门的术语,它只是一个非裸存储库。)

你的工作树,或者 Git 如何使提交变得有用

为了使提交有用——以便我们可以看到文件并处理它们——Git 必须提取一些提交。提交包含一堆文件,以某种仅 Git 的形式;我们需要它们以普通的日常形式出现。因此 Git 将它们提交中复制到工作区中。Git 将此工作区称为您的工作树工作树

从一个非常重要的意义上说,您的工作树实际上并不存储库中。它更像是就在存储库旁边:相邻,就在存储库旁边。如果您查看工作树中的所有文件,包括隐藏文件,您会发现一个.git目录2。适当的存储库在这里。这是 Git 将文件以压缩的内部格式存储为目录中的blob 对象的位置.git/objects。它们在这里没有正常的文件名。

这里的重点是该.git目录是您的工作树的子目录,而不是相反。因此,您的文件是的,可以在您的工作树中随意处理。您必须小心不要损坏子目录中的存储库文件.git:这些是 Git 的,按照 Git 的方式处理。当您要求 Git 这样做时, Git 有时只是将文件存储库中复制到您的工作树中。在此过程中,它扩展了压缩和 Git 化的内部对象,将它们转换回普通文件。


2如果您更喜欢术语文件夹,那很好,并注意一些工作树——特别是那些通过添加的工作树git worktree add——有一个.git 文件而不是.git目录/文件夹。Git 对目录内部发生的事情做出的承诺相对较少.git,包括它是否甚至是一个目录。这是与非常早期版本的 Git 的一个变化,早期版本非常随意地让您进入.git目录并更改内容。


Git 的索引,或进行新的提交

鉴于我们现在意识到 Git——像大多数 VCS 一样——已提交的文件和有用的文件是分开的,并且你处理有用的文件——你可能期望该git commit命令扫描你的工作树文件并使用它们来制作新的提交。不过,这不是 Git 的工作方式。(其他 VCS会这样做,这使它们更易于使用。)

相反,Git 始终保留每个文件的第三个“副本”,在 Git 所谓的indexstaging area中,或者(现在很少见)缓存中。“复制”这个词在这里用引号引起来,因为该文件的“复制”是 Git 的内部冻结格式。它已经被重复数据删除并准备好进行新的提交。不过,它实际上并不提交中,因此可以将其替换为.

当您在工作树中编辑文件时,您需要告诉 Git 将更新后的文件复制回 Git 的准备提交区域:索引/暂存区域。该git add命令执行此操作。准确地说,告诉Git应该使. 如果您已经自己删除了工作树副本,这包括删除文件的索引副本。git add filefilefile

请注意,这git rm将从两个位置删除文件:Git 的索引您的工作树。这通常比必须先从工作树中删除文件,然后使用git add(奇怪地)从 Git 的索引中删除副本更方便。但这就是一切。该git add命令将工作树文件复制到索引中,如果该文件之前不在索引中,那么现在它在索引中。或者,如果文件消失了,它会从 Git 的索引中删除该文件。无论如何,索引副本现在与工作树副本匹配,并且git add已经完成了它的工作。

这意味着,对于第一个近似值,无论如何,3索引代表您计划进行的下一次提交。在你真正进行下一次提交之前,你的工作是更新 Git 的索引。为此,您通常会更新工作树中的一些文件然后使用git add将更新的文件复制回 Git 的索引。现在您可以运行了git commit

当您运行时git commit,Git 会收集进行新提交所需的任何元数据。然后它将索引的内容变成一个冻结的快照,作为新提交的数据存储;它添加了其余的元数据,包括现在(到秒)的时间作为日期和时间戳;它写出提交。这会生成新提交的加密哈希 ID,它保证是唯一的。4

新的提交现在包含 Git 知道的所有文件。当你运行git commit. 这就是文件的来源。


3索引在冲突合并期间扮演扩展角色,并具有一堆较小的角色,我们稍后会看到其中一个角色。

4咳嗽鸽巢原理?什么鸽笼原理


未跟踪的文件

请注意,在所有这些与工作树(你可以随意使用)和索引(Git's)的大惊小怪中,没有什么说这两者中的文件必须匹配。索引中的文件在下一次提交中,以您运行时它们在索引中的任何形式。但是有些文件可以在你的工作树中,但不在 Git 的索引中。git commit

这就是未跟踪的文件。 它只是现在在你的工作树中的一个文件,但它现在不在 Git 的索引

您可以更改工作树中的文件。任何普通的计算机命令都可以做到这一点!但是您也可以更改哪些文件在 Git 的索引中:git rm --cached可以取出一个或多个,git add可以放入一个或多个取出一个或多个(甚至同时做这两个),方法是制作索引副本或副本匹配工作树。

git checkout填充 Git 的索引并写入您的工作树

当你git checkout用来选择一些特定的commit时,你是在告诉 Git 它应该:

  • 该提交中填充其索引(以便您准备好进行新的提交),并且
  • 更改工作树中的文件以匹配该提交中的文件。

当你的工作树中有一些未跟踪的文件时,该文件不在 Git 的索引中,所以 Git可能不需要对它做任何事情。但是,如果该文件您要切换到的提交中怎么办?现在 Git必须按照git checkout. 然后它将不得不破坏工作树中的(当前未跟踪的)文件,以使其与从提交中获得的索引副本匹配。

这就是为什么简短的回答是“不”

每个文件的可跟踪性或未跟踪性是您可以控制的,但它也是动态的。它不是存储库的属性。它是 Git 索引的属性,Git将从 commit 中填充。某些提交中的文件将永远存在于该提交中,因为任何提交的任何部分都无法更改。

如果您从 Git 的索引中取出一个文件并进行新的提交,那么新的提交将缺少该文件。这就是全部。如果您将文件放入 Git 的索引并进行新的提交,新的提交将拥有该文件,这也是所有内容。当您使用包含该文件的现有提交时,Git 将需要填写其索引。

Sparse checkout 提供了一种解决方法,但并不容易使用

Git 有一个叫做稀疏检出的特性。这是通过利用 Git 的索引确实与您的工作树分开的事实来实现的。当 Git 检查某个提交时——例如,无论是通过找到哈希 ID 的分支名称,还是直接通过原始哈希 ID——Git都会填写其索引。但是如果我们没有让 Git 将所有文件从它的索引复制到我们自己的工作树呢?这就是稀疏结帐可以做的事情。

我还没有调查过稀疏检出是否与与索引副本同名的未跟踪文件“运行良好”,但是因为 Git 在索引的缓存条目中使用了“跳过工作树”位,所以这应该可以工作。这只是理论,但根据您的情况,您也许可以使用这种工作模式。值得注意的是,如果当你这样做时,你所做的新提交将在其中包含文件(因为当你进行这些新提交时它将在 Git 的索引中),即使该文件根本不在你的工作树中,或者在您的工作树中有完全不同的内容。

(不幸的是,稀疏结帐不容易设置,并且可能有粗糙的边缘。Git 现在正在进行工作以使其更好,尽管这不是预期的用例。)


推荐阅读