修剪历史上连续几次提交

时间:2010-10-06 08:33:17

标签: git

无论如何要删除分支中的几个连续提交吗?

让我们说历史看起来像这样:

                      A -> B -> C -> D

现在,我想删除B和C引入的更改,因此历史记录如下:

                           A -> D

例如,一个典型的场景是醉酒的开发人员在提交B和C时提交垃圾,同时在D处写好文章。

我想出了一个相当差的(并且可能不是非常强大的方法):

# Get a patch for the good things
# Context lines are set to zero so applying
# the patch won't choke because of missing lines
# that were added by C or D
git format-patch --stdout -U0 revC..revD > CtoD

# Go back in time to last good revision before mayhem
git reset --hard revA

# Apply good things at this point
git apply --stat CtoD
git apply --check CtoD
git apply CtoD

# Add new files from patch
git add <any files that were created in CtoD patch>

# And commit
git commit -a -m "Removed B and C commits. Drunk dev fired"

这种做法远非完美。删除差异的上下文可能会在许多情况下使git-apply阻塞,并且文件必须手动git-add'ed。 我也可能完全忽略这一点......

有人能指出我这样做的正确方法吗?

感谢阅读!


编辑:

我忘了说我必须将所有这些内容推送到远程存储库,所以我尝试了rafl提议,虽然它在克隆上没问题,但是不可能将这些东西正确地推送到原点。

以下是已完成内容的详细(很长,抱歉!)列表:

##
# Create test environment
##
# all will happen below 'testing', so the mess can easily be wiped out
mkdir testing
cd testing

# Create 'origin' (sandbox_project) and the working clone (work)
mkdir sandbox_project work

# Create origin repos
cd sandbox_project
git init --bare

# Clone it
cd ../work
git clone ../sandbox_project
cd sandbox_project

# Create a few files :
# at each rev (A, B, C, D) we respectively create a fileA .. fileD
# at each rev, we also add a line to fileA

echo "This file was created in A" > fileA
git add .
git commit -a -m 'First revision'
git tag "revA"

for i in B C D; do
    echo "This file was created in $i" >> file$i
    echo "This change was done in $i" >> fileA
    git add file$i
    git commit -a -m "revision $i"
    git tag "rev$i"
done

# We push changes to origin
git push origin master

现在,它看起来像这样:

$ git log --graph --decorate --pretty=oneline --abbrev-commit

* e3dc9b7 (HEAD, revD, origin/master, master) revision D
* e21fd6a (revC) revision C
* a9192ec (revB) revision B
* a16c9dd (revA) First revision

我想删除B和C中引入的内容:

$ git rebase -i HEAD~3

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply e3dc9b7... revision D

$ git status

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   fileD
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#   both modified:      fileA
#

当然,fileA存在问题(问题我通过应用使用-U0生成的diff来解决我的解决方案。 在这里,我编辑fileA,删除冲突的行,并继续rebase:

This file was created in A
<<<<<<< HEAD
=======
This change was done in B
This change was done in C
This change was done in D
>>>>>>> e3dc9b7... revision D

编辑为:

This file was created in A
This change was done in D

然后:

$ git add fileA
$ git rebase --continue

# leave mesasge as-is

[detached HEAD e2d4032] revision D
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 fileD
Successfully rebased and updated refs/heads/master.

$ git push

To ../sandbox_project
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '../sandbox_project'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again.  See the 'Note about
fast-forwards' section of 'git push --help' for details.

嗯,这对我来说很正常,因为我重新开始,所以我尝试了:

$ git pull
Auto-merging fileA
CONFLICT (content): Merge conflict in fileA
Automatic merge failed; fix conflicts and then commit the result.

$ git log --graph --decorate --pretty=oneline --abbrev-commit

* e2d4032 (HEAD, master) revision D
* a16c9dd (revA) First revision

同样,fileA无法合并,因为它有B和C版本的修订版。在这里,我们再次进行,编辑fileA,并删除由和C。

引入的更改

然后:

$ git add fileA
$ git commit -a
[master b592261] Merge branch 'master' of ../sandbox_project
$ git push
Counting objects: 9, done.
Compressing objects: 100% (5/5), done.
Unpacking objects: 100% (5/5), done.
Writing objects: 100% (5/5), 674 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../sandbox_project
   e3dc9b7..b592261  master -> master

看起来不错,但是:

$ git log --graph --decorate --pretty=oneline --abbrev-commit

*   b592261 (HEAD, origin/master, master) Merge branch 'master' of ../sandbox_project
|\  
| * e3dc9b7 (revD) revision D
| * e21fd6a (revC) revision C
| * a9192ec (revB) revision B
* | e2d4032 revision D
|/  
* a16c9dd (revA) First revision

虽然最后,fileA没问题,我仍然有fileB和fileC我不想要。在这个过程中,我不得不两次解决fileA的合并冲突。

有任何线索吗?

1 个答案:

答案 0 :(得分:3)

我会使用git rebase -i HEAD~4

这会在$EDITORHEAD~4之间为HEAD生成一个包含一行的文件。删除要丢弃的提交的行,保存并退出编辑器,git将仅应用您在HEAD~4之上的文件中保留的提交。

然而,由于这实际上会重写您的历史记录,因此通常无法将其结果推到任何地方而不会搞砸所有其他也在该存储库之外工作的人。

作为替代方案,您可以使用几乎所有其他版本控制系统执行您所做的事情:还原错误提交。

$ git revert $commit_sha

这将创建一个新的提交,反向应用$commit_sha的差异。