GitLearnHub
Practice Flow
15px

Advanced Git Tools

The power tools: debugging, automation, big files, releases, signing, and what's actually inside the .git folder.

git bisect — Find the Bug Commit

Bisect does a binary search through your commit history to find exactly which commit introduced a bug. Instead of manually checking commits one by one, Git cuts the search space in half each round — 100 commits takes only ~7 checks.

bash
git bisect start
git bisect bad              # current state is broken
git bisect good v1.2.0      # last known good (tag, hash, or branch)

# Git checks out a midpoint. Test it, then tell Git:
git bisect good             # bug not present here
git bisect bad              # bug present here
# Repeat until Git identifies the exact commit

git bisect reset            # return to your branch when done
💡
Automate with a test script: git bisect run npm test — Git runs the script at each step and determines good/bad automatically based on the exit code.

git worktree — Two Branches at Once

A worktree lets you check out a second branch into a separate folder without stashing or cloning again. Both folders share the same .git history.

Typical use: you're mid-feature and an urgent hotfix arrives. Instead of stashing everything, open a second worktree for the hotfix.

bash
# Check out the hotfix branch into a sibling folder
git worktree add ../MyApp-hotfix hotfix/login-crash

# Both folders are now independent working directories
# ./MyApp            ← your feature, untouched
# ./MyApp-hotfix     ← hotfix branch, ready to work in

git worktree list              # see all active worktrees
git worktree remove ../MyApp-hotfix   # clean up when done

Git Hooks — Automate Quality Gates

Hooks are scripts in .git/hooks/ that Git runs automatically at specific points. They're how teams enforce rules without relying on people to remember.

Hook When it runs Common use
pre-commitBefore commit is createdRun linter, block direct commits to main
commit-msgAfter you type the messageEnforce Conventional Commits format
pre-pushBefore push sends dataRun tests, block force-push to main
⚠️
Hooks in .git/hooks/ are not tracked by Git — they won't be shared with the team. To share hooks, commit them to a .githooks/ folder and configure: git config core.hooksPath .githooks.

Submodules — Using Another Repo Inside Your Repo

A submodule lets you include a separate Git repository inside your own. Use this when your team has a shared library that lives in its own repo and needs to be used by multiple other repos.

💡
Submodules are for separate Git repositories. If you have multiple projects inside one .sln file, use <ProjectReference> in your .csproj instead — that is just a normal .NET feature, not a submodule.

Add a submodule:

bash
git submodule add <url> libs/SharedLib
git commit -m "Add SharedLib as submodule"
git push

Clone a repo that has submodules:

bash
# Clone and download submodules in one command
git clone --recurse-submodules <url>

# Or if you already cloned without submodules
git submodule update --init --recursive

Update a submodule to its latest version:

bash
cd libs/SharedLib
git pull origin main
cd ../..
git add libs/SharedLib
git commit -m "Update SharedLib to latest"
git push
⚠️
When someone pushes a submodule update, all teammates must run git pull followed by git submodule update --recursive to get the new version.

Git LFS — Large Files

Git stores every version of every committed file forever, which is a problem for large binaries (design files, videos, datasets) — they bloat every clone. Git LFS replaces them in history with small pointer files, storing the actual content separately.

bash
# One-time setup per machine
git lfs install

# Tell LFS which files to track (writes/updates .gitattributes)
git lfs track "*.psd"
git lfs track "*.mp4"

git add .gitattributes
git commit -m "chore: track large files with Git LFS"

After that, git add/commit/push work exactly as normal — LFS intercepts the tracked file types automatically.

⚠️
Both GitLab and GitHub support LFS, but both cap free storage/bandwidth and charge beyond it. Self-managed GitLab instances may need LFS enabled by an admin. Check your plan's limits before tracking large files at scale.

Tags & Releases

Tags mark a specific point in history — typically a release version. Unlike branch names, tags do not move as you add more commits.

bash
# Create a tag for the current commit
git tag -a v1.2.0 -m "Release version 1.2.0"

# Push the tag to the remote
git push origin v1.2.0

# List all tags
git tag

# Push all tags at once
git push origin --tags

On GitLab, tags appear under Repository → Tags and can be turned into Releases with release notes. On GitHub, tags appear under the repo's Releases tab, where you can create a Release directly from a tag.

Try it in Practice →

Commit Signing — Verified Badges

A signed commit proves it really came from you, not someone who set user.email to your address. GitLab and GitHub both show a Verified badge next to signed commits.

Simplest method — sign with the SSH key you may already have set up (see SSH Key Setup):

bash
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

Then add the same public key a second time under your account's signing keys section (separate from the authentication key entry) — GitLab: Edit profile → SSH Keys → Usage type: Signing; GitHub: Settings → SSH and GPG keys → New SSH key → Key type: Signing Key.

💡
Prefer traditional GPG instead? gpg --full-generate-key to create a key, git config --global user.signingkey <key-id>, then git config --global commit.gpgsign true — and paste the exported public key (gpg --armor --export <key-id>) into the GPG Keys section instead of SSH Keys.

Git Internals — How Git Actually Stores Things

You don't need this to use Git daily — but understanding it makes everything else make sense. Why does rebase "rewrite history"? Why is force-push dangerous? It comes from this.

Git stores four types of objects, each identified by a SHA hash:

Object What it stores
blobContents of one file (no filename, just bytes)
treeA folder — maps filenames to blobs and sub-trees
commitPointer to a tree + parent commit(s) + author + message
tagA named pointer to a specific commit

Why this explains common confusion:

bash
# Explore internals yourself
git cat-file -t HEAD          # shows object type: "commit"
git cat-file -p HEAD          # shows commit contents
git cat-file -p HEAD^{tree}   # shows the root folder snapshot

GitFlow vs GitHub Flow

These are two common patterns for how a team organises branches. Choose one and stick to it.

GitHub Flow GitFlow
Best forWeb apps, continuous deliveryVersioned releases, mobile, libraries
Main branchesmain onlymain + develop
Feature workBranch off main, MR back to mainBranch off develop, MR back to develop
ReleasesDeploy directly from mainCreate a release/x.y branch, merge to main + tag
HotfixesBranch off main, MR to mainBranch off main, MR to main AND develop
ComplexitySimple — one long-lived branchMore structure, more branch management
💡
If you are not sure which to use, start with GitHub Flow. It is simpler and works for most teams. Move to GitFlow if you find you need structured release management.