Squash my last X commits together using Git

前端 未结 30 3375
醉酒成梦
醉酒成梦 2020-11-21 05:17

How can I squash my last X commits together into one commit using Git?

相关标签:
30条回答
  • 2020-11-21 05:36

    If you want to squish every commit into a single commit (e.g. when releasing a project publicly for the first time), try:

    git checkout --orphan <new-branch>
    git commit
    
    0 讨论(0)
  • 2020-11-21 05:38

    Anomies answer is good, but I felt insecure about this so I decided to add a couple of screenshots.

    Step 0: git log

    See where you are with git log. Most important, find the commit hash of the first commit you don't want to squash. So only the :

    Step 1: git rebase

    Execute git rebase -i [your hash], in my case:

    $ git rebase -i 2d23ea524936e612fae1ac63c95b705db44d937d
    

    Step 2: pick / squash what you want

    In my case, I want to squash everything on the commit that was first in time. The ordering is from first to last, so exactly the other way as in git log. In my case, I want:

    Step 3: Adjust message(s)

    If you have picked only one commit and squashed the rest, you can adjust one commit message:

    That's it. Once you save this (:wq), you're done. Have a look at it with git log.

    0 讨论(0)
  • 2020-11-21 05:38

    ⚠️ WARNING: "My last X commits" might be ambiguous.

      (MASTER)  
    Fleetwood Mac            Fritz
          ║                    ║
      Add Danny  Lindsey     Stevie       
        Kirwan  Buckingham    Nicks                                              
          ║         ╚═══╦══════╝     
    Add Christine       ║          
       Perfect      Buckingham
          ║           Nicks            
        LA1974══════════╝                                    
          ║                  
          ║                  
        Bill <══════ YOU ARE EDITING HERE
      Clinton        (CHECKED OUT, CURRENT WORKING DIRECTORY)              
    

    In this very abbreviated history of the https://github.com/fleetwood-mac/band-history repository you have opened a pull request to merge in the the Bill Clinton commit into the original (MASTER) Fleetwood Mac commit.

    You opened a pull request and on GitHub you see this:

    Four commits:

    • Add Danny Kirwan
    • Add Christine Perfect
    • LA1974
    • Bill Clinton

    Thinking that nobody would ever care to read the full repository history. (There actually is a repository, click the link above!) You decide to squash these commits. So you go and run git reset --soft HEAD~4 && git commit. Then you git push --force it onto GitHub to clean up your PR.

    And what happens? You just made single commit that get from Fritz to Bill Clinton. Because you forgot that yesterday you were working on the Buckingham Nicks version of this project. And git log doesn't match what you see on GitHub.

    0 讨论(0)
  • 2020-11-21 05:38

    In question it could be ambiguous what is meant by "last".

    for example git log --graph outputs the following (simplified):

    * commit H0
    |
    * merge
    |\
    | * commit B0
    | |
    | * commit B1
    | | 
    * | commit H1
    | |
    * | commit H2
    |/
    |
    

    Then last commits by time are H0, merge, B0. To squash them you will have to rebase your merged branch on commit H1.

    The problem is that H0 contains H1 and H2 (and generally more commits before merge and after branching) while B0 don't. So you have to manage changes from H0, merge, H1, H2, B0 at least.

    It's possible to use rebase but in different manner then in others mentioned answers:

    rebase -i HEAD~2

    This will show you choice options (as mentioned in other answers):

    pick B1
    pick B0
    pick H0
    

    Put squash instead of pick to H0:

    pick B1
    pick B0
    s H0
    

    After save and exit rebase will apply commits in turn after H1. That means that it will ask you to resolve conflicts again (where HEAD will be H1 at first and then accumulating commits as they are applied).

    After rebase will finish you can choose message for squashed H0 and B0:

    * commit squashed H0 and B0
    |
    * commit B1
    | 
    * commit H1
    |
    * commit H2
    |
    

    P.S. If you just do some reset to BO: (for example, using reset --mixed that is explained in more detail here https://stackoverflow.com/a/18690845/2405850):

    git reset --mixed hash_of_commit_B0
    git add .
    git commit -m 'some commit message'
    

    then you squash into B0 changes of H0, H1, H2 (losing completely commits for changes after branching and before merge.

    0 讨论(0)
  • 2020-11-21 05:39

    I find a more generic solution is not to specify 'N' commits, but rather the branch/commit-id you want to squash on top of. This is less error-prone than counting the commits up to a specific commit—just specify the tag directly, or if you really want to count you can specify HEAD~N.

    In my workflow, I start a branch, and my first commit on that branch summarizes the goal (i.e. it's usually what I will push as the 'final' message for the feature to the public repository.) So when I'm done, all I want to do is git squash master back to the first message and then I'm ready to push.

    I use the alias:

    squash = !EDITOR="\"_() { sed -n 's/^pick //p' \"\\$1\"; sed -i .tmp '2,\\$s/^pick/f/' \"\\$1\"; }; _\"" git rebase -i
    

    This will dump the history being squashed before it does so—this gives you a chance to recover by grabbing an old commit ID off the console if you want to revert. (Solaris users note it uses the GNU sed -i option, Mac and Linux users should be fine with this.)

    0 讨论(0)
  • 2020-11-21 05:42

    You can use git merge --squash for this, which is slightly more elegant than git rebase -i. Suppose you're on master and you want to squash the last 12 commits into one.

    WARNING: First make sure you commit your work—check that git status is clean (since git reset --hard will throw away staged and unstaged changes)

    Then:

    # Reset the current branch to the commit just before the last 12:
    git reset --hard HEAD~12
    
    # HEAD@{1} is where the branch was just before the previous command.
    # This command sets the state of the index to be as it would just
    # after a merge from that commit:
    git merge --squash HEAD@{1}
    
    # Commit those squashed changes.  The commit message will be helpfully
    # prepopulated with the commit messages of all the squashed commits:
    git commit
    

    The documentation for git merge describes the --squash option in more detail.


    Update: the only real advantage of this method over the simpler git reset --soft HEAD~12 && git commit suggested by Chris Johnsen in his answer is that you get the commit message prepopulated with every commit message that you're squashing.

    0 讨论(0)
提交回复
热议问题