`git rebase master`总是跟着`git pull --rebase`然后`git push --force`? [英] Is `git rebase master` always followed by `git pull --rebase` and then `git push --force`?

查看:290
本文介绍了`git rebase master`总是跟着`git pull --rebase`然后`git push --force`?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我将主分支重新分配给我:

my-branch>>

然后我看到状态: > ➜supporTool git:(my-branch)git status
在分支my-branch
你的分支和'origin / my-branch'有分歧,
并且有分别有12个和11个不同的提交。
(使用git pull将远程分支合并到你的程序中)
没有提交,工作树清理

我明白提交订单已经在本地和远程(my-branch)之间发生了变化。

这意味着我总是必须做 git pull --rebase 然后 git push --force 使远程具有相同的提交顺序?



1)为什么我需要拉动,如果我是这个分支的唯一开发人员,并且我在遥控器之前,在重新装上主机之前?



2)如果有人在本地进行了rebase之后,但是在推送之前使用 git push --force


解决方案

这不是已更改的提交'顺序



要真正了解所有这些,您需要知道三件事:




  • 分支名称只是指向提交。具体来说,任何一个分支名称,比如 my-branch ,都指向一个单独的提交,Git称之为分支的尖端 提示提交

  • 分支 - 我们人们认为的分支,就是提交的。这个链接是通过向后的箭头在内部实现的:每个提交都指向前一个提交。用这些内部向后的箭头收集一连串的提交,并且你得到一个由分支 name 指向的包含一系列提交的分支 tip : >

    < pre>

    o <-o <-o <-o < - branch2

    (内部箭头全部向后的事实通常不是非常重要,所以我倾向于将其作为:

      o  -  o  -  o<  -  master 

  • >提交。完成复制提交后,它会更改一个(且只有一个!)分支 name 指向新的,最小的提交。


>

例如,假设起始条件如下所示:

  .. 。 -  o  -  A  -  B < - 主,起源/主
\
C - D - E - F < - 我的分支,起源/我的-branch

在您的存储库中, master B 的$ c>(一个常规的普通本地分支名称) - 我给了它们所有单字母名称,这些名称更多 wieldy 比40个字符长的哈希值和 origin / master (所谓的远程追踪分支名称)。远程追踪分行名称 origin / master 也指向提交 B



提交 C F 在您的分支上 my-branch ,但不在分支 master 上。 (这会给你4个这样的提交,而你的 git status 显示了11个,所以这个图很简单。)你的远程跟踪分支名称 origin / my-branch 还指向提交 F



但是, code> git rebase master 。这拷贝每次需要拷贝的提交 - 在这种情况下,所有提交都是 C D E F 。 Git 通过枚举当前分支 my-branch 的所有提交来获得这个列表,它们是 not 在分支 master 上。请注意,两个分支都包含commit A 以及任何更早的提交,所以这些提交可以从 my-branch git rebase 会抛出列表。这个可达性的想法取决于那些向后指向的箭头;这是箭头指向后方的事实实际上很重要的地方。

所以,让我们看看如果和当 git rebase 成功复制这些提交。我们得到:

  ...-- o  -  A  -  B < -  master,origin / master 
\\\
\ C'-D'-E'-F'< - 我的分支
\
C - D - E - F < - origin / my-branch

commit C ,原始文件,并提交 C',副本是 C' B 之上。它有 A B 之间的不同,加上从 A C 1 同样, D'建立在 C',依此类推。



当rebase完成时,它移动当前分支名称指向新的提示最多的提交, F'。但它不会移动任何其他名称。特别是,它不会移动 origin / my-branch - 这很有意义,因为 origin / my-branch 是为了记住你的 Git在 origin 的Git中为原始的 my-branch ,当你的Git最后通过 git fetch git push 与Git交谈时。 b
$ b

请记住,所有这些操作都发生在您的存储库中。据推测, origin 上的存储库都未更改。这个大概可以让我们陷入困境,这就是有租约的东西出现的地方 - 但在我们深入研究之前,再看看上面的图表。计算 my-branch 中有多少个提交,但不在 origin / my-branch 上。然后,计算 origin / my-branch 但不在 my-branch 上的提交数量。



记住,为了计算,我们使用可达性:我们沿着内部箭头向后。提交 A 两个分支中的第一个。更新的 my-branch 包括 A - B - C'-D'-E'-F':它有 A 。旧的 origin / my-branch 包含 ACDEF :它有 A 。所以我们不再依靠点击 A ,这意味着 my-branch 现在有五个(不是四个)提交 origin / my-branch 缺少,而 origin / my-branch 有四个提交我的分支缺乏。 (比较这与你的 git status :你开始在 my-branch 上进行11次提交,它们也不在 master ,并以同样的方式增加1,所以你有12 -11。)



所有剩余在 origin / my-branch 上的提交当然是我们在重新绑定时复制的。 my-branch 中的额外一个是我们拿起的那个 master :commit B






左边的名字是您如何告诉Git哪些 tip 提交推送。右边的名字是你如何告诉你的Git你希望他们的 Git在 结尾改变哪个分支。


$ b $当然,这些都是一样的,所以你的Git可以让你省去重复:my-branch 的重复重复。 (这句话是由冗余部门提供给你的。)所以你只需运行:

  git push origin my-branch 

或者使用现代版本(2.0或更高版本)的Git, 2 甚至更简单:

  git push 

(这可以从当前分支的上游设置中找到正确的东西)。但是请记住这一点很重要,即 git push origin my-branch:my-branch ,即使用你的 my-branch 来查找提示提交,让你的 Git hand 他们的提交Git,并让你的Git询问他们的 Git设置他们的 my-branch 。 (这是如何以及为什么可以在你的资源库中使用不同的分支名称​​ ),而不是你经常需要的东西,但是当你时需要它, )



当你的Git联系他们的Git时,你的Git通过移交这些提示提交ID开始。 (你可以一次推送多个分支,因此提示ID是复数)。他们的Git然后检查他们是否已经提交 - 通过他们独特的40个字符的散列ID - 如果没有,就有你的Git发送提交的实际内容,包括提交的父ID。他们的Git根据需要请求更多对象内容。这个对话继续下去,直到你的Git给他们的Git一些他们认可的东西:他们已经拥有的哈希ID,比如我们的图中提交 B



此时,您的Git已经交出了您必须提交的所有提交(以及任何其他内部Git对象 - 树和blob--所有这些对象都有自己独特的哈希ID),现在它们的Git开始评估过程。






2 这里的关键是配置设置 push.default ,在Git 2.0及更高版本中,如果您不更改它,它是 simple 。在早期版本的Git中,默认情况下它被设置为匹配






< h3>强迫或不强制

Git围绕着添加新内容的想法



具体来说,每当您进行新的提交时,只需将其添加到到现有的提交链。无论您现在在哪个分支,您都可以添加一个新的提交并向前移动分支标签以容纳它:

  before:
...-- C - D - E< - 我的分支

之后:
... - C - D - E-- F< -my-branch

因此Git希望分支标签为像这样移动:添加新的提交。



因此,对于正常的 git push ,没有 --force ,如果结果只添加新的提交,Git将允许推送。推送的复杂程度并不重要:

  before:
...-- o - o --o< - 分支

之后:o ------- o
/ \
...-- o - o - o --o - o - o - o --- o< - 分支
\ /
o - o - o - o

关键问题是:它只是添加,或者是它 ?以上是允许的,但下面不是:

 之前:
...-- o - o --o - o< - 分支

之后:
... - o - o - x - x
\
o - o< - 分支

分支标签只指向 em> commit,所以如果Git允许这种推送,最上面两行(现在标记为 x-x )的最后两个提交将被忘记。 p>

现在,回头看看你的rebase图。如果您推送新的 my-branch ,所有原始提交 - 您复制的文件以便将它们放在 master - 将被忘记。



当然,这正是你想要的。您正在放弃原来的新副本。你也希望 origin 放弃它们。



这就是 - force 进来:一个 git push --force 设置你的Git传递给另一个Git的强制标志。他们的Git根本不需要遵从力标志,但是如果他们这样做了,它只会告诉他们:是的,继续,忘记一些提交。



强制标记强大



现在,只要上游来源 Git仓库实际上并没有改变,使用 --force 或者它的等效工作正常。您复制了您的 CDEF 提交链,并且您推送了这些副本,因此如果它们忘记原始 CDEF ,这很好。



如果 my-branch 真的对你来说是私人的,我们'重做。我们不需要担心其他可能性。但如果不是?如果某人 else 可能说哦,哇,分支 my-branch 有很棒的东西,那么让我添加提交 G 并按下并且他们这样做了,并且 origin 上的Git现在具有链 CDEFG

  ...-- o  -  A  -  B < -  master 
\\ \
\ C'-D'-E'-F'[建议的替换]
\
C - D - E - F - G < - - my-branch

注意缺少 origin / here:这是 原始存储库上的图形。所以它的名字中没有 origin / 。这也是为什么 F'是为 my-branch 建议替换的提示。



如果他们的Git接受了建议的替换,他们的Git将失去commit G 没有commit G ,所以您没有将它复制到新的 G' $ b

强制租借



- force- with-lease 选项实际上是从多处理计算机的比较和交换或CAS指令中获得的,而在内部,它实际上被称为cas选项。它的工作方式是你的Git告诉他们的Git:我相信你的 my-branch 指向提交 F ,我希望你强行将它改为 F'



如果实际上他们的Git的 my-branch 指向 F code> - force-with-lease 推送成功,替换它们的 my-branch ,使它现在指向<$ c

另一方面,如果他们的Git的 my-branch / code>更新后, - force-with-lease 推送失败。您现在可以运行 git fetch 来引导任何新的提交,并找出它们的 my-branch 标签实际上是确实指向。请注意,您的Git从您的 原产地/我的分支。有力量与租约的变体,可以让你更好地控制它,但你不可能需要它们。



这就是所有它确实:它只是将强大的力量变成稍微弱一些的力量,但只有......。然而,如果你试图与其他人合作,在分支机构上进行重新设计或重写,并通过一些中央服务器,强制租赁可能会非常有用。


I rebased my-branch over master:

my-branch>> git rebase origin/master

Then I see the status:

➜  supporTool git:(my-branch) git status
On branch my-branch
Your branch and 'origin/my-branch' have diverged,
and have 12 and 11 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
nothing to commit, working tree clean

I understand the commits order have changed locally vs. remote (my-branch)

so does this mean I always have to do git pull --rebase and then git push --force to make the remote with the same commits order?

1) why do i need to pull if I'm the only developer on this branch and I was ahead of the remote, before rebasing on master?

2) what happens if someone else pushed something to the remote after I did the rebase locally but before pushing with git push --force ?

解决方案

It's not the commits' order that have changed. It's the commits themselves that have changed.

To really understand all of this, you need to know three things:

  • Branch names just point to commits. Specifically, any one branch name, like my-branch, points to a single commit, which Git calls "the tip of the branch" or the tip commit.
  • Branches—what we humans think of as branches, that is—are chains of commits. This chaining is achieved internally through backwards arrows: each commit points backwards to a previous commit. Collect up a chain of commits, with these internal backwards arrows, and you get a branch tip, pointed to by a branch name, that contains a series of commits:

    ...<- o <- o <- o <- o <- o    <-- branch1
                \
                 o <- o <- o <-o   <-- branch2
    

    (The fact that the internal arrows are all backwards is usually not terribly important, so I tend to draw this instead as:

    o--o--o   <-- master
    

    which is a lot more readable.)

  • The way git rebase works is that it copies commits. Once it's done copying commits, it changes one (and only one!) branch name to point to the new, tip-most commit.

For instance, let's say the starting condition looks like this:

...--o--A--B            <-- master, origin/master
         \
          C--D--E--F    <-- my-branch, origin/my-branch

Here we have, in your repository, master (a regular ordinary local branch name) pointing to commit B—I've given them all one-letter names here, which are more wieldy than 40-character-long hashes—and origin/master (a so-called remote-tracking branch name). The remote-tracking branch name origin/master also points to commit B.

Commits C through F are on your branch my-branch, but not on branch master. (This gives you 4 such commits, vs the 11 shown in your git status, so this drawing is simplified.) Your remote-tracking branch name origin/my-branch also points to commit F.

But then you ran git rebase master. This copies every commit that it needs to copy—which in this case is all of C, D, E, and F. Git gets this list by enumerating all the commits that are on your current branch my-branch, that are not on branch master. Note that both branches contain commit A, and any earlier commits, so these are the commits reachable from my-branch that git rebase throws out of the list. This reachability idea depends on those backwards-pointing arrows; this is where the fact that the arrows point backwards actually matters.

So, let's see what happens if and when git rebase successfully copies those commits. We get:

...--o--A--B              <-- master, origin/master
         \  \
          \  C'-D'-E'-F'  <-- my-branch
           \
            C--D--E--F    <-- origin/my-branch

The difference between commit C, the original, and commit C', the copy, is that C' is built atop B. It has whatever is different between A and B, plus the changes from A to C.1 Similarly, D' is built atop C', and so on.

When the rebase finished, it moved the current branch name to point to the new tip-most commit, F'. But it does not move any other name. In particular, it does not move origin/my-branch—which makes a lot of sense, since origin/my-branch is meant to remember what your Git saw in origin's Git for origin's my-branch, when your Git last talked with their Git via git fetch or git push.

Remember, all this action has taken place in your repository. Presumably the repository over on origin is all unchanged. This "presumably" can get us into trouble and that's where the "with lease" stuff comes in—but before we dive into that, take another look at the graph above. Count how many commits are on my-branch and yet are not on origin/my-branch. Then, count how many commits are on origin/my-branch but not on my-branch.

Remember, for this counting, we use reachability: we follow the internal arrows backwards. Commit A is the first one on both branches. The updated my-branch includes A--B--C'-D'-E'-F': it has A. The old origin/my-branch includes A-C-D-E-F: it has A too. So we stop counting upon hitting A, and that means that my-branch now has five (not four!) commits that origin/my-branch lacks, while origin/my-branch has four commits that my-branch lacks. (Compare this to your git status: you started with 11 commits on my-branch that were not also on master, and gained 1 more the same way, so you have 12-vs-11.)

All of the remaining commits on origin/my-branch are, of course, the ones we copied when rebasing. The extra one on my-branch is just the one we picked up that's also on master: commit B.


1If the changes from A to C end up replacing, rather than augmenting, the changes from A to B, then the tree associated with C' matches the tree for C. However, the parent ID remains different: C's parent is A, while C''s parent is B. This alone suffices to make the two commits different: they will have a different 40-character SHA-1 hash.


Pushing, with or without force and maybe lease

It's at this point, while you have this new graph, that you have decided to git push to get some or all of your new commits copied into the repository on origin.

The most fundamental bit of git push—the part it always does—consists of contacting another Git, via some URL, and taking some of your commit(s) and handing them over to that other Git. The URL normally comes from the remote name, origin. A remote is, to a first approximation, just a short name for the full URL. So the remaining interesting part, in fact the more-interesting part, is this handing-over process.

To hand over some commit(s), you tell your Git to find specific tip commits, usually by giving your Git a branch name like my-branch. To tell your Git what to tell the other Git, you also give your Git another branch name. You separate these two branch names with a colon, as in:

git push origin my-branch:my-branch

The name on the left is how you tell your Git which tip commit to push. The name on the right is how you tell your Git which branch you'd like their Git to change on their end.

Usually, of course, these are the same, so your Git lets you leave out the repetitive repeating repetition of repeating :my-branch. (This sentence is brought to you by the Department of Redundancy Department.) So you just run:

git push origin my-branch

or, with a modern (2.0 or later) version of Git,2 the even-simpler:

git push

(this figures out the right stuff from your current branch's upstream setting). But it's important to keep in mind that this "means" git push origin my-branch:my-branch, i.e., use your my-branch to find the tip commit, have your Git hand their Git that commit, and have your Git ask their Git to set their my-branch. (This is how and why it's possible to use different branch names in your repository vs their repository—not something you'll need often, but when you do want it, it turns out to be extremely handy.)

When your Git contacts their Git, your Git starts by handing over these tip commit IDs. (You can push more than one branch at a time, hence "tip IDs" is plural.) Their Git then checks to see if they already have the commits—by their unique, 40-character hash IDs—and if not, has your Git send over the actual contents of the commit, including the commits' parent IDs. Their Git asks for more object contents as needed. This conversation goes on until your Git has given their Git something they recognize: a hash ID they already have, such as commit B in our drawing.

At this point, your Git has handed over all the commits you must give them (plus any additional internal Git objects—trees and blobs—all of which have their own unique hash IDs), and now their Git begins the evaluation process.


2The key item here is the configuration setting of push.default, which in Git 2.0-and-later is simple, if you don't change it. In earlier versions of Git it was set to matching by default.


To force, or not to force

Git is built around the idea of adding new stuff.

Specifically, whenever you make a new commit, you simply add it on to the existing commit chain. Whatever branch you're on now, you add one new commit and move the branch label forward to accommodate it:

before:
...--C--D--E   <-- my-branch

after:
...--C--D--E--F   <-- my-branch

Git therefore expects branch labels to move like this: to add new commits.

So, for a normal git push, without --force, Git will allow the push if the result only adds new commits. It doesn't matter how complex the push is:

before:
...--o--o--o    <-- branch

after:       o-------o
            /         \
...--o--o--o--o--o--o--o---o   <-- branch
               \          /
                o--o--o--o

the key question is: does it only add, or does it drop something? The above is allowed, but the below is not:

before:
...--o--o--o--o    <-- branch

after:
...--o--o--x--x
         \
          o--o    <-- branch

A branch label only points to one commit, so if Git were to allow this push, the last two commits on the top row (now marked x--x) would be "forgotten".

So now, look back at your rebase graphs. If you push the new my-branch, all the original commits—the ones you copied so as to put them after the tip of master—would be "forgotten".

Of course, this is precisely what you want. You are abandoning the originals in favor of the new copies. You want origin to abandon them as well.

This is where --force comes in: a git push --force sets the "force flag", which your Git passes along to the other Git. Their Git need not obey the force flag at all, but if they do, it simply tells them: "yeah, go ahead and forget some commits."

The force flag is too powerful

Now, as long as the upstream origin Git repository hasn't actually changed here, using --force or its equivalent works fine. You copied your C-D-E-F commit chain and you're pushing the copies, so if they "forget" the original C-D-E-F, that's just fine.

If my-branch really is private to you, we're done. We don't need to worry about other possibilities. But what if it's not? What if someone else might have said "oh, wow, branch my-branch has great stuff, let me just add commit G and push" and they did that and the Git over on origin now has the chain C-D-E-F-G:

...--o--A--B                <-- master
         \  \
          \  C'-D'-E'-F'    [proposed replacement]
           \
            C--D--E--F--G   <-- my-branch

Note the lack of origin/ here: this is a drawing of the repository on origin; so it does not have origin/ in its own names. That's also why F' is the tip of the proposed replacement for my-branch.

If their Git takes the proposed replacement, their Git will lose commit G. You don't have commit G, so you did not copy it to a new G'.

Force with lease

The idea behind the --force-with-lease option is actually taken from multiprocessing computers' "compare and swap" or CAS instructions, and internally, it's actually called the "cas" option. The way it works is that your Git tells their Git: "I believe your my-branch points to commit F, and I'd like you to forcibly change it to F'."

If, in fact, their Git's my-branch does point to F, the --force-with-lease push succeeds, replacing their my-branch so that it now points to the F' you gave them.

If, on the other hand, their Git's my-branch got updated, the --force-with-lease push fails. You can now run git fetch to bring over any new commits and find out where their my-branch label actually does point. Note that your Git gets its notion of where "their" branch points ("I believe your my-branch is...") from your origin/my-branch. There are force-with-lease variants that let you control this more finely, but you are unlikely to need them.

That is all it does: it just modifies the strong "force" into a slightly weaker "force, but only if ...". If you are trying to coöperate with someone else, though, working on a branch that gets rebased or rewritten like this and pushed through some central server, "force with lease" can be quite helpful.

这篇关于`git rebase master`总是跟着`git pull --rebase`然后`git push --force`?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

1 code> A C 最终取代,而不是增加 A B ,那么与 C' $ c>匹配 C 的树。但是,父母ID仍然不同: C 的父母是 A ,而 C' 的父母是 B 。这足以让两个提交不同:它们将具有不同的40个字符的SHA-1哈希。






推送,有或没有强制和可能的租赁



在这一点上,当你有这个新图,你已经决定 git push 将一些或全部新的提交复制到 origin 中的存储库中。

最常见的是 git push - 它始终会执行的部分 - 包括通过某个URL联系另一个Git,并接受一些提交并将它们交给那另一个Git。该URL通常来自远程名称, origin 。第一个近似值是一个遥控器,它只是完整URL的简称。所以剩下的有趣部分,实际上更有趣的部分就是这个移交过程。



要交出一些提交,你告诉你的Git通常通过给你的Git一个分支名称,比如 my-branch 来寻找特定的 tip 提交。要告诉你的Git告诉其他 Git,你还要给你的Git另一个分支名称。您可以使用冒号分隔这两个分支名称,如下所示:

  git push origin my-branch:my-branch 
查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆