我在使用git branch时遇到问题。我发送了PR,将develop分支合并到master分支,但其中包括不必要的提交。我想将它们从PR中删除。
我必须从master创建功能分支。这是我的错误:(
───M1───M2───M3───S────M4 (master)
└────D1───D2───┘ (develop)
└────F1───F2 (feature)
除D1和D2之外,我如何仅包括F1和F2提交?
答案 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
之间,也没有M
除M1
与D
之间没有祖先/后代关系s。这就是后来的Git操作失败的原因。如果您进行实际合并,请使用普通的git merge
或git merge --no-ff
或非轻巧的GitHub clicky按钮,然后 then S
(因此使用{{1 }}也将是M4
和D1
的后代。
但这不是我们所拥有的。因此,当我们查看D2
和feature
时,我们会看到:
master
我们看到...--M1--M2--M3--S--M4 <-- master
\
D1--D2--F1--F2 <-- feature
具有从其尖端提交(包括尖端提交本身)可以到达的四个提交,而这些feature
无法到达。无论是否有指向master
的名称,这四个提交 是D1-D2-F1-F1
上的四个提交,而D2
上也没有。 (commit feature
在两个分支中,两个分支都位于其左侧。)
我认为,考虑这一点的一种好方法是,“南瓜合并”具有杀死刚刚合并的分支的副作用。提交master
和M1
实际上实际上是“死亡”的,必须至少被认为具有轻微放射性。作为Ahmad Khundaqji said,它们最终最终通常是无害的。但是它们会使您的提交历史看起来很丑陋,例如为什么一次提交两次,一次两次作为单独的提交,一次又一次一次较大的提交?,如果D1
实际上是合并的而不是壁球-“合并”-更糟糕的是,由于D2
的后代中提交的某些更改,它们可能稍后会引起冲突。
由于合并的分支现在“已死”,因此应将其删除。也就是说,您应该运行feature
。但是在这样做之前,请确保提交S
和git 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 rebase
或git checkout feature
恢复或终止操作时,即使您 not {{1 }}。
1 有个名称,您可以通过它们找到存储在Git的 reflogs master和master
>。任何参考名称的引用日志,包括分支名称的引用日志,都包含一个日志,该日志提交由哈希值标识的该分支名称(在某些特定时间戳记下)。例如,由feature
完成的对引用的每次更新都会将 previous 值保留在引用日志中,并为 new 值添加时间戳记何时刚刚写入了新值(以及有关正在发生的事情的刷新消息)。
运行git rebase --continue
以查看git rebase --abort
的引用日志条目。 feature
本身还有一个额外的引用日志。运行F2
或F1
来查看那个。请注意,旧条目最终会过期;有关更多信息,请参见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,在这种情况下为HEAD
和git rev-list --left-right
。如果任何复制候选者的补丁程序ID与另一半中的任何补丁程序ID均匹配,Git会得出结论,它们必须已经被精心挑选到HEAD
中,并且在复制过程中应予以省略。
这个结论通常是正确的,但在某些情况下,可能是错误的! 测试任何Git自动化操作的结果始终是一个好主意。出现问题的方法是,例如,如果一个提交本身将一行<upstream>
固定在一行上,在上游系列中,一行上有一个另一个流浪<upstream>
。这两个修复程序位于不同的源代码行上,并且可能具有不同的缩进,但对于S
,它们是相同的更改 ,因为补丁ID是通过删除行号和一些空白来构造的。