Git重新建立了一个分支,该分支不包含任何新提交,也没有推动力?

时间:2019-01-13 08:21:38

标签: git git-merge rebase git-rebase

很抱歉,这个问题已经被遮盖,但是我找不到遮盖器。

我在这里是因为工作中非常痛苦的经历:我将master重新设置到错误的分支,然后执行git push。 有一瞬间似乎把事情搞砸了。现在,我试图了解出了什么问题,以及即使我没有使用--force标志,为什么仍要推送我的更改。

有关git分支策略的一些信息:

我们有不同的较旧的“版本”分支,在这些分支上,我们仅实现错误修复(分支v1.0,分支v2.0等),因为我们需要支持这些较旧的版本。然后是master分支,我们实际上在该分支上实现了新功能。

每当在旧版本的软件中发现错误时,分支v1.0,我们从该版本分支出来,为错误修正创建功能分支。然后,我们将该功能分支中的更改重新基于v1.0分支,然后在v1.0上进行快速合并。然后通过合并提交,将v1.0合并到v2.0,最后合并到master,以便在该产品的所有较新版本中也修复了该错误。因此,在较旧版本的分支(例如,分支v1.0)上进行错误修复/更改的流程如下:

  1. 从v1.0分支(创建功能分支)
  2. 进行一些更改
  3. git rebase origin/v1.0将更改从功能分支移至最新的v1.0分支
  4. git push -f origin feature_branch
  5. 快进合并功能分支到v1.0
  6. 通过合并提交将v1.0合并到v2.0
  7. 通过merge-commit将v2.0合并到master
  8. 现在,在v1.0上进行的所有更改(仅限错误修正)也都在软件的较新版本上

例如,对于软件v1.0的错误修正,合并过程如下:

FB -> v1.0 -> v2.0 -> master

简而言之:v1.0是产品的最旧版本,v2.0将包含v1.0中的所有提交以及在v2.0版本中进行的其他功能提交,而master将包含所有从v2.0提交的内容以及用于该产品新版本的其他功能的提交。

我的错误操作: 因此,正如我所说,将错误修正合并回父分支时,我需要首先在父分支的基础上重新进行更改,因为与此同时父级上可能还有其他更改,然后进行快进合并回到父母身边。

我当时正在开发仅应进入master分支的功能,所以自然而然地我试图将master改组到我的分支中,以使我的更改超越所有其他更改。 但是我没有将master分支重新部署到我的功能分支中,而是在v1.0分支上,并将master重新部署到了v1.0分支中(因此,不是我的Feature分支)。重新建立到v1.0分支中。更糟糕的是,在没有进行彻底检查的情况下,我也将v1.0分支推入。 结果:v1.0分支现在看起来完全像master ..不好。

现在我的问题是:我只是执行了错误的git push,而不是--force push v1.0分支。 我对重新基准化的理解是,在进行重新基准化时,您将重写分支的历史记录,因此您需要使用git push -force,否则远程将不接受您的更改。 在这种情况下,由于master已经包含了v1.0分支的所有提交以及v1.0分支不包含的其他附加提交,因此在这种情况下是否没有历史记录重写?

我真的很想正确地理解这一点,因为如果我需要进行强制推动,就会有更多的警钟为我响起,我想这不会发生。

2 个答案:

答案 0 :(得分:2)

这有点长,因为您提出要真正了解正在发生的事情,因此,我将提供更多信息,而不仅仅是直接回答您的问题。但是,如果您没有其他选择:在推送前验证您的本地状态。 (紧接着,第二步:对力量推动持怀疑态度。)


人们习惯于认为“ rebase” ==“需要强行推动”,两者在某种程度上是相关的。不仅仅是强制迁移的行为产生了强制推送的需要。这是从分支的历史记录中删除提交的行为(例如branchX),然后仅是需要强制推动的branchX。

因此,请记住这一点,让我们逐步了解您的工作流程-首先是要正常工作,然后才是发生此错误的过程。首先,您的仓库可能看起来像

... O <--(origin/v1.0)(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

这里...的意思是“我们并不真正关心的一些历史”,x的意思是“一次提交,但在本次讨论中我将不专门提及该历史”,{ {1}}的意思是“合并提交,但在本次讨论中我将不专门提及该名称”。其他字母表示我可以按名称引用的提交。如果我可以按名称引用合并,则将其命名为M。然后M1/\显示提交之间的父子关系(右边的新提交),并且parens中的名称是带有箭头的引用(例如分支)显示引用的当前提交。

除了本地分支机构之外,我还展示了远程跟踪参考-即您的仓库对分支机构在远程位置的了解。

所以...

预期行为

1)从v1.0分支出来

--

在这里,我们刚刚创建了一个新引用,它指向与版本分支相同的提交。

2)进行一些更改

... O <--(origin/v1.0)(v1.0)(feature_branch)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

3) A <--(feature_branch) / ... O <--(origin/v1.0)(v1.0) \ .. M -- x <--(origin/v2.0)(v2.0) \ ... M -- x <--(origin/master)(master)

这一步似乎有些悲观。您是否经常对产品的旧版本进行同时更改?如果没有,我只会考虑对git rebase origin/v1.0进行新更改的情况下,将其作为异常处理步骤。上图不会改变,但是如果我们假设有中间变化

v1.0

然后此步骤将为您

      A <--(feature_branch)
     /
    | B <--(origin/v1.0)
    |/
... O <--(v1.0)
     \
   .. M -- x <--(origin/v2.0)(v2.0)
            \
         ... M -- x <--(origin/master)(master)

请注意, A' <--(feature_branch) / B <--(origin/v1.0) / | A |/ ... O <--(v1.0) \ .. M -- x <--(origin/v2.0)(v2.0) \ ... M -- x <--(origin/master)(master) 是“已移动”,因此feature_branch已从其历史记录中删除,而A和一个新的提交(B-的修补程序副本A')已添加到其历史记录中。

我仍在图片中显示A,因为它仍然存在,尽管目前没有人引用它。 (但是稍后我将再次谈论它……)这强调了一个重要的观点,这常常被误解:A 没有“移动” rebase。它创建了一个新提交A,与A'不同。这就是为什么我说A已从A的历史中删除。

无论如何,所有其他裁判都保留了他们已经拥有的所有历史记录。

4)feature_branch

这有点令人困惑,因为您没有证明自己以前曾按过git push -f origin feature_branch。如果没有,则不需要feature_branch标志-因为即使您从-f的本地历史记录中删除了提交,遥控器也对此一无所知。

也就是说,完善我上面所说的内容-仅当从您的历史记录中删除了属于该分支的远程历史记录的提交的引用中推入引用时,才需要强制推入。 / p>

因此,让我们假设在重新定基之前,您已经 推送了feature_branch,并且该图看起来真的像

feature_branch

(这是我在图中保留 A' <--(feature_branch) / B <--(origin/v1.0) / | A <--(origin/feature_branch) |/ ... O <--(v1.0) \ .. M -- x <--(origin/v2.0)(v2.0) \ ... M -- x <--(origin/master)(master) 的真实原因。)现在,如果没有A标志,您将无法推动feature_branch,因为该推动会删除{{1 }}来自远程-f的历史了解。

但是现在是个好时机……基于我对步骤3的评论,您应该警惕使用强制推力作为正常步骤的工作流程。就像远程用户知道Afeature_branch的一部分,并且必须告知历史记录是否已被编辑一样,是否还有其他开发人员进行过Afeature_branch编辑fetch ,则强行推将使他们的存储库处于损坏状态。他们将不得不恢复,特别是如果他们在pull上进行了其他更改;如果操作不正确,则可能撤消重新设置的基准。

也就是说,后feature-branch张图片应该是

feature-branch

(这一次我删除了push,因为我们已经担心了它。它仍然存在,在引用日志中仍然可以访问,但是最终 A' <--(feature_branch)(origin/feature_branch) / B <--(origin/v1.0) / ... O <--(v1.0) \ .. M -- x <--(origin/v2.0)(v2.0) \ ... M -- x <--(origin/master)(master) 会销毁它,除非您采取措施将其复活)

5)将A快速合并到gc

大概您也想在快进之后按下feature_branch。因为它是快速前进的(即使对于远程用户也是如此),所以不需要强制推动;也就是说,远程 ever 视为v1.0的一部分的所有提交仍是v1.0的一部分。

v1.0

5和6)向前合并

这很简单,也不必强行推动。

v1.0

好的。现在,据我了解,从... O -- B -- A' <--(v1.0)(origin/v1.0)(feature_branch)(origin/feature_branch) \ .. M -- x <--(origin/v2.0)(v2.0) \ ... M -- x <--(origin/master)(master) 构建功能时出现了问题。此时,我将为几个... O ----- B ---- A' <--(v1.0)(origin/v1.0)(feature_branch)(origin/feature_branch) \ \ .. M -- x ------- M <--(or-igin/v2.0)(v2.0) \ \ ... M -- x -- M <--(origin/master)(master) 提交添加不同的名称,并删除一些我们将不讨论的引用

master

因此,在执行步骤1和2之后,您已经

x

但是随后您开始完成上述工作流程,就好像它是一项... O ----- B ---- A' <--(v1.0)(origin/v1.0) \ \ .. M -- V ------- M <--(or-igin/v2.0)(v2.0) \ \ ... M -- W -- M <--(origin/master)(master) 功能一样。所以对于第3步

... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)
                        \
                         C -- D <--(feature2)

正如您所知,这将很麻烦。当前分支历史记录中的所有记录,而不是v1.0历史记录中的所有记录,都被视为“需要复制”。

git rebase origin/v1.0

合并提交将被忽略(origni/v1.0的默认行为;尽管冲突解决和/或“恶意合并”可能会打破此假设,但无论如何它们都不会带来明显的变化)。但是 V' -- W' -- C' -- D' <--(feature) / ... O ----- B ---- A' <--(v1.0)(origin/v1.0) \ \ .. M -- V ------- M <--(or-igin/v2.0)(v2.0) \ \ ... M -- W -- M <--(origin/master)(master) \ C -- D rebase不会被忽略。与往常一样,请注意,VW仍然存在,并且您所依据的当前分支的每个分支除外的历史记录保持不变。

与上述工作流程一样,您现在可以按V。而且如上所述,如果您在改组基准之前曾经W改过feature,现在就必须强行推入它... 但是您的典型工作流程却使您一直期待着,因此尽管它应该发出红色标记,但不会。

无论哪种方式,您都可以看到push会很高兴地快速前进到feature(因为v1.0的历史包括了feature的所有历史),这意味着feature将无力推动。

这就是问题的出处,但是下一步该怎么做?

我的第一条建议是减少对随意推力的适应。乍一看,由于强制推送和历史重写(例如v1.0)在某种程度上是相关的,这听起来可能是使用合并而不是重新设置基础的原因……但这并没有真正的帮助。如果您的功能已在v1.0

的分支上进行了编码
rebase

但随后错误地认为您需要转到master,合并将以静默方式运行,并且结果将同样错误。

... O ----- B ---- A' <--(v1.0)(origin/v1.0)
     \              \
   .. M -- V ------- M <--(or-igin/v2.0)(v2.0)
            \         \
         ... M -- W -- M <--(origin/master)(master)
                        \
                         C -- D <--(feature2)

这仍然将所有v1.0 -------------------------------- M <--(v1.0) / / ... O ----- B ---- A' <--(origin/v1.0) | \ \ | .. M -- V ------- M <--(origin/v2.0)(v2.0) | \ \ | ... M -- W -- M <--(origin/master)(master) | \ / C ---------------------- D <--(feature2) 更改合并到v2.0中。

那你能做什么?

您可以基于master不会收到相冲突的更改的乐观假设来构建工作流。在这种情况下,您会

1)从v1.0创建分支 2)进行更改 3)将v1.0快进到v1.0v1.0)上 4)尝试不加力地推feature

现在,如果您尝试将更改合并到错误的分支中,则合并很可能会失败(由于git merge --ff-only feature)。这只有在分支实际上已经分开的情况下才有帮助;但这至少不比现状差。

如果您确实转至正确的分支,并且步骤4成功,则说明您已完成。如果步骤4失败并给出有关非快速更改的错误,则(因为这是例外情况,而不是预期的步骤),它提示您应检查并确保为什么失败。如果一切正常,那么接下来您可以

v1.0

这是获取远程更改并在其上重新建立本地更改的简写。根据文档,这被视为“潜在危险”操作,但您正在执行的操作也是如此。并且至少在它周围放了一些结构,这样,只要您正确地合并,它就会按照您的意图执行。

然后,根据习惯,您应该始终快速查看一下本地结果,然后再进行处理-因为问题总是在解决之前更容易解决。因此,请查看日志,如果发现任何异常,请对其进行检查。返回到需求/故事/卡片/告诉您进行工作并验证要添加到哪个分支的所有内容。也许可以使用--ff-only之类的工具来可视化仓库的总体状态。

底线git pull --rebase 非常灵活且功能强大,因此,如果您明确地告诉它执行错误的操作,则可能会执行。这意味着您必须知道要告诉它做什么。好消息是,从错误中恢复通常不会太困难。容易gitk犯错才是最容易的,但是总有一种或多或少的方法。

答案 1 :(得分:1)

基本上没有。在基于分支的基础上,可以使用该(整个)分支作为要进行基础重构的提交的根(起点)。

因此,您没有更改刚刚添加到v1.0的现有历史记录。无需强行推动。

关于将来如何防止这种情况的最佳建议是避免重新定基础。从v1.0分支以创建修订。您已经在使用merge将更改带入master。