Branches, merging and conflicts

Working on something without disturbing everyone else · 10 min

In one breath

A branch is just a label pointing at a commit that moves forward as you commit. Branches let you work on a feature without touching main. Merging brings the work back. If both sides changed the same lines, git can't choose, and that's a conflict you resolve by hand.

A branch is a sticky note, not a copy

A ── B ── C        ← main
          ╲
           D ── E  ← feature/unequip-axe   ← HEAD (you are here)

The commands

CommandDoes
git branchList branches (* marks the current one)
git switch -c feature/unequip-axeCreate a branch here and move onto it
git switch mainMove to another branch. Your files change to match it
git merge feature/unequip-axeBring that branch's commits into the one you're on
git branch -d feature/unequip-axeDelete the label after merging (the commits stay)
git stash / git stash popPut uncommitted changes aside to switch branches, then bring them back

Older tutorials use git checkout -b / git checkout main. Same thing. switch is the newer, clearer command.

Team naming: feature/…, fix/…, often with your name or a ticket number: guy/fix-weapon-map-uproperty.

Two kinds of merge

Fast-forward: nothing to combine

A ── B ── C ── D ── E        ← main, after merging (main just slides forward)

If main hasn't moved since you branched, git just moves the main label forward to E. No new commit.

Merge commit: both sides moved

A ── B ── C ── F ────── M    ← main
          ╲            ╱
           D ── E ────┘      (M has two parents)

Someone added F to main meanwhile. Git combines both into a new merge commit M with two parents. Usually automatic, unless both sides edited the same lines.

Conflicts

<<<<<<< HEAD
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Combat")
=======
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Camera")
>>>>>>> feature/unequip-axe
  1. Top half (HEAD) is your current branch's version; bottom is the incoming branch's.
  2. Edit the file to what it should be, which may be one side, the other, or a mix, and delete all three marker lines.
  3. git add the file to mark it resolved, then git commit to finish the merge.
  4. Panicking? git merge --abort puts everything back as it was before the merge.

Compile and test after resolving. Git only checks that the text is combined, not that the code still makes sense. Two changes can merge cleanly and still break each other.

Binary files (.uasset) can't be merged at all. Git can't combine two edited Blueprints line by line, so you must choose one whole file. That's why Unreal teams lock assets (Git in Unreal teams).

Rebase (recognise it, use carefully)

git rebase main replays your branch's commits on top of the latest main, instead of a merge commit. The history is a straight line, but it rewrites your commits (new hashes). Rule: rebase only commits that are only yours. Never rebase a branch others have pulled.

Quiz yourself

What is a branch, physically?

A movable label pointing at a commit. Creating one copies nothing.

What is HEAD?

The branch (and commit) you currently have checked out. New commits move that branch forward.

Fast-forward merge vs merge commit?

Fast-forward: the target hasn't moved, so its label just slides forward, no new commit. Merge commit: both sides moved, so a new commit with two parents combines them.

What do the three conflict markers mean, and how do you finish resolving?

<<<<<<< HEAD your side, ======= divider, >>>>>>> branch their side. Edit to the right result, delete the markers, git add, git commit.

A merge had no conflicts. Is the code fine?

Not necessarily. Git only combined text. Compile and test, because changes can merge cleanly and still clash logically.

How do you back out of a merge halfway through?

git merge --abort.

Why can't two people's edits to the same .uasset be merged?

It's a binary file. Git can't combine it line by line, so one whole version must win. Hence asset locking.

What's the golden rule of rebase?

Only rebase commits nobody else has. It rewrites them with new hashes.