🌊

The risk and return of git rebase

2024/10/03に公開

Rebase is a formidable tool, that changes the quality of your work. Yet, it carries substantial risks. In this article, I’ll weave together my moments of inspiration with rebase and the lessons learned from my most significant missteps, showcasing the compelling allure and inherent dangers of this command.

Difference between Merge and Rebase

Merge

The merge command combines two branches by creating a new "merge commit" that ties together the changes from both branches. It takes the changes from the source branch and applies them to the target branch while maintaining the full history of both branches.

// from develop branch
git checkout -b feature

// after develop code and push changes
// update develop branch and merge
git pull origin develop:develop
git merge develop

Characteristics of Merge:

  • Preserves History: Merge keeps the entire history intact, reflecting the full timeline of when and how changes were introduced.
  • New Merge Commit: It results in an additional commit, known as the "merge commit," that represents the joining of the two branches.

Use Case:

Merge is ideal when you want to maintain a complete and accurate history of the development process, especially for collaborative projects with multiple contributors.

Rebase

The rebase command integrates changes by reapplying commits from one branch on top of another. Instead of creating a merge commit, rebase rewrites the commit history to make it appear as if the changes from the feature branch were made directly on top of the latest commits in the target branch.


// from develop branch
git checkout -b feature

// after develop code and push changes
// update develop branch and merge
git pull origin develop:develop
git rebase develop

Characteristics of Rebase:

  • Rewrites History: Rebase rewrites the commit history so that it appears as though the feature branch was created from the latest state of the main branch.
  • No Merge Commit: Rebase does not create an extra merge commit, making the commit history linear and cleaner.

Use Case:

Rebase is useful when you want to keep the project history clean and linear, especially when working solo or in projects with strict commit policies.

Pros and Cons

Merge Rebase
pros Preserves context Linear history
cons Commit clutter History rewriting

The reason I choose rebase

  • Once used only merge. That was enough for my work.
  • Many saids rebase is risky and felt lazy to learn.
  • One day, couldn't resolve the judge of This branch is out-of-date after multiple times of merge
  • rebase resolved it by cleaning old merge commits
  • Now believe in rebase.

Powerfulness of rebase

Interactive mode

One of the standout features of git rebase is its interactive mode (git rebase -i), which allows for precise control over commit history.

git rebase -i HEAD~n // amend n commits from latest

or

git rebase -i <commit_id> // start ammend from latest to <commit_id>
  • Pop up text editor in your terminali (like vim).
    You can also reorder the commits by sorting the lists.
// change "pick" to something you wanna do. 
pick 1234567 Commit message for the first commit
pick 89abcde Commit message for the second commit
pick fghijkl Commit message for the third commit

# Rebase 1234567..fghijkl onto 1234567 (3 commands)
#
# Commands:
# pick = use commit
# reword = use commit, but edit the commit message
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
# fixup = like "squash", but discard this commit's message
# exec = run command (the rest of the line is a shell command)
# drop = remove commit
  • Close the editor and force-push to the remote branch
// need -f option to push
git push -f

Double-edged sword

Felt like invincible with rebase, and believed it omnipotent and reliable. But I learned it could counter-attack under some conditions and throw the fruit your labor into void.

Tried to remove the old merge commits but some time merge commits looks like enormous like below:

// change "pick" to something you wanna do. 
pick 1234567 someone's commit 1
pick 89abcde someone's commit 2
pick fghijkl someone's commit 3
pick 1234567 someone's commit 4
pick 81abcde someone's commit 5
pick fghllil someone's commit 6
pick 1pi4567 someone's commit 7
pick 80mkcde someone's commit 8
pick m07ijkl someone's commit 9
.
.
.
pick sda3d6f someone's commit 199
pick jklfghi someone's commit 200
pick linpjkl my commit // I didn't notice it...

# Rebase 1234567..fghijkl onto 1234567 (3 commands)
#
# Commands:
# pick = use commit
# reword = use commit, but edit the commit message
# edit = use commit, but stop for amending
# squash = use commit, but meld into previous commit
# fixup = like "squash", but discard this commit's message
# exec = run command (the rest of the line is a shell command)
# drop = remove commit

I thought it only include the merge commits and replaced all to "drop" and carelessly force-pushed to remote branch.
I lost everything, and in that instant, the depth of my own ignorance hit me like a thunderbolt.

Look before leap

In this way, rebase possesses the power to alter history and plunge everything into oblivion. Before being bitten by the claws of rebase, keep the following points in mind:

  • Exercise caution akin to when using rm -rf.
  • Perform rebases only on personal feature branches. Avoid doing so lightly on develop or main.
  • Familiarize yourself with the recovery methods that will be introduced later.

Recovery

If your rebase goes awry, take a deep breath, make yourself a nice cup of tea, and relax for a moment. Don’t panic—if you haven’t gone on a wild commit spree and tangled up your branch beyond recognition, there’s still hope. Once you've settled down, try the following steps. You've got this, as long as you avoid making things worse in a fit of confusion!

PlanA: Backup branch

The most secure way would be utilizing backup branch. You can reset your devastiated branch if you are careful and prepared beforehand.

// create backup_branch
git checkout <feature_branch>
git checkout -b backup_<feature_branch>

// work on feature_branch
git checkout <feature_branch>
// failture something like rebase or reset --hard

// recovery from backup_branch
git reset --hard backup_<feature_branch>
git push -f

PlanB: Reflog

Totally had a mishap and forgot to back up? No need to throw in the towel just yet! You can always turn to git reflog, your trusty time machine for HEAD. With a little ingenuity, you can rewind the clock and rescue your lost commits. So, don’t despair—adventure awaits in your command line!

git reflog

then it shows like:

e5f4a3c HEAD@{0}: rebase finished: returning to refs/heads/feature-branch
e5f4a3c HEAD@{1}: rebase: squash 1a2b3c4 Fix typo in README
1a2b3c4 HEAD@{2}: commit: Add new feature implementation  // revert to this commit
c3d2e1f HEAD@{3}: merge main: Merge made by the 'recursive' strategy.
b2c1a0f HEAD@{4}: commit: Update documentation
a3f2e1b HEAD@{5}: commit: Initial commit

pick the target commit id and reset to it.

// get commit id to roll back
git reset --hard 1a2b3c4

Conlution

Rebase is an incredibly versatile and powerful command, Just like katana. However, it also harbors a terrifying ability to erase everything in an instant. It’s essential to respect its power without abusing it, nor should we fear it blindly. By mastering its applications and recovery techniques, we can become proficient engineers — true samurai in the realm of coding.

Discussion