Git branching: an introduction

Why one shared main breaks the moment two people work on it, and how a branch fixes it

This page assumes you can already clone, commit, push and pull. If not, start with Creating your first Git repository and Git and GitHub: your solo workflow.

Every step below is shown twice: first in Visual Studio Code / Positron, which is what we use day to day, and then on the command line, which is the same thing spelled out in words. Use whichever you like — they do exactly the same job.

1 The problem: two people, one branch

Alone on main, Git never argues with you. Nobody can change the remote behind your back, so push always works.

Add a second person and keep that single main, and it stops being true.

Both clone at commit C3. Both then work at the same time, and the shared history moves under the feet of whoever pushes second.

sequenceDiagram
  autonumber
  participant A as Person A — laptop (main)
  participant O as GitHub — origin/main
  participant B as Person B — laptop (main)
  Note over O: main = C1 → C2 → C3
  O->>A: git clone — A starts at C3
  O->>B: git clone — B starts at C3
  Note over A: commits C4-A locally<br/>(not pushed yet)
  Note over B: commits C4-B locally
  B->>O: git push — accepted
  Note over O: main = C1 → C2 → C3 → C4-B
  A--xO: git push — REJECTED<br/>non-fast-forward
  Note over A,O: A's main ends at C4-A, the remote ends at C4-B<br/>the two histories have diverged
  O->>A: git pull — merge C4-B into A's work
  Note over A: resolve the conflict if both<br/>edited the same lines
  A->>O: git push — accepted
  Note over O: main = C1 → C2 → C3 → C4-B → C4-A
Figure 1: Two people, one main. Person B pushes first, so Person A’s push is rejected — their local history and the remote have diverged.

Person A did nothing careless. They built on the newest commit they knew about, C3. Git refuses their push only because accepting it would silently throw away C4-B.

The real problem is not the clash. It is when the clash shows up: at push time, on one person’s laptop, alone, at the exact moment they wanted to be finished. And nobody reviews the fix.

ImportantThis is the whole argument for branches

On a single main, everyone writes to the same line of history and collisions surface at the worst possible moment.

Work on a branch instead, and the two changes sit side by side until someone deliberately brings them together. The clash then happens in a pull request on GitHub — visible, discussed and reviewed before it touches main.

The Lab’s rule follows: never push straight to main on a shared repository.

You can also fix this locally, without branches, by pulling first and merging on your own machine. That works, and on a solo project it is all you need. See Merging and merge conflicts for how, and for why it is a poor deal on a shared repository.

2 What a branch actually is

A branch is much less than most people imagine. It is a movable label pointing at one commit. Nothing is copied, no folder is duplicated.

  • main is a label on the newest commit of the main line of work.
  • Creating a branch puts a second label on that same commit.
  • Committing moves your label forward and leaves main where it was.
  • Switching branches rewrites the files in your folder to match whichever label you point at.

That is why branching is instant, and why having five branches costs nothing.

Diagram of a commit graph. A yellow row of four circles runs left to right, labelled Master by a box pointing at the third circle. From the second yellow circle a grey arrow drops down to a green circle, which is followed by a second green circle. A box labelled Develop points at the first green circle. The green row is a new branch that split off from the yellow row.
Figure 2: Creating a branch splits a new line of work off the main one. main (yellow) keeps going; the new branch (green) collects its own commits. Source: Camping Coder — Git branches simply explained.

Once the branch exists you switch onto it. From then on, every commit you make lands on the branch and main is untouched.

The same commit graph as before: a yellow row labelled Master and a green row labelled Develop branching off it. The green row is now highlighted with a pale yellow background and a note reading This is now the current branch, indicating the active branch has switched from Master to Develop.
Figure 3: Switching moves you onto the branch. Your project folder now shows that branch’s files, and new commits land there. Source: Camping Coder — Git branches simply explained.

When the work is done, the branch comes back through a merge. Git creates one commit on main that contains both lines of work.

Commit graph with a yellow row labelled Master along the top and a green row labelled Develop along the bottom. An arrow runs from the second green circle up into the third yellow circle, and a callout box reads Merge happens on this commit. Both lines of work end up in the yellow Master row.
Figure 4: Merging folds the branch back into main. The merge is a single commit that carries both lines of work. Source: Camping Coder — Git branches simply explained.

Read those three pictures as the same story as Figure 1, told safely:

  • Person A’s work sits on its own branch and can be pushed at any time — it never competes with main.
  • Person B’s work lands on main independently, and does not block Person A.
  • The two meet exactly once, at the merge, and that merge is a decision someone makes on purpose.

3 The branch workflow, end to end

Six steps, once per piece of work.

flowchart LR
  S1["<b>1. update main</b><br/>switch to main<br/>and pull"] --> S2["<b>2. branch</b><br/>create a branch<br/>for this change"]
  S2 --> S3["<b>3. work</b><br/>edit → stage<br/>→ commit"]
  S3 --> S4["<b>4. publish</b><br/>push the branch<br/>to GitHub"]
  S4 --> S5["<b>5. pull request</b><br/>review on GitHub<br/>→ <b>merge into main</b>"]
  S5 --> S6["<b>6. tidy up</b><br/>delete the branch<br/>go back to main"]
  S6 -->|"next change"| S1
Figure 5: One change, one branch, one pull request — then the branch is deleted and the loop starts again.

3.1 1. Start from an up-to-date main

Branch from the newest shared state, not from last week’s.

  1. Click the branch name in the status bar, bottom-left, and pick main.
  2. In the Source Control view (Ctrl+Shift+G), click Sync Changes to pull down what other people have merged.
Screenshot of the VS Code Source Control view. A repository named demo-travel-blog is listed with the branch name main next to a sync icon showing 3 down-arrow and 1 up-arrow. A large blue button below the commit message box reads Sync Changes 3 down-arrow 1 up-arrow, highlighted with a red rectangle. The same counters appear in the blue status bar at the bottom of the window.
Figure 6: Sync Changes pulls the incoming commits and pushes your outgoing ones in one click. The counters — 3↓ 1↑ — tell you how far behind and ahead you are. Source: VS Code — Source control quickstart.
git switch main
git pull

3.2 2. Create the branch

  1. Click the current branch name in the status bar.
  2. Choose Create new branch… at the top of the list.
  3. Type the name and press Enter — VS Code creates it and switches you onto it.

The same list is how you move between existing branches later.

Screenshot of a VS Code quick pick dropdown titled Select a branch or tag to checkout. The first highlighted entry reads Create new branch, followed by Create new branch from and Checkout detached. Below them, under a heading branches, is the local branch main with its last commit, and under a heading remote branches are two upstream branches.
Figure 7: Clicking the branch name opens a quick pick with Create new branch… at the top and all existing local and remote branches below. Source: VS Code — Source control overview.
git switch -c add-region-dummies    # create it and move onto it
git branch                          # which branches exist, and where am I?

git switch -c is the modern spelling of git checkout -b. Both do the same thing, and you will meet the older one in most tutorials.

TipName the branch after the change, not after yourself

add-region-dummies, fix-weights-2021, update-readme — lowercase, hyphens, no spaces. A reviewer should be able to guess the content from the name alone. eduard-branch or test2 tells nobody anything, including you in three weeks.

3.3 3. Do the work

Nothing changes about your daily rhythm. The commits simply land on your branch instead of on main.

  1. Edit and save your files.
  2. Open Source Control — changed files appear under Changes. Click one to see the diff.
  3. Hover a file and click + (Stage Changes) to include it in the next commit.
  4. Type a message in the box and click Commit.
Screenshot of the VS Code Source Control view showing a repository named agent-bikeshare on branch main. Under a Changes heading two files are listed, README.md and index.jade. The mouse hovers over index.jade and a plus button is highlighted with a red rectangle, with a tooltip reading Stage Changes.
Figure 8: Hover a changed file and click + to stage it. Staging is how you choose what goes into this commit and what waits for the next one. Source: VS Code — Source control quickstart.
Screenshot of the VS Code Source Control view. A commit message box contains the text Update title, and below it a blue Commit button. Under a Staged Changes heading the file index.jade is listed, and under a separate Changes heading the file README.md remains unstaged.
Figure 9: Staged files move to Staged Changes. Write a message that says what changed and why, then click Commit. Source: VS Code — Source control quickstart.
# edit clean_analyze.do
git add clean_analyze.do
git commit -m "Add region fixed effects to the wage regression"

3.4 4. Publish the branch

The first push has to tell GitHub the branch exists. After that, plain syncing is enough.

Click Publish Branch in the Source Control view. From then on the same button reads Sync Changes (see Figure 6).

git push -u origin add-region-dummies

Your branch is now on GitHub — backed up and visible — and main is still untouched.

3.5 5. Open a pull request and get it reviewed

A pull request (PR) is a request to merge your branch into main. It is where the two things that make collaboration work actually happen: review and conversation.

Install the GitHub Pull Requests extension and you can open, review and merge PRs without leaving the editor: the Pull Requests view lists them, and Create Pull Request starts a new one from your current branch. Otherwise just open the repository on GitHub.

  1. Open the repository — GitHub offers a Compare & pull request button right after your push.
  2. Give the PR a title that says what changes, and a description that says why.
  3. Request a reviewer. In the Lab, that is whoever else touches this repository.

Then:

  • The reviewer reads the diff, comments on specific lines, and either asks for changes or approves.
  • You push more commits to the same branch to answer the comments — the PR updates itself.
  • When it is approved, merge it. GitHub does the merge on the server, so you never had to fight it on your laptop.

3.6 6. Delete the branch and start again

Once merged, the branch has done its job. GitHub offers a Delete branch button on the merged PR — take it.

Switch back to main from the status bar, then click Sync Changes to bring the merge down.

git switch main
git pull                              # bring the merge down
git branch -d add-region-dummies      # remove the local copy

4 When your branch falls behind

Long-lived branches drift. If main moved while you were working, update your branch before asking anyone to merge it, and resolve any clash there — on your branch, where breaking something is cheap.

That is the subject of the next page: Merging and merge conflicts.

Keep branches short-lived — days, not months. The longer a branch lives, the further it drifts and the harder the merge.

5 Practise it

TipStart here: Learn Git Branching

https://learngitbranching.js.org/?locale=en_US

You type real Git commands and watch the commit graph rearrange itself. Roughly an hour gets you further than any amount of reading.

  • Introduction Sequence, levels 1–4 — commit, branch, checkout, merge. That alone covers everything on this page.
  • Remote tab, Push & Pull — clone, origin/main, push, pull, and exactly the rejected push from Figure 1.

On a real repository — GitHub Skills. Short guided courses that run inside a real GitHub repository, so you get the GitHub half too.

NoteSandboxes are practice, not the Lab’s rules

Tutorials will happily show you force-pushes, history rewriting and long-lived personal branches. Practise anything you like there; on a real repository follow MML’s spaces on GitHub.

6 Exercise: two people, one repository, no collisions

Replay the story from Figure 1 — but with branches this time, and watch the problem fail to happen.

  1. Pair up. Both clone the same repository and confirm you start from the same commit.
  2. Each create your own branch, named <yourname>-readme.
  3. Both edit the same line of README.md — deliberately.
  4. Both commit and publish your branch. Notice that neither push is rejected: you are not competing for main.
  5. Person A opens a pull request and merges it.
  6. Person B now opens theirs. GitHub reports a conflict in the pull request, before anything reaches main.
  7. Resolve it — see Merging and merge conflicts — then merge.
  8. Both of you: switch to main, pull, and confirm the two changes coexist.
  9. Delete both branches.

The lesson: the conflict still existed. It simply surfaced in a pull request, where it was visible and reviewable, instead of at push time on one person’s laptop.

7 Sources and further reading

The diagrams on this page come from other people’s work, and both are worth reading in full.

Where the pictures come from

Read more on branching

Elsewhere in these guides