Easier Git rebase of messy branches
Tags: github, git, webdev, devops
Published: 2023-04-24 23:04:58

## Introduction

Even when working with short lived branches, repositories with constant changes can cause lots of conflicts and result in outdated branches hard to rebase.

In this post, I'll show a way to rebase those branches with much less effort.

## Messy rebase branches

In this example, trying to rebase `branch-to-rebase` on `origin/main` will yield conflicts:

```bash
> git rebase origin/main

Auto-merging SomeFile.cs

CONFLICT (content): Merge conflict in SomeFile.cs
error: could not apply 57511bd... Change SomeFile
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 57511bd... Change SomeFile
```

If we run `git status`, we can can see the conflict is in the first of 16 commits:

```bash
> git status

interactive rebase in progress; onto 066147e

Last command done (1 command done):

   pick 57511bd Change SomeFile

Next commands to do (16 remaining commands):

   pick 2fcd87b Other Commit

   pick 7ffc7f5 Change other file

  (use "git rebase --edit-todo" to view and edit)

You are currently rebasing branch 'branch-to-rebase' on '066147e'.

  (fix conflicts and then run "git rebase --continue")

  (use "git rebase --skip" to skip this patch)

  (use "git rebase --abort" to check out the original branch)
```

This may be a conflict only in the first commit, but in general, these conflicts occur in most of the commits, and rebase will ask you to resolve them for each of the commits, which is impractical.

## How to easily rebase messy branches

First, we checkout the branch we want to rebase and fetch all branches: 

```bash
> git checkout branch-to-rebase

> git fetch --all
```

Then, we have to identify the last commit prior to the changes in the branch.

To find it, we use the `merge-base` command, passing the branch we will rebase (`branch-to-rebase`) and the branch to rebase onto (`origin/main`):

```bash
> git merge-base branch-to-rebase origin/main
```

With the commit in hand, we do a git reset to that commit. This will set the branch index to be equal to the commit's index:

```bash
> git reset f9fb326cb6cd58e0f31b433389b4a76f60319db1

Unstaged changes after reset:
M       SomeFile.cs
...
```

This means all the differences from the branch to the commit will be unstaged.

Now, we can stash the changes and rebase the branch. This will yield no conflicts, because there is no changes in the branch (all changes will be in the stash):

```bash
> git stash

Saved working directory and index state WIP on branch-to-rebase: d8dd56f ...

> git rebase origin/main

Successfully rebased and updated refs/heads/branch-to-rebase
```

> ⚠️ **Instead of a rebase**, we can create a new branch to keep the `branch-to-rebase` as a backup.
>
> This can be achieved using `git checkout -b clean-branch origin/main` instead of `git rebase origin/main`.

The next step is to pop the changes from the stash and resolve the conflicts, but now we just need to resolve the conflicts one time (from our stashed changes to `origin/main`)

```bash
git stash pop

# Resolve conflicts
```

Finally, just commit and push the branch to the remote repository.

```bash
git commit

git push --force
```

> ℹ️ **A bonus benefit is that this will have the effect of a squash rebase**, because it will produce just one commit.

## Git Alias

[In this post](https://blog.genezini.com/p/git-aliases-a-time-saving-secret-weapon-for-improved-workflow-and-productivity/), I explained how useful Git aliases are.

To make these workflow for rebase easier, I've created two aliases:

### 1 - git mb

This alias gets the name of the checked in branch and passes it to `merge-base`.

Usage:

```bash
> git mb origin/main

f2732648ef5b6804a63d44f1b498f19ddf01eeb6
```

Configuration:

```bash
mb = "!f() { git branch --show-current | xargs git merge-base $1; }; f"
```

### 2 - git reset-base

This alias gets the common base for the branch and the rebase target branch using the `mb` alias defined above and passes it to `reset`.

Usage:

```bash
> git reset-to-base origin/main

Successfully rebased and updated refs/heads/branch-to-rebase
```

Configuration:

```bash
reset-to-base = "!f() { git mb $1 | xargs git reset; }; f"
```

## Liked this post?

I post extra content in my personal blog. [Click here](https://blog.genezini.com/) to see.

## Follow me

- [On Feedly](https://feedly.com/i/subscription/feed/https://blog.genezini.com/index.xml)
- [RSS](https://blog.genezini.com/index.xml)
- [Linkedin](https://www.linkedin.com/in/danielgenezini/)
- [Twitter](https://twitter.com/GeneziniDaniel)

## References and Links

- [Git Aliases: a time-saving secret weapon for improved workflow and productivity](https://blog.genezini.com/p/git-aliases-a-time-saving-secret-weapon-for-improved-workflow-and-productivity/)
- [AJ ONeal's Video - How to Skip Rebase Conflicts](https://www.youtube.com/watch?v=Bwjh1boV8Q0&list=PLZaEVINf2Bq-_3gNXp_gzOIgu9HNVtVT4&index=3)