如何从拉取请求中排除不必要的提交?

时间:2019-02-10 09:05:14

标签: git github

我在使用git branch时遇到问题。我发送了PR,将develop分支合并到master分支,但其中包括不必要的提交。我想将它们从PR中删除。

  1. 我从master分支创建了developer分支,并添加了D1,D2提交。最后,我使用'squash and merge'将developer分支合并到master。
  2. 然后我从developer分支创建了一个功能分支,并添加了F1,F2提交。
  3. 与此同时,另一个人向主分支添加了M4提交。
  4. 我发送了一个拉取请求,以将功能分支合并到master。但是,此PR包括D1,D2,F1和F2提交。

我必须从master创建功能分支。这是我的错误:(


 ───M1───M2───M3───S────M4 (master)
    └────D1───D2───┘ (develop)
              └────F1───F2 (feature)

除D1和D2之外,我如何仅包括F1和F2提交?

4 个答案:

答案 0 :(得分:2)

因为您与第一个PR进行了壁球合并,所以Git不知道D1和D2已经包含在 master 分支中。

您需要rebase --onto将功能分支转移到 master 分支的(最新)提交:

git checkout feature
git rebase develop --onto master

结果将如下所示:

───M1───M2───M3───S────────────M4 (master)
    └────D1───D2──┘ (develop)   └────F1───F2 (feature)

现在,您可以push --force进行功能分支并更新PR。或关闭旧的PR并使用当前功能分支创建一个新的PR。

BTW:从您的分支名称开始,我假设您正在使用一种GitFlow分支模型。在此模型中,您不应使用壁球合并将 develop 分支合并到 master 中,因为对于已合并的提交,您始终会遇到此问题。

答案 1 :(得分:0)

只要D1,D2更改已被合并到主服务器中,通过新的请求请求再次发送它们就不会有问题(D1,D2将不被视为更改)

但是,在这种情况下,您需要使用M1 Commit重新构建Develop分支,以便PR更新所有内容,并且GIT仅将F1和F2检测为新事物。

最简单的方法是从Master到Develop创建PR,并在提取更新后将其合并。

答案 2 :(得分:0)

请PR的作者创建另一个最新的master分支(或存储库中的目标分支)

作者必须挑选必需的提交,并在需要时进行压扁并提交

作者发送新的PR

答案 3 :(得分:0)

Rene's answer是正确的,但是所有这些花哨的(使用字符集的图表)图都从您自己的图开始有点偏离:

───M1───M2───M3───S────M4 (master)
   └────D1───D2───┘ (develop)
             └────F1───F2 (feature)

原因是这样的:

  

...我使用'squash and merge'将dev分支合并到master。

clicky GUI上的“压缩和合并”按钮或命令行上的git merge --squash进行新的提交,表示不是合并。代替:

...--M1--M2--M3--S--M4   <-- master
      \         /
       D1-----D2   <-- develop

您实际得到的是:

...--M1--M2--M3--S--M4   <-- master
       \
        D1--D2   <-- develop

S与任何D之间,也没有MM1D之间没有祖先/后代关系s。这就是后来的Git操作失败的原因。如果您进行实际合并,请使用普通的git mergegit merge --no-ff或非轻巧的GitHub clicky按钮,然后 then S(因此使用{{1 }}也将是M4D1的后代。

但这不是我们所拥有的。因此,当我们查看D2feature时,我们会看到:

master

我们看到...--M1--M2--M3--S--M4 <-- master \ D1--D2--F1--F2 <-- feature 具有从其尖端提交(包括尖端提交本身)可以到达的四个提交,而这些feature无法到达。无论是否有指向master的名称,这四个提交 D1-D2-F1-F1上的四个提交,而D2上也没有。 (commit feature在两个分支中,两个分支都位于其左侧。)

我认为,考虑这一点的一种好方法是,“南瓜合并”具有杀死刚刚合并的分支的副作用。提交masterM1实际上实际上是“死亡”的,必须至少被认为具有轻微放射性。作为Ahmad Khundaqji said,它们最终最终通常是无害的。但是它们会使您的提交历史看起来很丑陋,例如为什么一次提交两次,一次两次作为单独的提交,一次又一次一次较大的提交?,如果D1实际上是合并的而不是壁球-“合并”-更糟糕的是,由于D2的后代中提交的某些更改,它们可能稍后会引起冲突。

由于合并的分支现在“已死”,因此应将其删除。也就是说,您应该运行feature。但是在这样做之前,请确保提交Sgit branch -D develop本身不包含在其他任何分支中-当然,在您的情况下,它们。如果它们 包含在其他分支中,则必须将这些分支重构为不再具有这两个提交的变体。

请注意,“ rebase and merge”(GitHub上的另一个clicky按钮-实际上所有三个按钮都通过内部下拉菜单组合为一个按钮,但这只是表示三个不同按钮的一种方式)消灭分支机构的副作用,因为变基确实意味着将旧提交复制到新提交中,然后停止使用旧提交以支持新副本

我将以此方式绘制后D1图,并在图片中保留“死角” D2

git rebase --onto master develop feature

这样可以更清楚地了解develop F1'-F2' <-- feature / ...--M1--M2--M3--S--M4 <-- master \ D1--D2 <-- develop \ F1--F2 [abandoned] 在它们唯一的提交哈希ID下仍然存在。它们不再容易找到,因为我们没有可以用来查找它们的名称。 1 从名称F1开始,我们首先找到了替代品复制我们要使用的F2而不是feature,然后复制我们应使用的F2'来代替F2,然后复制F1',然后复制{{1} },等等,倒退到过去。像F1这样的Git命令不会找到我们已经替换为源自M4而非源自S的闪亮的新旧提交。

(而且, now 可以安全地删除git log,只要没有其他分支 也包含F2'。请注意,包含{{ 1}}自动包含M1,所以这是我们在这里唯一需要提及的内容。)

在长格式develop命令中,我们有三个有趣的参数(当然还有选项关键字D1)。从头开始并向后工作,正如Git wont要做的那样,我们有:

  • D2作为附加参数,the git rebase documentation称为D1:告诉git rebase --onto master develop feature首先进行--onto。如果您自己执行此操作,则可以忽略最后一个参数。这实际上只是传递给feature 2 ,因此它应该始终是分支名称。

  • [<branch>]就像git rebase文档所说的git checkout feature:告诉git checkout提交 not 进行复制。如果省略此参数,则Git将使用配置为目标(或当前)分支的上游的任何内容。该名称是通过git rev-parse传递的,因此几乎可以是任何东西:原始哈希ID,标记名,develop输出,分支名称,相对操作(如git rebase等)上。

  • [<upstream>]就像git rebase文档所说的git describe:告诉HEAD~2 将提交副本放置在哪里复制完成后,它将在哪里重新指向分支。如果忽略此设置,则默认为--onto master,并且像git rebase一样,它会通过<newbase>传递。

因此,此命令行git rebase命令的意思是:

  
    

检出<upstream>后,复制分支提示中所有可到达的提交,不包括<upstream>所标识的提交中也可到达的所有提交,并排除您{{ 1}},感觉需要省略。 3 按照正确的拓扑排序顺序执行副本,以便先复制较早,较少依赖的提交,然后再复制较高依赖性的提交。 。放置每个副本,以使 first 复制的提交紧跟git rev-parse所标识的提交之后。复制完最后一个提交后,请在分支名称git rebase周围加上中文名称,使其指向最后复制的提交,或者如果没有复制任何提交,则直接指向feature所标识的提交。

  

当Git完成此操作后,您最终会得到看起来像我们绘制它们的方式的提交。 (您也位于分支develop上,除非Git员工已修复了我认为的git rebase中的一个小错误-好像您告诉rebase从做master开始,但是您例如,feature继续运行,它应该最终将您留在master上,而不是feature上。当然,如果重新定位必须因冲突而停止,则停在“分离式HEAD”模式,但是当您git rebasegit checkout feature恢复或终止操作时,即使您 not {{1 }}。


1 名称,您可以通过它们找到存储在Git的 reflogs master和master >。任何参考名称的引用日志,包括分支名称的引用日志,都包含一个日志,该日志提交由哈希值标识的该分支名称(在某些特定时间戳记下)。例如,由feature完成的对引用的每次更新都会将 previous 值保留在引用日志中,并为 new 值添加时间戳记何时刚刚写入了新值(以及有关正在发生的事情的刷新消息)。

运行git rebase --continue以查看git rebase --abort的引用日志条目。 feature本身还有一个额外的引用日志。运行F2F1来查看那个。请注意,旧条目最终会过期;有关更多信息,请参见git update-ref refs/heads/feature子命令和the git reflog documentation

请注意,git reflog feature(这是您在此处使用的子命令)确实只运行refs/heads/feature,因此您可以在此处使用HEAD而不是git reflog

删除任何分支都会删除其引用日志,但是git reflog HEAD引用日志中的条目仍然保留。为了能够“取消删除”分支,已经计划在某些将来的Git版本中保存已删除分支的引用日志,但是这些计划还很笨拙,没有重点,并且在实现方面存在一些问题。

2 此处的 literally 一词在字面上是正确的,因为git reflog expire是一个很大的shell脚本。但是现在git reflog show的许多部分都是用C编写的。git log -g命令也是用C编写的,并且在构建Git时,您将构建共享某些后端实现代码的二进制文件。如果git log -g是C代码,它调用的后端代码与否则的git reflog二进制代码相同,那么 literally 是调用HEAD还是现在? 象征性的在这里, literal 一词的正确语义是什么? literal 是否应该要求匹配的前端和后端,或者仅要求匹配的后端?

3 git rebase自己忽略的提交是:

  • 合并提交。这些实际上是不可能复制的。在某些模式下,git rebase将根据需要重新执行合并。这些模式是旧的git checkout和新的改进的git rebase;两者都很棘手,我不会在这里描述它们。

  • 在对称差异的另一侧使用相同的补丁ID。也就是说,虽然重新编制文档讨论要查找要用git checkout复制的提交列表,但实际上它使用git checkout来找到两组提交的对称差异:从{{1} },但不能从上游参数获取,也可以从上游参数获取,但不能从git rebase访问。

最后一部分使用git rebase命令的功能来区分此类提交来自哪个“边”:可以复制 的提交在右侧-可从{{1}到达},但不是--preserve-merges中的。但是,在此对称差异左侧的那些是“过去的截止点”,但可以从--rebase-merges到达。在这种特定情况下,这些提交是<upstream>..HEAD本身和<upstream>...HEAD。因此,Git使用HEAD为这两次提交计算补丁程序ID。它还为每个要复制的候选对象计算补丁ID,在这种情况下为HEADgit rev-list --left-right。如果任何复制候选者的补丁程序ID与另一半中的任何补丁程序ID均匹配,Git会得出结论,它们必须已经被精心挑选到HEAD中,并且在复制过程中应予以省略。

这个结论通常是正确的,但在某些情况下,可能是错误的! 测试任何Git自动化操作的结果始终是一个好主意。出现问题的方法是,例如,如果一个提交本身将一行<upstream>固定在一行上,在上游系列中,一行上有一个另一个流浪<upstream>。这两个修复程序位于不同的源代码行上,并且可能具有不同的缩进,但对于S,它们是相同的更改 ,因为补丁ID是通过删除行号和一些空白来构造的。