简体   繁体   English

为什么git pull不更新本地更改的文件?

[英]Why does git pull not update locally changed files?

From what I understand "git pull" does a fetch and merge. 据我了解,“ git pull”会进行获取和合并。 For an iOS project, I have the settings configured with distribution settings (certificates, provisioning profiles etc.). 对于iOS项目,我使用发布设置配置了设置(证书,配置文件等)。 However, when on the master branch, when I do a git pull and a git log , I see that git commits are up to date with the latest commit from the remote master branch BUT git diff still shows that my pbxproj file is different (ie with the provisining profile strings etc. which I have configured locally). 但是,当在master分支上时,当我执行git pullgit log ,我看到git提交与远程master分支的最新提交保持最新,但是git diff仍然显示我的pbxproj文件是不同的(即以及提供的配置文件字符串等(我已在本地配置)。

To be clear, this is ideal for me because I want only the code changes and not having to mess with setting the distribution and provisioning profiles every time I update the branch just before releasing to production. 明确地说,这对我来说是理想的,因为我只希望代码更改,而不必在每次发布分支之前(在发布到生产环境之前)更新分支和设置概要文件。 But I also want to understand the way git is merging. 但我也想了解git合并的方式。 I was expecting that git would merge the files which I had changed locally and/or indicate a conflict. 我期望git将合并我在本地更改的文件和/或表明冲突。 But no such thing is happening. 但是没有发生这样的事情。

Note : I know a "force update" with make my local master resemble remote exactly but want to understand the difference between this and the regular merge. 注意:我知道一个“强制更新”,它使我的本地主服务器完全类似于远程主机,但想了解此操作与常规合并之间的区别。

git will merge changes from the remote with locally committed changes. git会将来自远程的更改与本地提交的更改合并。 It will not attempt to merge with your uncommitted changes, and if there are remote changes in the same files, it will warn you that the merge would wipe out your local changes and will reject the merge. 它不会尝试与您未提交的更改合并,并且如果同一文件中有远程更改,它将警告您合并将清除您的本地更改并拒绝合并。

Update - I realized I didn't really answer "why": 更新 -我意识到我并没有真正回答“为什么”:

The reason for this distinction between locally committed changes, vs. uncommitted local changes, is that you can always get your locally committed changes back. 区分本地提交的更改和未提交的本地更改的原因是,您始终可以取回本地提交的更改。 They're in a commit, and you can checkout that commit, create a new branch there, even reset the current branch back to that point if need be. 他们处于提交状态,您可以checkout该提交,在该位置创建一个新分支,甚至在需要时将当前分支reset到该位置。 Nothing is lost by merging with locally committed changes. 与本地提交的更改合并不会丢失任何内容。

But your worktree (uncommitted local changes) gets no such protection. 但是您的工作树(未提交的本地更改)没有这种保护。 If git were to merge changes into it and the result were not what you wanted, you couldn't easily "back out" to what you had before. 如果git将更改合并到其中,而结果却不是您想要的,则无法轻松地“退回”到以前的状态。

We could also go into technical differences in what would be involved to merge with uncommitted changes, but those technical reasons could be overcome, if it were desirable to merge with uncommitted changes. 我们还可以就与未提交的更改合并所涉及的内容进行技术差异,但是,如果需要与未提交的更改合并,则可以克服那些技术原因。 Because doing so could result in data loss, though, it's instead desirable to avoid merging changes into any file that has local uncommitted changes. 但是,由于这样做可能会导致数据丢失,因此最好避免将更改合并到具有本地未提交更改的任何文件中。

From what I understand "git pull" does a fetch and merge. 据我了解,“ git pull”会进行获取和合并。

That's correct. 没错 More precisely, git pull actually runs git fetch , and then runs git merge . 更准确地说, git pull实际上运行 git fetch ,然后运行git merge Or rather, it used to do that literally, back in the old days; 或者更确切地说,它曾经是过去的字面意思。 in the last year or so, Git was modified to make git pull faster by doing the fetch and merge steps internally, rather than running separate commands, when possible—and you can also tell it to do git rebase instead of git merge , and there are some other corner cases. 在过去的一年左右的时间里,对Git进行了修改,使其可以通过在内部执行获取和合并步骤来加快git pull速度,而不是在可能的情况下运行单独的命令-并且您还可以告诉它执行git rebase而不是git merge ,还有其他一些极端情况。 But essentially, pull = fetch + merge. 但从本质上讲,pull =提取+合并。

The tricky part is what each of these two steps means . 棘手的部分是这两个步骤分别意味着什么 Both fetch and merge are, or can be, complicated. 获取和合并都很复杂,也可能很复杂。

Fetch

The fetch step is the simpler one: your Git calls up some other Git. 取回步骤比较简单:您的Git调用其他Git。 Your Git and their Git have a little conversation, in which their Git tells your Git about any new commits they have, that you don't. 您的Git和他们的Git进行了一段简短的交谈,其中他们的Git告诉您的Git有关他们拥有的任何新提交的信息,而您没有。 Your Git loads up these new commits into your repository, so that you have the commits now; 您的Git将这些新的提交加载到您的存储库中,以便您现在拥有这些提交; and then your Git sets up your remote-tracking names —names like origin/master —to remember their Git's master and so on. 然后,您的Git会设置您的远程跟踪名称 (如origin/master类的名称)来记住 Git的master (When git pull runs git fetch , it may limit your Git's fetching. In very old versions, this even inhibits your Git from updating your origin/master and the like as well. Modern Git does away with that last unhelpful peculiarity.) (当git pull运行git fetch ,它可能会限制您的Git的获取。在非常旧的版本中,这甚至也禁止您的Git更新您的origin/master等。ModernGit消除了最后的无用特性。)

As long as you don't complicate this on your own (by adding refspecs or various fetch flags), that's all there is for the fetch. 只要您自己不使它复杂化(通过添加refspecs或各种获取标志),就可以完成所有操作。 It's the merge part that remains tricky. 合并部分仍然很棘手。 Having done the fetch, git pull now runs git merge and this is where things get a little complex. 完成提取后, git pull现在运行git merge ,这会使事情变得有些复杂。

Merge 合并

The goal of a merge is the easy part: Git begins by assuming that you have been working and making commits, and someone else has also been working and making commits. 合并的目标很简单:Git首先假设您一直在工作并进行提交,而其他人也一直在工作并进行提交。 If we draw those commits with earlier commits towards the left, and later commits towards the right, giving them simple one-letter names instead of their actual incomprehensible Gitty hash IDs, we get something like this: 如果我们将这些提交与先前的提交朝左绘制,然后将其向右提交,给它们提供一个简单的单字母名称,而不是它们实际的难以理解的Gitty哈希ID,我们将得到如下所示:

          C--D--E   <-- branch (HEAD)
         /
...--A--B
         \
          F--G--H   <-- origin/branch

Here E is your latest commit on your branch. E是您在分支上的最新提交。 Its parent is commit D ; 其父是提交D ; D 's parent is C ; D的父母是C ; and so on. 等等。 Commit H is their latest commit, which you just brought in via git fetch . 提交H他们的最新提交,您刚刚通过git fetch引入了它。 H 's parent is G , whose parent is F , whose parent is B . H的父母是G ,其父母是F ,其父母是B We can see that these two lines of development happened in parallel, starting from the common point—the merge base —of commit B . 我们可以看到,这两条开发线是并行发生的,从提交B的共同点( 合并基础)开始

What git merge does for this case is to run, in effect, two git diff commands. git merge在这种情况下所做的实际上是运行两个git diff命令。 The first one finds out what you changed: 第一个找出您所做的更改:

git diff --find-renames <hash-of-B> <hash-of-E>

The second diff finds out what they changed: 第二个差异找出了它们的变化:

git diff --find-renames <hash-of-B> <hash-of-H>

Git can combine these two sets of differing changes, as long as they do not conflict with each other. Git可以将这两组不同的更改组合在一起 ,只要它们彼此不冲突即可。 Or rather, as long as Git doesn't think they conflict—Git does not know , it just makes a lot of assumptions about line-by-line differences from git diff . 或者说,只要Git认为它们发生冲突-Git不知道 ,它就对git diff的逐行差异做出了很多假设。 Having successfully (maybe) combined the changes, Git applies the combined changes to the snapshot in the merge base to produce your new snapshot, and makes a merge commit , with two parents: 成功(也许)组合了更改之后,Git将组合的更改应用于合并基础中的快照以生成新的快照,并与两个父级进行合并提交

          C--D--E
         /       \
...--A--B         I   <-- branch (HEAD)
         \       /
          F--G--H   <-- origin/branch

Making the new merge commit causes the branch name itself, branch , to point to the new commit, just as making a new ordinary commit does. 进行新的合并提交会使分支名称本身branch指向新的提交,就像进行新的普通提交一样。

This is a true merge: Git has to combine changes since a merge base , which means it must first locate the merge base commit, then do the two diffs. 这是一个真正的合并:Git必须合并自合并基础以来的更改 ,这意味着它必须首先找到合并基础提交,然后进行两个比较。 This is the verb part of a merge, to merge (two sets of changes). 这是要合并 (两组更改)的合并的动词部分。 Having done the to-merge verb part, Git makes a merge commit , merge as an adjective, or less formally, a merge , which uses merge as a noun. 在完成了要合并的动词部分后,Git进行了merge commit ,merge作为形容词或不那么正式地称为merge ,它使用merge作为名词。

Not all merges are true merges 并非所有合并都是真实的合并

Suppose that you run git fetch (perhaps via git pull ) and nothing at all has changed, so that you have: 假设您运行git fetch (也许通过git pull ),但什么都没有改变,所以您有:

...--D--E   <-- branch (HEAD), origin/branch

That is, you have two different names here. 也就是说,您在这里有两个不同的名称。 You have the name branch —your branch—pointing to your commit with hash ID E (really some big ugly Git hash). 您有一个名称branch -您的分支-指向您的哈希ID为E提交(确实是一些难看的Git哈希)。 You also have your remote-tracking name origin/branch , remembering their Git's branch named branch , pointing to your commit with hash ID E , as that same commit exists in both Gits. 您还具有远程跟踪名称origin/branch ,记住了它们在 Git的分支branch ,指向您的哈希ID为E提交,因为两个Git中都存在相同的提交。

Then, having done no work and made no commits, you run git fetch again later, only this time they have done some work and made some commits: 于是,成就了没有工作,取得了提交,运行git fetch又过去了,只是这一次,他们已经做了一些工作,取得了一定的提交:

...--D--E   <-- branch (HEAD)
         \
          F--G   <-- origin/branch

If you now ask your Git to merge your work (or lack of work) with their work, Git will find the merge base as usual, but discover that the merge base is commit E itself. 如果现在要求Git将您的工作(或缺少工作)与他们的工作合并,Git会像往常一样找到合并基础,但是会发现合并基础是提交E本身。 Obviously, diffing commit E against itself would find no changes. 显然,将提交E与自身进行比较不会发现任何变化。 Git could go ahead and build a new merge commit anyway, combining no-changes with some-changes to get: 无论如何,Git 可以继续构建新的合并提交,将无更改与某些更改结合起来以得到:

...--D--E------H   <-- branch (HEAD)
         \    /
          F--G   <-- origin/branch

where the snapshot in H exactly matches the snapshot in G . 其中H中的快照与G中的快照完全匹配。 But the default for git merge is to not bother . 但是git merge的默认设置是不打扰 Merge detects this as a fast-forward operation and will instead not do the to-merge verb, and not make the adjective-merge commit. 合并将其检测为快进操作,并且执行合并动作, 也不进行形容词合并提交。 Instead of making new commit H , Git will simply move the branch name branch forward: 不用执行新的提交H ,Git只会将分支名称branch向前移动:

...--D--E
         \
          F--G   <-- branch (HEAD), origin/branch

and check out commit G so that G 's snapshot fills the index and work-tree. 并检出提交G以使G的快照充满索引和工作树。

Git calls this a fast-forward merge but there's no merging going on! Git称这为快速合并,但没有合并! The fast-forwarding is simply an operation on the branch name. 快进只是对分支名称的操作。 There is ultimately no trace left behind that this has happened, which is why Git allows you to prohibit a fast-forward non-merge "merge". 最终没有任何痕迹发生,这就是为什么Git允许您禁止快速前进的非合并“合并”的原因。 However, for local branches that correspond to remote-tracking names, this kind of thing is actually extremely often what you want anyway, which is why this is Git's default. 但是,对于与远程跟踪名称相对应的本地分支,无论如何实际上实际上是您经常想要的东西,这就是为什么这是Git的默认设置。

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

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