Git checkout无意中删除了未跟踪的文件

时间:2018-02-06 09:01:45

标签: git

我遇到了Git的奇怪行为: 我有一个存储库,其中包含.gitignore文件中指定的许多未跟踪文件和文件夹。

我所做的确切步骤:

  1. 藏匿4个文件:git stash
  2. 几个月前检查了我的第一次提交:git checkout <hash of first commit>
  3. 环顾四周,没有改变任何东西
  4. 回到我的工作分支git checkout <my working branch>
  5. 应用了藏匿处:git stash apply
  6. 然后我注意到一些(不是全部)我未跟踪的文件和文件夹已经消失。怎么会这样?

    其他信息:

    • 隐藏文件与消失的文件无关,我注意到存储操作只是为了完整性

    • 我没有执行其中一个命令git stash --include-untrackedgit stash save -u,因为@Ashish Mathew猜到了

    • 似乎只有第一次提交时尚未加入.gitignore的文件和文件已消失,但后来又被添加到文件中

1 个答案:

答案 0 :(得分:5)

  

藏匿的文件与消失的文件无关......

确实

  

似乎只有第一次提交时尚未出现在.gitignore中的文件和文件已消失,但后来又被添加到文件中

这再加上一件事(几乎可以肯定)是问题的根源。幸运的是,您应该能够获取这些文件,或者至少某些版本这些文件。不幸的是,你必须把它们全部拼出来并和Git一起大惊小怪,你可能会得到错误的版本。请参阅底部的示例会话。

首先,请注意仅忽略未跟踪的文件

即使.gitignore文件要忽略它,也不会忽略未跟踪(被跟踪)的文件。仅忽略未跟踪的文件:跟踪文件,未跟踪但未被忽略,或未跟踪和忽略文件。

但是等一下:确切地说,未跟踪的文件是什么?

未跟踪文件是不在索引

中的文件

这个定义是Git中为数不多的简单明了的定义之一。或者,更确切地说,如果很清楚索引是什么。不幸的是,索引很难

我对索引的最佳一行描述是:*索引是您构建 next 提交的地方。*

此索引(也称为临时区域缓存)会跟踪 - 即索引 - 您的工作树。您的工作树是您工作的地方:它使您的文件采用正常的非Git格式。在Git存储库内的提交中永久存储且只读的文件具有特殊的,压缩的,仅Git格式。索引&#34;位于&#34;之间。这两个地方:它包含所有可提交的文件,来自您的工作树,都设置为提交。但索引中的文件是可更改(与提交内部不同),即使它们已经转换为特殊的Git格式。

这意味着您的索引实际上为空非常罕见。大多数情况下,它只匹配您当前的提交。那是因为你刚刚检查了那个提交,它将这些文件放入你的索引(只有Git形式,准备下次提交)和你的工作树(以常规普通文件形式,准备使用或编辑)。

如果修改文件F并运行git add Fgit add 将替换索引中的文件副本(格式为Git)之前。该索引并非为空 - 其中包含F,以及其他所有内容 - 它只是匹配当前提交,因此大多数Git命令都在在您在工作树中更改F之前,请提及F

所以,我们考虑一下:

  

几个月前检查了我的第一次提交:git checkout <hash of first commit>

这告诉Git:从第一次提交中填充索引和工作树。假设我们还没有实际运行此命令,只考虑:这将做什么?该提交中的内容是什么?

好吧,当你创建它时,该提交包含索引中的任何内容 - 无论你使用git add复制到索引中。这包括,例如,文件abc.txt,您后来决定未跟踪

要进行无人跟踪,您必须在某个时刻从索引中删除 abc.txt,可能是:

git rm --cached abc.txt

(将工作树副本留在原位,同时删除索引副本)。在git rm --cached之后,您执行了git commit。从您运行git rm --cached开始,到目前为止,索引中的文件不是。它在工作树中。所以未跟踪

从该提交中检出索引中的任何提交填充

现在你已经告诉Git检查你的第一次提交了,但是......好吧,第一次提交中有abc.txt。 Git需要将已提交的abc.txt版本复制到索引到工作树中。

此时,如果工作树中已经存在abc.txt,Git将检查您是否要使用其他abc.txt来破坏它。大多数情况下,Git会拒绝这样做,告诉你先把它移开。但是,如果工作树中的abc.txt与提交中的abc.txt匹配,那么使用提交中的abc.txt填充索引是安全的。毕竟,它与工作树中的那个匹配。

所以在这一点上,Git将该提交中的所有文件提取到索引和工作树中。 (对于这个一般性的想法有一些复杂的但是试图安全的例外:见Checkout another branch when there are uncommitted changes on the current branch。)而且,嘿嘿,现在git checkout <my working branch> 指数。现在它被追踪了!

所以现在你环顾四周,看看你的旧提交,并决定:

  

abc.txt

现在Git必须将索引和工作树内容从第一次提交(其中包含<my working branch>)切换到abc.txt的提示提交。该提交其中包含git checkout <hash>。 Git将从索引中删除文件...并将其从工作树中删除,因为它已跟踪

结帐完成后,现在索引中的 文件。嗯,它也不在工作树中( argh )。如果你把它放回工作树,现在它没有跟踪。但你在哪里可以得到它?

答案是盯着我们:它在第一次提交中。当您运行git checkout <my working branch>时,Git将文件复制到索引和工作中 - 树(除了它毕竟不必触及工作树版本)。当你运行<hash>以取回时,Git 删除了文件,但是提交是只读的,并且(大部分)是永久性的,所以文件仍然存在,只有Git形式,提交<hash>

诀窍是让它 out 提交git show hash:path > path 而不用将它放回索引中,这样它就会在正常的非Git中保持不变格式。这些天最简单的方法是使用git show hash:abc.txt > abc.txt ,例如:

git show

(请注意,默认情况下--textconv不会应用行尾转换和污迹过滤器 - 在现代Git中,您应该能够使用.gitgnore)来实现此目的。

你必须为Git删除的每个文件执行此操作,这可能会非常痛苦。

示例会话:README使Git OK使用clobbering数据

我为测试目的制作了一个小存储库。在此存储库中,我使用abc.txt和文件original进行了初始提交,其中包含一行$ mkdir tt $ cd tt $ git init Initialized empty Git repository in ... $ echo original > abc.txt $ echo for testing overwrite > README $ git add README abc.txt $ git commit -m initial [master (root-commit) a721a23] initial 2 files changed, 2 insertions(+) create mode 100644 README create mode 100644 abc.txt $ git tag initial $ git rm abc.txt rm 'abc.txt' $ git commit -m 'remove abc' [master 20ba026] remove abc 1 file changed, 1 deletion(-) delete mode 100644 abc.txt $ touch unrelated.txt $ echo abc.txt > .gitignore $ git add .gitignore unrelated.txt $ git commit -m 'add unrelated file and ignore rule' [master 067ea61] add unrelated file and ignore rule 2 files changed, 1 insertion(+) create mode 100644 .gitignore create mode 100644 unrelated.txt

$ git log --oneline --decorate
067ea61 add unrelated file and ignore rule
20ba026 remove abc
a721a23 (tag: initial) initial

我们现在有一个包含三个提交的存储库:

abc.txt

让我们将一些宝贵的数据放入(忽略)$ echo precious > abc.txt $ git status On branch master nothing to commit, working tree clean $ cat abc.txt precious

initial

现在让我们查看提交$ git checkout initial Note: checking out 'initial'. You are in 'detached HEAD' state. [mass snip] HEAD is now at a721a23... initial $ cat abc.txt original

.gitignore
哎呀,我们宝贵的数据已被破坏了!

abc.txt指令赋予Git删除文件的权限。为了证明这一点,让我们$ cp /dev/null .gitignore $ git add .gitignore $ git commit -m 'do not ignore precious abc.txt' [master 564c4fd] do not ignore precious abc.txt Date: Thu Feb 8 14:16:08 2018 -0800 1 file changed, 1 deletion(-) $ git log --oneline --decorate 564c4fd (HEAD -> master) do not ignore precious abc.txt 067ea61 add unrelated file and ignore rule 20ba026 remove abc a721a23 (tag: initial) initial $ echo precious > abc.txt $ git status On branch master Untracked files: (use "git add <file>..." to include in what will be committed) abc.txt nothing added to commit but untracked files present (use "git add" to track) 不被忽视(但也不被跟踪):

initial

现在,如果我们要求切换到$ git checkout initial error: The following untracked working tree files would be overwritten by checkout: abc.txt Please move or remove them before you switch branches. Aborting

{{1}}

因此忽略文件会产生令人讨厌的副作用:它们变得更加惹人喜爱。我(我认为,与过去的其他人一起)已经研究过Git的教学区别之间的区别是否被忽视,并且可能会破坏&#34;并且&#34;忽略但是珍贵,不要破坏&#34;并且无法简单地解决它并放弃了努力。

(我认为Git在某一方面对此有了更好的表现,但是这个例子表明它至少在Git 2.14.1中仍然很糟糕,这是我在这组测试中使用的版本。)