在Git中压缩前两次提交?

使用git rebase --interactive <commit>,您可以将任意数量的提交压缩到一个单独的提交中。

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

有什么方法可以实现吗?


适度相关:

在一个相关的问题中,我设法提出了一种不同的方法来满足压缩第一次提交的需要,也就是将其作为第二次提交。

如果你感兴趣:Git:如何插入一个提交作为第一个,转移所有其他人?

188916 次浏览

2012年7月更新(git 1.7.12 +)

现在,您可以将所有提交重置为root,并选择第二个提交Y与第一个X压缩。

git rebase -i --root master


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

这个命令现在可以用来重写从"$tip"一直到根提交。

参见在GitHub上提交df5df20c1308f936ea542c86df1e9c6974168472克里斯·韦伯(arachsys)

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


原答案(2009年2月)

我相信你会在SO问题中找到不同的方法。

查尔斯·贝利提供了最多的详细的回答,提醒我们提交是一个完整的树(不仅仅是与以前的状态不同)。
在这里,旧的提交(“初始提交”)和新的提交(压缩的结果)将没有共同的祖先。
这意味着你不能“commit --amend"初始提交到新的提交中,然后将前一个初始提交的历史(很多冲突)重新基于新的初始提交

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

更确切地说(A是最初的“初始提交”,B是随后的提交,需要压缩到初始提交中):

  1. 返回到我们想要形成初始提交的最后一个提交(detach 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)重新压缩到初始提交(# EYZ2)到tmp(表示压缩的新初始提交):简单的快进合并。

这适用于&;# eyz0 &;,但也适用于&;# eyz1 &;(通过这种方式可以将任意数量的提交压缩到初始提交中)

取消第一次和第二次提交将导致第一次提交被重写。如果你有多个基于第一次提交的分支,你会切断那个分支。

考虑下面的例子:

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

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

ab---HEAD


a---d

如果你真的想要,这是可以做到的。看看git-filter-branch,它是一个重写历史的强大(和危险)工具。

我重做了VonC的脚本自动做所有事情,不要求我任何东西。你给它两个sha1提交,它会把它们之间的所有内容压缩到一个名为“squashed history”的提交中:

#!/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

你可以使用git filter-branch。如。

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

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

为了避免这个问题,我总是在第一次提交时创建一个“no-op”,其中存储库中唯一的东西是一个空的.gitignore:

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

这样,就不会有任何理由破坏第一次提交。

在最后两次提交被推送到远程服务器之前,可以使用rebase交互来修改它们

git rebase HEAD^^ -i

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

# EYZ0

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。

有一种更简单的方法。让我们假设您在master分支上

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

$ git checkout --orphan new_branch

添加你的初始提交消息:

$ git commit -a

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

$ git branch -D master

将当前分支new_branch重命名为master:

$ git branch -m master

如果你只是想把所有的提交压缩成一个单独的初始提交,只需要重置存储库并修改第一次提交:

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

Git重置将保持工作树的完整性,因此所有内容仍然在那里。因此,只需使用git add命令添加文件,并使用这些更改修改第一次提交。与rebase -i相比,你将失去合并git注释的能力。