Git rebase - global-121/121-platform GitHub Wiki

git rebase

It is recommended to watch the related video before following the steps in the guide. If you don't know where to find it, reach out.

Local machine setup

To make your life easier with rebasing on your machine, do these things (one time only).

Set VSCode as your default code editor for git

Update your file at ~/.gitconfig adding (or editing) the following values:

[core]
    editor = code --wait
[merge]
    tool = vscode
[mergetool "vscode"]
    cmd = code --wait $MERGED
[diff]
    tool = vscode
[difftool "vscode"]
    cmd = code --wait --diff $LOCAL $REMOTE
[sequence]
    editor = code --wait

Before starting a rebase

Before doing anything, verify that your branch is up to date with origin, and that you have a clean working tree:

git fetch origin
git status

Should output something like

On branch aberonni/remove-wait-for-in-tests
Your branch is up to date with 'origin/aberonni/remove-wait-for-in-tests'.

nothing to commit, working tree clean

If your branch is not up to date and you don't know what to do, go to troubleshooting.

Starting a rebase

To start a rebase:

git rebase -i TARGET_BRANCH

For example, to rebase on main:

git rebase -i origin/main

Running a rebase with the -i flag means "interactive". This will prompt git to open your (previously configured) editor, so you can decide what to do with the commits that you are "reapplying" on the target branch. Once you have decided what you want to do, close the editor to initiate the rebase.

[!IMPORTANT] You should always check to see if all of these commits belong to you. The ones that don't should be dropped - not doing so will lead to nasty and confusing conflicts.

More info in the Dropping commits section.

[!TIP] If you don't know what to do in the editor, and all of the commits in the list are yours, then it is safe to just close the list without touching anything. If you're still not sure, ask!

Executing the rebase

During the rebase process, each of the commits that you have chosen will be "reapplied" on top of the target branch, in the chosen order.

If there are no conflicts, then you will see a message like the following, and then you can proceed to push:

Successfully rebased and updated refs/heads/aberonni/remove-wait-for-in-tests.

However, there could be conflicts during the rebase process. In that case, the rebase will be interrupted, and you will see an error like the following:

CONFLICT (content): Merge conflict in services/121-service/package.json
error: could not apply fd633410f... Add better reporter for tests
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply fd633410f... Add better reporter for tests

And you will have to do the following

  1. Solve all conflicts in the files manually (VSCode will help identify files with conflicts)
  2. Add or delete each file when you have solved the conflict (VSCode will tell you if you are trying to do this to a file that still has conflicts)
  3. Run the following command once you have solved all of the conflicts for that commit: git rebase --continue
  4. Git will prompt you to update the commit message. You can close the file to leave the commit message as-is.
  5. Repeat from step 1 until you have solved all conflicts

In the VSCode editor, during conflict resolution, "Current" always represents the changes on the "target branch", while "Incoming" represent the changes on your branch.

package-lock.json

You should never solve a conflict on this file manually. Instead:

  1. Solve any conflicts in package.json manually
  2. Run npm install in the folder that contains the package.json and the package-lock.json files in question
  3. The conflicts in package-lock.json should be automatically solved.

Pushing the rebase

To push a rebased branch, you need to "push force" because you have "rewritten git history" and so the git origin (GitHub) will not accept your version of history.

[!CAUTION] WARNING: This is a destructive operation. Do not do this if you do not understand what you are doing. If in doubt, stop and ask for help. Never do this on a branch that is not your own.

git push --force-with-lease --force-if-includes

If you see an error, it might be because the HEAD of your target branch has changed since you started rebasing. In this case, you need to update your local branch, and then run the rebase again:

git pull --rebase
git rebase -i TARGET_BRANCH

Dropping commits

It is important to always run an interactive rebase, because not doing so can lead to unexpected surprises. This is because, at times, you might be "carrying" commits that aren't yours.

A typical scenario in which this can happen is when changing target branch.

Scenario: Changing target branch

At times we will find ourselves working on a branch that does not branch off of main.

For example:

  • Gianni built a new endpoint on the branch feat/fancy-new-backend-endpoint
  • Andrew wants to build the frontend for that endpoint so he creates a branch based on Gianni's branch: feat/pretty-new-frontend-feature

From time to time, Gianni will make changes to their branch, which Andrew will need. So Andrew will run:

git checkout feat/pretty-new-frontend-feature
git fetch origin
git rebase -i origin/feat/fancy-new-backend-endpoint

So far, so good, and nothing out of the extraordinary.

But, at a certain point, Gianni's endpoint will be merged into main. From here onwards, Andrew will need to rebase on main:

git checkout feat/pretty-new-frontend-feature
git fetch origin
git rebase -i origin/main # the target branch has changed

[!IMPORTANT] The first time the target branch changes (eg. in this example from feat/fancy-new-backend-endpoint to main), there will be extra commits shown in the rebase editor.

Now Andrew will see a mix of their own commits, and Gianni's commits from feat/fancy-new-backend-endpoint. That is because git cannot tell that those commits have now been merged into main. So it is important that, in the rebase editor, Andrew manually drops all of the commits that do not belong to his branch. Not dropping those commits will lead to

  • a nasty, confusing, and unnecessary series of rebase conflicts
  • a confusing git history

[!NOTE] The necessity to drop commits can also happen in other scenarios. For example, if the target branch has had any operation on it that has modified it's history (eg. commits were squashed).

For the above reasons, it is important to always check the list of commits in the interactive rebase editor, and ensure that they all belong to you.

Troubleshooting

My branch is not up to date before rebasing

Do you just have pending changes? Then just push:

git push

Did you do a rebase already and forget to push? Then "push force":

git push --force-with-lease --force-if-includes

Did you rebase via GitHub UI? Then you need to "pull with rebase":

git pull --rebase

My rebase is in a weird state and I need help

Don't push anything to remote. Abort the rebase with the following command and ask for help:

git rebase --abort

Cheatsheet

# Before rebasing:
git checkout <BRANCH_YOU_WANT_TO_REBASE>
git fetch origin
git status
# Make sure everything is up to date with origin.
# Then start rebase, for example, if <TARGET_BRANCH> is main:
git rebase -i origin/main
# At this point, you edit the rebase file in your editor of choosing, and then close it.
# git will now initiate the rebase.
# If there are conflicts, you solve them (either in VSCode or manually).
# Then, for each file with conflicts solved:
git add/rm FILE # Or you can do this in VSCode
# Once all conflicts are solved, resume the rebase with:
git rebase --continue
# Once the rebase is complete, you push with:
git push --force # DESTRUCTIVE OPERATION
# If at any point your rebase ends up in a weird state, you can go back to step 0 with
git rebase --abort