git的同事的混帐推--force后拉--rebase丢失提交

问题描述 投票:8回答:4

我想我明白了--rebase怎么拉的git的工作,但这个例子是混淆了我。我也能猜到以下两种情况下会产生相同的结果,但它们之间的区别。

首先,工作的人。

# Dan and Brian start out at the same spot:
dan$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556

brian$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556

# Separately, each makes a commit (different files, no conflict)
dan$ echo 'bagels' >> favorite_foods.txt
dan$ git commit -am "Add bagels to favorite_foods.txt"

brian$ echo 'root beer' >> favorite_beverages.txt
brian$ git commit -am "I love root beer"

# Brian pushes first, then Dan runs `git pull --rebase`
brian$ git push

dan$ git pull --rebase
dan$ git log
commit 9e1140410af8f2c06f0188f2da16335ff3a6d04c
Author: Daniel
Date:   Wed Mar 1 09:31:41 2017 -0600

    Add bagels to favorite_foods.txt

commit 2f25b9a25923bc608b7fba3b4e66de9e97738763
Author: Brian
Date:   Wed Mar 1 09:47:09 2017 -0600

    I love root beer

commit 067ab5e29670208e654c7cb00abf3de40ddcc556
Author: Brian
Date:   Wed Mar 1 09:27:09 2017 -0600

    Shared history

这样精细的作品出来。在其他情况下,想象丹推,然后布莱恩(粗鲁)推--force'd了他的承诺。现在,当丹运行git pull --rebase,他的承诺也没有了。

# Dan and Brian start out at the same spot:
dan$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556

brian$ git rev-parse HEAD
067ab5e29670208e654c7cb00abf3de40ddcc556

# Separately, each makes a commit (different files, no conflict)
dan$ echo 'bagels' >> favorite_foods.txt
dan$ git commit -am "Add bagels to favorite_foods.txt"

brian$ echo 'root beer' >> favorite_beverages.txt
brian$ git commit -am "I love root beer"

# THIS TIME, Dan pushes first, then Brian force pushes.
dan$ git push

brian$ git push --force

dan$ git pull --rebase
dan$ git log  # Notice, Dan's commit is gone!
commit 2f25b9a25923bc608b7fba3b4e66de9e97738763
Author: Brian
Date:   Wed Mar 1 09:47:09 2017 -0600

    I love root beer

commit 067ab5e29670208e654c7cb00abf3de40ddcc556
Author: Brian
Date:   Wed Mar 1 09:27:09 2017 -0600

    Shared history

分支的起源版本有相同的状态Brian的push --force后,因为它在第一个场景一样,所以我预计从git pull --rebase相同的行为。我很困惑,为什么丹”提交丢失。

我明白拉--rebase说“把我的局部变化,和远程的人后应用它们。”我不希望当地的变化被扔掉。此外,如果丹已运行git pull(无--rebase),他承诺不会丢失。

那么,为什么丹输当他遇到git pull --rebase他的地方犯?将力推对我来说很有意义,但不应该只是将遥控器相同的状态,就像布赖恩先推了?

我怎么思考这个问题?

git version-control rebase
4个回答
12
投票

TL;DR: it's the fork point code

你得到git rebase --fork-point,其中故意丢弃丹的从你的资料库提交过的效果。参见Git rebase - commit select in fork-point mode(虽然我的答案有我不提东西,我将在这里)。

如果你自己运行git rebase,你选择--fork-point是否使用。该--fork-point选项时使用:

  • 您运行git rebase没有<upstream>参数(--fork-point是暗示),或
  • 您运行git rebase --fork-point [<arguments>] <upstream>

这意味着,在您的上游底垫,而无需应用--fork-point,你应该使用:

git rebase @{u}

要么:

git rebase --no-fork-point

一些细节的Git版本的依赖,因为--fork-point成为只在Git版本2.0的一个选项(但由于1.6.4.1曾被秘密地做git pull与方法变得越来越复杂,直到整个--fork-point东西被发明)。

Discussion

正如你已经知道,git push --force粗暴地覆盖分支指针,滴一些现有的承诺(或多个)。您预计,虽然,你会git pull --rebase恢复下跌承诺,因为你已经自己了吧。为了方便命名,让我们使用您的命名,其中丹的承诺时,布赖恩力推动被丢弃。 (作为一个记忆,让我们说“丹未能同步”。)

有时它会!有时候,只要你的Git有丹的承诺在你的仓库,你有丹在你的历史承诺,丹的承诺,当你衍合的提交将得到恢复。这包括你在哪里丹的情况。然而,有时不会,这也包括你在哪里丹的情况。换句话说,它不是基于你在所有谁。

完整的答案是有点复杂,而且值得注意的是,这种行为是你能控制的。

About git pull (don't use it)

首先,让我们做一个简短的说明:git pull,本质,只是git fetch后接git mergegit rebase.1您选择提前哪些命令通过提供--rebase或设置一个配置项,branch.branch-name.rebase运行。但是,您可以运行git fetch自己,然后运行git mergegit rebase自己,如果你这样做这样,你就可以使用额外options.2

最重要的是选择主要选项(合并VS重订)之前检查的获取结果的能力。换句话说,这让你有机会看到,有一个提交下降。如果你做了一个git fetch较早,并得到丹的承诺,那么,有或没有,你可能会或可能不会纳入丹的承诺,做了第二git fetch,你会看到这样的事情任何干预工作:

 + 5122532...6f1308f pu         -> origin/pu  (forced update)

请注意“(强制更新)”注释:这是什么告诉你,丹得到了降低。 (此处使用的分支名称是pu,这是一个在GIT回购GIT中,定期获得力更新,我只是剪切和粘贴在这里的实际git fetch输出)。


1还有几个耍赖技术上的差异,特别是在非常旧版本的Git(1.8.4之前)。还有,因为我最近提醒,一个等特殊情况下,在对当前分支没有提交(通常,进入一个新的空库)存储库中的git pull:这里git pull调用既不git merge也不git rebase,而是运行git read-tree -m和,如果成功,将分支名称本身。

2I认为你能提供的命令行上的所有必要的参数,但是这不是我的意思。特别是,有能力运行的获取和第二步就是我们想要的之间的其他Git命令。

Basics of git rebase

了解git rebase的主要和最根本的是,它的副本提交。该为什么本身根本的Git:什么,没有人,并没有Git的本身,可以更改提交(或任何其他的Git对象)任何东西,作为一个Git对象的“真实姓名”是其内容的加密哈希。 3因此,如果你把一个犯了数据库,任何修改,甚至单个位,去把目标回,你会得到一个新的,不同的哈希:一个新的和不同的提交。它可以是非常类似于原始,但如果它的任何一点以任何方式不同,它是一个新的,不同的提交。

要查看这些副本是如何工作的,提请提交图形的至少一部分。该图仅仅是一系列提交,从开始的最新-或尖犯,他的真名散ID存储在分支的名称。我们说这个名字指向承诺:

            D   <-- master

提交,我已经称为D这里,含有(作为它的散列的一部分提交数据)其父的散列ID提交,即,提交那是分支的尖端我们提出D之前。因此,“点”其父及其父点进一步回:

... <- C <- D   <-- master

该内部箭都向后这样的事实,通常不是很重要,所以我倾向于在这里忽略它们。当一个个字母的名字是不是很重要我只是画一个圆点每个承诺:

...--o--o   <-- branch

对于branchmaster“从分支”,我们应该得出两个分支:

A--B--C--D     <-- master
    \
     E--F--G   <-- branch

请注意,提交E点回犯B

现在,如果我们要重新基地branch,所以它配备后提交D(也就是现在master尖),我们需要承诺E复制到一个新的提交E'C“一样好”,除了它具有D作为其父(当然具有不同的快照作为其源基站以及):

           E'  <-- (temporary)
          /
A--B--C--D     <-- master
    \
     E--F--G   <-- branch

我们现在必须FG重复此,当我们都做了,使名称branch点到最后一个副本,G',放弃有利于新的连锁店品牌:

           E'-F'-G'  <-- branch
          /
A--B--C--D           <-- master
    \
     E--F--G         [abandoned]

这是git rebase是怎么一回事:我们挑选出一些组提交复制的;我们将它们复制到一些新的位置,一次一个,在家长的一阶(VS较为典型的儿童第一向后Git的顺序);然后我们再点的分支标签,最后复制提交。

请注意,这个工程即使是空的情况。如果名称branch直接指向B,我们重订它master,我们复制所有零个提交B之后到来,复制他们来D后。然后再点上的标签branch到最后复制的承诺,这是没有的,这意味着我们重新点branch提交D。这是完全正常的,在Git中,有几个分支名称都指向同犯。 Git的知道你是通过读取.git/HEAD,其中包含分支的名称哪个分支。分支本身-一些提交图形的是由图确定部。这意味着词“分支”是不明确的:见What exactly do we mean by "branch"?

还要注意,提交A没有父母的。这是资源库中的第一个承诺:没有先前的承诺。提交因此A是根犯,这仅仅是一个奇特的方式说“没有父母提交”。我们也可以有两个或更多的家长提交;这些都是合并的提交。 (我没有得出任何在这里,虽然,这往往是不明智的变基包含合并分支链,因为它根本不可能变基合并和git rebase必须重新执行合并接近它。通常git rebase只是完全忽略了合并,这导致其他问题。)


3Obviously,由Pigeonhole Principle,减少较长的位串,以固定长度的k比特的密钥散列任何必然有一些输入的碰撞。对于一个Git散列函数的一个关键要求是它避免意外碰撞。在“密码”的一部分是不是真的至关重要的Git的,它只是使得它很难(but of course not impossible)有人故意造成碰撞。碰撞造成的Git是无法增加新的对象,所以他们是坏的,但是,除了虫子在执行,他们实际上不破的Git本身,你自己的数据的Git只是进一步使用。


Determining what to copy

与垫底的一个问题在于确定哪些承诺复制。

在大多数情况下,它似乎很容易:你想让Git复制你的提交,而不是别人的。但是,这并非总是如此,在大型分布式环境中,用行政和管理人员等,有人来变基别人的提交有时是适当的。在任何情况下,这不是Git是如何做它摆在首位。取而代之的是,Git使用的图形。

命名提交 - 如,写branch,往往选择不只是承诺,而且还提交的父提交,父亲的父亲,等等,所有的方式回到根犯。 (如果有合并提交,我们通常选择所有其父的承诺,同时遵守他们都背对着根源。一个图可以有多个根,所以这让我们选择多股要回多根,以及分支与合并股回到一个根)。我们所说的集合中的所有的承诺,我们发现,从开始一个承诺,做这些父遍历,设定可达提交的时候。

出于多种目的,包括git rebase,我们需要让这个EN-集体选择停止,而我们使用Git的看中一套操作来做到这一点。如果我们写master..branch作为修订选择,这意味着:“所有提交的分支的末端到达,除了任何承诺从主尖到达。”再看看这个图:

A--B--C--D     <-- master
    \
     E--F--G   <-- branch

branch到达的提交是GFEBA。从master到达的提交是DCBA。所以master..branch指:从较大的A + B + E + F + G组减去掉A + B + C + d组。

在集减法,去掉一些从未有摆在首位很简单:你不需要做任何事情。因此,我们从所述第二组中删除A + B,留下E + F + G。另外,我喜欢用我不能在计算器上画法:即禁止的颜色红色提交(停止)和绿色(去),下面所有的向后箭头在图形中,从红色的提交(master)绿色表示承诺采取(branch)。只要确保红色绿色覆盖,或者你做红的第1,当你在做绿色不重它们上色。这是直观obvious4这样做环保先行,然后用红色覆盖;或做红的第1和不覆盖,给出了相同的结果。

无论如何,这是一种方式git rebase选择提交复制。底垫的文件称此为<upstream>。你写:

git checkout branch; git rebase master

和Git知道上色master提交红色和电流分支branch提交绿色,然后复制只是绿色的。此外,同样的名字 - master-告诉git rebase放在哪里的份。这是相当考究,高效:一个参数,master,告诉混帐东西都复制并放在哪里的份。

问题是,它并不总是工作。

在有些情况下它打破了几种常见的情况。当你想更加限制副本,例如,向上突破的一个大分支成两个较小的发生之一。另一个发生在一些,但不是全部,你自己的提交已被复制(樱桃采摘或squash-“合并”)到你衍合到另一个分支。罕见,但并非前所未闻的,有时上游刻意舍弃了部分提交,你也应该这么做。

对于一些案件,git rebase可以对付他们使用git patch-id:它实际上可以告诉大家,一个提交得到复制,只要两次提交具有相同的修补程序ID。对于其他人,你必须手动拆分底垫目标(重订调用此<newbase>)使用--onto标志:

git rebase --onto <newbase> <upstream>

这限制了提交复制到那些<upstream>..HEAD,同时开始<target>后的副本。这分裂了从<upstream>说法,意思是你现在可以自由选择任何<upstream>,消除正确提交,而不是某些设置由“中拷贝走”确定离开副本的目标。


4本是他们不想写出来证明数学家使用的短语。 :-)


The --fork-point option

如果你经常变基提交到一个origin/whatever分支(或类似)也就是本身,还定期与删除所有提交的目标特别是重建基础,可能很难决定哪些承诺复制。但如果你有,你origin/whatever引用日志,一系列的提交哈希值,显示一些提交在那里出现,但已不再,这是可能的Git使用该放弃,从到副本集中,一些或所有这些提交。

我不能完全肯定--fork-point内部是如何实现的(它没有很好的记录)。对于this answer,我做了一个测试版本库。该选项为,勿庸置疑,为了依赖性:git merge-base --fork-point origin/master topic返回从git merge-base --fork-point topic origin/master不同的结果。

对于这样的回答,我看着source code。这表明,Git会查找在第一个非选项的参数引用日志,称这arg1,然后使用它使用解析为一个提交ID的下一个这样的说法arg2,完全无视任何额外的参数找到一个合并基础。在此基础上,git merge-base --fork-point $arg1 $arg2的结果是essentially5的输出:

git merge-base $arg2 $(git log -g --format=%H $arg1)

作为the documentation says

更一般地,两次提交之间来计算合并基础从,一个由所述命令行上的第一次提交参数指定;另一提交的是一个(可能是假设的)提交即跨在命令行上的所有剩余的提交的合并。

因此,在合并基础不一定包含在每个提交的参数,如果指定了两个以上的提交。与git-show-branch(1)选项一起使用时,这是从--merge-base不同。

所以--fork-point试图找到当前哈希作为第二个参数,和上游的电流值的假想合并基础及其所有引用日志记录的值之间的合并基础。这是什么导致的下降提交,比如在我们的例子中的一个丹排除。

请记住,使用--fork-point模式仅仅修改,在内部,所述<upstream>参数git rebase(没有也改变其--onto目标)。比方说,例如,在同一时间,上游有:

...--o--B1--B2--B3--C--D    <-- upstream
                 \
                  E--F--G   <-- branch

,因为它是,的--fork-point的目的是检测这种形式的改写:

...--o-------------C--D     <-- upstream
      \
       B1--B2--B3           <-- upstream@{1}
                \
                 E--F--G    <-- branch

和“敲除”通过选择B1作为内部B2参数提交B3B3,和<upstream>。如果你离开了--fork-point选项,Git的一切意见这种方式来代替:

...--o-------------C--D     <-- upstream
      \
       B1--B2--B3--E--F--G  <-- branch

让所有的B提交是“我们的”。在上游分支中的提交是DCC的父o(及其父母,到根目录)。

在我们的特定情况下,Dan的承诺,那将被丢弃,类似的这些B承诺一一。它与下降和--fork-point保持着--no-fork-point


5如果,是一个使用--all,这将产生多个合并基地,该命令将失败,并打印什么。如果所得的(单数)的合并基础不是提交已经在引用日志,则该命令也将失败。第一种情况下与纵横交错合并作为最近的祖先产生。当选择的祖先是足够老在从引用日志过期,或者从来没有在它摆在首位出现第二种情况(我敢肯定,这两个是可能的)。我有这样的第二种失败的例子就在这里:

$ arg1=origin/next
$ arg2=stash-exp
$ git merge-base --all $arg2 $(git log -g --format=%H $arg1)
3313b78c145ba9212272b5318c111cde12bfef4a
$ git merge-base --fork-point $arg1 $arg2
$ echo $?
1

我觉得这里跳过3313b78...的想法是,它是那些“可能假设提交”我从文档中引用的一个,但实际上它是正确的承诺与git rebase使用,并且它是在没有--fork-point使用的一个。


Git before 2.0, and conclusion

在版本的Git之前2.0(或者1.9晚),git pull这是基础重建会计算这个叉点,但git rebase从来没有。这意味着,如果你想这样的行为,你不得不使用git pull得到它。现在git rebase--fork-point,你可以选择何时获得它:

  • 添加的选择,如果你绝对需要它。
  • 使用--no-fork-point或者如果你绝对不希望它一个明确的上游(如果有上游的默认@{u}就足够了)。

如果您运行git pull,你没有--no-fork-point option.6 无论git pull origin master(假设当前分支的上游origin/master)抑制叉点git rebase origin/master会,我不知道的方式:我主要是避免git pull。 据检测(见Breaking Benjamin's comment below),在Git中2.0+,git pull --rebase始终使用叉点模式,即使有额外的参数。


6AT至少,到现在为止我只是测试它,以确保。


0
投票

这两种方案都没有混乱,这就是push --force是如何工作的。

我就不解释了第一个场景,因为你也明白。但是,当brian$ git push --force这种情况发生第二种情形中,Brian的本地仓库不知道丹的推送到远程的任何东西。

因此,它需要Brian的本地副本和远程在那里取代一切,因为它是一个force push

git pullgit pull --rebase之间的区别是,

git pull会比较起源与当地的变化。这就是为什么即使你有,你可以git pull未提交更改。

git pull --rebase不会比与当地的变化,但它需要从原点一切。这就是为什么如果有未提交的更改不能git pull --rebase

希望你明白我说的。任何问题??


0
投票

是啊,这看起来正确的。 Brian的git push --force将要设置的最新承诺,因为他有他的本地系统上的一个。如果布赖恩第一拉任何最新提交,然后git push --force结果将是相同的。

下面是详细信息的好线索:Force "git push" to overwrite remote files


0
投票

是的,你git pull --rebase的理解是正确的。

这是因为提交9e11404推到远程,而在此之后,提交2f25b9a是强制更新远程(9e11404如果力从远程删除)。

当丹执行git pull --rebase,git的检测9e11404和产地/分支点2f25b9a。但混帐认为9e11404早已存在于远程(.git\logs\refs\remotes\origin\branch具有内容update by push 9e1140410af8f2c06f0188f2da16335ff3a6d04c),所以它什么都不做衍合。或者你可以使用git pull --rebase=interactive验证,你会发现它显示noop变基。如果你想手动变基原产/支顶9e11404,您可以在交互窗口中添加pick 9e11404,其结果将是你所期望的。

© www.soinside.com 2019 - 2024. All rights reserved.