如何恢复从远程本地存储库中删除的文件,以及将来如何防止这种丢失?

时间:2018-10-10 15:39:03

标签: git github

最近,我意外地将我所有的项目文件都推送到了github仓库中,而没有创建.gitignore文件,然后在事实发生后将其添加到github中,并删除了在最初推送时会被忽略的文件(如果有.gitignore文件)存在。完成此操作后,我将存储库拉到本地git,以为我只会得到.gitignore文件,但是所有对开发很重要的文件(.project,.classpath,*。jar等)都将被忽略。我在日食中被删除了。

如何恢复这些丢失的文件,以及将来如何在不删除文件的情况下添加.gitignore文件。

感谢您的帮助。

2 个答案:

答案 0 :(得分:0)

尝试删除.gitignore文件,将其推送到远程存储库,然后键入

git pull

答案 1 :(得分:0)

我从评论中看到您已经恢复了文件,这很好,这意味着我不必为此编写特定的说明(尽管我将在其中进行介绍)。但是:了解.gitignore的作用很重要。它实际上并不会导致文件被忽略! (这意味着.gitignore并不是很好的名字,但是 good 的名字确实很长。可能应该将其命名为.gitxyz.gitconfuse或简短而令人难忘,但您不认为这意味着“忽略”。)

那么, .gitignore到底是做什么的?好吧,这需要快速深入到Git的其他部分,许多教程或介绍对此都做了掩盖,这引起了很多混乱,这就是Git的 index

背景

首先,让我们注意Git主要要做的是存储 commits 。提交可保存您所有文件的完整快照,包括该提交所包含的所有文件,但这有点多余:“提交具有提交所具有的内容。”这里的重要部分是每个文件都有一个完整副本,这些文件被冻结且不可变,可以永久保存(或至少在提交本身继续存在的情况下保存)。

(提交还包含一些元数据,例如一些信息,例如您的姓名和作为作者/提交人的电子邮件地址,以及您的日志消息。最重要的是,每个提交还都包含上一个的真实名称(哈希ID)或 parent 提交。但我们不在这里介绍。)

这些冻结的文件在每次提交时永久且不变地保存,如果不压缩它们将占用大量空间,因此它们采用特殊的,压缩的,仅Git的形式。实际上,由于 已冻结,因此如果两个不同的提交对文件使用相同的数据,则这两个提交实际上会 share 冻结的副本。这意味着Git始终将文件的相同副本放入每个新提交中,这一事实根本不占用空间,因为它实际上只是在重用旧文件。但是无论如何,这些文件对任何 Git都没有用:没有其他系统可以直接读取它们,甚至Git都没有写东西:它们被冻结了。

因此,要让您查看和处理文件,Git必须将保存在某些提交中的所有文件解冻到某种工作区中。 Git将此区域称为工作树工作树。这是一个好名字,因为它是您工作的地方。

其他非Git版本控制系统通常在此处停止:它们具有已提交的文件(可能存储为增量而不是完整文件),工作树,仅此而已。当您使用这些系统之一并进行 new 提交时,这会花费时间,有时会花费很多时间。有时,您可以在等待时出去吃午餐。不过,使用Git,您可以运行git commit -m message并- zip -完成。

Git从其 index 获得所有这些速度。但是 index 是这个名称的可怕名称,因此Git也将其称为临时区域,有时甚至称为缓存,具体取决于谁/什么正在打电话。索引的作用是保留(以一种特殊的仅Git形式,但这次不冻结),所有要在 next 提交中的文件。

最初,索引将从您签出的所有提交中填充。也就是说,git checkout <some-commit-specifier>找到包含完整冻结文件集的提交。 Git将冻结的文件(以及指向其内容的链接)复制到索引中,一路找出文件的全名,以便索引包含Git需要放入工作树中的所有文件的列表。 。这些现在采用特殊的仅Git格式,但是未冻结。然后,Git还将文件放入工作树,将其扩展为有用的格式。

最终结果是索引与提交匹配,但是索引未冻结。工作树同时匹配提交和索引 ,当然它们是未冻结的,文件具有其有用的形式。您现在可以照常进行工作了,这解释了为什么为什么您必须始终git add进行文件操作!

git add的作用是将工作树文件复制到索引中。如果文件已在索引中,则这将覆盖先前的副本。现在,新副本为仅Git格式(但仍未冻结)。如果文件以前不在索引中,则它现在在索引中。无论哪种情况,索引仍然可以使用。 git commit除了收集诸如您的姓名,电子邮件和日志消息之类的元数据外,所有要做的事情都是冻结索引。

因此,对于索引,我知道的最好的简短描述是:索引包含您建议的 next 提交。其中包含所有文件。特殊的仅Git形式,但尚未冻结。这就是为什么它也称为 staging区域:的原因,它具有所有文件,可以进行暂存并准备就绪。

分段,未分段,未跟踪,被忽略

现在您知道每个文件都有三个副本,您可以担心,所有这些都将变得有意义。例如,让我们考虑一个README.txt文件。您运行git checkout master开始,Git找到master的提交并检出,使该提交成为当前或HEAD提交:

  • HEAD:README.txt被冻结在当前提交中。它永远不会改变-这是该提交的一部分。

  • :README.txt被复制到索引中,并在此过程中取消冻结。可以更改,但当前与HEAD:README.txt匹配。

  • :README.txt从索引复制到工作树,扩展为有用的形式。可以更改,但当前与:README.txt匹配。

所有三个副本都匹配,因此Git对该文件一言不发。

如果现在更改工作树副本并运行git status,则Git的status命令将HEAD和索引副本进行比较。它们是相同的,因此对此一无所知。它比较索引和工作树副本,它们是不同的,因此git status说文件未上演提交

一旦运行git add README.txt,它将复制(并压缩)工作树版本到:README.txt中。现在,这两个匹配,但是HEAD:README.txt:README.txt不同。因此,git statusHEAD与索引进行比较,并说文件已被暂存以提交

请注意,您可以再次更改工作树副本。现在,文件在所有三个版本中都不同,并且git status告诉您,文件都是为提交而准备的(HEAD和索引不匹配),也不是为提交而准备的(索引和工作-树也不匹配)。这全部基于两个git diff的结果:一个从HEAD到索引,一个从索引到工作树。

但是,如果您有一个HEAD提交中的文件,您从索引和工作树中删除了,该怎么办?好吧,现在比较HEAD与索引时,已将其删除。因此,Git表示已执行删除操作。索引和工作树匹配,因此Git对此一无所知。无论如何,您的下一次提交不会拥有文件。

如果您的文件在工作树中但不在索引中,该怎么办?如果它在HEAD提交中,则它仍是分阶段删除:文件不会在下一个提交中。但这在索引和工作树中也有所不同,因此它是未跟踪的

如果文件不在HEAD中,并且不在索引中,但是在工作树中,则说明该文件为未跟踪

这告诉我们拥有一个未跟踪文件的含义:当且仅当该文件当前不在索引 中时,该文件才被跟踪。由于您可以操纵索引(随时随地添加或删除文件),因此可以随时将其添加到索引中或从索引中删除,从而随时更改某些文件的跟踪性。索引。

如果文件不在索引中并且不在工作树中,则该文件不存在。只是 在工作树中,而不是在索引中的文件是未跟踪的。您可以git add进行文件操作,然后Git对您发牢骚,,不休。在.gitignore(或.git/info/exclude)中列出文件主要是关闭Git up 。它实际上不会导致文件被取消跟踪,这与文件是否在索引中有关。文件进入索引后,便会对其进行跟踪,并且.gitignore无效。它只是防止git status困扰您。因此,也许应该是.gitignore而不是.git-dont-complain-about-these-files-if-they-are-untracked

它还有另一个重要作用。您可以运行git add .git add somedirgit add --all来基于Git搜索目录/文件夹中的整个文件列表来添加一堆文件。如果您将某些文件列出为忽略文件,则git add将跳过它们(如果尚未跟踪)。也就是说,跟踪的文件肯定在Git中,因此git add会在更改后将其复制到索引中。但是未跟踪的文件还不在Git中,因此如果不忽略该文件,则会进行en-masse添加。在这里,“忽略”是正确的词。因此,也许该文件应命名为.git-dont-complain-about-these-files-if-they-are-untracked-and-dont-auto-add-them-with-an-en-masse-add-operation

不幸的是,在.gitignore中列出文件还有另一个副作用,那就是告诉Git在某些情况下删除或破坏文件是可以的。因此,.gitignore的全名可能为.git-dont-complain-about-these-files-if-they-are-untracked-and-dont-auto-add-them-with-an-en-masse-add-operation-but-do-feel-free-to-clobber-these-files-sometimes。想象一下,如果那是文件名!至少不会那么混乱。

使用git rm --cached删除文件的缺点

如上所述,如果要使某些文件不被跟踪,则必须将其从索引中删除。如果您使用git rm --cached filename,Git将从索引中删除该文件(因此现在未对其进行跟踪),但是 not 从工作树中删除该文件(因此仍然可以在其中使用该文件)它)。您的下一个提交不会拥有您想要的文件。

但是所有 old 提交,永久冻结的时间提交, do 都有文件...所有那些旧提交仍然存在。如果您检出了其中一项提交,Git将不得不将冻结的文件复制到索引中,然后将索引副本复制到工作树中。那会破坏您的工作树版本。那样行吗? Git的答案是检查.gitignore中的文件!

如果.gitignore中未列出文件 ,Git将无法随意对其进行破坏。但是您会抱怨它没有被追踪。为解决这些投诉,您可能会在.gitignore中列出文件。然后,签出旧提交将使用提取的,未冻结的,未压缩的文件破坏您的文件,然后回到新的提交删除文件,因为它们现在与冻结的文件相同一个,所以它是“安全的”。

Git需要(但没有)一种方法来列出工作树文件,因为对此进行了关闭,但从不破坏。如果以及何时添加,将使您处理自己的情况。但是,如果可能的话,最好不要完全陷入困境。

恢复您的力量

同时,如果您丢失文件,请记住至少在旧的冻结提交中有一些版本。您可以提取那些旧的冻结版本。有两种方法可以做到这一点:

  1. git show:运行git show commit:path,例如git show v1.0:README.txtgit show a123456:path/to/file.ext。这样会将冻结的保存文件扩展为标准输出,因此您可以使用I / O重定向保存它,例如git show v1.0:README.txt > README.txt.old

  2. git checkout具有一种模式,在该模式下,它不检出整个提交,而是从一些现有提交中填充索引和工作树的 part 。 (此命令可能永远不应该被称为git checkout,因为如果您尚未保存更改,它会具有破坏性,请谨慎操作。)运行git checkout v1.0 -- README.txtgit checkout a123456 -- path/to/file.ext将从中提取命名文件。命名的提交,将其复制(解冻)到索引中-因此现在您的下一个提交将具有那个文件的那个版本-然后进入您的工作树。 / p>

    如果您要恢复文件的整个目录,或者使用*.jar之类的全局模式,则此功能更有用,因为您可以git checkout的目录或模式:

    git checkout HEAD~2 -- '*.jar'
    

    此处HEAD~2是要使用的提交(沿第一父级链从当前提交向后退了两步),还有*.jar,如果有任何引用,则需要引用以保护它不受shell攻击当前目录中的*.jar个文件是Git应该匹配的 pathspec 。 (我认为这应该等效于**/*.jar,但如果不是,那也是有效的pathspec。)由于这会填充您的索引,因此您之后必须撤消该索引,例如再次git rm --cached或{ {1}}(它也需要路径说明,因此可以git reset)。

这些冻结的文件是否足以满足您的当前情况,当然取决于情况。