Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
289 views
in Technique[技术] by (71.8m points)

branch - Trimming Git Commits/Squashing Git History

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.

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.

Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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


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)

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 ready article:
(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)

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

$ 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 HEAD is 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:

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:

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

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 Dustin in the comment):

  • "git rebase -i" learned new action "fixup" that squashes the change but does not affect existing log message.
  • "git rebase -i" also learned --autosquash option that is useful together with the new "fixup" action.

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

The fixup action 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 first commit message only)
The resulting commit message will only be the first commit one.

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

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

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.

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

$ 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 third commit uses only the beginning of the first commit message.
A rebase --interactive --autosquash will move the squashed commit below the relevant one:

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 --autosquash option (and the fact that your second commit message is based on the first commit you want to be squashed with).

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.

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

[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:

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

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)

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


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...