简体   繁体   中英

How to squash commits in Git?

I am trying to squash my commits and merge my branch to master as one single commit. This is what I'm trying. I switch to master branch and then i do

git merge --squash <branch_name>

And I get

Automatic merge went well; stopped before committing as requested
Squash commit -- not updating HEAD

After that I commit. But however this is what I get

$ git commit -m "Resolved"
On branch master
nothing to commit, working tree clean

For some reason the changes are not reflected and I get the message nothing to commit. I've gone through many posts and questions on stack but nothing of help until now.

It's not completely clear to me what commits you are squash-merging and hence why you are getting this result. You can clear up the first question to yourself (it won't really help me much since I don't have the commits) with:

git log --decorate --oneline --graph --boundary master...<branch-name>

(note the three dots here). This will show what commits you have now on master , and what commits you will be bringing in from <branch-name> via the merge base commit(s) of these two branches.

In any case, though, I can make a good guess, because the way git merge works is to compare this merge base to the two branch tips. Here's an example graph fragment from before doing a merge (at this point, it does not matter whether this is a regular merge or a squash merge):

...--B--C--D   <-- master (HEAD)
      \
       E--F--G   <-- feature

Each single uppercase letter represents a commit (whose real ID is a Git hash ID like a9f3c72 or whatever). The merge base commit here is commit B : it's where the chain of commits, starting from both master and feature at the same time and working backwards (leftwards in this graph), first comes together. Commit B , in other words, is the latest commit that is on both branch master and branch feature . This is what makes it the merge base commit.

Git will now, in effect, run:

git diff B D   # see what "we" did on branch master
git diff B G   # see what "they" did on branch feature

Git must then combine these changes: if we changed README to add a line at the end, Git should take this extra line added to the end. If they changed foo.py in some way (added one line and deleted another, perhaps), Git should take their change to foo.py . If we both did exactly the same things , though, Git should take just one copy of that change. For instance, if we made the same change to foo.py on master , we don't need their change after all: it's covered by our change.

Let's say that we changed README and both we and they fixed the same thing in foo.py , but they also changed doc.txt and main.py . So our final set of changes is to keep our added line in README , keep our foo.py change, and pick up the doc.txt and main.py changes. The effect is that Git applies all of these to the contents of the merge base commit B . This gives us the contents for a new commit H . (Pay some attention to what's in H as it may come back to haunt us.) Git updates the index (where the next commit to make goes) and work-tree (where we can see what will be or was committed) to this new content, ready for committing.

Now regular vs squash merge suddenly matters, because if Git is to make a regular merge commit, it will do this:

...--B--C--D---H   <-- master (HEAD)
      \       /
       E--F--G   <-- feature

This new merge commit H , which combines all the work done in commits CD with all the work done in commits EFG , will point back to both commit D , the previous tip of master , and to commit G , the previous and still current tip of feature .

If Git is to make a squash commit, however—well, it stops after saying:

Automatic merge went well; stopped before committing as requested
Squash commit -- not updating HEAD
$ 

It makes us make the commit. Once we make this commit, we get the new commit H , but this time it does not point back to both D and G . This time, the new commit H points back only to D :

...--B--C--D---H   <-- master (HEAD)
      \
       E--F--G   <-- feature

Let's say this all works the way it should, and we do in fact make commit H . This gives rise to the case I think is the most likely.

The likely case

Let's see what happens now if we run git merge --squash feature again .

Git starts out the same as before, by finding the merge base: the point where branches master and feature join. That's commit B , again.

Now Git diffs the two branch tips. This time, the tip of master is H , so the two diffs are:

git diff B H
git diff B G

Git now goes to combine these changes. This time, we changed README , foo.py , doc.txt , and main.py . (Remember, these are the changes we said we got by combining everything.) Meanwhile they (in feature ) changed foo.py the same way we did, changed doc.txt the same way we did, and changed main.py the same way we did.

Git therefore takes all of our changes, and none of theirs. The result matches commit H exactly . Git now stops with the same message as before.

This time, when we run:

git commit

to finish things, Git compares our index (what we have staged for commit) to our HEAD commit and finds that these are exactly, totally, 100% identical. We already have all the work from feature . Git says "nothing to commit" and also "working tree clean" as there is nothing to commit and the work-tree matches the index.

The less-likely possibility

The other way we could get the same effect here, without making a squash commit H first, is if the commit series EFG "undoes itself" enough that it doesn't matter. For instance, suppose F is the matching change to foo.py (it's a copy of commit C , perhaps), but commit G is a revert of commit E . Now instead of touching doc.txt and main.py , the sum of the changes from B to G is included in our original B -to- D changes. The git merge --squash has commits to merge, but again there's no effect on the final source tree . Our index and work-tree will match commit G and git commit won't make new commit H at all.

In terms of "commit differences" this is the same scenario as before: whatever change, if any, gets introduced on the other branch, we already have. But this time we didn't get it by squash-merging: we just already have it anyway.

If you want to squash commits go to HEAD(last) commit and just:

git reset --soft HEAD~2 && git commit

After enter this command you will be asked to write commit message to new commit.
This command will squash your last two commits in one with new commit message you write.

And then merge your branch how it is described here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM