git checkout而不删除未跟踪的文件

时间:2018-11-16 12:59:12

标签: git

再现

mkdir gittest && cd gittest
git init
touch file1 file2
git add ./file1
git add ./file2
git commit -a -m "master init"
git update-index --skip-worktree ./file2
git rm --cached ./file2
git commit -a -m "file2 removed from master"
git checkout -b branch2
git add ./file2
git commit -a -m "file2 added to branch2 instead."
git checkout master
ls file2

-

ls: cannot access 'file2': No such file or directory

当我已经告诉它不要跟踪git中的file2时,为什么血腥的master正在删除file2时删除master。是一个错误吗?

2 个答案:

答案 0 :(得分:0)

您已删除master上的file2,并将其添加到branch2中。由于它是在branch2中跟踪的,并且不在master上,因此将删除该文件。这是正常现象。

答案 1 :(得分:0)

  

当我已经告诉它不要跟踪git中的file2时,为什么血腥的master正在删除file2时删除master。是一个错误吗?

它是根据设计来运行的。您可能会称该设计为bug,但我认为您认为它不是真正的设计,正如您自己的短语不要在file2 中跟踪master所揭示的那样。对于任何给定的文件,“被跟踪”的状态不是,是诸如masterbranch2之类的分支名称的功能。对于每个文件,仅跟踪或未跟踪。仅当文件现在位于索引中时,才会跟踪文件。

(由于git checkout的工作方式, 可以扩展该短语以指代特定的提交,而分支名称始终指的是特定的提交。因此, 谈论某个分支中正在跟踪的文件,但如果这样做,您将容易误导自己。)

索引是什么和做什么

要真正弄清这一切,我们首先要谈一下索引:索引到底是什么?为了获得正确的心理印象,请从所有提交都是只读的概念开始。每个提交包含与该提交一起Git存储的每个文件。这些文件将被冻结(只读),并以一种特殊的,压缩的,仅Git的形式存储。

永久存储文件虽然很好,但是我们需要某种方式使用处理文件。为此,Git提供了工作树。工作树中的文件是可读写的(一般来说,可以;您可以根据需要更改特定文件)。无论您计算机上的内容是什么,他们都有正常的日常计算机使用表格。您可以与他们一起做所需的任何事情。但是Git本身对它们的关注相对较少。

大多数版本控制系统都在这里停止:它们具有已提交的冻结文件以及可以处理的工作树文件。但是,Git在提交和工作树之间插入了这个特殊的 index 东西。对于为什么,您确实必须问Linus Torvalds,但是我们可以观察到这确实做了很多事情,包括使git commitgit checkout 真正地快< / em>与其他版本控制系统相比。但这也使您刚遇到的这个大麻烦:它提供了跟踪文件的概念。

通常和最初,索引中的内容只是提交后出来的文件的未冻结版本,但仍是压缩且仅Git格式。这意味着该文件非常适合Git冻结为 new 提交。因此,新的提交完全不需要重新压缩文件:它已经在索引中了。因此,对于大多数文件,有三个有效副本:

     commit             index             work-tree
----------------  ------------------  ------------------
frozen, Git-only  unfrozen, Git-only  unfrozen, ordinary

您可以将任何副本复制到任何其他副本,当然除了提交外,因为该副本已冻结。将复制到索引的很简单,但是几乎是不可见的,因为git checkout可以这样做,但是从复制 索引(写入索引后)到工作树 ,所以您看到的是“将文件从某些提交复制到工作树” ,却没有意识到中间有一个“要编制索引”的步骤。

从工作树复制到索引也很简单:git add就是这样做的。 git add步骤在git add期间压缩并Git化文件,以便一旦进入索引,就可以将其冻结。

也许索引所做的最大事情就是索引总是 新提交的源。 1 当您运行git commit时,Git只会冻结索引中的文件完全不看工作树,并使用这些文件进行新的提交。然后,新提交成为 当前提交,以便索引副本和提交副本匹配,就像每个文件的索引副本和提交副本紧随其后匹配一样您第一次提交的git checkout

然后,这为我们提供了索引的单行摘要:这是您建议在下一次提交中放入的文件集。如果您删除来自索引的文件,使用git rm,您建议您的 next 提交不会包含该文件。


git commit -a这样的

1 命令似乎是从工作树中提交的,它们的工作原理是先将文件添加到索引中,然后再提交。在需要时,它们会创建一个特殊的临时索引,将文件添加到临时索引,然后从临时索引提交。这使Git看起来好像是从工作树中提交的,但事实并非如此:它是从某个地方的 an 索引提交的,即使它是一个特殊的临时索引。


git checkout branch-or-commit填充索引和工作树

每次git checkout提交时,Git都必须将该提交的文件提取到索引工作树中。它需要文件进入索引,以便索引与提交匹配。它需要文件进入工作树,以便您可以看到它们。一旦所有这些都到位,git checkout将更新HEAD(Git会在此更新git checkout的位置),以便当前提交就是您刚刚检查过的提交退出,并且您在分支机构中或处于适当的“分离头”模式。

但是请注意发生了什么:

  • master从提交中填充了索引。
  • 索引的内容确定要跟踪的文件。

这意味着一组跟踪文件更改。如果您在file2上并且file2不在索引中,则git checkout branch2根本不存在(因此毫无疑问)或它在工作树中存在,并且因此无法追踪。但是一旦您branch2,在file2 顶端的提交中确实包含file2,因此git checkout master进入索引并Git覆盖工作树副本。现在跟踪文件 。如果然后file2,Git会发现file2当前已被跟踪,但是您想要到达的提交中不是,因此Git 会删除 git rm --cached来自索引工作树。

这是用git update-index --skip-worktree删除文件的可怕危险:它会将副本保留在工作树中,同时立即将其从索引中删除。但是,如果现在在索引中 中,则很有可能在其他一些提交中也是。如果您曾经检查过那些提交,则文件将返回索引;如果您随后从该提交中移走 ,该文件将从索引和工作树中的中都删除,现在它就消失了。 / p>

--assume-unchanged无济于事

此操作是在某些文件的索引条目上将两个特殊控制位之一设置的(另一个为git add)。索引条目仅在文件实际位于索引中的情况下存在:在索引中删除文件:从索引中删除文件会删除该条目,因此会删除控制位。

设置控制位后,将文件的索引副本与文件的工作树副本进行比较的各种Git命令将跳过它们的比较。这意味着Git不会建议您使用git add -a将工作树更新复制回索引,也不会建议索引中的文件,但是由于某些原因而丢失了。工作树,丢失了。

这都不会影响索引中的实际含义。文件的索引副本保持不变,并且您进行的每个新提交都将继续快照文件的索引副本。只是git status不会更新索引副本(如果您已更改工作树副本),而git clean不会抱怨索引副本已过期(如果您已更改工作树副本)。

关于目录的附注

Git从不跟踪目录。具体来说,您不能将目录添加到索引。相反,Git所做的是,如果索引中有一些跟踪的 file ,而Git希望在工作树中 创建该索引,并且该文件的名称包含当前不存在的目录存在时,Git会自行创建目录,以便将文件放入其中。

多数情况是这样,除了Git有时会从该目录中删除最后一个文件后,有时还会删除该目录。这里似乎有一些奇怪的情况(尤其是在Git的非常老的版本,1.8之前的版本),因为在没有明显理由的情况下,我让Git留下了空目录。如果需要,{{1}}命令将从工作树中删除空目录。