Story Time: My First Taste of the Real Power of Git
Ok, so I inherited a hot mess of a project—I've written a bit about it in another article. A chaotic work project that had spiraled into a state of 😬😬😬. No version control. A deathmatch of duplicated files named things like old_filename.extension
and even a collection of zipped folders that served as backups whenever something got changed.
As I said in the article describing it, the project worked, and that counts for something. Whoever built and maintained it—whether it was one person or multiple—was probably thrown into the deep end and made the best of their situation. I respect that "get-it-done" energy.
Laying the Groundwork
Before I got into the main challenge, I introduced some software best practices:
- Version control (finally!)
- Reusability (the DRY principle)
- Documentation
- Removing unused files and dependencies (as much as time allowed)
And then, the thing that brought me to the main point of this article: Updating the npm packages.
The Package Update Saga
This project hadn’t received any TLC (tender loving care) in a while, so almost all the npm packages were outdated. Time to bring them back from the brink.
Just a quick FYI on npm package semantic versioning (SemVer) and what it means:
If a package, let's say nodemon
, is listed as follows:
"nodemon": "^3.1.9"
-
3
→ Major version -
1
→ Minor version -
9
→ Patch version
So, MAJOR.MINOR.PATCH.
Strategy: Updating in Stages
Since patch upgrades are very unlikely to introduce breaking changes, I started with:
npx npm-check-updates -t patch -u && npm install
Everything worked fine.
Next, I moved to minor updates:
npx npm-check-updates -t minor -u && npm install
Tested it. Everything seemed fine. I committed my changes. Or so I thought...
The Bug Hunt Begins
A few more commits in, and I noticed a logout bug had slipped through my manual tests. It must have been introduced in one of the minor version updates.
Time to go back—commit by commit—until I figured out exactly when it happened.
Step 1: Checking Recent Commits
I ran:
git log -n 8
This showed me the last 8 commits, with their messages and commit hashes:
commit 943e6.......77dd (HEAD -> some-branch-name)
Author: John Doe <john.doe@companymail.com>
Date: Wed Feb 26 12:02:20 2025 +0200
update lorem ipsum something something
commit 49fbfa........50f
Author: John Doe <john.doe@companymail.com>
Date: Tue Feb 25 17:32:33 2025 +0200
update major version of dotenv and nodemon
commit b144ba8f3........49d00
Author: John Doe <john.doe@companymail.com>
Date: Tue Feb 25 17:21:00 2025 +0200
remove unused packages according to depcheck and npm ls
commit 601......09342b4e0
Author: John Doe <john.doe@companymail.com>
Date: Tue Feb 25 13:23:37 2025 +0200
update minor versions of npm packages
commit bad04.........1d8
Author: John Doe <john.doe@companymail.com>
Date: Thu Feb 20 12:08:51 2025 +0200
update patches
I copied these commit hashes into a text file for easy reference.
Step 2: Finding When the Bug Was Introduced
To go back to a specific commit, I used:
git checkout <commit-hash>
I manually went back one commit at a time until I found exactly when the bug was introduced.
Eventually, I pinpointed the issue—it was introduced in commit 60.....23 (let’s call it the "bug-source commit").
Step 3: Fixing the Bug
I checked out a new branch based on the bug-source commit:
git checkout -b fix/bump-down-package-name 60.....23
I made the fix and committed it. Let’s call this commit 3a (the bug-fix commit).
Now, here's the issue:\
I had gone back in time and fixed a bug, but that fix didn't exist in later commits (3, 2, 1, and 0).
At any given point in the Git history, there was still a version where the bug was present.\
This is the main thing I wanted to highlight in this article:
Step 4: Ensuring the Fix Exists in Future Commits
The answer? Git rebase.
I rebased my fix branch on the bug-source commit:
git rebase 60.....23
Now, my commit history looks like this:
commit 0 (bug-spot commit, where I first noticed the bug)
commit 1
commit 2
commit 3
commit 3a (bug-fix commit)
commit 4 (bug-source commit, where the bug was introduced)
At this point, the fix existed in the timeline, but I needed to merge it into my working branch (let’s call it wip-branch
).
So, I switched back to wip-branch
:
git checkout wip-branch
Then, I rebased the fix onto it:
git rebase fix/bump-down-package-name
And voilà! The fix was now applied across all future commits.
Lessons Learned
- Always test thoroughly after minor updates.
- Minor updates can introduce breaking changes.
- Git rebase is a lifesaver.
- When you need to go back in history and fix something properly, rebase keeps your commit history clean.
- Commit in small, logical chunks.
- If I had updated one package at a time, tracking the issue would have been much easier.
Final Thought
As I write this, I realize I probably could have just amended the bug-source commit instead of rebasing, but hey—that’s the power of Git. There’s always more than one way to solve a problem.
There is also something that could have helped locate the bug commit more easily than manually going one-by-one - git bisect. That would have automated the process of finding the commit using a binary search algorithm through the commit history to pinpoint the exact change that introduced the bug. But I only found out about that whilst researching this article.
Top comments (0)