You don't need to be a wizard to understand git. You just need the right story. This guide starts from absolute zero — what a branch even is — and ends with the single most debated button in software development.
Before we talk about branches, commits, or pull requests — before any of the vocabulary — let's answer the one question most guides skip: what problem does Git actually solve?
Everyone who's worked on a shared document has lived this. It's chaos. Nobody knows which file is current, what changed, or who did what.
Git was invented to make that chaos impossible. It's a version control system — a piece of software that watches every change made to a collection of files, records who made it, when they made it, and what exactly changed. You never need FINAL_v2_REAL_this_one.html again. There is always one authoritative version, and a complete, inspectable history of how it got there.
Git was created in 2005 by Linus Torvalds — the same person who created the Linux operating system — specifically because he needed a better way to coordinate thousands of developers contributing to Linux from all over the world. He needed something fast, distributed, and impossible to corrupt. That's git.
Free, open-source software that runs on your computer. It tracks changes, manages history, and handles the entire branching/merging system. It has no interface — it's a command-line program. Git works completely offline. You could use it entirely alone, forever, and it would still be useful.
A website and cloud service owned by Microsoft that hosts git repositories online. It adds a web interface, pull requests, code review, issue tracking, and team collaboration features. GitHub is to git what Google Docs is to a word processor — a polished, cloud-connected layer on top of the underlying technology.
Alternatives include GitLab, Bitbucket, and Gitea — all hosting services for git repos.
When developers say "push to GitHub" they mean: take the history git is tracking locally on my machine, and upload it to the shared online copy. When they say "clone the repo" they mean: download the entire project, including its full history, onto my machine so git can track my changes locally.
Here are the terms that come up constantly — the ones people nod at in meetings while quietly having no idea what they mean. Every single one of these will make sense by the end of this guide, but here's a cheat sheet to start.
That's the vocabulary. Now let's walk through how these pieces connect in practice — starting with the journey every piece of code takes from idea to the official codebase.
A lot of guides assume you already understand branches, commits, and pull requests. Most people nod along and quietly don't. This section fixes that — in plain English, with the same storytelling world we'll use for the rest of the guide.
Your own private writing desk
Imagine the codebase is a grand shared library — thousands of books, all neatly organised, all officially approved. This is main. It's the source of truth. Everyone on the team reads from it. No one is allowed to just walk in and scribble changes directly into the books. That would be chaos.
So instead, when you want to work on something new, you walk to a side room and make yourself a perfect photocopy of the entire library. This copy is yours alone. Your private writing desk. You can do whatever you want here — tear out pages, rewrite chapters, experiment wildly — and none of it touches the real library until you say so.
That private copy is a branch.
When you run git checkout -b my-feature, git creates a new branch — a pointer that says "I'm working on a parallel version of this codebase, starting from where main is right now." Your changes stay on your branch until you choose to bring them back.
The name main is just convention — it's the branch everyone agreed is "the real one." Some older repos call it master. The important thing is: it's the official, published, everyone-reads-from-this version. Your branch is the scratchpad you work on before you're ready to publish.
Your sealed, timestamped envelopes
You're sitting at your writing desk — your branch — and you start working. You write a new feature. You fix a bug. You rename a variable. At some point, you pause and think: "I want to save a snapshot of exactly where things stand right now."
So you gather up all the changes you've made, seal them in an envelope, and stamp the exact date and time on the outside. You write a short note on the front describing what's inside: "Added login button" or "Fixed crash on mobile" or, honestly, sometimes just "WIP". You drop it in your out-tray.
That sealed, timestamped envelope is a commit.
On your branch, you might make one commit or fifty. Small, frequent commits are generally better — they're easier to understand, easier to undo, and easier to describe. But in practice, most developers accumulate a messy pile of them: "fix", "fix again", "ok this time for real". This is normal. This is human. And — spoiler — it's exactly why the squash strategy exists.
Knocking on the library door
You've been working at your desk for a few days. You have a stack of envelopes — your commits — and you think you're done. The feature works. The bug is fixed. You're ready to bring this back to the shared library.
But you can't just walk in and start re-filing things. That's not how good libraries work. Instead, you go to the front desk and say: "I'd like to propose adding these envelopes to the official collection. Here's what they contain. I think they belong."
The librarians — your teammates — come over to inspect your work. They read through what you've done, ask questions, suggest improvements, maybe point out a typo or a better approach. This conversation is the code review. The formal request that started it is the pull request — you're requesting that the library pull in your changes.
Great question. You've already pushed your branch up to the server. The PR is you asking main to pull your changes in. It's a request directed at the destination, not the source. GitHub calls them Pull Requests. GitLab calls them Merge Requests. Same thing, different name.
Once the librarians are satisfied — approvals given, concerns resolved — comes the moment of truth. Someone clicks the big green button. And that is exactly when your merge strategy matters. Because that click is what decides how your envelopes get filed into the archive forever.
Now you understand what a branch is, what commits are, and what a PR does. There's one question left: when those envelopes get filed into the archive, how should they be filed? That's where the real debate begins.
Git gives you three ways to integrate a branch into main.
GitHub puts all three options in a dropdown button on every pull request — right before you click merge. Most developers click the green button without thinking.
That's a mistake.
Your merge strategy is your history strategy. A messy git log isn't just aesthetically offensive — it makes debugging harder, reverting riskier, and onboarding slower. The good news: once you understand what each strategy actually does to the commit graph, the right choice becomes obvious for almost every situation.
Two of these are pure git. One is GitHub's terminology. One of them is almost always the right default. Let's get into it.
Remember how we said each commit gets stamped with a unique fingerprint? That fingerprint has a name: the SHA. Understanding what it is — and what it means to change one — is the single most important thing you can know before choosing a merge strategy.
SHA stands for Secure Hash Algorithm. That sounds intimidating, so forget the name. Here's what it actually is in plain terms.
Git takes everything about a commit — every line of code changed, the commit message, the author, the timestamp, and crucially the SHA of the commit that came before it — feeds it all into a mathematical blender, and out comes that 40-character fingerprint. Change even a single character anywhere in that input and the entire SHA becomes something completely different.
When a medieval messenger sealed a letter with wax and pressed their signet ring into it, that seal was proof: this letter has not been touched since it left my hands. If someone broke the seal and resealed it — even perfectly — it would be a different seal from a different moment in time. The SHA is git's wax seal. It is mathematically impossible to fake.
Here's what makes git genuinely remarkable — and genuinely unforgiving. Every commit's SHA is calculated using its parent's SHA. Which means every commit is cryptographically tethered to every commit that came before it, all the way back to the very first commit ever made in the repo.
Pull on any link in the chain and you can verify the entire history back to the beginning. Nothing can be quietly altered. If someone changes a commit from three weeks ago, its SHA changes — which forces the next commit's SHA to change — which cascades through every single commit that came after it. The tampering is immediately visible.
This is where rebase becomes genuinely consequential. When you rebase, git takes your commits and replays them on top of a different parent commit. The code changes inside each commit are identical. But the parent SHA changed. Which means the input to the fingerprint calculation changed. Which means the SHA itself changes.
To git, a commit with a new SHA is a completely different commit — full stop. It doesn't matter that it has the same message, the same diff, the same author. The old commit and the new commit are two distinct objects in git's database. The old one still exists on your feature branch. The new one is what lands on main. They are not the same thing.
Your teammate Alex has been working on a branch that forks off your feature branch at commit a3f9b2c. Alex's branch has a3f9b2c as a parent — it's baked into Alex's commit chain. Then you rebase and merge. Your commits now have new SHAs. a3f9b2c no longer exists in main's history — it's been replaced by something like b7d2e4f.
Alex's branch is now pointing at a commit that, as far as main is concerned, does not exist. When Alex tries to sync, git has no idea how to reconcile these two histories. Conflicts. Lost work. Confusion. All because a SHA got rewritten without warning.
You're the only person who ever worked on your feature branch. Nobody forked off it. Nobody has those SHAs saved anywhere. You rebase and merge. New SHAs land on main. The old ones evaporate quietly when you delete the branch.
This is why experienced solo developers reach for rebase confidently. The SHA rewrite only matters if someone else has a reference to those old SHAs. If nobody does — it's a non-event. Clean history, zero drama.
Rebase isn't dangerous because it's hard to execute. It's dangerous because it silently breaks the assumptions of anyone who trusted those original SHAs. A developer who doesn't understand SHA chaining will use rebase carelessly, cause chaos, and have no idea why. A developer who does understand it will use rebase deliberately — knowing exactly whose world they might be disrupting, and checking first whether they're disrupting anyone at all.
The PR is reviewed. The librarians are happy. The green button is right there. All that's left is deciding how your envelopes get filed into the archive — and this, right here, is the crux of one of the most reliably heated debates in software development.
You've got a stack of sealed, timestamped envelopes — your commits. Some are polished. Some say "WIP" or "fix typo" or "ok seriously this time." The archive doesn't care about your feelings. It just wants to know: in what form do these arrive on the shelf? Developers have been arguing about the answer since git was invented, with the passion of people who feel it says something about their professional character.
You hand the Publisher every single envelope, in the exact order you sealed them. Your "fixed a typo" envelope from 2am goes on the shelf. Your "WIP, not sure about this" envelope goes on the shelf. The brilliant final version goes on the shelf. All of them.
The Publisher then adds one special envelope on top — a cover note that says "these N envelopes arrived together as a delivery on this date." It doesn't contain any new writing. It just records that a group of work arrived together and now belongs to the archive. The shelf shows your authentic, warts-and-all creative process. Every decision, every false start, every 2am correction. The timeline is sacred and unbroken.
You take your entire stack of envelopes — all 14 of them, including the drafts, the "WIP", the "ok this time for real" — and you feed them through a machine that extracts every single change from every envelope and presses them all into one single, brand new book. "The Authentication Feature." Done.
The individual envelopes are destroyed in the process. They don't go into storage. They don't get archived somewhere. They cease to exist. The Publisher's shelf gets one entry: the finished book, with today's date.
Unlike Option B, nothing gets destroyed here. All 14 of your envelopes go onto the shelf — every draft, every fix, every individual step — in the exact order you wrote them. Someone reading the archive can see the full story of how your feature was built, one envelope at a time.
But here's where it gets complicated. Before each envelope hits the shelf, the Publisher re-stamps it. Not just the date — the entire identity fingerprint gets replaced. Remember those wax seals from the SHA section? Every single one gets broken and re-sealed with a brand new seal. The words inside each envelope are identical to what you wrote. But officially, as far as the archive is concerned, these are now completely different envelopes from the ones you sealed in your side room.
The shelf looks clean and linear — as if you wrote all 14 envelopes one after another, in perfect sequence, right alongside everyone else's work. No trace of the side room. No evidence of parallel development. Just a tidy, single-file timeline.
a3f9b2c will find a ghost.When developers talk about "linear history" they mean something very simple: the entire timeline of the project is one single, unbroken line. Every commit follows the previous one. There are no forks. No branches converging. No parallel lanes. Just: A → B → C → D → E, all the way from the first commit ever to the most recent one.
The history forks into multiple parallel lanes and then rejoins. Reading it requires you to track several timelines at once. On a project with 10 developers and hundreds of PRs a month, this becomes a web of tangled branches — what developers call "spaghetti history."
One single, unbroken line. Every commit, from every developer, in perfect chronological sequence. You can read git's history like a book — left to right, one thing at a time, no lane-switching required.
git bisect that can automatically find the exact commit that introduced a bug by doing a binary search through history. It cuts the list in half, tests the midpoint, cuts again. On a straight line of 1,000 commits it finds the culprit in about 10 steps. On a tangled non-linear graph? Bisect gets confused by the forks and becomes unreliable or useless. A linear history makes this tool work at full power.
git log on a linear repo and you get a clean, chronological narrative — every change, from every developer, in the order it happened. You can answer "what changed on this codebase between March and April?" in seconds. On a non-linear history, the same question requires mentally untangling a web of parallel timelines. It's genuinely exhausting at scale.
Every team is deciding: does the shelf belong to the reader — clean, curated, easy to navigate — or to the writer, a faithful record of every moment the work was touched? Neither answer is wrong. But picking one and enforcing it across your whole team is non-negotiable. A shelf where some items are books, some are pamphlets, and some have been re-stamped without warning is a shelf no one trusts.
| Strategy | History shape | Individual commits | Pure git? | SHA rewrite? |
|---|---|---|---|---|
| Merge Commit | Non-linear (graph) | ✓ All preserved | ✓ Yes | ✗ No |
| Squash & Merge | Linear | ✗ Collapsed to one | Partial | ✗ No |
| Rebase & Merge | Linear | ✓ All preserved | Partial | ✓ Yes |
git merge --no-ff · the original
You've met Option A. Here's what it actually looks like inside git — the graph, the command, and when to reach for it.
git merge --no-ff feature-branch # GitHub does this automatically when you click "Create a merge commit"
git merge --squash · the pragmatist's choice
You've met Option B. Here's the git reality — and why this is the one I'd set as your team's default today.
# Git's underlying mechanics: git merge --squash feature-branch git commit -m "feat: add user authentication" # GitHub does both steps for you in one click
git rebase + fast-forward · the perfectionist's trap
You've met Option C. Here's what the SHA rewrite looks like in actual git — and the exact conditions under which this becomes worth it.
# What GitHub does under the hood: git checkout feature-branch git rebase main # replays commits, creates new SHAs git checkout main git merge --ff-only feature-branch # fast-forward, no merge commit
If you're setting up a new repo right now, go into GitHub's branch protection settings and disable everything except Squash and merge. You can always open it up later.
Here's the thing most articles won't say directly: picking one strategy and enforcing it matters more than which strategy you pick. A mixed-strategy repo has a history that's impossible to reason about — some PRs are one commit, some are twenty, some have a merge commit. You can't write meaningful git log filters. You can't automate changelogs. It's chaos.
Enforced via GitHub's allowed merge methods setting.
The right default for most teams. Forgives messy development, produces readable history, and maps perfectly to a PR-based workflow. Enable this, disable the others.
Excellent if your team has genuine commit discipline and every developer understands SHA rewriting. Otherwise it's squash with extra risk.
The right choice for open source and long-lived parallel branches. The wrong choice as a product team default unless you love reading merge spaghetti.
One last thing: the GitHub dropdown defaults to whatever strategy was last used. That means if one developer clicks "Create a merge commit" once, the next person to open a PR will see that as the default. Lock your strategy in GitHub's repository settings under Settings → General → Pull Requests and uncheck the strategies you don't want. This is a five-second change that prevents months of history inconsistency.