The Problem: One Timeline Cannot Hold Two People's Half-Finished Work
Picture a four-week school project: your team of two is building a small quiz app in Python. You already know Git from earlier work — you run git init once, and after that every git commit saves a snapshot of the whole project, one after another, like frames in a strip of film. Aisha and Rohan are both working on the same repository today. Aisha is halfway through building a login screen — the code doesn't run yet, half the functions are stubs. Rohan just found and fixed a genuine bug: the quiz was awarding the wrong number of points per question.
If both of them commit onto the same single line of history, here is what actually happens: Rohan's clean, working bug fix gets recorded right after Aisha's broken, half-built login code. Anyone who checks out the latest commit right now — to demo the app, or just to keep working — gets Aisha's unfinished login mixed in with Rohan's fix. There is no way to grab "just Rohan's fix" without also getting "Aisha's broken login," because on a single timeline, every commit sits after every earlier one, no exceptions.
What the team actually needs is two timelines that exist at the same time: one where Aisha's login work can be messy and incomplete without affecting anyone else, and one where Rohan's fix is available immediately. Git's answer to this is branching — and understanding exactly what a branch is (and isn't) is the entire subject of this chapter.
What a Branch Actually Is — Correcting the Biggest Misconception
Most students meet the word "branch" and assume it means what it means in a file explorer: a whole separate copy of the project folder, like duplicating a folder and renaming it quiz-app-copy. This is wrong, and it is worth stating plainly why. If branching meant copying files, creating ten branches on a 500 MB project would use 5 GB of disk space, and Git would need to copy every file every time you typed git branch. In real repositories with thousands of files, that would make branching painfully slow. It isn't slow — creating a branch in Git is close to instantaneous, on projects of any size.
Here is what a branch really is. Recall that every commit already stores a reference to its parent commit — that's how Git reconstructs history as a chain. A branch is nothing but a small, movable label — a pointer — attached to one specific commit. The label main is just a name that says "the tip of the main line of work is currently this commit." When you make a new commit while that label is active, Git does two things: it creates the new commit (pointing back at the old tip as its parent), and then it slides the label forward to point at the brand-new commit. The label moves; nothing is copied.
A useful mental picture: imagine your commit history as a numbered set of save-points in a video game, permanently recorded and never erased. A branch is not a duplicate save file — it is a sticky note that says "Player 1 continue from save #7" placed on one specific save-point. You can place a second sticky note, "Player 2 continue from save #7," on that very same save-point. Now two people can play forward independently from the same starting point, and neither one's progress overwrites the other's, because each sticky note only moves when its own owner plays. That second sticky note is exactly what git branch feature-login does.
One more piece completes the picture: Git always needs to know which branch label you are currently "standing on" so it knows which one to slide forward on your next commit. That current-position pointer is called HEAD. Ninety-nine percent of the time, HEAD points at a branch name, and that branch name points at a commit — so HEAD moves indirectly, by riding along with whichever branch you've checked out.
Creating and Switching Branches: A Worked Example
Continuing the quiz-app scenario: the repository currently has one commit, made by whoever set up the project.
$ git log --oneline
4f1a002 (HEAD -> main) Initial quiz app commit
Aisha wants to build the login screen without disturbing main. She creates a new branch and switches to it in a single command:
$ git switch -c feature-login
Switched to a new branch 'feature-login'
The flag -c means "create." This single command did two separate jobs: it created a new label called feature-login pointing at the exact same commit 4f1a002 that main points at, and it moved HEAD to point at this new label. (You will also see the older, equivalent command git checkout -b feature-login in tutorials and in most existing codebases — both do the same thing; switch is the newer, less overloaded command introduced specifically to make branch operations less error-prone than the older multi-purpose checkout.)
Notice: no files were copied, no new folder appeared. The files on disk right now are still identical to what main has, because both labels point at the same commit. Aisha now edits and commits normally:
$ echo "# login page" > login.html
$ git add login.html
$ git commit -m "Add login page skeleton"
[feature-login 9e0b3aa] Add login page skeleton
1 file changed, 1 insertion(+)
$ echo "validateForm()" >> login.html
$ git add login.html
$ git commit -m "Add login form validation"
[feature-login a1c9f2d] Add login form validation
1 file changed, 1 insertion(+)
Each commit here only moved the feature-login label. The main label is untouched, still sitting at 4f1a002. Meanwhile, Rohan — working in the same repository — switches to main (not feature-login) and commits his bug fix directly there:
$ git switch main
Switched to branch 'main'
$ git commit -am "Fix scoring calculation off-by-one"
[main 7c2d891] Fix scoring calculation off-by-one
1 file changed, 1 insertion(+), 1 deletion(-)
Now the two labels point at two different, independent commits, and each history contains only what its own author intended. Running git log --oneline --graph --all — which draws every branch at once — shows the split clearly:
* a1c9f2d (feature-login) Add login form validation
* 9e0b3aa Add login page skeleton
| * 7c2d891 (HEAD -> main) Fix scoring calculation off-by-one
|/
* 4f1a002 Initial quiz app commit
Read this from the bottom up: one shared starting commit, then two lines of stars diverging upward — Rohan's single commit on the right on main, Aisha's two commits on the left on feature-login. This exact structure is what the first diagram below draws visually.
Formalizing It: The Commit Graph
What you've just built is, formally, a directed acyclic graph — a set of nodes (commits) connected by directed edges (each commit's arrow points backward to its parent), with no cycles, because a commit can never be its own ancestor. "Directed acyclic graph" sounds intimidating, but you have already drawn one by hand in the log output above; the formal name just describes the shape you already understand.
Every commit is identified not by a number like "commit 5" but by a long fingerprint computed from its exact contents — its files, its message, its parent, and the time it was made. Git computes this fingerprint using a hash function called SHA-1, producing a 40-character string written in hexadecimal — base 16, using digits 0–9 and letters a–f, where each single character represents one of 16 possible values (compare this with the base-10 digits 0–9 you use for ordinary numbers, or the binary 0–1 you may have seen for representing data as bits). You will rarely need all 40 characters; Git and most tools show only the first 7, like a1c9f2d, since that is already unique enough to identify one commit inside almost any real project. A branch label, then, is precisely: a name that stores one of these hashes, and gets rewritten to a new hash every time you commit while that branch is checked out.
Merging: Bringing Timelines Back Together
Once Aisha's login screen actually works, the team wants it combined with Rohan's fix so both live on main. This is done with git merge, and understanding it requires knowing there are two genuinely different cases, depending on whether main moved forward since the branch was created.
Case 1 — fast-forward merge. If main had stayed completely still (nobody had committed to it since Aisha branched off), Git wouldn't need to combine anything — feature-login's history is simply main's history plus some extra commits on the end. Git would just slide the main label forward to match feature-login's tip. No new commit is created; this is called a fast-forward merge because the label is only "forwarded," not merged in the mathematical sense.
Case 2 — three-way merge. But in our scenario, main did move — Rohan's commit 7c2d891 is sitting there. Git cannot just slide the label forward, because that would silently throw away Rohan's commit. Instead Git performs a genuine three-way merge: it looks at three points — the shared ancestor (C1, where the branches split), the tip of main (C2, Rohan's fix), and the tip of feature-login (F2, Aisha's login work) — computes what changed in each direction since the common ancestor, and combines both sets of changes. Since both sets of changes touched different files (Rohan edited the scoring file, Aisha added new login files), Git can combine them automatically:
$ git switch main
Switched to branch 'main'
$ git merge feature-login
Merge made by the 'ort' strategy.
login.html | 2 ++
1 file changed, 2 insertions(+)
This creates a brand-new commit — call it M — that is special because it has two parents instead of one: C2 (the old tip of main) and F2 (the old tip of feature-login). The main label now points at M. This is exactly what the second diagram above shows: two converging arrows meeting at the green merge commit. Notice something important that trips students up — the feature-login label itself did not move. It still points at F2, exactly where it always did. Merging reads a branch's history to build a new commit; it does not touch the branch you're merging in.
When Timelines Collide: Merge Conflicts
The automatic merge above worked because Rohan and Aisha edited different files. Automatic merging also works fine if they edit the same file but different, non-overlapping lines — Git is genuinely line-aware, not just file-aware. Trouble starts only when both branches change the exact same lines in incompatible ways since the common ancestor, because Git then has no principled way to decide which version you want.
Suppose instead both Aisha and Rohan had separately edited the scoring constant in quiz.py — Rohan changed it from 10 to 15 points per question on main, while Aisha, unaware, changed it from 10 to 12 on feature-login while adjusting login-page score displays. Running git merge feature-login now produces:
$ git merge feature-login
Auto-merging quiz.py
CONFLICT (content): Merge conflict in quiz.py
Automatic merge failed; fix conflicts and then commit the result.
Git does not guess. It stops, and it edits quiz.py to show you both versions side by side, marked with conflict markers:
<<<<<<< HEAD
POINTS_PER_QUESTION = 15
=======
POINTS_PER_QUESTION = 12
>>>>>>> feature-login
Everything between <<<<<<< HEAD and ======= is what your current branch (main) has; everything between ======= and >>>>>>> feature-login is what the branch you're merging in has. Resolving a conflict means a human decides — Git cannot: you edit the file down to the single line you actually want (say, the team agrees on 15), delete all three marker lines, then tell Git the conflict is resolved:
$ git add quiz.py
$ git commit -m "Merge feature-login: resolve scoring conflict, use 15 points"
The resulting merge commit still has two parents, exactly like the conflict-free case — a conflict only changes how much manual work happens in between; it does not change the shape of the resulting graph.
A Sane Branching Habit for a Class Project
The pattern used above generalizes into a simple, disciplined habit worth adopting from your very first team project: keep main always in a working state, and do every new piece of work — a feature, a bug fix, an experiment — on its own short-lived branch, named after what it does (feature-login, fix-scoring-bug, not temp or test2). Merge back into main only once that branch's work runs correctly. This way, main is always safe to demo, always safe for a teammate to branch off from, and the history of why each change happened stays readable in the branch names and merge commits themselves — a genuine record of who built what, not a single tangled sequence of unrelated edits.
git branch— list all branches in the repository; add a name (git branch fix-bug) to create one without switching to itgit switch <name>— move HEAD to an existing branch (the older equivalent isgit checkout <name>)git switch -c <name>— create a new branch and switch to it in one step (older equivalent:git checkout -b <name>)git log --oneline --graph --all— draw the full commit graph, all branches, in the terminalgit merge <name>— merge<name>into whichever branch is currently checked outgit branch -d <name>— delete a branch after it's safely merged (Git refuses if it isn't, protecting you from losing unmerged work);-Dforces deletion regardless
Common Misconceptions, Corrected
"Branching duplicates the project." Already addressed above, but worth restating as the single most important correction in this chapter: a branch is a pointer to one commit, not a folder copy. Creating a hundred branches costs almost nothing in disk space or time, because no file content is duplicated — only a small label is added.
"Merging deletes the branch you merged in." False, and the diagram above proves it directly: after merging feature-login into main, the feature-login label was still sitting exactly at F2. Git leaves it there deliberately, in case you need to keep committing on it or reference it later. If you're done with it, deleting it is a separate, explicit step (git branch -d feature-login) that you choose to take, not something merge does automatically.
"A merge conflict means you did something wrong." A conflict is not an error in your Git usage — it is Git correctly refusing to silently pick a winner when two people made genuinely different decisions about the same line. It is a signal for a human conversation ("15 points or 12?"), not a bug to be afraid of.
Check Your Understanding
- Two students each run
git switch -c experimentfrom the same commit, on their own separate laptops, in their own separate local repositories. Does this create one shared branch or two independent ones? Explain using the "pointer" definition of a branch.
Answer: Two independent ones. Each repository is a separate collection of commits and labels; a branch name is only a pointer inside one specific repository. Nothing is shared between the two laptops unless they explicitly push/pull, which is a separate mechanism from branching itself. - After running
git merge feature-x, you see the message "Merge made by the 'ort' strategy" with no mention of a fast-forward. What does this tell you about the state of your current branch before the merge?
Answer: Your current branch (saymain) must have advanced with at least one commit of its own afterfeature-xwas created — otherwise Git would have taken the cheaper fast-forward path instead of building a two-parent merge commit. - You delete a branch with
git branch -d old-featureand Git refuses, printing a warning. What is the most likely reason, and what does that tell you about how safe branch deletion normally is?
Answer: Git refuses-dwhen the branch has commits that were never merged into your current branch — deleting it would make those commits unreachable. This shows-dis a safety-checked delete by design; only-Dforces deletion and discards that safety check. - In the conflict example in this chapter, why did Git fail to auto-merge
quiz.pybut succeed automatically onlogin.htmlin the earlier example?
Answer: Because both branches modified the exact same line ofquiz.py(thePOINTS_PER_QUESTIONconstant) since their common ancestor, giving Git two incompatible values with no rule for choosing between them.login.html's changes came from only one branch, so there was nothing to reconcile.
Summary
- A branch is a lightweight, movable pointer to one commit — never a copy of project files — and creating one is effectively instantaneous.
- HEAD tracks which branch (and therefore which commit) you currently have checked out; committing moves the current branch's pointer forward, leaving other branches untouched.
- Commit history forms a directed acyclic graph; each commit points back to its parent(s), and each commit is identified by a 40-character hexadecimal SHA-1 hash, usually shown abbreviated to 7 characters.
git mergeeither fast-forwards a branch label (no divergence) or, when both branches advanced independently, creates a new commit with two parents by combining changes since their common ancestor.- A merge conflict happens only when both branches changed the same lines differently; Git marks the disagreement with
<<<<<<</=======/>>>>>>>markers and waits for a human decision before the merge can complete. - A disciplined habit — keep
mainalways working, build every change on its own named branch, merge back only once it works — keeps a team's shared history clean and every teammate's work isolated until it's ready.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where git branching: organizing team development is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting git branching: organizing team development to at least 3 other topics you have studied.