首页 > 解决方案 > Git - 推送一个目录然后忽略它

问题描述

我有一个文件夹,其中包含我在 PhpStorm 上执行的所有 http 请求文件。我首先将此文件夹添加到我的 gitignore 文件中:

/app/http/

然后我强制将它推送到我的远程存储库:

git add -f app/http
git commit -m 'http folder added'
git push

但在那之后,我的文件夹并没有被忽略。推送后如何忽略它?

标签: gitphpstormpush

解决方案


TL;博士

你得不到你想要的。不要那样做。如果可以,请考虑改为存储一个点文件,例如.gitignorelist *。如果没有,请考虑让一个不是 Git 的程序在需要时自动创建空文件夹。

您开始时有许多不正确的假设:

  • 您认为您添加了一个文件夹,但 Git 提交只存储文件,而不是文件夹。我们马上就会明白这意味着什么,以及为什么 Git 会这样。
  • 您认为您推送了文件夹和/或文件。Git 不推送文件。Git 推送提交
  • 您认为在其中列出某些.gitignore内容会使 Git 忽略它。事实并非如此。

这些都导致了你遇到的问题。

你需要从这个开始:Git 的基本存储单元——无论如何,你将与之交互的那个单元——是commit。Git 是关于提交的,因此您需要详细了解提交的确切含义和作用。

提交

Git 提交由两部分组成:数据和元数据:

  • 每个提交中的数据都包含一个完整的文件快照。这里没有文件夹,只有名称可能包含嵌入斜杠的文件,例如path/to/file.ext.

  • 每个提交中的元数据包含您的姓名和电子邮件地址、提交时间的日期和时间戳以及解释提交原因的日志消息等内容。包含在这个元数据中——它是供使用的,而不是供 Git 使用的——是一些真正适用于 Git 的元数据提交哈希 ID列表。通常这个列表中只有一个提交哈希 ID。

每个提交都有一个唯一的哈希 ID。实际上,此哈希 ID 是提交的真实名称。这就是 Git 检索数据(您的文件)和元数据的方式。所以你需要使用提交哈希,以便 Git 可以找到提交。但是这里有一个问题:提交哈希 ID 又大又丑,人类无法使用。

分行名称

这个问题的解决办法很明显:我们有一台电脑;我们可以让计算机为我们存储哈希 ID,使用人类可以处理的简单名称(例如masterdevelop)来记住正确的哈希 ID。这就是分支名称:一个分支名称包含一个(且只有一个)提交的哈希 ID。根据定义,我们让 Git 在分支名称中记住的提交是分支中最新最新的提交。Git 将此称为提示提交

如果我们说,Git 将使用存储在名称中的哈希 ID 来git checkout master检查提示提交。如果我们向分支添加提交,Git 将写出新提交,其(单个)哈希 ID 设置为旧分支提示。新提交获得一个新的唯一哈希 ID,Git 将其写入分支名称,现在提交是分支提示。mastermaster

之前的提交仍然存在,Git 可以自己找到它:Git 使用分支名称查找最后一次提交的哈希 ID,然后读取该提交并使用其元数据查找父级的哈希 ID。然后 Git 使用该哈希 ID 来读取父提交。如果合适,Git 会使用提交的存储父级再返回一步。

绘制你的提交和分支

从视觉上看,如果我们画这个,我们会得到:

... <-F <-G <-H   <--branch

wherebranch是分支名称,它存储了一些我们将在H这里调用的哈希 ID。这允许 Git找到 H. H它本身将我们将调用的提交的 ID 作为其父哈希 ID 存储G。所以H 指向 G,允许 GitG从其数据库中检索。 G反过来指向较早的 commit F,依此类推。整个过程有效,因为分支名称指向最后一次提交,从那里,Git 可以向后工作。

你的工作树

任何现有提交中的任何内容都无法更改!一旦提交,每次提交都会被冻结。这包括存储在该提交中的所有文件。因为提交是冻结的——只读的——它们非常适合归档。但是提交中的文件以特殊的、冻结的、压缩的、只读的和仅 Git 的格式存储,只有 Git 可以读取。这些文件实际上是无法更改的,就像任何 Git 提交都无法更改一样。这使得这些文件对于完成任何工作完全无用。

这个问题的解决方案是让 Git从一个提交中提取冻结的提交文件到一个工作区,在那里文件被转换回您的计算机可以使用的普通文件。这就是git checkout——或者在 Git 2.23 及更高版本中——所做的git switch:你告诉它你想使用哪个分支,Git 使用分支名称来找到正确的哈希 ID,然后 Git 从那个提交中提取所有冻结的文件到你的工作树中工作树,您可以在其中看到它们并与它们一起工作。1


1这掩盖了很多关于 的细节git checkout,所以它只是一个概述,而不是一个精确的定义。


指数

所以,正如你从这张图片中看到的那样,当你处理提交时,实际上有两组文件处于活动状态:提交的副本,它们一直被冻结(并且根本不可见),以及您可以查看和处理的工作副本。一些版本控制系统到此为止,每个文件只有两个副本。然而,Git 没有。

Git 有不同的名称,例如indexstaging area,或者有时(现在很少)cache。它有两个(或三个)名称的原因可能是多种因素的组合:索引本身很复杂,但您大多数时候使用它的方式相对简单。暂存区一词是指您使用它的方式。我喜欢称它为索引,因为它在合并过程中扮演着扩展的角色,你最终需要知道这一点。

无论如何,考虑索引的一个好方法是假装它拥有每个文件的第三个副本。2 这个额外的索引副本是冻结格式,但实际上并未冻结:您可以用新副本覆盖它。索引中每个文件的副本是git commit在进行新提交时将使用的副本。这意味着您可以将索引视为将进入下一次提交的文件,或者更短,索引是您建议的下一次提交

因此,实际上git checkoutgit switch实际上是将文件从您选择的提交复制到索引工作树。索引副本现在准备好进行新的提交,工作树副本是一个普通文件,存储在一个文件夹中,因为您的操作系统需要它。 Git在提交和索引中的文件副本根本不存储在文件夹中。它们采用特殊的、只读的、仅限 Git 的格式。3

做什么git add——这就是在修改工作树中的文件后必须继续使用它的原因——是将文件的工作树副本复制到索引中。也就是说,它获取您更新的工作树文件,重新压缩它并将其转换为 Git 的内部冻结格式,并用新副本替换旧的索引副本。如果您git add的文件根本不在索引中,则会将文件添加到索引中,但如果您是索引中git add的文件,只会替换冻结格式的副本。

索引不能包含文件夹名称。索引中的文件只有长名称,例如path/to/file.ext. 因此,当 Git从索引构建提交时,新提交中的唯一内容是文件。4 这就是阻止您存储文件夹的原因。

当 Git提取提交时,如果该提交中的文件被命名path/to/file.ext并且您的工作树没有path文件夹,或者该path文件夹缺少to文件夹,Git 将根据需要创建 path和创建path/to。因此,Git 没有存储path并且path/to一开始并不重要,除了一种方式:不可能在 Git 提交中存储一个空文件夹(空目录)。5


2从技术上讲,索引保存的不是文件的副本,而是文件的模式名称blob 哈希 ID的副本。git ls-files --stage但是,除非您使用and进入索引的内部工作git update-index,否则将索引视为保存副本就足够了。

3从技术上讲,这些实际上是具有哈希 ID 的blob 对象,就像提交一样。blob 对象的哈希 ID 取决于文件内容,如果内容匹配,文件可以在多个不同的提交之间共享,因为每个提交实际上只是存储 blob 哈希 ID。这一切都隐藏在另一层间接之下:提交存储哈希 ID,树对象存储名称和模式和哈希 ID。但是你不需要知道任何这些来使用Git。您确实需要了解提交哈希 ID 和索引。

4从技术上讲,索引可以存储:

  • 普通文件(模式100644100755),或
  • 符号链接(模式120000),或
  • 一个gitlink(模式160000)。

所有这三个都与哈希 ID 相关联:文件或符号链接的 blob 哈希,或 gitlink 的子模块提交哈希 ID。

5有一些技巧,没有一个是完全令人满意的:请参阅如何将空目录添加到 Git 存储库? 其中唯一真正有效的是空子模块技巧


跟踪和未跟踪的文件

当您提取提交时,您将获得其每个文件的三个副本。一种是冻结副本,在当前或HEAD提交中。第二个是索引中的冻结格式但可替换的副本。第三个是您可以在工作树中实际使用的唯一副本。您可以查看和触摸工作树副本,因为它们是实际文件,在实际文件夹中,在您的计算机上,而不是以某种特殊 Git 方式存储的内部 Git 实体。

但是,因为您的工作树是的,这意味着您可以创建和删除文件和文件夹,而无需 Git 参与。Git 不会使用任何这些文件,直到您将它们复制到 Git 的索引中。使用git checkout(或git switch和/或git restore),您可以命令 Git用 Git 拥有的副本覆盖您的工作树文件,但索引副本将进入下一次提交。

那么如果你创建了一个工作树文件并且将它添加到索引中会发生什么呢?答案是:Git 称之为未跟踪文件

一个未跟踪的文件,很简单,就是现在在你的工作树中,但现在不在 Git 的索引中的文件。现在这部分至关重要,因为您不仅可以将新文件复制到 Git 的索引git add当然,使用.git rm

跑步:

git rm path/to/file

告诉 Git:从你的 index 和 my work-tree 中删除该文件的副本。现在它已经从两者中消失了,它不会出现在下一次提交中。跑步:

git rm --cached path/to/file

告诉 Git:从索引中删除该文件的副本,但不要触摸工作树副本。 现在它已经从索引中消失了,它不会出现在下一次提交中。

因此,如果您希望某个文件不在提交中,则必须将其从索引中删除。就目前而言,这很好。如果您从索引中删除它,或者一开始就没有将它放在索引中,并且该文件现在存在于您的工作树中,则该文件是未跟踪文件

正如我们将看到的,未跟踪的文件可能很烦人,但也可能非常重要。

git status

当你运行 时git status,Git 会运行两组比较。第一个比较将HEAD提交与索引进行比较。对于每个相同的文件,Git 什么也没说。对于不同的文件——或者是新的或者已经被删除的——Git 说这个文件是为提交暂存的。请注意,您不能更改HEAD提交的内容,6因此,根据定义,此处的任何更改都是您在索引中更改的内容。

运行第一个比较HEAD-vs-index 后,Git 现在运行第二个比较。它将索引中的所有文件与工作树中的文件进行比较。对于每个相同的文件,Git 什么也没说。对于不同的文件或已被删除的文件,Git 表示该文件不会暂存以进行提交

请注意,我们尚未提及新文件。对于您的工作树中但不在您的索引中的每个文件......好吧,这些正是您的未跟踪文件git status通过将它们列为“未跟踪”来处理这些问题。


6虽然您无法更改任何提交的内容,但您可以git checkout选择不同的提交作为HEAD提交。或者,当然,您可以运行git commit并进行的提交,现在新的提交就是提交HEAD


git status闭嘴

文件的大约一半目的.gitignoregit status 停止抱怨未跟踪的文件。gitignore在文件中列出文件名或模式git status 不会抱怨文件未被跟踪。

这不会从索引中 取出任何文件。如果文件已经在 Git 的索引中,则在其中列出该文件.gitignore无效。 该文件索引中,因此该文件的副本在下一次提交中......当然,除非您自己删除它,使用git rm.

避免自动添加您不想提交的文件

文件的其余用途.gitignore是使您能够使用整体git add操作,而无需当前未跟踪的一些文件复制到 Git 的索引中。例如,列出*.o*.pyc意味着您现在可以git add .git add *。Git 将跳过未跟踪和忽略的文件,同时将其他更新或新的工作树文件复制到 Git 的索引中。

因此,Untracked-and-ignored 在两个方面很有用

由于这些文件不会被复制到索引中,因此它们不会出现在下一次提交中。运行git status这些未跟踪忽略的文件,您根本不会看到它们被提及:它们不在索引中,因此它们未被跟踪,但git status不会抱怨。git add .使用未跟踪和忽略的这些文件运行,git add不会将它们复制到索引中。

但是您已经在某些现有提交中拥有了这些文件

在您的情况下,有些提交在app/http/. 当你运行时:

git checkout <commit-specifier-or-branch-name>

并且您通过此操作选择的提交是包含文件的提交,例如app/http/foo.html,Git 将:

  • app/http/根据需要创建
  • 写入foo.html该文件夹
  • 将冻结的副本复制app/http/foo.html到其索引中

并且您将准备好处理/使用该文件。但是现在该文件Git 的索引中,所以它被跟踪git status告诉你它并且任何 en-massegit add都会将工作树文件复制到索引中,以便更新foo.html将在下一次提交中。

即使您复制foo.html到 Git 的索引中,以便索引副本仍然是副本,该副本也将在您所做的新提交中。Git 从索引进行提交,并且索引具有旧副本。

显而易见的解决方法是完全删除索引副本,然后进行新的提交。现在索引副本不存在,app/http/foo.html新提交中没有。如果app/http/在 中.gitignore,Git 不会,从现在开始在新工作中添加 foo.html任何一个。

但是,如果您对所有 app/http/文件执行此操作,那么,在将来,克隆此存储库并将此提交提取到一个新的、否则为空的工作树中,您将不会得到一个名为 的文件夹app/http,因为不会有其名称的文件首先app/http/让 Git 注意到它需要创建app文件夹,然后是http文件夹中的app文件夹。

此外,假设您现在有一个状态,其中您有提交(a123456...无论如何是一些哈希 ID)并且提交中没有。如果你:app/http/foo.htmlb6789ab...app/http/foo.html

git checkout a123456

Git 将从该提交中提取所有文件,包括app/http/foo.html, 到您的工作树和 Git 的索引中……然后,如果您:

git checkout b789abc

Git 将从提交中提取文件,注意app/http/foo.html需要删除,因为它在上一次提交中——因此现在在索引中——并且不在你现在选择的提交中,因此必须从索引。所以 Git 将从app/http/foo.html索引和你的 work-tree中删除。

app/http/在顶层.gitignore*app/http/.gitignore,不仅告诉 Git 它不应该自动这些文件添加索引中,它还允许 Git在某些情况下销毁工作树中的这些文件,包括这个特定的文件。因此,如果您选择它们​​,您在某些现有提交中拥有这些文件的事实会使事情变得危险.gitignore。在确保所有提交都没有并且忽略这些文件之后,在检查历史提交时要小心!


推荐阅读