Skip to content

Instantly share code, notes, and snippets.

@aledpardo
Last active January 28, 2023 00:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aledpardo/543ac99ac6d9148e94cef9be273c48c4 to your computer and use it in GitHub Desktop.
Save aledpardo/543ac99ac6d9148e94cef9be273c48c4 to your computer and use it in GitHub Desktop.
How to use GIT Rebase

How to use GIT rebase

When need to have a more concise git history of changes.

TL;DR;

To rebase your local branch commits into 1:

git rebase -i HEAD~# number of commits

To put your changes as the last ones on top of develop branch

git checkout develop
git pull
git checkout <branch> # your branch name
git rebase develop
#then we push to remote repository
git push -u origin branch # your branch name

Step-by-step guide

What's rebase?

You can take a look on rebase's explanation using git rebase --help.

For our usage, rebase will give us the benefit to:

Simplify the git history, allowing us to aggregate commits with meaningless messages, like 'fix' into a single commit (this time with opportunity to write down a new and more meaningful message); Move our changes to the top of the main branch, with opportunity to fix any conflicts and having a better and chronological order of the commits (i.e. by the order we merge commits back to source branch and not by the date/time the commits where created).

How to use iterative Rebase to unify commit

Take a look on below git history shown using git log --oneline:

asdfw3 fix from MR comments (HEAD)
09jsdf lint fixes
09snei fix bug in api
fu2801 Task-01 Add feature
...

Clearly, the developer implemented the task Task-01, then made a sequence of 3 fixes, all connected with the first commit.

Thi's not a problem as it's how Git works, however, the git history now is bigger and noisy.

We could, and should, improve it using the rebase command, as below:

git rebase -i HEAD~4

Let's take a closer look to the flags passed to the command:

  1. -i is flag to enter in iterative mode

  2. HEAD~4 tells the starting commit and number of commits to look back, here 4 as it's the number of commits we want to shrink.

When you run the command, your default git editor will be open. It'll show something like this:

pick fu2801 task-01 Add feature
pick 09snei fix bug in api
pick 09jsdf lint fixes
pick asdfw3 fix from MR comments
# Rebase 309a629..f226132 onto 309a629 (4 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Usually, we use the following options:

fixup (f)
squash (s)
pick (p)

You can apply the changes you need to your commits, for example:

To keep only the first commit message and fix up the other 2:

pick fu2801 Task-01 Add feature
f 09snei fix bug in api
f 09jsdf lint fixes
f asdfw3 fix from MR comments

After saving the changes your git history will now look like this:

fu2801 Task-01 Add feature (HEAD)
...

Done.

You now have a better and cleaner git history. It's now time to go to next section to check how to ensure your changes are going to be on top of the remote repository. Rebase on top of source branch

It's quite common that other developers are pushing more commits to the remote repository while we are working on our changes.

To move our changes to the top of the source branch (commonly the develop branch), we need to run the following sequence of commands:

git checkout develop
git pull
git checkout feature/task-01
git rebase develop

The first 3 commands are quite simple. The fun starts after we run the rebase command. Git will start to rewrite our history of commits adding our commit at last. This process may be interrupted if a conflict is found.

If a conflict occurs, the rebase command will stop and wait until you:

  1. fix the conflicts (one by one on your editor of choice)
  2. stage the changes (git add .)

Once all conflicts are solved, run:

git rebase --continue

After the rebase completes, you're ready to run git push your changes to the remote repository. ;)

FAQ

Rebase ate my commit ;(

If you find yourself in such situation, no worries. Git has a hidden commit for every step you've done before, during and after your changes made with rebase.

Use the git reflog command and find the hash just before you run rebase.

Here's an example of reflog's content, extracted from a task-api repository.

git reflog
5dc04f5 HEAD@{0}: commit (amend): [task-80] Addfunction to API
c77c030 HEAD@{1}: Branch: renamed refs/heads/feature/task-80-to2.3 to refs/heads/feature/task-80
c77c030 HEAD@{3}: cherry-pick: [task-80] Add function to API
9eefc58 HEAD@{4}: checkout: moving from task-79 to task-80
9eefc58 HEAD@{5}: checkout: moving from master to task-79
cda39e6 HEAD@{6}: checkout: moving from task-80 to master
f584435 HEAD@{7}: commit (amend): [task-80] Add function to API
5d410ca HEAD@{8}: commit: [task-80] Add function to API
cda39e6 HEAD@{9}: reset: moving to cda39e6
cd348c1 HEAD@{10}: commit (amend): [task-80] Add function to API
6a71bbe HEAD@{11}: commit: [task-80] Add function to API
cda39e6 HEAD@{12}: checkout: moving from master to feature/task-80
cda39e6 HEAD@{13}: clone: from git@github.com:repo/repo.git

Can you see how git keeps a trace of all our moves?

To rollback to a specific point on time of the reflog, for example, where we see HEAD@{7}, run:

git reset --hard f584435

OK, now you're back on time (DeLorean feelings) and can check if everything is working and maybe even start rebasing again.

Can't push, I have conflicts with myself

Let's exemplify, if you:

  1. rebase your work
  2. rebase on top of develop
  3. push to remote
  4. open the pull request
  5. do additional changes to your code, fixes from MR for example

If you rebase at this point, your local repository will be different than the remote repository by 1 commit. And if you try to push your changes git will block you saying your local repository is not up to date. But you can't as the remote has the older copy of your code and local has to sent to remote.

In this case, run:

git push --force-with-lease

Done. Now the remote history is overwritten with your local repository one.

WARNING ⚠

A forced push should be done carefully. As it'll totally rewrite your commits on remote repository.

If you're working alone on the branch you're targeting, it's a safe command.

But, if you're collaborating with other developers, warn them before you push your changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment