Git-在特定提交之前压扁历史上的所有提交

时间:2018-12-03 05:12:15

标签: git

我有一个要转换为Git的Mercurial回购。提交历史记录非常大,我不需要新存储库中的所有提交历史记录。将提交历史记录转换为Git之后(在推送到新的回购之前),我想将某个标记之前的所有提交压缩为一个提交。

所以,如果我有:

commit 6
commit 5
commit 4
commit 3
commit 2
commit 1 -- First commit ever

我想结束:

commit 6
commit 5
commit X -- squashed 1, 2, 3, 4

注意:我需要压缩成千上万的提交。因此,手动选择/标记它们不是一个选择。

4 个答案:

答案 0 :(得分:4)

要精确地做到所有这些, 步骤将是

  1. 检出特定提交
  2. 将所有内容压缩到该特定提交中
  3. Cherry-pick这之后发生的提交
  4. 删除现有分支机构
  5. 将您最近煮过的头保存到相同的分支名称中

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 ... "

您可以进一步扩展它,以根据所有提交消息构建SQUASH_MESSAGE。

答案 1 :(得分:2)

到目前为止,其他答案都建议重新设定基准。在某些情况下,此可以起作用,具体取决于转换为Git的存储库中的提交图。使用--rebase-merges进行新的更佳变位肯定可以做到。但这是一种笨拙的方式。理想的方法是转换从您要保留的第一个提交开始。也就是说,将Mercurial出口商导出到Git,就像Git的 first 提交一样,您要假装的修订版是根目录。让Mercurial出口商继续将提交的后代一次出口到进口商,就像出口商总是要从事这项工作一样(无论如何)。

能否以及如何 执行此操作取决于您要使用哪种工具进行转换。 (我实际上并没有进行任何这些转换,但是大多数人似乎都使用hg-fast-exportgit fast-import。我对hg-fast-export的内部细节并没有太多关注,但没有明显的原因, 不能这样做。)


从根本上(内部),Mercurial存储作为变更集提交。对于Git,情况不是 :Git会存储快照。但是,Mercurial 签出(即提取)快照,方法是根据需要将变更集汇总在一起,因此,如果您的工具通过执行hg checkout(或其内部等效方法)而工作,则不会出现问题首先是这里:您只需避免在想要的第一个快照之前签出修订,然后将其导入到Git中,然后生成的Git历史记录将从所需的点开始。


但是,如果您使用的工具不方便,请注意,在将整个存储库历史记录(包括所有分支和合并)转换为Git快照后,您的 Git 存储库使此操作相对容易通过。您的Git历史记录可能如下所示:

          o-..-o            o--o   <-- br1
         /      \          /
...--o--o--....--o--*--o--o--o--o   <-- br2
      \         /             \
       o--...--o               o   <-- master

其中提交*是您希望在Git存储库中看到的第一个提交。 (请注意,如果在*之前有多个历史记录,那么您会遇到另一个问题,如果没有其他历史记录修改,就无法进行这种转换。但是,*处于启用状态就像这张图中的choke point一样,在此处剪裁图形很容易。)

要删除*之前的所有内容,只需使用git replace进行一个非常类似于 提交*但没有父项的替代提交:

git replace --graft <hash-of-*>

您现在有了Git的大多数人将使用的替代品,而不是*,它没有父提交。然后使用无操作筛选器在所有分支和标签上运行git filter-branch

git filter-branch --tag-name-filter cat -- --all

这会将每个 reachable 提交(包括替换项*但不包括*及其自身的历史记录)复制到新提交中,然后更新您的分支和标记名。删除refs/originals/命名空间(有关详细信息,请参见the git filter-branch documentation),如果愿意,可以尽早清除原始对象(多余的提交最终将自行消失),然后完成。

答案 2 :(得分:0)

尽管我建议将git reset --soft压榨一组 个提交(as in here),但还是建议使用以下选项:

  • 拥有一个原始的Git存储库
  • 执行patches between two tags(如果可以从一个标签转到另一个标签),
  • 将每个补丁应用于新的Git存储库,在其中将这些压缩的提交作为一个补丁存储在另一个补丁中。

请注意,这适用于通过git rebase --root option的第一次提交。

答案 3 :(得分:0)

假设原始分支为 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

如果要保留合并提交,则需要在“ git rebase”中使用选项“ -p”。