Home > front end >  How to go back two commits, then squash three into one?
How to go back two commits, then squash three into one?

Time:10-13

I originally updated a file on GH. In the PR, I ended up making 3 commits. I wanted to squash them into one commit using the CLI. I ended up making two more commits in error. Now, I can't figure out how to get back.

Here is my git log --oneline:

4t0po5ec2 (HEAD -> page-update, origin/page-update) Merge branch 'page-update' of github.com:usern>
45yh4t106 Fixes links
wer42q158 Reverts animation
g0otle08b Updates syntax
adf4213bd Fixes links
t549tie217 (master) Review.

I'm trying to squash the first three commits (adf4213bd, g0otle08b, wer42q158) into one after (master) Review. The latest two commits (45yh4t106, 4t0po5ec2) were made in error of trying to do so and are not needed.

I've tried git reset --soft HEAD~3, but it takes me further before master. I've tried git pull origin master --rebase, but this keeps the latest commit as well, meaning there is more than one commit in the end.

CodePudding user response:

I would use interactive rebase, i.e.

git rebase -i HEAD~5

and mark fixup for every commit except the oldest one. Delete the lines with the commits to be dropped entirely.

Maybe, after all that, edit the commit message. (And force push, I believe that's inevitable when rewriting history).

CodePudding user response:

NOTE: Make a backup of your repo and working tree to avoid losing any (uncommitted) work.

Looking at the history, it is obvious that you have rewritten local history and then merged the old history back, adding those rewritten commits again. To get rid of that, move your local branch back by one commit. You can use git reset --hard HEAD^ for that. Let me repeat the note from above: reset --hard will remove any uncommitted work that you had. If you do, any uncommitted changes are lost and gone for good.

Back to your initial problem:

It sounds like you wanted to squash all commits of your branch that happened after master. git reset --soft is a simply way to achieve this:

With --soft, reset will keep your index and all the changes previously committed with be in status "staged", meaning they will be part of the next commit.

git checkout page-update
git reset --soft master
git commit # now enter your new commit message

Now, when pushing, Git will complain because you have changed commits that are already pushed. Assuming the commits have not been merged anywhere and nobody has built newer commits on top of them, you must force-push (and not pull the old commits from your remote repository).

CodePudding user response:

I agree with @mbojko, interactive rebase is the tool I would use.

Rebasing can be a bit daunting if you've never done it before - so here is a detailed description of how to run the interactive rebase for your scenario.

1. Start the interactive rebase:

git rebase -i master^

# rebase the commits from your head to one before master
# (`master^` means one commit before master).

or

git rebase -i HEAD~5

# rebase the last five commits
# same as above, just a different way of writing it

git will open an editor that looks something like this:

pick t549tie217 (master) Review.
pick adf4213bd Fixes links
pick g0otle08b Updates syntax
pick wer42q158 Reverts animation
pick 45yh4t106 Fixes links
pick 4t0po5ec2 Merge branch 'page-update' of github.com:usern>

Note that the commits are listed in reverse order from oldest at the top to most recent at the bottom!

This lets you choose what happens with each commit (the "interactive" part of the rebase).

2. To squash the three commits together into one and discard the last two commits then edit the file to look like this (I've changed "pick" on some of the lines).

pick t549tie217 (master) Review.
pick adf4213bd Fixes links
fixup g0otle08b Updates syntax
fixup wer42q158 Reverts animation
drop 45yh4t106 Fixes links
drop 4t0po5ec2 Merge branch 'page-update' of github.com:usern>

At the bottom of the file is a comment describing what each command will do, but here's a quick summary:

  • p, pick: the default, will use the commit and message as-is.
  • f, fixup: squash the commit into the previous commit and use the previous commit's message
  • d, drop: drop the commit

So that means

pick t549tie217 (master) Review.    # keep this one
pick adf4213bd Fixes links          # keep this one
fixup g0otle08b Updates syntax      # merge into adf4213bd
fixup wer42q158 Reverts animation   # merge into adf4213bd
drop 45yh4t106 Fixes links          # drop commit
drop 4t0po5ec2 Merge branch 'page-update' of github.com:usern>  # drop commit

3. Once you save and exit the editor git will perform the rebase. If everything has gone well then your branch will be in the desired state.


Note - if you start the rebase and then decide don't want to do anything - i.e. you want to abort the rebase - then just comment out every line in the file.

This will abort the rebase because there's nothing for git to do:

#pick t549tie217 (master) Review.
#pick adf4213bd Fixes links
#fixup g0otle08b Updates syntax
#fixup wer42q158 Reverts animation
#drop 45yh4t106 Fixes links
#drop 4t0po5ec2 Merge branch 'page-update' of github.com:usern>

CodePudding user response:

I strongly advise to always use --graph with git log (especially for human viewing, there may be a point to not use it in scripts), otherwise you can't make sense of branches and merges in the history you are viewing.

The "git reset HEAD~3 goes too far back" is probably the indication that the sequence of commits after master is not a straight line but a fork&merge sequence :

# given the message of topmost commit, it is a merge commit
* 4t0po5ec2 (HEAD -> page-update, origin/page-update) Merge branch 'page-update' of github.com:usern>
# blind guess on the history: the local and remote branches forked after master
|\
* | 45yh4t106 Fixes links
* | wer42q158 Reverts animation
| * g0otle08b Updates syntax
| * adf4213bd Fixes links
|/
* t549tie217 (master) Review.

This will also lead to unexpected things if you run git rebase master -- by default, git rebase completely ignores all merge commits in its list.

(note: the diagram above is a blind guess, for all we know the fork point can even be before master, or there could be other intermediate merges)


If you want to restore a linear history in your repo :

  • figure out which of the two branches is the one you want to keep (let's stick to my diagram above, and assume you want to write your commits on top of g0otle08b Updates syntax)
  • go to that "head" commit : git reset --hard g0otle08b
  • cherry-pick the other two : git cherry-pick wer42q158 45yh4t106

You should now have something like :

* (HEAD -> page-update) b4rfdp0dl Fixes links    # rewritten, new sha
* m3rglbl0b Reverts animation                    # rewritten, new sha
* g0otle08b Updates syntax
* adf4213bd Fixes links
* t549tie217 (master) Review.

and it will be easier to rearrange your commits (e.g, with git rebase -i)

  •  Tags:  
  • git
  • Related