与自己合并时出现冲突

7

我之前遇到过合并冲突,当时怀疑是自己引起的。现在我又遇到了同样的问题,这次我确定是我导致了冲突。

昨天我对我的分支进行了一些更改,commitpush了这些更改。这是该分支的第一个、也是目前唯一的提交(本地和远程)。

今天我又做了一些更改(显然是在同一文件中),添加了这些更改,并使用commit --amend命令提交了它们,然后试图push它们。

这就是我遇到合并冲突的地方:Updates were rejected because the tip of your current branch is behind

我解决了更改,添加、提交并推送了它们。现在一切都好了。

但我想从我的错误中吸取教训。那么,错误是什么?(我认为这与使用git commit --amend有关)

3个回答

5

amend -- 修改历史记录。

初始状态

Origin: A
Local:  A

然后你提交并推送了。
Origin: A - B
Local:  A - B

然后你使用了修改命令。
Origin: A - B
Local:  A - C // local commit B replaced by new commit C

然后您尝试推送并收到消息,需要先拉取。提交C包含来自B的更改,但它是不同的提交。因此,在拉取后会发生冲突。


4
首先,这是:

Updates were rejected because the tip of your current branch is behind

“不是合并冲突”。合并冲突是一种更具体的情况。它发生在尝试将两组不同的更改合并为一个时,但更改重叠的“三向合并”中。您正在运行“git push”,这不会合并任何内容。

与“git push”的相反操作不是“git pull”。 “git pull”命令是一个方便的命令,可以依次为您运行两个Git命令。第一个命令是“git fetch” - 这是“git push”的相反操作,或者至少是最接近的操作。 “git pull”运行的第二个命令通常是“git merge”,这可能导致合并冲突。或者,您可以指示Git先运行“git rebase”而不是“git merge”;“git rebase”也可能导致合并冲突。

需要记住的一些关键事实

这可能会变得非常令人困惑,为了使用Git,学习Git实际上如何执行所有这些操作非常重要,因为Git倾向于让原始实现显示出来。因此,作为基本概述,请记住以下内容:

  • What matters most, most of the time, is commits.
  • Every commit has a single "true name", which is its hash ID. Hash IDs are those big ugly 40-character things like face0ff (but longer). No two different commits ever have the same ID, and the ID is determined entirely by the contents of the commit.
  • The contents of a commit are a little bit detailed but not too long—to see one in its entirety, run git cat-file -p HEAD, which just prints it out—but the main important thing to remember is that the contents include:

    • the parent commit, which is again one of those hashes;
    • the commit message (which --amend seems to let you edit); and
    • the source tree that you commit, i.e., whatever you have git add-ed.


    When you use git commit --amend, this seems to change the commit, but it doesn't. It can't! The commit's ID is determined by its contents. If you change the contents, you get a new and therefore different commit.

  • Because commits have their parent IDs inside them, Git (and you) can draw a chain of commits, starting from the more recent ones and working backwards. Git runs everything backwards: we like to think of branches as going forwards. If we have a time line of commits, where we make commit A, then B, then C, and so on, we'd expect them to go "A then B then C":

    A -> B -> C
    

    but Git does them backwards, so they're actually:

    A <- B <- C
    
  • Therefore, a branch name just points to the tip-most commit. This is why Git keeps talking about "the tip of your branch".

  • And, that means we want to draw our graphs like this:

    A--B--C   <-- branch
    

    The name branch points to the latest commit, C. Git then goes backwards, from C to B to A. Most of the time we don't have to worry about that part, but we do need to remember that the name just points to the tip commit.

这影响分支的生长方式

分支名称指向分支末端。假设您有其中一条链:

A--B--C   <-- branch

现在你创建了一个新的提交 D。Git所做的是使用C作为父提交,创建D,然后将branch指向D。我们可以绘制如下图:

A--B--C--D   <-- branch

“git commit --amend”的作用非常简单明了。它不是通过让“D”指向“C”来将“D”附加在“C”的末尾,而是让“D”指向“B”(即“C”的父级)。然后,它通过让“分支”指向“D”,将“C”推到一边。”
     C
    /
A--B--D   <-- branch

现在我们也可以看到git push的作用了
当你运行git push时,你的Git会与另一个Git进行一些对话。你的Git告诉他们的Git关于你的提交。他们的Git告诉你的Git关于他们的提交。然后,你的Git将你的一些提交发送给他们的Git - 你有哪些他们没有的提交,并且你要求你的Git发送。
假设他们已经有A-B-C,而你有A-B-D。通过请求你的Git git push branch,你让你的Git发送给他们你的D(它也会发送你的AB,但他们已经有这些了;再次强调,这些都是由那些大而丑陋的哈希ID完成的,而不是简单的单字母名称,但思想是相同的)。
然后是最后一部分,这就是问题所在。你的Git要求他们的Git将他们的branch指向与你的branch相同的提交。也就是说,你要求他们将他们的branch指向D
你的D指向B,而不是C。如果他们将他们的branch设置为指向新共享的D,他们将“丢失”他们的C
当然,由于你的amend,你希望他们“丢失”他们的C,就像你的Git将它推到一边时所做的那样。所以你可以使用--force。强制标志将你的Git的礼貌请求 - “请,如果可以的话,将你的branch更改为指向D - 变成了一个命令:“立即将你的branch设置为指向D!”他们仍然可以拒绝,但通常他们会服从。
如果只有C被“丢失”,那就没关系:这就是你想要的。但是,如果他们的Git也是其他人推送的Git,可能第三个人,拥有第三个Git存储库,已经编写了提交E并将其推送到他们的Git,使得他们实际上有A-B-C-E。如果你将你的D发送给他们,它指向B,并让他们将他们的branch设置为指向你的D,他们将失去C-E链,而不仅仅是C提交。
这就是为什么(以及何时)修改是不好的。
如果你修改了已经分享过的提交,可能会有其他人在此基础上进行开发。如果你现在“撤回”这个提交,可能会给其他人带来问题。
另一方面,如果你从未分享过提交,或者其他Git是你自己的私有存储库,或者你和所有第三方预先同意这种“撤回”是正常和预期的,那么你就不会有问题。
否则——如果“撤回”是不好的——那么当其他Git有一个提交时,你应该确保你推送的任何新提交仅仅是添加到他们的提交中。你可以通过合并或变基来实现这一点,这就是git merge或git rebase的用处。恰巧的是,这两个命令是git pull的后半部分——但让我们留到另一个时间再讨论。

1
是什么错误?(我猜跟使用git commit --amend有关)
是的,与使用git commit --amend有关。通过修改提交的SHA1(改变提交的SHA1),您已经覆盖了git历史记录。如果未发布更改(推送到某些远程分支),则Amend没问题,但一旦您这样做了,您真的不应该在历史记录中操作,因为如果有人已经获取了您的更改,则当他们再次尝试拉取时,可能会失败(提示:与其修改提交,您应该只需创建一个具有更改的新提交)。
现在,git将拒绝使用您的分支更新远程分支(具有修订公共提交),因为您的分支头提交不是您要推送到的分支的当前头提交的直接后代。
如果你想用经过修改的本地历史记录替换远程历史记录,你需要进行强制推送。 如果不这样做,Git将拒绝推送,并要求你拉取(在这种情况下没有帮助,因为你会合并相同内容但不同提交消息的内容)。
然而,git push --force-with-lease更安全。点击链接了解更多信息。
如果您在检查分支时发现要强制或删除的分支在“origin”处发生了某些情况,可能会导致丢失其他人的工作。不知道重置和重建分支决策的人可能会尝试在您获取并重新设置它的时间和推送以用重置结果替换它的时间之间推送到该分支。
我们可以通过可选地允许用户告诉“git push”来使这些推送更加安全:
我正在基于“branch”的值仍然是这个对象的假设进行强制/删除。 如果该假设不再成立,即如果自我准备此推送以来分支发生了某些事情,请不要继续并失败此推送。

1
谢谢。这也非常有帮助。 - Bernd Strehl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接