进行git pull时忽略对特定文件的更改 [英] Ignoring changes to a specific file when doing git pull

查看:268
本文介绍了进行git pull时忽略对特定文件的更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

There's a file in my project that I'd like to change locally without it being overwritten every time I pull from the repo, i.e. I want to reject incoming changes to that specific file. My solution so far has been to do git stash --> git pull --> git stash pop

该文件位于本地和仓库中的 .gitignore 中.我试过了 git update-index-假定不变 git update-index --skip-worktree ,但无济于事.我当时正在考虑做 git rm --chached ,但是从我读过的内容来看,这似乎会从存储库中删除文件,这不是我想要的.

The file is in .gitignore both locally and in the repo. I've tried git update-index --assume-unchanged and git update-index --skip-worktree, but to no avail. I was thinking of doing git rm --chached, but from what I've read it seems like this will delete the file from the repo, which is not what I want.

推荐答案

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

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

As you have seen, this does no good if the file is actually committed. That's because .gitignore does not mean ignore this file, it really means shut up about this file if it's untracked. If it's not untracked (if it is tracked), listing the file has no effect at all.

我尝试了 git update-index-假定不变 git update-index --skip-worktree ,但无济于事.

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

For confirmation, it would be helpful if you showed exactly where this goes wrong. For now, I'm assuming that where it goes wrong is that those commands appear to work—they do not complain at all—but a later git fetch && git merge complains that it would overwrite the contents of the file. (You may be spelling this git pull, but if so, I recommend splitting it into its two component Git commands until you really understand what each command does on its own.)

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

This is where things get complicated. We must understand Git's model for commits and merges. With this comes the role of the index (aka staging area aka cache), and the role of the work-tree, during a git merge.

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

First, let's do a quick overview of commits and the merge process. You already know1 that each commit has a complete snapshot of all of your committed files, and each commit contains the hash ID(s) of its parent commit(s). Hence if we were to draw the graph of commits after git fetch but before git merge runs, we might see this:

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


1 如果您已经了解所有这些,请阅读有关Git的更多信息,例如


1If you don't already know all of this, read some more about Git, e.g., the chapter on branching in the Git Book.

在这种情况下, git merge 要做的是找到 shared 提交,这是两个提交 I ,您的名字 master 指向其,并提交 L ,您的 origin/master 指向.在这里,提交 F .

What git merge is going to do in this case is to find the shared commit, the common starting point, for both your commit I, to which your name master points, and their commit L, to which your origin/master points. Here, that's commit F.

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

Next, Git will compare the contents saved in commit F to the contents saved in your own latest commit, I. That tells Git what you changed. The comparison is the same as the one you can view if you run:

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

Git还将比较保存在提交 F 中的内容与保存在其最新提交 L 中的内容.这告诉Git 他们发生了什么变化:

Git will also compare the contents saved in commit F to the contents saved in their latest commit L. That tells Git what they changed:

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

现在您可以看到 git merge 的实际工作方式:它将您所做的更改他们所做的更改结合在一起.使用合并的更改,Git提取与基本提交(commit F )一起保存的内容,并将 combined 更改应用于两组更改中的所有文件.如果一切顺利的话,结果就是应该作为合并提交的快照.然后Git将执行此操作,提交合并并调整您当前的分支:

Now you can see how git merge actually works: it combines what you changed with what they changed. Using the combined changes, Git extracts the contents saved with the base commit—commit F—and applies the combined changes to all of the files changed in the two sets of changes. The result, if all goes well, is the snapshot that should be committed as the merge; and Git will do that, committing the merge and adjusting your current branch:

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

索引和工作树

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

The index and the work-tree

There is a fundamental problem with files that are frozen into commits: they are (a) frozen, and (b) in a special, compressed, Git-only form. The frozen part is great for source code management: these files will never change, so you can always get back your previous work, by checking out an old commit. The special compressed Git-only form is useful for keeping your storage space under control: since Git saves every version of every file ever, if they weren't special and compressed, you might run out of disk space pretty fast. But it creates a problem: How do you get at the frozen file? How can you change it?

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

Git's answer to this is the work-tree. Doing git checkout on some commit expands and thaws the files that are saved in that commit. The thawed-out, reconstitute files go into your work-tree, where you can work with them and change them.

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

In other version control systems, that's the end of the story right there: you have your frozen files, which you can't change, and your unfrozen work-tree, which you can. But Git adds this intermediate form, which Git calls the index, or the staging area, or the cache, depending on who / which part of Git is doing this calling.

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

Understanding the index is crucial to using Git, yet it's rarely explained very well. People (and IDEs) try to paper over it and hide it from you. This doesn't work, and the reason it doesn't work is important—especially in your case.

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

The best description I know of for the index is that it's what will go into your next commit. When Git is extracting the frozen files, instead of unfreezing and de-compressing them straight into your work-tree, it first just unfreezes them (or more precisely, collects them into a single unified list that isn't frozen—as opposed to the more structured, frozen lists inside commits). These now-unfrozen copies go into the index. They are still all Git-ified, compressed and taking minimal storage.

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

Once a file is unfrozen into the index, only then does Git de-compress it into the work-tree format. So it's first unfrozen (index copy), and then it's extracted into the work-tree. This means the index has a copy that's ready to be frozen into the next commit.

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

If you change the file in the work-tree, you must run git add on that file to copy (and compress and Git-ify) the file so that it fits into the index. Now the index copy matches the work-tree copy, except that the index copy is in the special Git-only form. Now it's ready to go into the next commit.

这是 git status 的工作方式:对于每个文件,它将工作树副本与索引副本进行比较,如果文件不同,则表示文件未上演提交.它还将索引副本(特殊的仅Git格式)与 HEAD 提交副本进行比较,如果索引副本与不同,则表示文件已暂存[em]提交.因此,如果工作树中有10,000个文件,并且索引和 HEAD 提交,则实际上总共有30,000个副本(1万x 3副本).但是,如果这三个副本中只有两个不同,则只有两个文件以 git status 列出(并且经过Git验证)副本相对较小).

This is how git status works: for each file, it compares the work-tree copy to the index copy, and if those are different, it says the file is not staged for commit. It also compares the index copy—in the special Git-only format—to the HEAD commit copy, and if those are different, it says the file is staged for commit. So if there are 10,000 files in the work-tree and the index and the HEAD commit, there are actually 30,000 copies total (10k x 3 copies). But if only two of them are different in terms of these three copies, only two files get listed in git status (and the Git-ified copies are relatively tiny).

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

Until you run git commit, files that are different in the index are simply different in the index. When you do run git commit, Git freezes the index—without even looking at the work-tree!—and makes this your new HEAD commit. Your new commit now matches the index, so now all the index copies of files match their `HEAD commit copies.

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

(Aside: during a conflicted merge, the index takes on an expanded role. Instead of just one copy of each file, it now holds up to three copies of each file. But we're not looking at conflicted merges here, so we don't have to worry about this.)

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

Now we can see what these two bits do. When you run git status, Git normally compares the work-tree copy of every file to the index copy of the same file. If it's different, Git says that you have a change that is not staged for commit. (If the file isn't even in the index at all, Git says the file is untracked, and then the .gitignore file matters. But if the file is already in the index, the file is tracked, and the .gitignore file does not matter.)

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

If you set either the assume-unchanged or skip-worktree bits, git status won't compare the work-tree version of the file to the index version. They can be as different as you like, and git status will say nothing about them.

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

Note that git commit completely ignores these bits! It simply freezes the index copy. If the index copy matches the work-tree copy, this means you've kept the file the same when committing it again. Your new commit has the same frozen copy as your previous commit. Your index copy continues to match your HEAD commit copy.

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

The problem comes about when Git needs to change the file. Let's say that you have set the skip-worktree bit (this is the one you should set, in general, as the other is meant for a different problem, although in practice either one works). You have also modified the work-tree copy. Running git status won't complain, because git status won't actually compare the work-tree copy to the index copy any more.

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

But now you run git merge, and the merge wants to take the changes to the file. Git compares commit F to commits I and L, for instance, and finds that although you have not committed a new version of the file in I, they did commit a new version of the file in L. So Git will take their changes, make those go into the new merge commit M, and then ... extract M into your work-tree, clobbering your copy of the file.

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

Clobbering your file is bad, so Git doesn't do that. Instead, it just fails the merge.

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

Ultimately, what you must do is save your version of the file somewhere. That could be inside Git—as a commit, for instance—or outside of Git, by copying the file outside the repository. Then you can either merge your changes with their changes, or re-do your changes after just taking their version.

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

This is what git stash does, in fact. It makes a commit—well, really two commits. The special thing about these commits is that they are not on any branch. Having made the commits, git stash runs git reset --hard to throw away your changes to the file from the index and work-tree. You had no index changes, and even if you did the index copy is saved in the stash commits, so the --mixed part of the reset is safe, and your work-tree copy is saved in the stash commits, so the --hard part of the reset is safe. Now that your index and work-tree are clean you can merge safely. Then git stash pop—which is really git stash apply && git stash drop—can merge your stashed work-tree version of the file with your current version of the file, using a less-safe internal merge that works only on the work-tree copy. The drop step drops the stash commits, so that they become unreferenced2 and will eventually be removed entirely.

这里有几种使用 git stash 的替代方法,但是没有一个是那么简单,也没有一个是漂亮的.您最好使用 git stash .

There are several alternatives to using git stash here, but none are as simple, and none are pretty. You might as well use git stash.

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

Last, you can stop committing the file entirely. Once the file is no longer in the index, it becomes untracked and is not in any future commits. This is ultimately the best solution, in my opinion, but it has one really huge drawback: the file has been committed in the past. This means that if you ever check out an old commit (that does have the file), the file will be in both the current commit—that old commit you've just checked out—and the index, and will be tracked. When you switch away from that old commit to a new commit that doesn't have the file, that will remove the file! This is what you mean when you say:

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

I was thinking of doing git rm --cached, but from what I've read it seems like this will delete the file from the repo ...

具体来说,它从 index 中删除文件,而仅保留工作树副本.该不会从存储库中删除文件,但是存储库本身主要由 commits 组成,并且从存储库中删除"是一个

Specifically, it deletes the file from the index, while leaving the work-tree copy alone. This does not delete the file from the repo, but the repo itself is mainly composed of commits, and "delete from the repo" is a nonsense phrase. You literally can't delete the file from the existing commits: they are frozen forever in time. You can only avoid putting the file into future commits.

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

This works, but leaves that trap I outlined above: going back to a historical commit restores the file to the index (because git checkout commit means populate the index from a commit and use that to populate the work-tree). Once in the index, it will be in future commits. Switching to a commit that doesn't have the file requires removing it from the index, which implies removing it from the work-tree, and now the work-tree copy is gone.

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

So, if you want to go this route, which is a good way to go, you should:

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

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

and make that all part of one commit, after which the old configuration file is never used again. Tell people to move their configuration to the new location before switching to the new version of the foo program. Make this a major version bump, because the behavior is different.

2 请参见像(a)Git一样.

这篇关于进行git pull时忽略对特定文件的更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆