GitLearnHub
Practice Flow
15px

Branching & Merging

Branches are Git's superpower. This page covers creating and switching branches, getting changes reviewed, and resolving the conflicts that come with teamwork.

Branching

A branch is your own private workspace. When you start on a new feature or bug fix, you create a branch so your changes do not affect the rest of the team until you are ready.

Think of branches like separate copies of the project — except they are very cheap to create and Git is smart enough to merge them back together later.

bash
# Create a new branch and switch to it
git switch -c feature/user-login

# Switch to an existing branch
git switch main

# List your local branches
git branch

# List ALL branches, including ones that only exist on the remote
git branch -a

# Switch onto a branch that exists on the remote but not yet locally
# (Git auto-creates a local copy that tracks the remote one)
git switch develop

# Delete a branch (only after it has been merged)
git branch -d feature/user-login
💡
Right after cloning you land on the default branch (usually main). If your team's active work lives on develop or a feature branch, run git branch -a to see it, then git switch develop to start from the right place.
💡
Branch names like feature/user-login or fix/null-error keep things organised. Use a short description of what you are working on.
⚠️
Never commit directly to main. Always work on a branch and merge it through a Merge Request.

Try it in Practice →

Common mistake here →

See the side-by-side comparison →

Detached HEAD — What It Means

Normally HEAD points to a branch name, and the branch moves forward as you commit. A detached HEAD means HEAD is pointing directly to a commit hash instead of a branch — any commits you make won't belong to any branch and will be lost when you switch away.

It happens when you run git checkout <commit-hash> or git checkout <tag> to inspect old code. Git warns you:

bash
HEAD detached at a1b2c3d

How to get out safely:

bash
# Just go back to a branch (discard any commits made in detached state)
git switch main

# Or, if you made commits you want to keep — save them to a new branch first
git switch -c my-exploration-branch

Common mistake here →

Merge Requests (GitLab) / Pull Requests (GitHub)

When your feature branch is ready, you open a Merge Request (MR) — called a Pull Request (PR) on GitHub — on your Git host. This is how your code gets reviewed by a teammate and merged into the main branch.

How to open a Merge Request:

  1. Push your branch: git push -u origin feature/your-branch
  2. Go to your project on the host — a banner will appear offering to open the merge/pull request. Click it.
  3. Fill in a clear title and a short description of what changed and why
  4. Set the target branch to main (or develop if your team uses GitFlow)
  5. Assign a reviewer from your team
  6. Tick Delete source branch when merge request is accepted — keeps the repo tidy
  7. Click Create Merge Request

Your reviewer will read the code, leave comments if needed, and approve it. Once approved, click Merge.

💡
You can link an MR to an issue automatically. In your MR description, write Closes #42 (replace 42 with the issue number) and the host will close the issue when the MR is merged.
💡
On GitHub this is called a Pull Request (PR) instead of a Merge Request — the workflow is identical: push your branch, GitHub shows a banner to open a PR, assign a reviewer, then merge. Closes #42 in the PR description auto-closes a linked issue the same way.

Resolving Conflicts

A conflict happens when two people edit the same lines in the same file. Git cannot decide which version to keep, so it asks you to choose.

When a conflict happens, the file will contain markers like this:

bash
<<<<<<< HEAD (your version)
public string GetName() => "Alice";
=======
public string GetName() => "Bob";
>>>>>>> feature/rename-user (their version)

Everything between <<<<<<< and ======= is your version. Everything between ======= and >>>>>>> is theirs. Edit the file to keep what is correct, then remove all the markers.

Steps to fix a conflict:

  1. Open the conflicted file in your editor
  2. Decide what the file should look like (often a mix of both versions)
  3. Delete the <<<<<<<, =======, and >>>>>>> lines
  4. Save the file
  5. Stage and commit:
bash
git add src/main.py
git commit -m "Resolve merge conflict in GetName method"
💡
VS Code users: When a conflict exists, VS Code shows coloured sections with buttons — Accept Current Change, Accept Incoming Change, Accept Both. Use these instead of editing the markers manually.

Visual Studio users: Right-click the conflicted file in Solution Explorer → Resolve Conflicts to open the built-in merge tool.

Common mistake here →

Fast-forward vs Non-fast-forward Merges

When you merge a branch, Git uses a fast-forward if the target branch hasn't moved since you branched off — it just slides the pointer forward, no merge commit needed. If both sides have new commits, Git creates a merge commit with two parents.

Fast-forward: A ─ B ─ C ─ D ─ E ← main just slides, no merge commit Non-fast-forward: A ─ B ─ C ─ F ─ M ← M is the merge commit └─ D ─ E ─┘

Merge Requests / Pull Requests use --no-ff by default, so merging always creates a merge commit — making it clear exactly when each feature landed in main.

Most merges here happen by clicking Merge on an MR/PR, but the same thing happens locally with:

bash
git switch main
git merge feature/your-branch          # fast-forward if possible, merge commit if not
git merge --no-ff feature/your-branch  # always create a merge commit
💡
Prefer git rebase (see Staying in Sync) to bring main into your feature branch — it keeps history linear. Save plain git merge for the one-way trip of landing a finished branch into main.

Common mistake here →

See the side-by-side comparison →