简体   繁体   English

Git分支发散,而没有本地更改

[英]Git branch diverged while there're no local change

Let me explain, I have a branch named "development" which I only read the code and make no change, I ran into branch diverged problem last week for no reason, to fix it, I have to run below commands: 让我解释一下,我有一个名为“开发”的分支,该分支仅读取代码且未做任何更改,上周无缘无故地遇到了分支分歧的问题,要对其进行修复,必须在以下命令中运行:

git fetch origin 
git reset --hard origin/development

After this, git status did say that "Your branch is up-to-date with 'origin/development'". 在此之后, git status确实说“您的分支是最新的'起源/开发'”。

//----- // ------

Today, I did a fetch again, and ran git status , it gave: 今天,我再次进行了获取,并运行了git status ,它给出了:

Your branch and 'origin/development' have diverged, and have 45 and 51 different commits each, respectively. 您的分支和“来源/开发”已经分开,分别具有45和51个不同的提交。

I can understand that there're 51 new commits on the remote origin/development, because some of my colleagues commit and push codes to it. 我可以理解,在远程起源/开发中有51个新的提交,因为我的一些同事向它提交并推送了代码。

But I just cannot understand why there're 45 local commits which don't exist on the remote, I've never changed anything on the local! 但是我只是不明白为什么在遥控器上不存在45个本地提交,我从未在本地更改任何东西!

//----- // ------

My first question is, do git reset --hard origin/development really give me a clean and up-to-date branch? 我的第一个问题是, git reset --hard origin/development真的能给我一个干净且最新的分支吗?

If so, then the best guess is that those 45 commits used to be on the remote but they're deleted by someone, right? 如果是这样,那么最好的猜测是那45个提交曾经在远程上,但是它们被某人删除了,对吗? Or are there any other possible reasons? 还是还有其他可能的原因? I'm 101% sure that I've never changed anything on the local. 我有101%的把握确保我从未在本地进行任何更改。

Is it normal to delete the existing commit history on the remote? 删除远程服务器上的现有提交历史记录是否正常? What actions would cause the deleting of the existing commit history on the remote? 哪些操作将导致删除远程服务器上的现有提交历史记录?

Thank you! 谢谢!

Or are there any other possible reasons? 还是还有其他可能的原因?

Try git log development..origin/development and git log origin/development..development - it will show different commits between local and remote branches. 尝试git log development..origin/developmentgit log origin/development..development它会显示本地和远程分支之间的不同提交。 Hope this will help to find out the reason of this behavior. 希望这将有助于找出此行为的原因。 For more information see this question . 有关更多信息,请参见此问题

You're on the right track. 您走在正确的轨道上。

I'm going to be a bit long winded here because I don't have time to make this shorter. 我在这里会有点烦,因为我没有时间将其缩短。 :-) Here's how this happens, as an illustration. :-)这是这种情况的发生,例如。 Remember that there are two (or more) repositories involved here, which I will just label "yours" and "theirs". 请记住,这里涉及两个(或更多)存储库,我将它们分别标记为“您的”和“他们的”。 Also, each single uppercase letter here stands for some commit—some git object with a 40-character SHA-1 "true name" like e59f6c2d348d465e3147b11098126d3965686098 —and the lowercase o s also stand for commits (each of which have their own unique 40-character "true names" but we don't have any reason to try to describe them, and with 40 or more commits we'd run out of uppercase letters). 此外,每个单独的大写字母在这里代表了一些承诺,一些带有40个字符的SHA-1“的真名”混帐对象像e59f6c2d348d465e3147b11098126d3965686098 -和小写o S还代表提交(每一个都有自己独特的40个字符“真实姓名”,但我们没有任何理由来描述它们,并且如果提交40次或更多次,我们将用大写字母表示出来)。

The initial setup 初始设置

At some point you did a git fetch or git fetch origin (or anything sufficiently similar). 在某个时候,您执行了git fetchgit fetch origin (或任何足够类似的操作)。 Your git called up their git over the Internet-phone and asked their git to package up any commits they had that you didn't. 您的git通过Internet电话调出了他们的git,并要求他们的git打包所有您没有的提交。 They did so and delivered you their commits, along with telling you that their branch-label development pointed to (contained the 40-character SHA-1 ID of) a commit I will draw in as B here. 他们这样做并发表你自己的提交,以告诉你,他们的分支标签一起development指着(包含的40个字符的SHA-1 ID)提交我会画为B这里。 That commit had some parent commit or commits—I'll assume just one—which git records by storing its raw 40-character SHA-1 ID; 那个提交有一些父提交或多个提交(我假设只有一个),git通过存储其原始的40个字符的SHA-1 ID来进行记录; the parent had another parent, and so on. 父母有另一个父母,依此类推。 This produces a chain: 这产生了一条链:

... <- A <- X <- o ... <- o <- B   <-- [their "development"]

This particular chain contains 45 commits, though I did not draw all of them (and there may be some branch-and-merge within the o sequence, but the key is that there's definitely one particular commit A and another particular commit B here). 这个特定的链包含45个提交,尽管我没有全部绘制(并且o序列中可能有一些分支合并,但是关键是这里肯定有一个特定的提交A和另一个特定的提交B )。 I also labeled one X ; 我还标了一个X ; we'll see why in a while. 我们一会儿会明白为什么。

Your git, having received all of this, does not make any of your branches point to commit B , because your branches are your branches , not to be messed-with until you ask. 收到所有这些信息后, 您的 git不会使您的任何分支都指向提交B ,因为您的分支是您的分支 ,除非您提出要求,否则请不要混乱。 In order to remember that origin —ie, their git—said development pointed to B , your git instead stores the ID of B under your "remote branch" label, origin/development : 为了记住该origin (即他们的git),说development指向B您的 git而是将B的ID存储在“远程分支”标签下, origin/development

... - A - X - o ... - o - B   <-- origin/development

I've drawn the left-pointing arrows without arrow-heads this time just to make things fit better. 这次我画了没有箭头的左箭头,只是为了使情况变得更好。 In git, a commit always points back to its parents, because commits are permanent and can never be changed , so it's impossible for a parent to point to its children since the children are created after the parents. 在git中,提交始终指向其父级,因为提交是永久性的,并且永远无法更改 ,因此父级不可能指向其子级,因为子级是在父级之后创建的。

Once your fetch finished, though, you ran git reset --hard to make your (local) branch, development , also point to commit B : 一旦你的fetch完成,不过,你跑git reset --hard ,让您的 (本地)的分支, development指出,提交B

... - A - X - o ... - o - B   <-- development, origin/development

The break-up and re-union 分手与重聚

This brings us to just before your most recent git fetch when everything became rather strange. 当一切变得很奇怪时,这使我们进入了最近的git fetch之前。 You ran git fetch again, so your git called up their git again and asked their git to send over whatever SHA-1 IDs they had that you didn't ... and this time they sent over 51 new commits (or maybe more, but 51 that apply here). 您再次运行git fetch ,因此您的git再次调用了他们的git,并要求他们的git发送您没有的任何SHA-1 ID ...这次,他们发送了51多个新提交(或者可能更多,但此处适用51条)。 We can draw in this new chain, which is now stored in your own repo, like this: 我们可以绘制这个新链,该链现在存储在您自己的仓库中,如下所示:

... - A - X - o ... - o - B   <-- development
       \
        Y - o - ... - o - o - C   <-- origin/development

As before, your git changed their development to be your origin/development . 和以前一样,您的git将他们的development更改为您的origin/development Your git did not change any of your own local branches, so it left your development pointing to commit B . 您的git 并未更改您自己的任何本地分支,因此它使您的development指向B提交。

You're now in the state where you have 45 commits they don't (everything "before and up to B but after A "—we can write this in "git revspec" form as A..B ), and they have 51 they just gave you (everything "before and up to C but after A "). 您现在处于这样的状态,您有45次提交(不是“在B之前,直到B但在A之后”,我们可以用“ git revspec”形式将其写为A..B ),而它们有51他们只是给了你(一切“在C之前,直到C但在A之后”)。

How did they get there? 他们如何到达那里? The answer is, they "rolled back" the 45 commits that they no longer have, and instead added 51 new commits. 答案是,他们“回滚”了他们不再拥有的45个提交,而是添加了51个新提交。 Precisely how they did it, and who did it, are not knowable, but we can make a very good guess. 确切地讲他们是如何做的,是做的,是未知的,但是我们可以做出一个很好的猜测。

Check out the bold-font phrase above again: commits are permanent. 再次检查上面的粗体字:提交是永久的。 You can't alter a commit. 您不能更改提交。 And yet, git has rebase -i (and other tools) that let you seem to alter old commits. 但是,git有了rebase -i (和其他工具),使您似乎可以更改旧的提交。

These actually work by copying commits. 这些实际上通过复制提交而起作用。 You (as someone using git) identify a commit you want to "change", in this case, commit X . 您(作为使用git的人)标识要“更改”的提交,在这种情况下,请提交X You instruct git to extract commit X , then you make some slight change—maybe not even a change to the source code, maybe just a change to the commit message—and you make a new commit Y . 您指示git提取提交X ,然后进行一些微小的更改—甚至可能没有更改源代码,可能只是更改了提交消息—然后进行了新的提交Y (A better name for this is X' , indicating that it's a copy of X with some slight change, but I don't know for sure whether they did this, or simply discarded X and started the copying from the first o after X . You can do the latter quite easily in git rebase -i by deleting the pick line.) (这方面的一个更好的名字是X' ,表明它是一个副本X有一些细微的变化,但我不肯定知道他们是否这样做,或者干脆放弃X ,并开始从第一复制oX 。您可以通过删除pick线在git rebase -i非常容易地执行后者。)

Once you have a commit copied-but-changed (or skipped and using the next commit), that commit itself has a new SHA-1 "true name", so every subsequent commit gets its own new ID as well. 一旦您提交了一个复制但已更改(或跳过并使用下一个提交)的提交,该提交本身就具有一个新的SHA-1“真名”,因此每个后续提交也将获得自己的新ID。 This makes a new chain that more or less parallels the old chain. 这使得新链或多或少与旧链相似。

What do you do next? 下一步你要怎么做?

In this case, you had no new commits of your own, so for you it's really simple: you just use git reset --hard again to point your development to commit C : 在这种情况下,您没有自己的新提交,因此对您而言,这很简单:您只需再次使用git reset --hard即可指向您的development以提交C

... - A - X - o ... - o - B   [abandoned]
       \
        Y - o - ... - o - o - C   <-- development, origin/development

If you had your own commits on your development , you would have had a harder job, or at least, you would have if you wanted to continue cooperating with origin : you'd have to copy your commits, and only your commits, from your development to add copies onto the end of their commit C . 如果您在development拥有自己的提交,那么您的工作将会更加艰苦,或者至少,如果您想继续与origin进行合作,您将不得不:从您的副本中复制提交,并且复制您的提交将副本添加到提交C末尾的development

(Git now has a nice way to do this semi-automatically, using --fork-point , but it's still a bit annoying and difficult. This is why it's generally bad for an "upstream" like origin to rewind-and-replace history: it forces everyone "downstream" to do extra work.) (GIT现在有一个很好的方式,自动半做到这一点,利用--fork-point ,但它仍然是一个有点恼人的和困难的。这就是为什么它是普遍不好的“上游”之类origin倒带和替换历史:它迫使每个“下游”人员都要做额外的工作。)

Aside: what happens to the "abandoned" commits? 撇开:“被遗弃的”提交会怎样?

They stick around for a while, 30 days by default, findable through git's "reflogs". 它们停留了一段时间,默认为30天,可通过git的“ reflogs”找到。 After that, their permanence goes away because the reflog that keeps them around expires. 此后,它们的持久性消失了,因为保留它们的引用日志到期了。 So it's not quite true that commits are permanent; 因此,提交是永久的并不是很正确。 instead, they're read-only, but get removed (garbage-collected) once they are "unreferenced". 取而代之的是,它们是只读的,但是一旦它们被“取消引用”,它们就会被删除(垃圾回收)。

As long as you keep a visible reference (like a branch or tag name) pointing to them, though, they'll remain in your repository. 但是,只要您保留指向它们的可见引用(如分支或标签名称),它们就会保留在您的存储库中。

This leads to a way to think about git commits without driving yourself crazy: the commits are permanent, but the labels move. 这导致了一种思考git commit的方法,而又不会使自己发疯: 提交是永久的,但是标签会移动。 For "normal" commits, the label simply moves to the newly-added commit. 对于“普通”提交,标签仅移至新添加的提交。 When you use a command like "git rebase" to "change history", git simply copies the old commits, then pastes the label on the end of the new chain of commits. 当您使用“ git rebase”之类的命令来“更改历史记录”时,git只会复制旧的提交,然后将标签粘贴到新的提交链的末尾。

(This is also how git commit --amend works: it doesn't change the final commit, instead it makes a new commit whose parent is the same as the parent of the old commit, and then moves the branch label. That is: (这也是git commit --amend工作方式:它不会更改最终提交,而是进行一个新提交,其父代与旧提交的父代相同,然后移动分支标签。即:

... - C - D   <-- label

becomes: 变成:

        D     [abandoned]
       /
... - C - D'   <-- label

If you close your eyes to D and ignore the little tick on D' , it looks like you've changed the final commit.) 如果您不理会D而忽略D'上的小勾号,则好像您已更改了最终提交。)

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

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