gpt4 book ai didi

git - Git Checkout意外删除了未跟踪的文件

转载 作者:行者123 更新时间:2023-12-04 06:01:52 25 4
gpt4 key购买 nike

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

我所做的确切步骤:


存放了4个文件:git stash
检阅了我几个月前的第一次提交:git checkout <hash of first commit>
环顾四周,不改变任何东西
回到我的工作分支,做git checkout <my working branch>
应用了隐藏:git stash apply


然后我注意到一些(不是全部)未跟踪的文件和文件夹消失了。怎么可能?

附加信息:


隐藏的文件与消失的文件无关,我注意到隐藏操作只是为了完整性
我没有执行命令git stash --include-untrackedgit stash save -u之一,如@Ashish Mathew所猜测的
似乎只有文件和文件夹在第一次提交时就消失了,而这些文件和文件夹在.gitignore中还没有出现,但后来又被添加到其中了。

最佳答案

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


确实。


似乎只有文件和文件夹在第一次提交时就消失了,而这些文件和文件夹在.gitignore中还没有出现,但后来又被添加到其中了。


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

首先,请注意,只有未跟踪的文件会被忽略

未取消跟踪(已跟踪)的文件永远不会被忽略,即使.gitignore文件说忽略它。仅忽略未跟踪的文件:已跟踪文件,未跟踪但未被忽略的文件或未跟踪并被忽略的文件。

但是,等等:未跟踪的文件到底是什么?

未跟踪的文件是不在索引中的文件

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

我对索引的最好的一行描述是:*索引是构建下一个提交的地方。*

该索引,也称为暂存区和缓存,可跟踪您的工作树,即索引。工作树是您工作的地方:文件具有其正常的非Git格式的文件。在Git信息库中,永久且只读存储在提交中的文件具有特殊的,压缩的,仅Git格式。索引位于这两个位置之间:它具有工作树中所有可提交的文件,都已设置为可提交。但是索引中的文件是可变的(不同于内部提交中的文件),即使它们已经转换为特殊的Git格式。

这意味着您的索引实际上为空的情况非常罕见。大多数情况下,它仅与您当前的提交匹配。那是因为您刚刚签出该提交,这会将这些文件放入索引(仅Git形式,准备进行下一次提交)和工作树(以常规普通文件形式,准备使用或编辑)。

如果修改文件F并运行git add F,则git 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-only形式存在于提交 <hash>中。

诀窍是使它脱离commit git show hash:path > path而不将其放回索引中,从而使其以普通的非Git格式存在。这些天最简单的方法是使用 git show,例如:

git show hash:abc.txt > abc.txt


(请注意,默认情况下 --textconv不会应用行尾翻译和污迹过滤器-在现代Git中,您应该能够使用 .gitgnore做到这一点)。

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



会话示例: README使用破坏性数据使Git正常运行

我为测试目的做了一个很小的存储库。在此存储库中,我使用 abc.txt和文件 original进行了一次初始提交,其中文件 abc.txt包含一行读取 initial的行:

$ 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


让我们将一些宝贵的数据放入(忽略的) .gitignore中:

$ echo precious > abc.txt
$ git status
On branch master
nothing to commit, working tree clean
$ cat abc.txt
precious


现在,让我们看看commit abc.txt

$ 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


糟糕,我们的宝贵数据已被破坏!

initial指令,它赋予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)


现在,如果我们要求切换到

$ 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


因此,忽略文件有一个令人讨厌的副作用:文件变得容易崩溃。我(与过去的其他人一起)一直在研究向Git教授“忽略并可能破坏”和“忽略但珍贵,不要破坏”之间的区别,并且无法简单地解决它并放弃了努力。

(我认为Git在这一点上表现得更好,但是此示例表明,至少在Git 2.14.1(这是我在这组特定测试中使用的版本)上,它仍然很糟糕。)

关于git - Git Checkout意外删除了未跟踪的文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48639081/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com