修剪 Git 提交/压缩 Git 历史记录

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2302736/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-19 04:06:33  来源:igfitidea点击:

Trimming Git Commits/Squashing Git History

gitbranch

提问by Dan Rosenstark

I check my code into a Git branch every few minutes or so, and the comments end up being things like "Everything broken starting again" and other absurdities.

每隔几分钟左右,我就会将我的代码检查到一个 Git 分支中,而这些评论最终变成了诸如“一切都坏了,重新开始”之类的荒谬言论。

Then every few minutes/hours/days I do a serious commit with a real comment like, "Fixed bug #22.55, 3rd time." How can I separate these two concepts?I would like to be able to remove all my frequent-commits and just leave the serious ones.

然后每隔几分钟/小时/天,我就会认真地提交一个真实的评论,例如“已修复错误 #22.55,第三次”。如何区分这两个概念?我希望能够删除我所有的频繁提交,只留下严重的提交。

采纳答案by Dan Rosenstark

Using Squash Instead

改用壁球

Recently, I've been working in another branch and using squash. The other branch is called temp, and then I use git merge temp --squashto bring it into the real branch that gets pushed to the server.

最近,我一直在另一个分支工作并使用squash. 另一个分支称为 temp,然后我用git merge temp --squash它把它带入推送到服务器的真实分支中。

Workflow is something like this, assuming I'm working in Ticket65252:

工作流程是这样的,假设我在工作Ticket65252

git branch -d temp #remove old temp bbranch
git checkout -b temp
# work work work, committing all the way
git checkout Ticket65252
git merge temp --squash
git commit -m "Some message here"

Advantages over using rebase? Way less complicated.

使用优势rebase?不那么复杂。

Advantages over using reset --hardand then reset --soft?Less confusing and slightly less error prone.

使用reset --hard然后的优势reset --soft更少混淆,更不容易出错。

回答by VonC

Edited answerwith now (in the second half of this entry) the new Git1.7 fixup! action and --autosquashoption for quick commit reordering and message editing.

编辑答案现在(在这个词条下半年)新Git1.7修正!--autosquash用于快速提交重新排序和消息编辑的操作和选项。



First, the classic squashing process, as done before Git1.7.
(Git1.7 has the same process, only made faster by the possibility of automatic commit reordering as opposed to manual reordering, and by cleaner squashing messages)

首先是经典的压缩过程,就像在 Git1.7 之前所做的那样。
(Git1.7 具有相同的过程,只是通过自动提交重新排序而不是手动重新排序的可能性以及通过更干净的压缩消息使速度更快)

I would like to be able to remove all my frequent-checkins and just leave the serious ones.

我希望能够删除我所有的频繁签到,只留下严重的签到。

This is called squashing commits.
You have some good example of "comit cleaning" in this Git readyarticle:
(Note: the rebase interactive feature came along since September 2007, and allows for squashing or splitting or removing or reordering commits: see also the GitPro page)

这称为压缩提交
在这篇Git 就绪的文章中,您有一些关于“comit 清理”的很好的例子:(
注意:rebase 交互功能自 2007 年 9 月以来出现,并允许压缩或拆分或删除或重新排序提交:另见GitPro 页面

A word of caution: Only do this on commits that haven't been pushed an external repository. If others have based work off of the commits that you're going to delete, plenty of conflicts can occur. Just don't rewrite your history if it's been shared with others.

警告:仅对尚未推送到外部存储库的提交执行此操作。如果其他人的工作基于您要删除的提交,则可能会发生大量冲突。如果已与他人共享,请不要重写您的历史记录。

alt text

替代文字

The last 4 commits would be much happier if they were wrapped up together

如果最后 4 次提交打包在一起会更开心

$ git rebase -i HEAD~4

pick 01d1124 Adding license
pick 6340aaa Moving license into its own file
pick ebfd367 Jekyll has become self-aware.
pick 30e0ccb Changed the tagline in the binary, too.

# Rebase 60709da..30e0ccb onto 60709da
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
#

rebase using the last four commits from where the HEADis with HEAD~4.
We're just going to squash everything into one commit.
So, changing the first four lines of the file to this will do the trick:

使用变基从那里过去四年提交HEADHEAD~4
我们只是将所有内容压缩为一次提交。
因此,将文件的前四行更改为此将起作用:

pick 01d1124 Adding license
squash 6340aaa Moving license into its own file
squash ebfd367 Jekyll has become self-aware.
squash 30e0ccb Changed the tagline in the binary, too.

Basically this tells Git to combine all four commits into the the first commit in the list. Once this is done and saved, another editor pops up with the following:

基本上这告诉 Git 将所有四个提交合并到列表中的第一个提交中。完成并保存后,另一个编辑器会弹出以下内容:

# This is a combination of 4 commits.
# The first commit's message is:
Adding license

# This is the 2nd commit message:

Moving license into its own file

# This is the 3rd commit message:

Jekyll has become self-aware.

# This is the 4th commit message:

Changed the tagline in the binary, too.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# Explicit paths specified without -i nor -o; assuming --only paths...
# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   LICENSE
#   modified:   README.textile
#   modified:   Rakefile
#   modified:   bin/jekyll
#

Since we're combining so many commits, Git allows you to modify the new commit's message based on the rest of the commits involved in the process. Edit the message as you see fit, then save and quit.
Once that's done, your commits have been successfully squashed!

由于我们合并了如此多的提交,Git 允许您根据流程中涉及的其余提交修改新提交的消息。编辑您认为合适的消息,然后保存并退出。
完成后,您的提交已成功压缩!

Created commit 0fc4eea: Creating license file, and making jekyll self-aware.
 4 files changed, 27 insertions(+), 30 deletions(-)
  create mode 100644 LICENSE
    Successfully rebased and updated refs/heads/master.

And if we look at the history again…

如果我们再看一遍历史……

alt text

替代文字



Note: for "commit squashing" purposes, Git1.7(February 2010) has introduced 2 new elements (as mentioned by Dustinin the comment):

注意:出于“提交压缩”的目的,Git1.7(2010 年 2 月)引入了 2 个新元素(正如Dustin在评论中提到的):

  • "git rebase -i" learned new action "fixup" that squashes the change but does not affect existing log message.
  • "git rebase -i" also learned --autosquashoption that is useful together with the new "fixup" action.
  • git rebase -i“学习新动作” fixup“压缩更改但不影响现有日志消息。
  • git rebase -i”还学习--autosquash了与新的“修复”操作一起有用的选项。

Both (fixup action and --autosquashoption) are illustrated in this Thechnosorcery Networks blog entry. Those features have been cooking since last June 2009and debated further last December.

两者(修复操作和--autosquash选项)都在Thechnosorcery Networks 博客条目 中进行了说明。自2009 年 6 月以来,这些功能一直在酝酿中,并在去年 12 月进行了进一步辩论。

The fixupaction or directive is for squashing a commit you would have manually reordered in the commit edit list of a rebase --interactive, while ignoring the second commit message, which will make the message edition step faster (you can just save it: the squashed commit will have the firstcommit message only)
The resulting commit message will only be the first commit one.

fixup动作或指令是挤压提交你会手动重新排序,在提交的编辑列表rebase --interactive,而忽略了第二次提交的消息,这将使得报文编辑更快一步(你可以将它保存:压扁承诺将有仅第一个提交消息)
生成的提交消息将仅是第一个提交消息。

  # s, squash = use commit, but meld into previous commit
  # f, fixup = like "squash", but discard this commit's log message

The --autosquashoption is about making the commit reordering process automatically for you:

--autosquash选项是关于为您自动进行提交重新排序过程:

If you know what commit you want to squash something in to you can commit it with a message of “squash! $other_commit_subject”. Then if you run @git rebase --interactive --autosquash commitish@, the line will automatically be set as squash, and placed below the commit with the subject of $other_commit_subject.

如果你知道你想压缩什么提交,你可以用“ squash! $other_commit_subject”消息提交它。然后,如果您运行@git rebase --interactive --autosquash commitish@,该行将自动设置为壁球,并放置在主题为 $other_commit_subject 的提交下方。

(Actually, the squash!can only use the beginningof another commit message)

(实际上,squash!只能使用另一个提交消息的开头

$ vim Foo.txt
$ git commit -am "Change all the 'Bar's to 'Foo's"
[topic 8374d8e] Change all the 'Bar's to 'Foo's
 1 files changed, 2 insertions(+), 2 deletions(-)
$ vim Bar.txt
$ git commit -am "Change all the 'Foo's to 'Bar's"
[topic 2d12ce8] Change all the 'Foo's to 'Bar's
 1 files changed, 1 insertions(+), 1 deletions(-)

$ vim Foo.txt
$ git commit -am "squash! Change all the 'Bar's"
[topic 259a7e6] squash! Change all the 'Bar's
 1 files changed, 2 insertions(+), 1 deletions(-)

See? Here the thirdcommit uses only the beginning of the firstcommit message.
A rebase --interactive --autosquashwill move the squashed commit below the relevant one:

看?这里第三次提交仅使用第一次提交消息的开头。
Arebase --interactive --autosquash会将压缩的提交移动到相关提交下方:

pick 8374d8e Change all the 'Bar's to 'Foo's
squash 259a7e6 squash! Change all the 'Bar's
pick 2d12ce8 Change all the 'Foo's to 'Bar's

The message edition would be:

消息版本将是:

# This is a combination of 2 commits.
# The first commit's message is:

Change all the 'Bar's to 'Foo's

# This is the 2nd commit message:

squash! Change all the 'Bar's

Meaning by default you would keep the squashing operation recorded in the commit message.
But with the fixup! directive, you could keep that squashing "invisible" in the commit message, while still benefiting from the automatic commit reordering with the --autosquashoption (and the fact that your second commit message is based on the first commit you want to be squashed with).

这意味着默认情况下,您会将压缩操作记录在提交消息中。
但是随着修复!指令,您可以在提交消息中保持压缩“不可见”,同时仍然受益于使用该--autosquash选项的自动提交重新排序(以及您的第二个提交消息基于您想要压缩的第一个提交的事实)。

pick 8374d8e Change all the 'Bar's to 'Foo's
fixup cfc6e54 fixup! Change all the 'Bar's
pick 2d12ce8 Change all the 'Foo's to 'Bar's

The message by default will be:

默认情况下,消息将是:

# This is a combination of 2 commits.
# The first commit's message is:

Change all the 'Bar's to 'Foo's

# The 2nd commit message will be skipped:

#    fixup! Change all the 'Bar's

Notice that the fixup!commit's message is already commented out.
You can just save out the message as-is, and your original commit message will be kept.
Very handy for including changes when you realize that you forgot to add part of an earlier commit.

请注意,fixup!提交的消息已被注释掉。
您可以按原样保存消息,您的原始提交消息将被保留
当您意识到忘记添加早期提交的一部分时,包含更改非常方便

Now if you want to fixup or squash based on the previous commityou just did, Jacob Helwig(the author of the Technosorcery Networks blog entry) recommends the following aliases:

现在,如果您想根据刚刚执行先前提交进行修复或压缩Jacob Helwig(Technosorcery Networks 博客条目的作者)建议使用以下别名:

[alias]
    fixup = !sh -c 'git commit -m \"fixup! $(git log -1 --format='\''%s'\'' $@)\"' -
    squash = !sh -c 'git commit -m \"squash! $(git log -1 --format='\''%s'\'' $@)\"' -

And for doing a rebase interactive which will always benefit from the automatic reordering of commits meant to be squashed:

并且进行 rebase 交互,这将始终受益于要压缩的提交的自动重新排序:

[alias]
    ri = rebase --interactive --autosquash


Update for Git 2.18 (Q2 2018): "git rebase -i" sometimes left intermediate "# This is a combination of N commits" message meant for the human consumption inside an editor in the final result in certain corner cases, which has been fixed.

Git 2.18 更新(2018 年第 2 季度):在某些极端情况下,最终结果中的“ git rebase -i”有时会留在中间” # This is a combination of N commits”消息供人类在编辑器中使用,该问题已得到修复。

See commit 15ef693, commit dc4b5bc, commit e12a7ef, commit d5bc6f2(27 Apr 2018) by Johannes Schindelin (dscho).
(Merged by Junio C Hamano -- gitster--in commit 4a3bf32, 23 May 2018)

请参阅Johannes Schindelin ( ) 的commit 15ef693commit dc4b5bccommit e12a7efcommit d5bc6f2(2018 年 4 月 27 日(由Junio C Hamano合并-- --提交 4a3bf32 中,2018 年 5 月 23 日)dscho
gitster

rebase --skip: clean up commit message after a failed fixup/squash

During a series of fixup/squash commands, the interactive rebase builds up a commit message with comments. This will be presented to the user in the editor if at least one of those commands was a squash.

In any case, the commit message will be cleaned up eventually, removing all those intermediate comments, in the final step of such a fixup/squash chain.

However, if the last fixup/squash command in such a chain fails with merge conflicts, and if the user then decides to skip it (or resolve it to a clean worktree and then continue the rebase), the current code fails to clean up the commit message.

This commit fixes that behavior.

The fix is quite a bit more involved than meets the eye because it is not only about the question whether we are git rebase --skiping a fixup or squash. It is also about removing the skipped fixup/squash's commit message from the accumulated commit message. And it is also about the question whether we should let the user edit the final commit message or not ("Was there a squash in the chain that was not skipped?").

For example, in this case we will want to fix the commit message, but not open it in an editor:

pick  <- succeeds
fixup   <- succeeds
squash  <- fails, will be skipped

This is where the newly-introduced current-fixupsfile comes in real handy. A quick look and we can determine whether there was a non-skipped squash. We only need to make sure to keep it up to date with respect to skipped fixup/squash commands. As a bonus, we can even avoid committing unnecessarily, e.g. when there was only one fixup, and it failed, and was skipped.

To fix only the bug where the final commit message was not cleaned up properly, but without fixing the rest, would have been more complicated than fixing it all in one go, hence this commit lumps together more than a single concern.

rebase --skip:在修复/挤压失败后清理提交消息

在一系列 fixup/squash 命令期间,交互式 rebase 构建带有注释的提交消息。如果这些命令中至少有一个是squash.

在任何情况下,提交消息最​​终都会被清理,删除所有这些中间评论,在这样一个修复/压缩链的最后一步。

但是,如果此类链中的最后一个 fixup/squash 命令因合并冲突而失败,并且用户随后决定跳过它(或将其解析为干净的工作树,然后继续 rebase),则当前代码无法清理提交消息。

此提交修复了该行为。

修复比表面上看到的要复杂得多,因为它不仅仅是关于我们是进行修复git rebase --skip还是压缩的问题。这也是关于从累积的提交消息中删除跳过的修复/挤压的提交消息。这也是关于我们是否应该让用户编辑最终提交消息的问题(“链是否存在未被跳过的挤压?”)。

例如,在这种情况下,我们将要修复提交消息,但不会在编辑器中打开它:

pick  <- succeeds
fixup   <- succeeds
squash  <- fails, will be skipped

这是新引入的current-fixups文件真正派上用场的地方。快速浏览一下,我们可以确定是否存在未跳过的壁球。我们只需要确保它在跳过的 fixup/squash 命令方面保持最新。作为奖励,我们甚至可以避免不必要的提交,例如,当只有一个修复并且失败并被跳过时。

仅修复最终提交消息未正确清理的错误,而不修复其余部分,比一次性修复所有问题要复杂得多,因此此提交将不仅仅是一个问题。



Git 2.19 (Q3 2018) fixes a bug: When "git rebase -i" is told to squash two or more commits into one, it labeled the log message for each commit with its number.
It correctly called the first one "1st commit", but the next one was "commit #1", which was off-by-one(!).

Git 2.19(2018 年第三季度)修复了一个错误:当“ git rebase -i”被告知将两个或多个提交压缩为一个时,它会用其编号标记每个提交的日志消息。
它正确地将第一个称为“第一次提交”,但下一个是“ commit #1”,它是一对一(!)。

See commit dd2e36e(15 Aug 2018) by Phillip Wood (phillipwood).
(Merged by Junio C Hamano -- gitster--in commit 36fd1e8, 20 Aug 2018)

请参阅Phillip Wood ( )提交的 dd2e36e(2018 年 8 月 15 日)(由Junio C Hamano合并-- --commit 36​​fd1e8,2018 年 8 月 20 日)phillipwood
gitster

rebase -i: fix numbering in squash message

Commit e12a7ef("rebase -i: Handle "combination of <n>commits" with GETTEXT_POISON", 2018-04-27, Git 2.18) changed the way that individual commit messages are labelled when squashing commits together.
In doing so a regression was introduced where the numbering of the messages is off by one. This commit fixes that and adds a test for the numbering.

rebase -i:修复壁球消息中的编号

Commit e12a7ef(" rebase -i: Handle "combination of <n>commits" with GETTEXT_POISON", 2018-04-27, Git 2.18) 改变了将提交压缩在一起时标记单个提交消息的方式。
在这样做时引入了回归,其中消息的编号以一为单位。此提交修复了该问题并添加了编号测试。

回答by Dan Rosenstark

Using Soft Reset Instead of Rebase to Squash GIT History

使用软重置而不是 Rebase 来压缩 GIT 历史记录

I think the lengthof VonC's answers speaks volumes -- literally -- about how complicated git rebaseis. This is my extension of another answer to a question of mine.

我认为VonC 的回答的长度足以说明问题的复杂git rebase程度。这是我对我的问题的另一个回答的延伸。

  1. You have a branch ticket-201that you branched from master. You want to pretend that all the commits from ticket-201never happened, but that you did all the work in one shot.
  2. Soft reset to the branch point using git reset --soft hashwhere hashshould be a commit hash that is in ticket-201's log.
  3. Commit your changes using add then commit. Now the branch history will only have the first commit and the new one with the new stuff.
  1. 你有一个分支ticket-201,你从master. 你想假装所有的提交ticket-201从未发生过,但是你一次完成了所有的工作。
  2. 使用git reset --soft hashwherehash应该是ticket-201日志中的提交哈希软重置到分支点。
  3. 使用添加然后提交提交您的更改。现在分支历史将只有第一次提交和带有新内容的新提交。

Making Up Histories From Arbitrary Commits in Different Branches

从不同分支的任意提交中编造历史

Using resets you can rewrite the history as you see fit, though your edits will lose the charm of having the right timestamp. Assuming you don't care about that (the times/dates on your files will be enough, perhaps?), or if you want to fiddle with the commits as you go, you can follow these steps:

使用重置,您可以根据需要重写历史记录,但您的编辑将失去拥有正确时间戳的魅力。假设您不关心这个(文件上的时间/日期就足够了,也许?),或者如果您想在进行时摆弄提交,您可以按照以下步骤操作:

  1. Checkout a new branch at commit0(pretend that's a hash): git checkout -b new-history commit0
  2. Now you can get the files from commit5: git reset --hard commit5
  3. Switch back to your index point: git reset --soft commit0
  4. Commit and this will be the second commit in the branch.
  1. commit0(假装这是一个散列)处签出一个新分支:git checkout -b new-history commit0
  2. 现在您可以从commit5以下位置获取文件:git reset --hard commit5
  3. 切换回您的索引点: git reset --soft commit0
  4. 提交,这将是分支中的第二次提交。

This idea is simple, effective and flexible.

这个想法简单、有效且灵活。

回答by Jason Punyon

Use git rebase -i to pick and squash your commits together.

使用 git rebase -i 来挑选和压缩你的提交。