Branching and merging

A branch is just a movable pointer to a commit — that one fact is why Git branches are fast to create and cheap to throw away, and it's the key to understanding what merging actually does underneath.

Beginner

4 min read

What a branch actually is: a pointer, not a copy

main:    A -- B -- C
                     \
feature:              D -- E

A branch is not a separate copy of the project's files — it's a lightweight, movable label pointing at a specific commit. git branch feature creates a new pointer, initially pointing at the same commit main currently points to; committing on feature moves that pointer forward, leaving main's pointer exactly where it was. This is why creating a branch in Git is close to instant regardless of project size — it's creating one small pointer, not duplicating any files.

Working on a branch without affecting main at all

git checkout -b feature-login    # create and switch to a new branch
# ... make commits on feature-login ...
git checkout main                # switch back — main is completely unaffected

git checkout -b creates a branch and switches to it in one step (the modern equivalent, git switch -c, does the same thing with clearer naming — both take the new branch's name as an argument). Every commit made while on feature-login only moves the feature-login pointer — main stays exactly where it was, completely unaffected, the whole time. This is the actual mechanism behind "work on something risky without touching the stable version": the risky work genuinely lives on its own line of commits, isolated by construction, not by convention.

Merging: bringing one branch's commits into another

main:     A -- B -- C ------------ M
                     \             /
feature:              D -- E -- (merged in)

git checkout main then git merge feature-login brings feature-login's commits into main — Git creates a new merge commit (M above) that has two parents, one on each branch, combining both histories into one. After merging, main contains everything from both lines of work; feature-login still exists as a branch (until explicitly deleted), but its work is now also part of main's history.

The fast-forward case: when merging is trivial

Before: main: A -- B          feature: A -- B -- C -- D
After (fast-forward):
main: A -- B -- C -- D   (main's pointer just moved forward to D — no merge commit needed)

If main hasn't moved at all since feature-login branched off (nobody else committed to main in the meantime), merging doesn't need to combine two diverging histories at all — Git just moves main's pointer forward to match feature-login's latest commit. This is called a fast-forward merge, and it's the simple case; the two-parent merge commit from the previous section only becomes necessary once both branches have diverged with independent commits.

Merge conflicts: when Git genuinely can't decide for you

<<<<<<< HEAD
def greet(name):
    return f"Hi {name}"
=======
def greet(name):
    return f"Hello, {name}!"
>>>>>>> feature-login

A conflict happens when both branches changed the same lines of the same file in different, incompatible ways — Git can automatically merge changes to different files, or different parts of the same file, but it has no way to guess which of two conflicting changes to the same lines is correct. It marks the conflicting section directly in the file with <<<<<<</=======/>>>>>>> markers, and resolving it means manually editing the file to keep the correct version (one side, the other, or a combination), removing the conflict markers entirely, then git adding the resolved file and completing the merge with git commit.

Why branches are the actual point, not just a nice feature

main:        A -- B ------------------- M1 -- M2
              \                        /      /
feature-a:     C -- D ----------------/       
                                              /
feature-b:                    E -- F --------

Multiple people (or the same person, on multiple unrelated tasks) can work simultaneously, each on their own branch, without their in-progress work interfering with each other or with main at all — this is the practical foundation nearly all real collaborative Git workflows are built on, whether that's a team's pull-request process or one person just keeping "the stable version" and "the thing I'm currently experimenting with" cleanly separate. Cheap, disposable branches are what make it normal to create one for every single feature or fix, rather than branching being a rare, heavyweight event.

Further reading

Check your understanding

A quick comprehension check — not tracked, not graded, just for you.

1. What is a Git branch, physically?

2. Why does committing on a feature branch leave main completely unaffected?

3. What makes a merge a 'fast-forward' merge rather than creating a two-parent merge commit?

4. Why does Git sometimes require a human to manually resolve a merge instead of merging automatically?