[英]Git - Squash All Commits in History Before Specific Commit
I have a Mercurial repo that I am converting to Git.我有一个要转换为 Git 的 Mercurial 存储库。 The commit history is quite large and I do not need all of the commit history in the new repo.
提交历史非常大,我不需要新仓库中的所有提交历史。 Once I convert the commit history to Git (and before pushing to the new repo), I want to squash all the commits before a certain tag into one commit.
一旦我将提交历史转换为 Git(并且在推送到新存储库之前),我想将某个标签之前的所有提交压缩为一个提交。
So, if I have:所以,如果我有:
commit 6
commit 5
commit 4
commit 3
commit 2
commit 1 -- First commit ever
I want to end up with:我想结束:
commit 6
commit 5
commit X -- squashed 1, 2, 3, 4
Note: There are thousands of commits that I need to squash.注意:我需要压缩数以千计的提交。 So, manually picking/marking them one by one is not an option.
因此,手动挑选/标记它们不是一种选择。
The other answers so far suggest rebase.到目前为止,其他答案都建议 rebase。 This can work, in some cases, depending on the commit graph in the converted-to-Git repository.
这可以工作,在某些情况下,这取决于在转换到Git仓库提交图表。 The new fancier rebase with
--rebase-merges
can definitely do it.带有
--rebase-merges
的新爱好者 rebase 绝对可以做到。 But it's kind of a clumsy way to go about it.但这是一种笨拙的方法。 The ideal way to do this is to convert commits starting at the first one you want to keep.
执行此操作的理想方法是从您要保留的第一个提交开始转换提交。 That is, have your Mercurial exporter export to Git, as Git's first commit, the revision you want to pretend is the root.
也就是说,让您的 Mercurial 导出器导出到 Git,作为 Git 的第一次提交,您要假装的修订版是根。 Have the Mercurial exporter go on to export that commit's descendants, one at a time into the importer, in the same way that the exporter was always going to do this job (whatever way that may be).
让 Mercurial 导出器继续将提交的后代导出到导入器中,一次一个,就像导出器总是要做这项工作一样(无论以何种方式)。
Whether and how you can do this depends on what tool(s) you are using to convert.无论你如何能做到这一点取决于你使用的转换什么工具(S)。 (I have not actually done any of these conversions, but most people seem to use
hg-fast-export
and git fast-import
. I have not looked much at the inner details of hg-fast-export
but there's no obvious reason it couldn't do this.) (我实际上并没有进行任何这些转换,但大多数人似乎使用
hg-fast-export
和git fast-import
。我没有过多地查看hg-fast-export
的内部细节,但没有明显的原因它不能“T做到这一点。)
Fundamentally (internally), Mercurial stores commits as changesets.从根本上(内部),Mercurial 将提交存储为变更集。 This is not the case for Git: Git stores snapshots instead.
Git不是这种情况:Git 存储快照。 However, Mercurial checks out (ie, extracts) snapshots, by summing together changesets as required, so if your tool works by doing
hg checkout
(or the internal equivalent thereof), there is no issue here in the first place: you just avoid checking out revisions prior to the first snapshot you want, and import those into Git, and the resulting Git history will begin at the desired point.但是,Mercurial 通过根据需要将变更集汇总在一起来检查(即提取)快照,因此如果您的工具通过执行
hg checkout
(或其内部等效项)来工作,那么首先这里没有问题:您只需避免检查在您想要的第一个快照之前删除修订,并将它们导入 Git,生成的 Git 历史记录将从所需的点开始。
If the tools you have make this inconvenient, though, note that after converting the entire repository history, including all branches and merges, into Git snapshots, your Git repository makes this relatively easy as a second pass.但是,如果您使用的工具使这变得不方便,请注意,在将整个存储库历史记录(包括所有分支和合并)转换为 Git 快照后,您的Git存储库将相对容易地作为第二遍。 Your Git history might, eg, look like this:
例如,您的 Git 历史记录可能如下所示:
o-..-o o--o <-- br1
/ \ /
...--o--o--....--o--*--o--o--o--o <-- br2
\ / \
o--...--o o <-- master
where commit *
is the first commit you wanted to see in your Git repository.其中 commit
*
是您希望在 Git 存储库中看到的第一个提交。 (Note that if there are multiple histories going back before *
, you have a different issue and cannot do this kind of transformation in the first place without additional history-modification. But as long as *
is on a sort of choke point , as it is in this diagram, it's easy to snip the graph here.) (请注意,如果在
*
之前有多个历史记录,您会遇到不同的问题,并且在没有额外的历史修改的情况下首先无法进行这种转换。但只要*
处于某种阻塞点,因为它在这张图中,很容易在这里剪下图表。)
To remove everything before *
, simply use git replace
to make an alternative commit that's very much like commit *
, but has no parent:要删除
*
之前的所有内容,只需使用git replace
进行一个非常像commit *
的替代提交,但没有父级:
git replace --graft <hash-of-*>
You now have a replacement that most of Git will use instead of *
, that has no parent commit.你现在有了一个大多数 Git 将使用的替代品,而不是
*
,它没有父提交。 Then run git filter-branch
over all branches and tags, with the no-op filter:然后在所有分支和标签上运行
git filter-branch
,使用 no-op 过滤器:
git filter-branch --tag-name-filter cat -- --all
Or, once git filter-repo
is included with Git (or if you've installed it):或者,一旦
git filter-repo
包含在 Git 中(或者如果您已经安装了它):
git filter-repo --force
(be careful with the --force
option when using filter-repo
: this makes it destroy the old history in this repository, but in this csae, that's what we want). (在使用
filter-repo
时要小心--force
选项:这会破坏这个存储库中的旧历史,但在这个 csae 中,这就是我们想要的)。
This will copy every reachable commit, including the substitute *
but excluding *
and its own history, to new commits, then update your branch and tag names.这会将每个可访问的提交(包括替代
*
但不包括*
及其自己的历史记录)复制到新提交,然后更新您的分支和标签名称。
If using filter-branch, remove the refs/originals/
name-space (see the git filter-branch
documentation for details), force early scavenging of the original objects if you like (the extra commits will eventually fall away on their own), and you're done.如果使用 filter-branch,请删除
refs/originals/
命名空间(有关详细信息,请参阅git filter-branch
文档),如果您愿意,可以强制尽早清除原始对象(额外的提交最终会自行消失),你就完成了。
To do all of those precisely, Steps will be为了准确地完成所有这些,步骤将是
function git_squash_from() {
COMMIT_TO_SQUASH=$1
SQUASH_MESSAGE=$2
STARTING_BRANCH=$(git rev-parse --abbrev-ref HEAD) # This will be overwritten
CURRENT_HEAD=$(git rev-parse HEAD)
echo From $CURRENT_HEAD to the successor of $COMMIT_TO_SQUASH will retain, from $COMMIT_TO_SQUASH to beginging will be squashed
git checkout $COMMIT_TO_SQUASH
git reset $(git commit-tree HEAD^{tree} -m "$SQUASH_MESSAGE")
git cherry-pick $CURRENT_HEAD...$COMMIT_TO_SQUASH
git branch -D $STARTING_BRANCH
git checkout -b $STARTING_BRANCH
}
git_squash_from 87ef7fa "Squash ... "
You can extend it further to build the SQUASH_MESSAGE from all commit messages.您可以进一步扩展它以从所有提交消息构建 SQUASH_MESSAGE。
Suppose the original branch is master , and the new branch is new .假设原来的分支是master ,新的分支是new 。
git checkout --orphan new commit4
git commit -m "squash commits"
git branch tmp master
git rebase commit4 tmp --onto new
git checkout new
git merge tmp
git branch -D tmp
The option "-p" is needed in "git rebase" if you want to keep the merge commits.如果要保留合并提交,则“git rebase”中需要选项“-p”。
While git reset --soft
could be an option for squashing one set of commits ( as in here ), I would recommend, for multiple set of commits:虽然
git reset --soft
可能是git reset --soft
一组提交的选项(如此处所示),但我建议,对于多组提交:
Note this applies to the first commit, through the git rebase --root
option .请注意,这适用于第一次提交,通过
git rebase --root
选项。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.