Trading-systems ops · git fundamentals · part 2

Branching:

parallel lines of work, same repo.

If commits are snapshots and a repo is a folder of snapshots over time, a branch is a way to fork into two parallel lines of snapshots and bring them back together. This is how teams collaborate, how features get tested without breaking main, and how you'll work once you outgrow the "everyone-edits-main" pattern.

Prerequisite: limbo-state lesson
Time: 20 min
Includes: interactive demo
Why we need branches at all

Working only on main doesn't scale.

You're one person on a small project, you commit straight to main, you push, you're done. It works. But the moment any of these become true, single-branch flow breaks down:

The problems

1. Half-done features. You start a refactor at 10am, get interrupted at 11am with a critical bug. Now main has half-refactored code blocking the bug fix.

2. Multiple people. Two developers push at the same time; one overwrites the other; the trading system breaks at 9:14 AM.

3. Risky experiments. "Let me try a totally different strategy approach" — but you can't, because main is live.

4. Code review. Other people need to see your work before it joins main. Hard to do if you've already merged it.

What branches give you

1. A separate line where you can commit half-finished work without affecting main. Park it, switch back to main, fix the bug, switch back to your work.

2. Two people can each work on their own branches, never colliding, then combine when ready.

3. An experiment lives on its own branch. Doesn't work? Throw the branch away. Doesn't touch main.

4. Review happens via Pull Requests — your branch is reviewed before it joins main.

Branches are git's answer to "I need to work on this without breaking that". They're cheap (no file copies), fast (instant create), and fundamental — every git workflow you'll ever see is built on them.

The mental model

A branch is a named pointer to a commit.

Most beginners imagine a branch as a "copy of the whole codebase". It isn't. A branch is just a label that points at a specific commit. When you make a new commit on that branch, the label moves forward to the new commit. That's it.

a1b c2d e3f 71a 7116 main HEAD
Each circle is a commit. They form a chain — each one points back to its parent.
The main label points at the most recent commit. When you commit on main, the label moves forward to the new commit.
HEAD is "where I am right now" — usually it points at a branch (here: main). When you switch branches, HEAD moves.
Creating a new branch literally just creates a second label pointing at the same commit. No copying. No new files. Instant.
Interactive · build a branch + merge

Watch a branch fork, work, and rejoin main.

main branch feature/raise-lots HEAD = current position
HEAD: main
Branches: main → c1
Start. One branch (main) with one commit. HEAD is on main. Try each button in order to see how branches fork, accumulate commits independently, and rejoin.
The commands

Three verbs cover 95% of day-to-day branching.

1

git branch <name> — create

Creates a new branch label pointing at your current commit. Does NOT switch to it. Useful when you want to mark "this is where I was" without leaving where you are.

2

git checkout -b <name> — create and switch

The combo: creates the branch AND switches HEAD to it. This is what 99% of feature work starts with. New flavour: git switch -c <name> (same thing, less ambiguous verb).

3

git checkout <name> — switch to existing

Moves HEAD to an existing branch. Your working directory updates to match that branch's content. Newer alias: git switch <name>. Note: local edits in working dir can block this — same "limbo state" trap as before. Commit or stash first.

Mac · ~/Documents/ExecutionProject
$ git branch                                  # list branches; * marks current
* main

$ git checkout -b feature/raise-lots          # create AND switch
Switched to a new branch 'feature/raise-lots'

$ git branch
  main
* feature/raise-lots                              # HEAD is here now

$ git checkout main                             # jump back to main
Switched to branch 'main'
Convention, not magic

main is just a branch — but it's the canonical one.

Git itself doesn't treat main as special. It's a branch like any other. But by convention (and by GitHub's default settings), main represents "the version of the code we consider authoritative" — the version that goes to production, the version other branches rebase onto, the version a fresh git clone gives you.

main
What runs on Lightsail. What goes live. What other branches start from.
feature/*
Short-lived branches for new work. Created from main, merged back into main, deleted.
fix/*
Bug-fix branches. Same lifecycle as feature/* but the name signals "urgent / restore correct behaviour".
Used to be called master. Most projects renamed it to main around 2020. Old projects (and some git defaults) still use master. They behave identically.
Protected branches: on GitHub you can mark main as protected — no direct pushes, must go through a Pull Request. Common for team repos to prevent accidental damage.
This codebase ships from main directly (no protection). For solo work that's fine. Once a second person joins, protect main and require PR review.
Bringing branches back together

Merge: "take that branch's work and add it to mine."

After you finish work on a feature branch, you usually want it back in main. That's a merge. The command is short, the result has two flavours depending on whether main moved while you were away.

Fast-forward merge

If main didn't get new commits while you were on the feature branch, git just slides the main label forward to the tip of your feature branch. Linear history. Easy.

# on Mac
git checkout main
git merge feature/raise-lots
# Fast-forward
# main moved from a1b → 7116

Three-way merge (with merge commit)

If main got new commits while you worked, git can't just slide. It creates a new merge commit that has TWO parents (your branch tip + main's new tip) and combines both lines.

# on Mac
git checkout main
git merge feature/raise-lots
# Merge made by the 'recursive' strategy.
# New commit f9a3b on main with 2 parents.

After the merge, your feature branch still exists (pointing at its last commit). You can delete it with git branch -d feature/raise-lots — the commits live on as part of main's history; only the label is removed.

When the merge can't be automatic

Conflicts: two branches changed the same line.

Sometimes both branches edited the same lines of the same file. Git can't guess which version is right — it asks you. This is a merge conflict. It looks scary at first, but the pattern is simple once you've seen it.

Mac · git merge feature/raise-lots
$ git merge feature/raise-lots
Auto-merging config/strategies/rsi_sniper.py
CONFLICT (content): Merge conflict in config/strategies/rsi_sniper.py
Automatic merge failed; fix conflicts and then commit the result.

$ cat config/strategies/rsi_sniper.py
TRANCHES = [
<<<<<<< HEAD                                           ← main's version starts here
    {"id": "T1", ..., "lots": 1, ...},
    {"id": "T2", ..., "lots": 1, ...},
=======                                                  ← divider
    {"id": "T1", ..., "lots": 3, ...},
    {"id": "T2", ..., "lots": 3, ...},
>>>>>>> feature/raise-lots                              ← feature's version ends here
]

Three markers tell you exactly what's going on:

<<<<<<< HEAD — everything below this, up to =======, is what was on main.
======= — the divider between the two versions.
>>>>>>> feature/raise-lots — everything above this, down to =======, is what was on the feature branch.
Resolving · step by step

Open the file. Decide. Delete the markers.

There's no magic. You read the two versions, pick the right one (or write a combination), delete the conflict markers, and commit. Git just wanted human judgement.

Before (the conflict)

<<<<<<< HEAD
    {"id": "T1", ..., "lots": 1, ...},
    {"id": "T2", ..., "lots": 1, ...},
=======
    {"id": "T1", ..., "lots": 3, ...},
    {"id": "T2", ..., "lots": 3, ...},
>>>>>>> feature/raise-lots

File is unreadable as Python — broken syntax until you fix it. Your editor highlights the markers.

After (decided on 3 lots)

    {"id": "T1", ..., "lots": 3, ...},
    {"id": "T2", ..., "lots": 3, ...},

Markers deleted. Feature's version kept. (You could equally have kept main's, or written a third option entirely.) File is valid Python again.

Mac · finishing the merge
$ nano config/strategies/rsi_sniper.py      # edit and remove markers

$ git status
You have unmerged paths.
  (fix conflicts and run "git commit")
Unmerged paths:
        both modified: config/strategies/rsi_sniper.py

$ git add config/strategies/rsi_sniper.py    # tell git you resolved it

$ git commit                                   # merge complete
[main f9a3b4c] Merge branch 'feature/raise-lots'

If you panic mid-merge and want to undo: git merge --abort rewinds everything. Your branches are exactly where they were before. Conflicts are reversible right up until the final commit.

How teams actually use this

The pull-request workflow.

For solo work, you branch, work, merge into main, push. For team work, there's one extra step: pull request (PR). You push your branch to GitHub, open a PR, others review, then it gets merged. This is the workflow you'll see in every open-source project and most paid teams.

1

Create the branch + commits locally

git checkout -b feature/raise-lots, do your work, commit as usual.

2

Push the branch to GitHub

git push -u origin feature/raise-lots. The -u sets up tracking so future pushes are just git push.

3

Open a pull request on GitHub

Click "Compare & pull request" on the GitHub page. Add a description: what changed, why, how to test. Tag reviewers.

4

Reviewers comment; you push fixes

Each new commit you push to the branch automatically updates the PR. Conversation happens inline on specific lines.

5

Merge via the GitHub button

Once approved, click "Merge". GitHub does the merge for you. Your branch joins main. You can delete the branch from GitHub afterwards.

Common mistakes

Three branching habits that always hurt later.

Long-lived feature branches

A branch that lives for 3 months drifts further and further from main. By the time you try to merge, half the files have changed underneath you. Result: a huge, unreviewable, conflict-ridden merge.

Better: rebase or merge main into your branch weekly. Or break the feature into smaller branches.

Force-push to shared branches

git push --force overwrites the remote's history with yours. If someone else has pulled your branch in the meantime, their work is gone. On main: catastrophic.

Better: never force-push main. On your own feature branches it's OK before others pull.

Commit directly to main on a team repo

Skips review. Skips CI. Skips the conversation. The whole point of PR flow is that important changes get a second pair of eyes. Direct commits to main short-circuit that — and the first person to find out is usually production.

Better: protect main on GitHub so direct pushes are rejected.

There's a fourth one worth mentioning that's more technical: detached HEAD — checking out a specific commit (not a branch). You can commit there, but the commits aren't on any branch, so they get garbage-collected when you switch away. Git warns you. If you see "you are in detached HEAD state", run git switch <some-branch> immediately.

Lesson · takeaway

Branches are cheap.
Merges are where the work is.

A branch costs nothing — it's a label, not a copy. So make them freely: every feature, every experiment, every "let me try something" gets its own branch. The discipline isn't in creating branches, it's in finishing them — merge soon, merge often, never let a branch drift more than a week from main.

Conflicts feel scary because the file briefly becomes invalid. But they're git asking for your judgement, not git breaking. Read the markers, pick the right version (or combine), delete the markers, commit. Five minutes per conflict, max, once you've done a few.

← Back to lesson 1: The limbo state — why your commit didn't go through

← → navigate · F fullscreen · click to advance
1 / 12