简体   繁体   English

Git - 在特定提交之前压缩历史中的所有提交

[英]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-exportgit 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为了准确地完成所有这些,步骤将是

  1. Checkout to the specific commit签出到特定的提交
  2. Squash everything before it to this particular commit将它之前的所有内容压缩到此特定提交
  3. Cherry-pick the commits that happened after this樱桃挑选在此之后发生的提交
  4. Delete your existing branch删除现有分支
  5. Save your recently cooked head into the same branch name将你最近煮熟的头保存到相同的分支名称中

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组提交的选项(如此处所示),但我建议,对于多组提交:

  • having one original Git repo拥有一个原始的 Git 存储库
  • doing patches between two tags (if you can go from one tag to the next),在两个标签之间打补丁(如果你可以从一个标签转到下一个),
  • applying each patch to a new Git repo where you store those squashed commits as one patch after the other.将每个补丁应用到一个新的 Git 存储库,在那里您将这些压缩的提交存储为一个接一个的补丁。

Note this applies to the first commit, through the git rebase --root option .请注意,这适用于第一次提交,通过git rebase --root选项

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM