Git - Fixing Rebase Errors When Rebasing Goes Wrong

August 16, 2023

In our last several posts, we've been covering how to use the "git rebase" command to modify the git log or the histories of git repositories.

 

While "rebase" can be very helpful to better organize histories and make the code progress flow easier for newer team members or 3rd parties to understand, if used the wrong way, it can lead to disastrous consequences for the team involved.

 

In this blog post, we'll delve into the perils of using the "rebase" command, and create a scenario where it's improperly used, the problem it causes, and a possible solution!

 

We'll explore a real-world scenario involving two developers, CodingInGreen and CodingInBlue, working together on a remote repository on GitHub on a critical project.

 

CodingInGreen creates a new folder named "rebase_error_green" and initializes a repository locally inside using the "git init" command. He then connects the remote repository on GitHub to his local repository, and adds CodingInBlue as a collaborator on the project.

 

CodingInGreen then creates a file called "script.js", makes 2 local commits, and pushes those commits to the remote repository.

To begin working, CodingInBlue then clones the remote repository to his local machine and makes two local commits on the master branch.

Here's where the story takes an interesting turn.

 

Unbeknownst to CodingInBlue, CodingInGreen, in an honest mistake, opts to rebase his work. He uses the "git rebase -i <commit-hash>" command to drop his commit that he made where he modified the documentation.  

 

He then employs the dreaded "git push origin master --force" command, inadvertently overwriting the remote repository's history on GitHub, and work that CodingInBlue's local commit history was based upon (eik!!!!!).

Here is a screenshot of an interactive rebase screen that CodingInGreen would see to drop the commit. He changes the "pick" keyword to "drop" and writes the file and exits out of Vim (the text editor that Git uses by default). If you need more instructions on how to do a drop a commit using rebase, click here for that blog post.

Now if CodingInGreen runs the "git log" command, he can see that the last commit was dropped. If he runs the "cat" command on the "script.js" file, we can see that it no longer contains the commented documentation line, now it only has the original one line of code.

And if CodingInBlue runs "git log" on his local repository, notice how the history is different from CodingInGreen's local repository. Notice how the commit that CodingInGreen dropped during his rebase still appears in CodingInBlue's local history! We've got a broken history here.

Let's say that CodingInBlue now wants to push his changes to the remote repository because he's unaware of what CodingInGreen has been doing. This will succeed because CodingInBlue has all the commits and the new changes he introduced on his local machine. After the operation is successful, take a look at the remote repository on GitHub. It contains the modified code for the log to console and the commented modified documentation line.

Now if CodingInGreen wants to make new changes, commit them, and push to the remote repository, it will fail with the following error because he is several steps behind from the history of the remote repository and the history of CodingInBlue's local machine:

To dive into this error a bit deeper, what's happening is that for CodingInGreen, the HEAD or pointer of the commits are a mismatch between the local and remote repository, thus producing the error which causes the push to fail.

 

At this point CodingInGreen should talk to a developer lead and CodingInBlue about the changes that were introduced and work out a plan on how they can resolve in a coordinated way.

 

One solution is that CodingInBlue can use the following commands to reset the repository to match where CodingInGreen last left off and they can rebuild what will be temporarily lost changes into the codebase afterwards. It's like cleaning up someone else's mess unfortunately, so if you make this mistake it might lead to an awkward interaction.

 

Disclaimer: If you're about to reset the repository like CodingInBlue is about to do, save your changes that you made after the commit where you're resetting to. You don't want to lose your work that can be re-applied later!

 

First use "git log" to see the commit history to find the specific commit needed to reset back to.

Here we find the commit at the bottom, the comment is "CodingInGreen - Add script.js". This is the commit that CodingInGreen's HEAD or pointer is currently pointing to, but it also exists in CodingInBlue's git log history, so he can reset back to that point.

 

CommitHash:

 

5d086fece3a53a049ac9f5a93412a3e6a183a8d7

 

CodingInBlue can use these commands to reset back to that commit.

Here's what that looks like in the terminal:

Below in this screenshot, the "git log" command now shows the git log for both directories, "rebase_error_blue" and "rebase_error_green" which correspond to CodingInBlue and CodingInGreen's working directories. They are now pointing to these same commit hash.

 

And the code that lives inside "script.js" in both "rebase_error_green" and "rebase_error_blue" directories now matches.

There may be other solutions out there to this problem, and this is just one. It will depend on the size of your codebase and the number of developers affected to decide what method will provide the least headache.

This cautionary tale of CodingInGreen and CodingInBlue underscores the importance of understanding Git commands thoroughly and using them wisely.

 

While the "rebase" command offers benefits in the right context, its misuse can lead to tangled histories and baffling scenarios. By grasping the intricacies of Git operations and adhering to best practices, developers can steer clear of the pitfalls associated with rebase and ensure smooth collaboration in their version-controlled endeavors.