真正扁平化一个 git merge
Posted
技术标签:
【中文标题】真正扁平化一个 git merge【英文标题】:Really flatten a git merge 【发布时间】:2012-08-29 12:27:09 【问题描述】:*** 上几乎没有关于“扁平化合并”的问题,答案通常是“git rebase”。这些答案虽然错过了一个关键点 - 提交顺序。
假设有一个分支 A 的提交时间为 6 月 1 日和 8 月 1 日,分支 B 的提交时间为 7 月 1 日(UPDATE 以恢复下面描述的用例:分支是完全独立的,没有共同祖先,例如来自 2 个不同的存储库)。将 B 合并到 A 时,将有以下历史记录(每个 git log):
Merged branch 'B'
Aug 1
Jul 1
Jun 1
现在,我正在寻找的是获得相同结果的方法,但没有合并提交(因此具有该顺序的底层线性历史记录,是的,这意味着重新提交提交)。 git rebase 在这里没有帮助,就像它一样,您将获得以下历史记录:
Jul 1
Aug 1
Jun 1
或
Aug 1
Jun 1
Jul 1
换句话说,git rebase 总是将一个分支堆叠在另一个分支之上,而我正在寻找能够散布按作者提交日期排序的提交的解决方案。
显然,对于简单的情况,可以通过使用 git rebase -i 手动对 git rebase 进行后处理来实现所需的安排,但这对于大型历史来说并不实用,所以我正在寻找自动化命令/脚本。
用例?如果 A 和 B 代表同一项目的不同部分,恰好位于不同的 repos 中,并且时间已经到了通过将它们合并在一起来纠正这一点,那么自然希望线性历史按照实际的开发顺序展开。
【问题讨论】:
听起来您正试图将线性开发路径固定在非线性版本控制系统上。将所有提交按时间顺序排列似乎是“自然的”,但这将是一个虚假的历史,因为您的团队实际上当时并没有相互合作。至关重要的是最终状态,整合两个团队的努力,而不是在合并步骤不完整之后。 @jordan002:问题指定了“团队”在两个分支上“合作”作为起始条件的事实。至于“至关重要”,这个问题正是关于它是什么,而不是关于开发方法的意见。 @pfalcon:实际上,在您的问题中并没有这样说。此外,您要在这里解决的实际问题是什么?我们了解您想要做什么;但是您要解决的问题是什么? FWIW,git rebase
处理合并合理的好。 IE。正如人们所期望的那样,提交的顺序被保留了。除非您希望它们按时间顺序排列,因为根据 non-linear 历史记录,rebase
必须处理的提交是 not 按该顺序排列的。
我目前正面临着类似的情况,并且理解为什么@pfalcon 想要这个。就我而言,这两个团队正在合作,事实上,一个仓库中的提交在逻辑上与另一个仓库中的提交相关并需要提交。所以,时间顺序确实有意义。
【参考方案1】:
经过一番思考,我想出了如何做How do I run git rebase --interactive in non-interactive manner?,这也为这个问题提供了完全脚本化的解决方案。
1. 将来自不同仓库的 2 个分支合并到一个仓库中(git remote add + git fetch)
2. 将一个分支重新设置在另一个之上(以非交互方式)(顺序很重要,请考虑您希望将哪个分支的第一次提交作为合并分支的第一次提交)。 p>
3. 准备以下脚本 (rebase-reoder-by-date
):
#!/bin/sh
awk '
/^pick/
printf "%s %s ", $1, $2;
system("echo -n `git show --format='%ai' -s " $2 "`");
for (i = 3; i <= NF; i++) printf " %s", $i; printf "\n";
' $1 | sort -k3 > $1.tmp
mv $1.tmp $1
4. 运行:GIT_SEQUENCE_EDITOR=./rebase-reoder-by-date git rebase -i <initial commit>
免责声明:所有这些操作都应该在原始存储库的副本上进行,审查/验证/测试组合分支以确保它是您所期望的并包含您所期望的,请随身携带备份。
【讨论】:
【参考方案2】:[查看我的另一个答案以获得完全自动化的解决方案。我会将此作为最终解决方案的路径示例,以防有人会面临类似的不太明显的解决任务。]
好的,这不是问题的真正答案(完全脚本化的自动化解决方案),而是思考和示例如何(基于交互式变基)处理可以自动化。
首先,对于最终解决方案git filter-branch --parent-filter
看起来正是我们所需要的。除了我的 git-fu 不允许我用它编写 1-、2- 或 3-liner 之外,编写独立脚本来解析所有修订的方法并不酷,而且比 rebase -i 更费力。
因此,如果提交的作者日期可见,则可以有效地使用 rebase -i。我的第一个想法是使用 git filter-branch --msg-filter
临时修补提交消息以从作者日期开始,运行 rebase -i,然后取消修补消息。
第二个想法是:为什么要麻烦,最好修补 rebase -i 使用的 rebase 提交列表。所以,这个过程是:
-
像往常一样,将来自不同存储库的分支 A 和 B 合并到一个存储库中。
在另一个分支上重新设置(非交互式)一个分支。考虑哪个分支应该基于哪个分支,具有初始提交权(不能用 rebase 轻松重写)。
开始
git rebase -i
在另一个控制台中,转到 $REPO/.git/rebase-merge/
运行:awk '/^pick/ printf "%s %s ", $1, $2; system("echo -n git show --format='%ai' -s " $2 "
"); for (i = 3; i <= NF; i++) printf " %s", $i; printf "\n"; ' git-rebase-todo > git-rebase-todo.new; mv git-rebase-todo.new git-rebase-todo
这似乎是重新排序提交的正确位置/方式:sort -k3 git-rebase-todo >git-rebase-todo.new; mv git-rebase-todo.new git-rebase-todo
切换到原始控制台并在编辑器中重新加载 git-rebase-todo 文件,然后退出编辑器。
瞧!实际上,如果git rebase -i
可以在“非交互”模式下工作,这完全可以编写脚本,我为此提交了How do I run git rebase --interactive in non-interactive manner?。
【讨论】:
【参考方案3】:在合并之前将单独的开发留在单独的行中会有什么问题?如果他们是分开的,那么他们就是分开的。
有很多方法可以按时间顺序查看历史记录,而无需在尝试时破解历史记录。你试过git log --pretty --date-order
吗?
【讨论】:
好的,如果问题中的一般描述还不够,这里有更具体的例子:项目的客户端和服务器部分最初创建为 2 个单独的 git 存储库。但是他们的开发是并行的,比如向服务器添加功能,向客户端添加相关代码等。所以,没有“单独的开发线”,只有 repos 被分开。后来,很明显客户端和服务器都是一个项目,它们是这样工作的,剩下的就是将它们合并到1个repo中,代表它们的common 发展路线。 您可以将上面的“服务器”和“客户端”替换为“主应用程序”和“库”,或者替换为“A 语言实现”和“B 语言实现”,或者“接口”和“实施”。显然,这样的用例或多或少是通用的,这就是我提出问题的方式,希望找到社区可重用的解决方案,而不是仅仅抓挠我的一时之痒。是的,它更像是智力挑战(“git 可以做很多事情,它可以做到这一点”)。所以,是的,我想找到一个解决方案,如果 devel 从一开始就“正确”地完成,那么 repo 看起来会是这样,而不仅仅是一种解决方法 为了记录,我遇到了这个 SO 问题,因为我正在尝试合并从 Subversion 克隆的两个 Git 存储库。 Subversion/Git 转换过程不太擅长挑选单个子目录,因此我们创建了单独的 Git 存储库。【参考方案4】:实际上,如果我理解正确,您可以使用git-stitch-repo 轻松实现此目的。
【讨论】:
有趣的工具 不幸的是,结果是不同的分支,而不是一个。这个工具的结果就是这个问题的起点以上是关于真正扁平化一个 git merge的主要内容,如果未能解决你的问题,请参考以下文章