628

你可以将git rebase --interactive <commit>任意数量的提交压缩成一个。

除非您想将提交压缩到初始提交中,否则这一切都很好。这似乎是不可能的。

有什么方法可以实现吗?


中度相关:

在一个相关的问题中,我设法提出了一种不同的方法来解决第一次提交的压缩需求,也就是说,使它成为第二次提交。

如果您有兴趣:git:如何将提交作为第一个插入,然后转移所有其他提交?

4

9 回答 9

864

2012 年 7 月更新(git 1.7.12+

您现在可以将所有提交 rebase 到 root,并选择第二个提交Y与第一个X.

git rebase -i --root master

pick sha1 X
squash sha1 Y
pick sha1 Z
git rebase [-i] --root $tip

此命令现在可用于重写从 " $tip" 到根提交的所有历史记录。

请参阅Chris Webb ( )在 GitHub上的提交 df5df20c1308f936ea542c86df1e9c6974168472arachsys

评论中所述,如果您需要在远程存储库中发布该返工,则在任何操作之后都需要一个git push --force-with-lease(比Mikko Mantalainen提醒我们的更安全)。--force rebase


原始答案(2009 年 2 月)

我相信您会在 SO 问题“如何组合 git 存储库的前两个提交? ”中找到不同的方法。

Charles Bailey在那里提供了最详细的答案,提醒我们提交是一棵完整的树(不仅仅是与以前的状态不同)。
在这里,旧提交(“初始提交”)和新提交(挤压的结果)将没有共同的祖先。
这意味着您不能commit --amend将初始提交“”转换为新的提交,然后将前一个初始提交的历史重新定位到新的初始提交(很多冲突)

(最后一句话不再适用于git rebase -i --root <aBranch>

而是(使用A原始的“初始提交”,并且B需要将后续提交压缩到初始提交中):

  1. 回到我们想要形成初始提交的最后一个提交(分离 HEAD):

     git checkout <sha1_for_B>
    
  2. 将分支指针重置为初始提交,但保持索引和工作树不变:

     git reset --soft <sha1_for_A>
    
  3. 使用“B”中的树修改初始树:

     git commit --amend
    
  4. 临时标记这个新的初始提交(或者您可以手动记住新的提交 sha1):

     git tag tmp
    
  5. 回到原来的分支(本例假设为 master):

     git checkout master
    
  6. 将 B 之后的所有提交重播到新的初始提交:

     git rebase --onto tmp <sha1_for_B>
    
  7. 删除临时标签:

     git tag -d tmp
    

这样,“ rebase --onto”不会在合并期间引入冲突,因为它会将在最后一次提交 ( )之后B的历史记录重新压缩为初始提交 ( )Atmp(表示压缩的新初始提交):微不足道的快进仅合并。

这适用于“ A-B”,但也适用于“ A-...-...-...-B”(任何数量的提交都可以通过这种方式压缩到最初的提交中)

于 2009-02-28T22:18:47.493 回答
31

我重新编写了 VonC 的脚本以自动完成所有操作,而不要求我做任何事情。你给它两个提交 SHA1,它会将它们之间的所有内容压缩到一个名为“压缩历史”的提交中:

#!/bin/sh
# Go back to the last commit that we want
# to form the initial commit (detach HEAD)
git checkout $2

# reset the branch pointer to the initial commit (= $1),
# but leaving the index and working tree intact.
git reset --soft $1

# amend the initial tree using the tree from $2
git commit --amend -m "squashed history"

# remember the new commit sha1
TARGET=`git rev-list HEAD --max-count=1`

# go back to the original branch (assume master for this example)
git checkout master

# Replay all the commits after $2 onto the new initial commit
git rebase --onto $TARGET $2
于 2010-04-18T13:05:52.017 回答
29

如果您只是想将所有提交压缩为一个初始提交,只需重置存储库并修改第一个提交:

git reset hash-of-first-commit
git add -A
git commit --amend

Git reset 将保持工作树完好无损,所以一切都还在。因此,只需使用 git add 命令添加文件,并使用这些更改修改第一次提交。与 rebase -i 相比,您将失去合并 git 注释的能力。

于 2014-05-12T08:57:43.590 回答
24

对于它的价值,我总是通过创建一个“无操作”的第一次提交来避免这个问题,其中存储库中唯一的东西是一个空的 .gitignore:

https://github.com/DarwinAwardWinner/git-custom-commands/blob/master/bin/git-myinit

这样,就没有任何理由搞乱第一次提交。

于 2011-10-07T20:30:37.127 回答
5

这会将第二次提交压缩到第一次:

A-B-C-... -> AB-C-...

git filter-branch --commit-filter '
    if [ "$GIT_COMMIT" = <sha1ofA> ];
    then
        skip_commit "$@";
    else
        git commit-tree "$@";
    fi
' HEAD

AB 的提交消息将从 B 获取(尽管我更喜欢从 A 获取)。

与 Uwe Kleine-König 的答案具有相同的效果,但也适用于非初始 A。

于 2012-03-16T10:14:38.577 回答
3

挤压第一次和第二次提交将导致第一次提交被重写。如果您有多个基于第一次提交的分支,您将切断该分支。

考虑以下示例:

a---b---HEAD
 \
  \
   '---d

将 a 和 b 压缩成一个新的提交“ab”会产生两个不同的树,这在大多数情况下是不可取的,因为git-mergegit-rebase将不再在这两个分支上工作。

ab---HEAD

a---d

如果你真的想要这个,它可以做到。查看git-filter-branch以了解用于重写历史记录的强大(且危险)工具。

于 2009-03-01T14:41:54.730 回答
3

您可以为此使用 git filter-branch 。例如

git filter-branch --parent-filter \
'if test $GIT_COMMIT != <sha1ofB>; then cat; fi'

这导致 AB-C 丢弃 A 的提交日志。

于 2011-01-17T18:22:12.993 回答
-1

您可以使用 rebase interactive 来修改最后两个提交,然后再将它们推送到远程

git rebase HEAD^^ -i
于 2011-12-27T18:48:07.357 回答
-6

有一种更简单的方法可以做到这一点。假设你在master分支上

创建一个新的孤立分支,它将删除所有提交历史记录:

$ git checkout --orphan new_branch

添加您的初始提交消息:

$ git commit -a

摆脱旧的未合并的 master 分支:

$ git branch -D master

将当前分支重命名new_branchmaster

$ git branch -m master
于 2013-10-25T21:15:12.863 回答