February 20, 2026
Most developers treat Git as a simple file backup tool, running git add, git commit, and git push on a daily basis. But Git is actually a content-addressable filesystem managed by a graph database of objects (commits, trees, and blobs).
When a project scales and developers collaborate in fast-moving teams, knowing how to manipulate this database becomes essential. In this guide, we will explore advanced Git mechanics: rebuilding dirty commit histories, recovering raw code from catastrophic hard resets, automating bug hunting with binary searches, and searching deep within code history.
Before submitting a branch for peer review, you should clean up your local commit history. A pull request containing twenty commits filled with "wip", "debug", or "typo fix" messages increases review time and pollutes the main branch. Interactive rebasing allows you to rewrite local history before making it public.
To start an interactive rebase, run the following command, specifying how far back in the graph you want to start editing:
# Edit the last 6 commits on the current branch
git rebase -i HEAD~6
This opens your default terminal text editor containing a list of commits, ordered from oldest to newest:
pick a1b2c3d Feat: add user auth controller
pick e4f5g6h Fix: validation logic
pick i7j8k9l wip: test database
pick m0n1o2p typo
pick q3r4s5t Feat: add token regeneration
To modify the commits, replace the command word pick with one of these options:
reword (or r): Keeps the commit contents but pauses to let you edit the commit message. Use this to enforce commit lint guidelines.squash (or s): Merges the commit into the previous one, opening an editor to combine the commit messages.fixup (or f): Merges the commit into the previous one but discards its message. This is ideal for cleaning up minor typos.edit (or e): Pauses the rebase process to let you add, remove, or modify files at that specific point in time.If two commits modify the same lines of code, the rebase will pause, and Git will print a conflict warning. Do not panic or run away. Open the conflicted files in your editor, search for the conflict markers (<<<<<<< and >>>>>>>), choose the correct code representation, and save.
Once resolved, run these commands to proceed:
# Add the resolved files to the staging area
git add src/controllers/auth.js
# Continue the rebase process
git rebase --continue
If you make a mistake and want to abort the entire rebase operation, returning your branch to its exact starting state, run:
git rebase --abort
We have all done it: running git reset --hard only to realize we wiped out a half-finished feature, or deleting a branch before merging it. Because Git maintains objects in a database, your code is rarely deleted immediately.
git reflogGit keeps a private log of where the HEAD reference has pointed over the last 90 days. This log is called the reflog.
git reflog
The output displays every checkout, commit, pull, and reset action:
e4f5g6h HEAD@{0}: reset: moving to HEAD~2
a1b2c3d HEAD@{1}: commit: Feat: add user auth controller
f9d8s7a HEAD@{2}: checkout: moving from main to dev
To recover the state of your branch before the accidental hard reset, locate the state reference identifier (e.g., HEAD@{1}) and reset your branch to it:
git reset --hard HEAD@{1}
git fsckWhat if you ran git reset --hard on files that were added to the staging area (git add) but never committed? Since there is no commit hash in git reflog, standard recovery commands will not show them.
However, when you run git add, Git immediately creates a raw database object (a blob) containing your file contents, even if you never commit it. To find these loose, unreferenced objects, use the filesystem check utility:
git fsck --lost-found
This prints a list of all dangling objects in your database:
dangling blob 0320b556abad0c96fe268505dfd85e540fe41c27
dangling blob 3770afac5696caebe41ec29070957e81ff750212
Git saves these raw objects inside the .git/lost-found/other/ directory. To find your lost code, you can inspect their contents using cat-file:
# Print the contents of a dangling blob to see if it is your file
git cat-file -p 0320b556abad0c96fe268505dfd85e540fe41c27
Once you locate the correct blob, redirect its output back to a new file in your workspace:
git cat-file -p 0320b556abad0c96fe268505dfd85e540fe41c27 > src/utils/recovered.js
Your uncommitted file is now successfully recovered.
git bisectWhen a bug is introduced into a large codebase, finding the exact commit that broke the application can be slow. Running a binary search manually across hundreds of commits takes hours. The git bisect tool automates this process.
Git Bisect Binary Search
[Good Commit] -- [x] -- [x] -- [?] -- [x] -- [x] -- [Bad Commit]
|
Test this commit!
Start the bisect session and define the boundaries:
git bisect start
# Mark the current commit as bad (bug present)
git bisect bad
# Specify a commit hash in history where you know the bug did not exist
git bisect good c4e5f6a
Git will automatically checkout a commit in the middle of the range. Run your test suite or start your application to check if the bug is present.
git bisect bad.git bisect good.Git will repeat this process, narrowing down the commit range exponentially until it identifies the exact commit hash that introduced the issue. Once identified, exit the session:
git bisect reset
If you have an automated test script (e.g., a Jest test that fails when the bug is present), you can automate the entire search. Git will check out commits and run the script automatically:
git bisect start HEAD c4e5f6a
# Run the test command automatically on each step
git bisect run npm run test:unit
Git will run the test on each checkout state and locate the breaking commit in seconds without any manual intervention.
Standard search tools (like grep or ripgrep) only search the current state of files on disk. If a function or configuration line was deleted months ago, you cannot find it with grep. Git has a built-in search engine called the Pickaxe.
To search for a specific string (such as a function name) across the entire history of commits, use the -S flag:
# Search for commits where the string 'calculateTaxRates' was added or removed
git log -S "calculateTaxRates" --oneline
If you want to view the diffs for each of those commits, add the patch flag:
git log -S "calculateTaxRates" -p
If you need to search using regular expressions instead of exact string matches, use the -G flag:
# Search for regular expression matches in the diff history
git log -G "const\s+[\w]+Settings\s*=" --oneline
This is invaluable for tracking down when a particular third-party API endpoint was removed or when a environment variable config was modified.
The git stash command is commonly used to clean up a workspace, but its advanced flags offer much more control.
By default, git stash only saves changes made to tracked files. If you created new files, they are ignored. To stash them without committing, use the -u (untracked) flag:
git stash -u
If you have modified multiple files but only want to stash some changes while keeping others in your working directory, run an interactive stash:
git stash -p
Git will display each change chunk (hunk) individually and ask:
y: Stash this hunk.n: Do not stash this hunk.q: Quit and stash what has been selected so far.Never rely on index numbers like stash@{0}, because they shift every time you save new changes. Always assign a custom message to your stash:
git stash save "refactored checkout flow - work in progress"
To view your stashes and apply a specific one without removing it from the list:
git stash list
# Apply a specific stash by its identifier
git stash apply stash@{2}
To apply the stash and delete it from the stack at the same time:
git stash pop stash@{2}
Using these advanced Git techniques will keep your commit history clean, protect your code from accidental loss, and speed up debugging workflows.