我已经在功能部门工作了好几个月了。在中间我们将master合并到分支中几次。现在,在分支完成之后,我们意识到很多来自功能分支的提交都有混乱的提交消息,将这些中的几个压缩在一起会很好。
但是,如何在不降入地狱的情况下这样做呢?我已经尝试过变基,但由于master拥有超过一百个新提交,我们的功能分支也是如此,而且事实大师已经在几个接触点合并到我们的分支中,我遇到了数百个在重新定位时合并冲突。有没有更简单的方法?我只想将一些提交消息压缩在一起。
感谢。
答案 0 :(得分:5)
git merge --squash
我只是窃Lars Kellogg-Stedman的beautiful blog post。
我认为这比公认的答案更简单,更简洁。
Lars提供了几个选项,但第一个选项是使用临时分支合并提交,如下所示:
稍微修改Lars所说的话:
git checkout -b work master
(这将创建一个名为work
的新分支,并将其作为当前分支。)
git merge --squash my_feature
这会将my_feature
分支中的所有更改引入并分段执行,但不会创建任何提交。
git commit -m "my squash commit message"
这时,您的work
分支应该与原始的my_feature
分支相同(运行git diff master不应显示任何更改),但在master之后只有一个提交。 / p>
git checkout my_feature
git reset --hard work
git push -f
git branch -D work
答案 1 :(得分:2)
git rebase -i <commit>
基本上,你需要找到你想要处理的分支点&#34;分叉&#34;,然后使用它作为<upstream>
的{{1}}参数。< / p>
选择性地将各种提交压缩在一起的最简单方法是使用git rebase
(然后以交互方式编辑rebase指令)。我认为这就是你在做的事情。
问题当然是你要击中的问题 - 这也是 git rebase 。 :-)如果你天真地运行它,它不仅给你一个交互式的rebase,让你大惊小怪的说明,从而重新构建你的提交链,它还试图更改你的提交的基础链
让我们快速看看git rebase -i
做什么(交互与否)以及我所谓的&#34;提交链&#34;这里。
与Git的大多数事情一样,坐下来(或站在白板上,或类似的东西)并绘制至少部分提交图是个好主意。提交图以向后的方式形成(并因此绘制)。从某个分支上的最新提交开始,我们(或Git)必须读取每个提交,并将其绘制为一个节点(附加或不附加名称/ ID),并从每个提交到其父提交的向外箭头(s )。
大多数提交只有一个父级,正如我们刚才提到的,最近的提交是其ID存储在当前分支名称中的提交。由于Git中的所有内容都会倒退,因此最终看起来像:
git rebase
我们说名称... <- o <- o <- o <-- branch
,指向分支上的最终提交。 (最终提交也有一个特殊的名称:它是提示提交。)
虽然这里的所有内部箭头都是向后的,但我们大多不需要关心它,所以对于非白板图纸,我只是将它们排除在外:
branch
当然,对于&#34; rebase&#34;这个分支,它必须分支一些东西。这意味着我们还有一个更多的主线分支:
...--o--o--o <-- branch
我用...--o--*--o--...--o <-- mainline
\
A--B--...--Z <-- branch
标记的一个提交是分支发散的点。提交*
在两个分支上,就像在主线上的所有早期(向左)提交一样。 *
的权限仅在*
(顶行提交)上或仅在mainline
(底行)上。我给出了底行提交单字母名称而不是Git实际使用的大丑陋ID,因为丑陋的哈希不是人类友好的。当然,这只能让我在这里编写26个提交,而不是数百个,但这应该可以用于示例。
现在,branch
做的是复制提交。它有:由于技术原因,几乎不可能改变任何现有提交的内容。当您运行git rebase
时,您正在执行此操作以更改某些内容。通常,您要做的是根据图形更改提交的位置(这也会更改它们所基于的源树)。而不是上面的图表,你改变&#34; -i.e。,复制到新的提交 - 旧的提交,结果如下:
git rebase
新副本&#34;同样好&#34;作为原始,或者更确切地说,甚至更好 - 新的和改进的 - 因为它们在不同的点上,并从不同的源基础开始。它们重新基于。
您有时可以只运行...--o--*--o--...--o <-- mainline
\ \
\ A'-B'-...--Z' <-- branch (copies)
\
A--B--...--Z [originals, now abandoned]
,但有时您必须为此运行git rebase
。但究竟,git rebase mainline
论证的确是什么呢? (就此而言,有时你怎么能把它留下来?)
那个论点 - mainline
- 实际上是两个的东西。一个是非常明显的:它的副本。名称mainline
指向mainline
分支上的提示提交。我们希望副本能够在那之后进行,所以我们说&#34;重新加入主线&#34;。
mainline
不太明显的事情就是告诉rebase 不要复制的内容。
请记住,就在刚才,我们注意到提交mainline
位于两个分支上(就像之前的所有提交一样)。这些是我们 想要复制的提交。我们希望rebase从 *
之后的第一次提交开始,即提交*
, 1 ,然后继续{{}的提示1}},即提交A
。
你可以,如果你需要 - 有时你做 - 将它拆分,告诉branch
用Z
添加副本的位置,这样剩下的参数可用于&#34;什么不是复制&#34;。在我们的特殊情况下,我们并不需要这样做。
1 请注意,在git rebase
--onto
之后还有另一个提交。或许,更好的说法是*
向mainline
方向后的第一次提交。但是,Git使用的精确定义是由the gitrevisions
two-dot range syntax提供的。
在我们的情况下,针对此特定问题,我们希望执行*
选择/压缩/编辑事项,而不实际将副本移动到新基础。也就是说,而不是:
branch
我们想要更像的东西:
git rebase -i
这样做的方法是命名,而不是...--o--*--o--...--o <-- mainline
\ \
\ A'-B'-...--Z' <-- branch (copies)
\
A--B--...--Z [originals, now abandoned]
,而是提交...--o--*--o--...--o <-- mainline
|\
| AB--CDE--F'--...--XYZ <-- branch (copies, with squashing)
\
A--B--...--Z [originals, now abandoned]
本身:
mainline
这意味着我们必须找到提交*
的哈希ID。
这样做的一种方法是绘制图形(这是一个很好的练习,但它变得乏味)。另一个是让Git使用git rebase -i <hash-ID-of-commit-*>
为您绘制图形(您可能想要使用 DOG :*
,它以紧凑和友好的方式呈现输出,尾部 - 摇摆可选)。但是,一般情况下,您可以使用git log --graph
找到ID:
--decorate --oneline --graph
这将打印两个分支上的第一个(即最近/最尖端病毒)提交的ID。 (事实上,这被称为合并基础也是一个线索:它是git merge-base
如何运作的关键。但这完全是另一个答案。)< / p>
因此,你想做:
git merge-base mainline branch
然后在git merge
命令之后复制并粘贴ID,以便rebase限制副本基础上的副本,然后紧接着它。由于与您正在复制的原始提交相同的基础,因此它们不会发生合并冲突(除非您重新组织某些提交以不同的顺序进行) ,无论如何)。
关于git merge-base your-branch the-other-branch
需要注意的一点是不会复制合并提交。在一般情况下,它通常不会尝试。它确实有git rebase -i
标志,但这实际上重新执行合并。但默认情况下,它只是完全放弃它们。这意味着,如果您有一个图形:
git rebase
并且您要求Git将--preserve-merges
重新定位到...--o--*--o--o <-- mainline
\
\ C--D
\ / \
A--B G--H <-- branch
\ /
E--F
,新副本为branch
(完全省略mainline
)或A-B-C-D-E-F-H
(仍然省略G
1}}完全,但首先做底行而不是第一行。这有点可能在执行第二行时产生合并冲突,无论以哪种方式完成第二行,或者在添加A-B-E-F-C-D-H
时,尤其是在首先生成G
时必须解决的合并冲突时
如果您运行H
而没有额外参数 - 不说G
或提供提交ID - 该命令会查看当前分支的上游设置(由git rebase
设置。如果根本没有设置,mainline
需要额外的参数。如果是上游,它通常是一个远程跟踪分支,如git branch --set-upstream-to
,并且rebase假装你输入 - 除了在Git 2.0中使用隐式上游打开 fork point 机器(这太复杂了,不能进入这里)。