繁体   English   中英

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

[英]Ignoring changes to a specific file when doing git pull

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

该文件在本地和存储库中都位于.gitignore中。 我已经尝试了git update-index --assume-unchangedgit update-index --skip-worktree ,但无济于事。 我当时正在考虑做git rm --chached ,但是从我阅读的内容git rm --chached ,这似乎会从存储库中删除文件,这不是我想要的。

该文件位于.gitignore本地和仓库中。

如您所见,如果文件确实被提交,那么这样做是没有用的。 那是因为.gitignore并不意味着忽略该文件 ,实际上是意味着如果该文件未被跟踪, 则将其关闭 如果取消跟踪(如果已跟踪),则列出文件完全无效

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

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

这就是事情变得复杂的地方。 我们必须了解Git的提交和合并模型。 随之而来的是在git merge期间索引的角色(即暂存区又称为缓存 )和工作树的角色。

合并如何运作

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

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

1如果您还不了解所有这些,请阅读有关Git的更多信息,例如, 《 Git Book》中有关分支章节


什么git merge会在这种情况下,做的是找到共享的承诺,共同的出发点,为您的承诺I ,到你的名字master点,以及它们的承诺L ,到您的origin/master点。 在这里,是F

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

git diff --find-renames <hash of F> <hash of I>   # what we changed

Git还将比较提交F中保存的内容与最新提交L保存的内容。 这告诉Git 他们改变了什么:

git diff --find-renames <hash of F> <hash of L>   # what they changed

现在您可以看到git merge实际工作原理:它将您更改的内容更改的内容结合在一起。 使用合并的更改,Git提取通过基本提交(提交F保存的内容,并将合并的更改应用于两组更改中所有更改的文件。 如果一切顺利,结果将是应作为合并提交的快照。 然后Git将执行此操作,提交合并并调整您当前的分支:

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

索引和工作树

冻结到提交中的文件存在一个基本问题:文件被(a)冻结,并且(b)以特殊的,压缩的,仅Git的形式存在。 冻结的部分非常适合源代码管理:这些文件将永远不会更改,因此您总是可以通过签出旧提交来找回以前的工作。 特殊的仅压缩Git压缩形式可用于控制您的存储空间:由于Git曾经保存过每个文件的每个版本,如果不是特殊的和压缩的,则可能会很快用完磁盘空间。 但这带来了一个问题: 如何获取冻结的文件? 您如何更改?

Git的答案是工作树 在某些提交上执行git checkout扩展并解冻保存在该提交中的文件。 解冻的重组文件进入您的工作树,您可以在其中使用它们并进行更改。

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

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

我所知道的关于索引的最佳描述是它会在您的下一次提交中使用 当Git是提取冻结的文件,而不是解冻和,他们去压缩直入你的工作树,它最初只是解冻他们(或者更准确地说,它们收集到一个统一的名单,是不是冷冻的反对提交中结构化,冻结的列表)。 这些现在未冻结的副本将进入索引。 它们仍然都是经过Git验证,压缩并占用最少存储空间的。

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

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

这就是git status工作方式:对于每个文件,它将工作树副本与索引副本进行比较,如果它们不同 ,则说明该文件未暂存为commit 它还将索引副本(采用特殊的仅Git格式)与HEAD提交副本进行比较,如果它们不同 ,则表示文件已暂存进行提交 因此,如果工作树中有10,000个文件,并且索引和HEAD提交,则实际上总共有30,000个副本(10k x 3个副本)。 但是,如果只有他们两个人在这三个副本方面是不同的 ,只有两个文件获得上市git status (和Git的,指明分数副本相对较小)。

在运行git commit ,索引中不同的文件只是索引中的不同。 运行git commit ,Git的冻结指数看也不看工作树! -和使这个新的HEAD提交。 您的新提交现在与索引匹配,因此文件的所有索引副本现在都与它们的`HEAD提交副本匹配。

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

假设不变和跳过工作树位

现在我们可以看到这两位的作用。 当您运行git status ,Git通常会将每个文件的工作树副本与同一文件的索引副本进行比较。 如果不同,则Git表示您所做的更改未准备提交 (如果文件根本不在索引中,Git会说该文件未跟踪那么 .gitignore文件.gitignore重要。但是,如果该文件已经在索引中,则该文件会被跟踪,而.gitignore文件会没关系。)

如果您设置了假定不变或跳过工作树位,则git status 不会将文件的工作树版本与索引版本进行比较。 它们可以随您的喜欢而不同,并且git status不会对它们说什么。

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

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

但是,现在您运行git merge ,并且合并要对文件进行更改 例如,Git将提交F与提交IL进行比较,发现尽管您尚未在I 提交文件的新版本, 但他们 确实L提交了文件的新版本。 所以Git会拿自己的变化,使那些进入新的合并提交M ,然后......抽取M到你的工作树,重挫你的文件的副本。

破坏文件很糟糕,因此Git不会这样做。 相反,它只是使合并失败。

你应该怎么做?

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

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

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

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

我当时正在考虑做git rm --cached ,但是从我阅读的内容来看,这似乎会从存储库中删除文件...

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

这行得通,但是留下了我上面概述的陷阱:返回到历史提交会将文件恢复到索引(因为git checkout commit意味着从提交中填充索引,并使用它来填充工作树 )。 一旦进入索引,它将在将来的提交中。 切换到没有文件的提交要求其从索引中删除,这意味着从工作树中删除它,现在工作树副本已消失。

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

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

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


2请参阅“像(a)Git一样思考”

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM