进行git pull时忽略对特定文件的更改

时间:2018-10-08 13:06:28

标签: git

我的项目中有一个文件,我希望在本地进行更改,而每次我从存储库中提取文件时都不会覆盖该文件,即我想拒绝对该特定文件的传入更改。到目前为止,我的解决方案是进行git stash --> git pull --> git stash pop

该文件位于.gitignore本地和存储库中。我试过了 git update-index --assume-unchangedgit update-index --skip-worktree,但无济于事。我本来打算做git rm --chached,但从我读过的内容来看,这似乎会从存储库中删除文件,这不是我想要的。

1 个答案:

答案 0 :(得分:3)

  

该文件位于.gitignore本地和存储库中……

如您所见,如果文件确实被提交,那么这样做是没有用的。这是因为.gitignore并不意味着忽略此文件,它的确意味着针对此文件如果未被跟踪闭嘴。如果未跟踪 (如果已跟踪),则列出文件完全没有效果

  

我尝试过git update-index --assume-unchangedgit update-index --skip-worktree,但无济于事。

为便于确认,如果您确切地指出问题出在哪里,将会很有帮助。现在,我假设问题出在哪里,那些命令似乎起作用了—它们根本没有抱怨—但是后来的git fetch && git merge抱怨说它将覆盖文件的内容。 (您可能会拼写此git pull,但如果这样,我建议将其拆分为两个组成部分的Git命令,直到您真正了解每个命令各自执行的操作为止。)

这是使事情变得复杂的地方。我们必须了解Git的提交和合并模型。随之而来的是 index (又是临时区域又是 cache )的角色,以及工作树的角色。在git merge期间。

合并的工作方式

首先,让我们快速回顾一下提交和合并过程。您已经知道 1 ,每个提交都具有所有提交文件的完整快照,并且每个提交都包含其父提交的哈希ID。因此,如果我们要在git fetch之后但在git merge运行之前绘制提交图,我们可能会看到:

       G--H--I   <-- master (HEAD)
      /
...--F
      \
       J--K--L   <-- origin/master

1 如果您尚不知道这一切,请阅读有关Git的更多信息,例如chapter on branching in the Git Book


在这种情况下,git merge要做的是找到共享提交,这是您的提交I和您的名字{ {1}}指向您的master指向的提交及其提交L。在这里,提交origin/master

接下来,Git将把保存在提交F中的内容与保存在您自己的最新提交F中的内容进行比较。这告诉了Git 您所做的更改。比较结果与您运行后可以查看的结果相同:

I

Git还将比较保存在提交git diff --find-renames <hash of F> <hash of I> # what we changed 中的内容与其保存在最新提交F中的内容。这告诉Git 他们发生了什么变化:

L

现在,您可以了解git diff --find-renames <hash of F> <hash of L> # what they changed 的实际工作原理:它将您所做的更改他们所做的更改结合在一起。使用合并的更改,Git提取使用基本提交(commit git merge)保存的内容,并将 combined 更改应用于两组更改中的所有文件。如果一切顺利,结果将是应作为合并提交的快照。然后Git将执行此操作,提交合并并调整您当前的分支:

F

索引和工作树

被冻结为提交的文件存在一个基本问题:文件被(a)冻结,并且(b)以特殊的,压缩的,仅Git的形式存在。 frozen 部分非常适合源代码管理:这些文件将永远不会更改,因此您可以通过签出旧提交来始终取回以前的工作。特殊的仅压缩Git压缩形式对于保持您的存储空间很有用:由于Git曾经保存过每个文件的每个版本,因此如果不需要对其进行特殊压缩,您可能会用完磁盘空间相当快。但这会带来一个问题:如何获取冻结的文件?您如何更改它?

Git的答案是工作树。对某些提交执行 G--H--I / \ ...--F M <-- master (HEAD) \ / J--K--L <-- origin/master ,以扩展并解冻该提交中保存的文件。解冻后的重组文件进入您的工作树,您可以在其中使用它们并进行更改。

在其他版本控制系统中,这就是故事的结尾:您拥有无法更改的冻结文件以及可以进行冻结的工作树。但是Git会添加这种中间形式,Git称为索引,临时区域或缓存,具体取决于Git的谁/哪个部分正在执行此调用。

了解索引对于使用Git至关重要,但是很少对其进行很好的解释。人们(和IDE)尝试将其覆盖并向您隐藏。这不起作用,并且不起作用的原因很重要,尤其是在您的情况下。

我对索引最了解的描述是它是下一次提交的内容。当Git提取冻结的文件时,它首先 just 解冻它们,而不是直接将它们解冻并解压缩到您的工作树中(或更准确地说,将它们收集到一个统一的列表中, t冻结-与提交中结构化,冻结的列表相反。这些现在未冻结的副本将进入索引。它们仍然都是经过Git验证,压缩并占用最少存储空间的。

一旦文件解冻到索引中,Git才将其解压缩为工作树格式。因此,它是第一个未冻结的(索引副本),然后然后提取到工作树中。这意味着索引具有准备好冻结到 next 提交中的副本。

如果您在工作树中更改文件,则必须在该文件上运行git checkout以便对其进行 copy (并压缩和Git-ify),以使其适合指数。现在,索引副本与工作树副本匹配,只是索引副本采用特殊的仅Git形式。 现在,可以进行下一次提交了。

这是git add的工作方式:对于每个文件,它将工作树副本与索引副本进行比较,如果它们是不同的,则表示文件是没有上演。它还将索引副本(特殊的仅Git格式)与git status提交副本进行比较,如果索引副本不同,则表示文件已暂存为 commit < / em>。因此,如果工作树中有10,000个文件,并且索引和HEAD提交,则实际上总共有30,000个副本(10k x 3副本)。但是,如果这三个副本中只有两个 不同,则HEAD中只会列出两个文件(而经过Git修饰的副本相对较小)。

在运行git status之前,索引中不同的文件只是索引中的不同。当您执行运行git commit时,Git冻结索引(甚至不查看工作树!),并将其作为您的新git commit提交。现在,您的新提交与索引匹配,因此文件的所有索引副本现在都与它们的“ HEAD提交副本”匹配。

(此外:在发生冲突的合并过程中,索引将扮演扩展角色。它现在不仅保留每个文件的一个副本,而且还保留最多个每个文件的三个副本。但是我们在这里不考虑冲突的合并,因此我们不必为此担心。)

假设不变和跳过工作树位

现在我们可以看到这两位的作用。运行HEAD时,Git通常会将每个文件的工作树副本与同一文件的索引副本进行比较。如果不一样,Git会说您所做的更改不是暂未提交。 (如果文件根本不在索引中,Git会说该文件是 untracked ,而{em> then git status文件很重要。但是如果文件已在索引中,已跟踪文件,而.gitignore文件无关紧要。)

如果您设置了假设不变或跳过工作树位,则.gitignore 不会将文件的工作树版本与索引版本进行比较。它们可以根据您的喜好而不同,git status对此一无所知。

请注意,git status完全忽略了这些位!它只是冻结索引副本。如果索引副本与工作树副本匹配,则意味着在再次提交文件时,文件保持相同。您的新提交与先前的提交具有相同的冻结副本。您的索引副本继续与您的git commit提交副本匹配。

问题出在Git需要更改文件时。假设您已经设置了skip-worktree位(通常,这是您应该设置的位,因为另一个实际上是另一个问题的意思,尽管实际上任何一个都起作用)。您还修改了工作树副本。运行HEAD不会抱怨,因为git status实际上不会再将工作树副本比较到索引副本。

但是现在您运行git status,并且合并要对文件进行更改。例如,Git将提交git merge与提交FI进行比较,发现尽管您尚未提交L中文件的新版本,但 确定I中提交文件的新版本。因此,Git会进行更改,使这些更改进入新的合并提交L,然后...将M提取到您的工作树中,破坏文件的副本。

弄乱文件很不好,因此Git不会这样做。相反,它只是使合并失败。

您应该怎么做?

最终,您必须做的是将文件的版本保存在某个位置。通过将文件复制到存储库之外,这可以在Git内部(例如作为提交)或在Git外部。然后,您可以将更改与更改合并,也可以在获取版本后重新进行更改。

M实际上就是这样做的。它会提交一次-实际上,有两个 提交。这些提交的特别之处在于它们不在 any 分支上。进行提交后,git stash运行git stash来放弃对索引和工作树中文件的更改。您没有索引更改,即使您已将索引副本保存在隐藏提交中,所以重置的git reset --hard部分是安全的,并且您的工作树副本也保存在隐藏提交中,因此重置的--mixed是安全的。现在您的索引和工作树是干净的,您可以安全地合并了。然后--hard(实际上是git stash pop)可以使用安全性较低的内部合并(仅适用于工作树)将文件的隐藏工作树版本与文件的当前版本合并复制。 git stash apply && git stash drop步骤会删除隐藏提交,以使它们成为未引用 2 ,并最终将其完全删除。

这里有多种使用drop的替代方法,但是没有一个是那么简单的,而且没有一个是漂亮的。您最好使用git stash

最后,您可以完全停止提交文件。一旦文件不再在索引中,它就会成为未跟踪的并且不在任何 future 提交中。在我看来,这最终是最好的解决方案,但它有一个非常巨大的缺点:文件已经在过去提交过。这意味着,如果您签出 old 提交(确实有文件),则文件将位于当前提交(您刚刚签出的那个旧提交)和 索引,并将对其进行跟踪。当您从旧提交切换到没有没有文件的新提交时,那个将删除该文件!这就是您说的意思:

  

我当时正在考虑做git stash,但是据我看来,这似乎会从存储库中删除文件...

具体来说,它从 index 中删除文件,而保留工作树副本。该不会从存储库中删除文件,但是存储库本身主要由 commits 组成,并且“从存储库中删除”是nonsense phrase。您实际上不能从现有的提交中删除文件:它们会被永久冻结。您只能避免将文件放入将来提交中。

这有效,但是留下了我上面概述的陷阱:返回到历史提交会将文件还原到索引(因为git rm --cached表示从提交中填充索引,并使用该索引来填充工作-tree )。一旦在索引中输入,它将在将来的提交中。切换到没有拥有文件的提交,需要从索引中删除它,这意味着从工作树中删除它,现在工作树副本是不见了。

因此,如果您想走这条路,这是个好方法,则应该:

  • 完全停止使用该文件:将其重命名为git checkout commit
  • 为实际配置切换到新的(不同的)文件名,并将此文件完全保留在存储库之外(例如,将其存储在config.sample中)

,并将其全部提交一次,之后将不再使用旧的配置文件。在切换到新版本的$HOME/.fooconfig程序之前,告诉人们将其配置移动到新位置。使它成为主要版本,因为行为不同。


2 请参见Think Like (a) Git