首先,这是:
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
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
现在你创建了一个新的提交 D
。Git所做的是使用C
作为父提交,创建D
,然后将branch
指向D
。我们可以绘制如下图:
A
“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
(它也会发送你的
A
和
B
,但他们已经有这些了;再次强调,这些都是由那些大而丑陋的哈希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的后半部分——但让我们留到另一个时间再讨论。