A Practical 30-Minute Mini-Book for dev teams
- Introduction
- The Git Mental Model
- Training Playground — Real Bug
- High-Value Git Techniques
- Feature Branch → PR Workflow + Best Practices
- Git Merge Strategies
Git has quietly become the backbone of modern software development. Whether you build cloud APIs, enterprise-grade .NET systems, or distributed microservices, Git is the invisible engine powering your team’s collaboration. Yet despite its ubiquity, most developers interact with only a fraction of its real capability. We learn just enough to survive:
- clone the repository
- pull the latest changes
- write code
- commit
- push
- …and hope for the best
This surface-level workflow works—until it doesn’t:
- A merge conflict appears hours before deployment.
- A feature branch dissolves into a messy chain of “fix”, “fix2”, “final”, and “final-final”.
- A pull request becomes impossible to review because it includes debug logs, temporary edits, and half-baked experiments.
These are not failures of Git—they are failures in how we use Git.
This mini-book is written for mid-level developers who already work in production environments, collaborate with teams, and use Git daily—but want to use it properly, not just functionally.
The goal is to give you a concise, practical, battle-tested set of Git techniques that truly matter in modern enterprise delivery:
- cleaner, intentional commits
- safer experimentation without fear of breaking things
- confident recovery from mistakes
- professional pull request hygiene
- a branching workflow that scales gracefully
All demonstrations in this guide use GitHub + VSCode on a MacBook, but the concepts map 1:1 to Azure DevOps + Visual Studio on Windows.
Git is Git everywhere.
UIs change, Git doesn’t.
Before we dive into advanced workflows such as partial staging etc., we must pause to build a solid mental foundation.
Git feels complex until you understand its internal structure.
Once you internalize the Git mental model, everything becomes intuitive.
Git is a distributed snapshot system made of four distinct areas:
┌────────────────────────────┐
│ Remote Repository │
│ (GitHub / Azure DevOps) │
└──────────────┬─────────────┘
▲
│ git push / git pull
│
┌────────────────────────────┐ │
│ Local Repository │ <────────┘
│ (Your machine’s commit │
│ history) │
└──────────────┬─────────────┘
▲
│ git commit
│
┌────────────────────────────┐
│ Staging Area │
│ (“Index” — the changes │
│ you choose to commit) │
└──────────────┬─────────────┘
▲
│ git add / git add -p
│
┌────────────────────────────┐
│ Working Directory │
│ (Real files you edit on │
│ disk or in VSCode) │
└────────────────────────────┘
Where your real files live.
This is what you see in VSCode or Visual Studio.
A selective approval zone where you decide which changes go into the next commit.
Enables:
- staging only parts of a file
- grouping meaningful changes
- cleaner commit histories
- noise-free PRs
Skipping it with:
git add .is like “deploy everything because I fixed one line.”
After committing, changes move here.
Benefits:
- full offline capability
- ability to safely rewrite history
- recover almost anything
- powerful tools like rebase/reset/bisect
GitHub or Azure DevOps.
git pushsends your workgit pullfetches team changes
Together:
Working Dir → Staging → Local Repo → Remote RepoOnce you understand this pipeline, Git stops feeling magical and starts feeling predictable.
We use a small PowerShell game: Guess the Number, which contains a deliberate bug.
Demonstrates:
- partial staging
- clean commits
The game incorrectly consumes attempts on invalid or out-of-range input:
if (-not [int]::TryParse($guessRaw, [ref]$guess)) {
Write-Host "That doesn't look like a valid number. Try again."
continue # ❌ BUG: attempt consumed
}
if ($guess -lt 1 -or $guess -gt $MaxNumber) {
Write-Host "Please enter a number between 1 and $MaxNumber."
continue # ❌ BUG: attempt consumed
}This bug mirrors real-world issues in validation logic, retry loops, and user workflows.
Never fix anything directly on main.
git switch -c bugfix/preserve-attempts-on-invalid-inputThis keeps work isolated and PRs clean.
Senior developers rely on a small set of high-impact Git techniques. We will discuss two of them here.
continue # ❌ still consumes an attempt$attempt-- # ✅ FIX
continueThis belongs in the bugfix commit.
Write-Host "Thanks for playing!"becomes:
Write-Host "Thanks for playing Guess the Number!" # ENHANCEMENTThis belongs in a separate enhancement commit.
git add -p src/guess-the-number.ps1Example hunk:
+ $attempt--
+ continue
Press:
y
Skip enhancement hunk:
n
git commit -m "Preserve attempts on failed validation"git add -p src/guess-the-number.ps1Press:
y
git commit -m "Improve end-of-game message"Final history:
git log -2 --oneline
<hash2> Improve end-of-game message
<hash1> Preserve attempts on failed validation
- Cleaner commits
- Clearer PRs
- Accurate history
- Less noise
- Professional Git hygiene
If you adopt only one advanced Git technique, make it this one.
The biggest barrier to mastering Git is fear—fear of losing work, breaking the branch, or making an irreversible mistake.
Technique 2 eliminates that fear by showing how to undo commits, unstage mistakes, discard edits, and recover anything using Git’s time machine: reflog.
Add below line to end of file src/guess-the-number.ps1:
Write-Host "Have a good time ahead!"
You commit too fast:
git add src/guess-the-number.ps1
git commit -m "Last message"
Undo the commit but keep changes staged:
git reset --soft HEAD~1
Undo staging:
git restore --staged src/guess-the-number.ps1
Then stage and commit intentionally:
git add src/guess-the-number.ps1
git commit -m "Last message"
Remove the commit completely:
git reset --hard HEAD~1
This deletes the commit and its changes from the working directory.
View all HEAD movements:
git reflog
Example:
766dd43 HEAD@{0}: reset: moving to HEAD~1
ab0239d HEAD@{1}: commit: Last message
Restore the lost commit:
git reset --hard ab0239d
| Command | Purpose |
|---|---|
git reset --soft HEAD~1 |
Undo commit, keep changes |
git restore --staged |
Unstage safely |
git restore <file> |
Discard uncommitted edits |
git reset --hard |
Wipe working directory + commit |
git reflog |
Recover anything |
Once developers understand these tools, Git becomes safe, predictable, and empowering.
This covers:
- creating feature branches
- clean commits
- PR titles/descriptions
- merge strategies
We implement a simple feature: adding a -ShowHints parameter to the Guess‑the‑Number game.
This example is intentionally small, safe, and perfect to demonstrate a good PR.
Start from main:
git switch main
git pull
git switch -c feature/show-hints
param(
[int]$MaxNumber = 100,
[int]$MaxAttempts = 7,
[string]$ShowHints = "off"
)if ($ShowHints -eq "on") {
if ($secret -gt 50) {
Write-Host "[Hint] The number is greater than 50."
}
else {
Write-Host "[Hint] The number is 50 or below."
}
Write-Host ""
}git add -p src/guess-the-number.ps1
git commit -m "feat: add -ShowHints parameter"
Push the branch:
git push -u origin feature/show-hints
History is clean due to:
- partial staging
- single meaningful commit
Example:
[#1234] Add -ShowHints flag to Guess the Number
Example PR body:
### What changed?
- Added -ShowHints parameter.
- Prints a “greater than 50” or “50 or below” hint before the game starts.
### Why?
- Makes the game friendlier for beginners.
### How to test?
pwsh src/guess-the-number.ps1 -ShowHints on
pwsh src/guess-the-number.ps1 -ShowHints off
### Potential risks?
- None; this only affects initial output.
Diff shows only ~8–10 lines.
Easy to review, minimal risk.
- Squash and merge → preferred for small feature PRs
- Merge commit → preserves branch history
- Rebase and merge → linear history
In GitHub:
- Review
- Approve
- Merge (prefer "Squash and merge" for this example)
Then update your local main:
git switch main
git pull
This section teaches developers how to:
- Work safely in feature branches
- Produce clean, readable commit history
- Write high‑quality, reviewer‑friendly PRs
- Avoid common PR mistakes (junk commits, large diffs, rebasing PR branches)
- Collaborate professionally in enterprise Git workflows
This is the workflow expected in all modern teams using GitHub or Azure DevOps.
- Keeps every commit from the feature branch
- Creates a merge commit (
M) - Preserves branch structure
- Produces a non-linear graph
A --- B --------- M
\ /
C --- D
- Full traceability
- Shows the real development timeline
- No commit rewriting
- Ideal for collaborative or long-running branches
- Large features
- Multi-developer branches
- Enterprise/audited environments
- Replays branch commits onto main
- Preserves commits but rewrites them
- Produces a clean, linear history
- No merge commit
A --- B --- C' --- D'
- Cleaner than merge commit
- Preserves granular commits
- Easier to debug via
git bisect
- Medium-sized feature branches
- Clean, structured commit histories
- Collapses all branch commits into one
- Adds that commit directly to main
- Removes branch structure
- Produces a very clean linear history
A --- B --- E
(E = combined diff of C + D)
- Extremely clean history
- Perfect for small PRs
- Easiest rollback (one commit)
- Hides noisy or experimental commits
- Small PRs
- Bugfixes
- Cosmetic changes
- Teaching Git hygiene
| Strategy | Keeps All Commits? | History Style | Merge Commit? | Best For |
|---|---|---|---|---|
| Merge Commit | ✔ Yes | Non-linear | ✔ Yes | Large/team branches |
| Rebase + Merge | ✔ Yes (rewritten) | Linear | ❌ No | Clean multi-step PRs |
| Squash + Merge | ❌ No (one commit) | Linear | ❌ No | Small PRs, fixes |
- Merge Commit → “Record the story exactly as it happened.”
- Rebase and Merge → “Rewrite the story so it looks cleaner.”
- Squash and Merge → “Summarize the entire story into one chapter.”