Branching, Merging, and Collaboration with GitHub

SA
StudyAI
AI-generated study notes
· Published Updated Pro Plus model

From the Make notes on docker an GitHub curriculum

TL;DR

You'll learn how Git branches let you work on changes in isolation, how merging and rebasing bring that work back together, and how pull requests turn this into a team workflow on GitHub. You'll see exactly what a merge conflict looks like and how to resolve one by hand. By the end, you can confidently branch, merge, rebase, and open a clean pull request.

1. The Mental Model

A branch is just a movable pointer to a commit — nothing more. Creating a branch doesn't copy your files; it creates a tiny label you can move independently. Merging is the act of combining two label-histories back into one. Branches are cheap, disposable experiments — commit often, branch freely, and never fear deleting one.

2. The Core Material

2.1 Branches: creation, switching, and what's actually happening

Letter board with humorous quote 'What in the actual hell?' on a vibrant yellow backdrop.
Photo by Brett Sayles on Pexels

Every commit in Git has one or more parent commits, forming a graph. A branch name (like main or feature/login) is a pointer to one specific commit in that graph. HEAD is a pointer to "where you currently are" — usually it points at a branch, which points at a commit.

git branch feature/login        # create a new branch pointer at current commit
git switch feature/login        # move HEAD to point at that branch
# or combined:
git switch -c feature/login

When you commit on feature/login, only that branch's pointer moves forward. main stays exactly where it was. This is why branches are cheap: you're not duplicating the repository, you're just adding a new label and letting history diverge.

git log --oneline --graph --all   # visualize all branch pointers and their history

2.2 Merging: fast-forward vs. three-way merge

A lone yellow taxi navigates around a curved, empty highway with a merge sign visible.
Photo by dh tang on Pexels

When you merge, Git looks at the relationship between the two branches' histories.

Fast-forward merge: if main hasn't moved since you branched off it, Git just slides the main pointer forward to match your feature branch. No new commit is created — history stays linear.

Three-way merge: if both branches have new commits since they diverged, Git needs a real merge commit. It uses three points: the common ancestor, and the tip of each branch, to compute what changed on each side and combine them.

git switch main
git merge feature/login

If the merge is clean, Git creates a new commit with two parents — this is the merge commit, and it's how Git remembers that two lines of work came together.

graph LR
    A["C1 (common ancestor)"] --> B["C2 (main)"]
    A --> C["C3 (feature/login)"]
    B --> D["Merge commit M"]
    C --> D

2.3 Rebasing: rewriting history for a linear story

Classic typewriter with text 'rewrite... edit...' on paper.
Photo by Suzy Hazelwood on Pexels

Rebase takes the commits from your branch and replays them, one by one, on top of a different base commit — usually the latest main. The result looks like you'd branched off recently and never needed a merge commit at all.

git switch feature/login
git rebase main

This rewrites the SHA hashes of every commit on feature/login (because their parent changed), which means never rebase commits that others have already pulled — you'd be creating duplicate, conflicting history for them. The safe rule: rebase local/private branches, merge shared/public branches.

graph TD
    subgraph "Before rebase"
    A1["C1"] --> A2["C2 (main)"]
    A1 --> A3["C3 (feature)"]
    end
    subgraph "After rebase"
    B1["C1"] --> B2["C2 (main)"]
    B2 --> B3["C3' (feature, replayed)"]
    end

2.4 Merge conflicts: what they are and how to fix them

Green toy soldiers facing off on a white background, symbolic of conflict and strategy.
Photo by Saifee Art on Pexels

A conflict happens when the same lines of the same file were changed differently on both branches, and Git can't decide which version is correct. Git pauses the merge/rebase and marks the file:

<<<<<<< HEAD
const PORT = 3000;
=======
const PORT = process.env.PORT || 8080;
>>>>>>> feature/login

Everything between <<<<<<< HEAD and ======= is your current branch's version; everything between ======= and >>>>>>> feature/login is the incoming branch's version. You manually edit the file to keep what's correct (often a combination), delete the conflict markers, then:

git add server.js
git commit          # finishes a merge conflict resolution
# or, if you were rebasing:
git rebase --continue

If things go badly, you can always bail out cleanly:

git merge --abort
git rebase --abort

2.5 GitHub collaboration: forks, pull requests, and code review

On a team, the typical flow is: branch → commit → push → open a pull request (PR) → review → merge.

git push -u origin feature/login

Then on GitHub, you open a PR comparing feature/login into main. This gives your team a diff view, a place for comments, and (usually) required status checks like CI tests before merging is allowed. For open-source projects where you don't have write access, you fork the repo (your own copy on GitHub), push your branch to your fork, and open a PR from your fork back to the original repo.

GitHub offers three merge strategies for a PR:

  • Merge commit — keeps full history, adds a merge commit (same as git merge).
  • Squash and merge — combines all PR commits into one clean commit on main. Great for messy "WIP" commit histories.
  • Rebase and merge — replays PR commits individually onto main, keeping them separate but linear.
flowchart LR
    A["Fork or branch repo"] --> B["Create feature branch"]
    B --> C["Commit changes locally"]
    C --> D["git push origin feature-branch"]
    D --> E["Open Pull Request"]
    E --> F{"CI passes & review approved?"}
    F -->|No| C
    F -->|Yes| G["Merge / Squash / Rebase into main"]
    G --> H["Delete feature branch"]

3. Worked Example

Let's say you're adding a dark-mode toggle to a small web app. The repo has main with one file, app.js, containing a function renderPage().

git switch -c feature/dark-mode

You edit app.js, adding a toggleTheme() function, and commit:

git add app.js
git commit -m "Add toggleTheme function"

Meanwhile a teammate merges a PR into main that also touches app.js, adding a logAnalytics() call right near where you added your function. You push your branch and open a PR. GitHub shows: "This branch has conflicts that must be resolved."

You update your local main and try to merge it into your feature branch to deal with the conflict locally first:

git switch main
git pull origin main
git switch feature/dark-mode
git merge main

Git shows:

Auto-merging app.js
CONFLICT (content): Merge conflict in app.js
Automatic merge failed; fix conflicts and then commit the result.

Opening app.js, you see:

function renderPage() {
<<<<<<< HEAD
  toggleTheme();
=======
  logAnalytics("page_view");
>>>>>>> main
  drawLayout();
}

Since both changes are needed, you keep both lines, delete the markers:

function renderPage() {
  toggleTheme();
  logAnalytics("page_view");
  drawLayout();
}

Then:

git add app.js
git commit -m "Merge main into feature/dark-mode, resolve conflict"
git push origin feature/dark-mode

GitHub's PR now shows "no conflicts" and your checks run. A teammate reviews, leaves a comment ("add a comment explaining toggleTheme"), you push one more small commit, they approve, and you click Squash and merge — collapsing your 3 messy commits into one clean entry in main's history. You finish by deleting the branch, both locally and on GitHub:

git branch -d feature/dark-mode
git push origin --delete feature/dark-mode

4. Production Pitfalls & Best Practices

4.1 Real-World Best Practices

  • Branch naming conventions — use prefixes like feature/, bugfix/, hotfix/ so your team and CI tooling can filter and automate by branch type.
  • Keep branches short-lived — merge within days, not weeks. Long-lived branches drift far from main and produce painful conflicts.
  • Protect main — enable GitHub branch protection rules requiring PR review and passing CI before merge; this stops accidental direct pushes.
  • Pull before you push — always git pull --rebase origin main on your feature branch before pushing, to catch conflicts early and locally.
  • Write small, focused PRs — a 50-line PR gets reviewed carefully; a 2,000-line PR gets rubber-stamped. Smaller PRs mean fewer conflicts too.
  • Use draft PRs — open a PR as a draft early to get CI feedback and show intent, then mark it "Ready for review" once done.
  • Squash messy WIP commits — use git rebase -i HEAD~5 (interactive rebase) to clean up "fix typo", "oops", "wip" commits before opening a PR.

4.2 Common Bugs & Anti-Patterns

Bad: force-pushing a shared branch after rebase, wiping teammates' work

git rebase main
git push --force origin feature/shared-branch   # dangerous on shared branches

Fixed: use force-with-lease, and only on branches you own

git rebase main
git push --force-with-lease origin feature/my-own-branch

--force-with-lease refuses to push if someone else has added commits you haven't seen — a safety check plain --force lacks.

Bad: resolving a conflict by blindly keeping "ours" without reading the other side

git checkout --ours app.js
git add app.js

This silently discards your teammate's work. Fixed: open the file, read both sides, merge the intent manually, then git add only after you've actually reconciled the logic.

Bad: committing directly to main "just this once"

git switch main
git commit -am "quick fix"
git push origin main

Fixed: always branch, even for tiny fixes

git switch -c hotfix/typo-fix
git commit -am "Fix typo in README"
git push -u origin hotfix/typo-fix
# then open a PR

This keeps main protected and gives every change a review trail.

Bad: merging a stale feature branch without updating it first, causing a huge conflict wall during the PR merge itself. Fixed: regularly sync your feature branch with main throughout development:

git switch feature/login
git fetch origin
git rebase origin/main

Doing this every day or two means you resolve small conflicts incrementally instead of one giant one at the end.

4.3 When to Use What (Decision Matrix)

Situation Approach
Your branch is private, not yet pushed/shared Rebase freely to clean history
Branch is already pushed and others are using it Merge only — never rebase shared history
PR has messy "wip"/"fix typo" commits Squash and merge
You want to preserve individual commit granularity on main Rebase and merge
main moved since you branched, no conflicts yet Fast-forward merge happens automatically
Two people edited the same lines differently Manual conflict resolution, read both sides

5. Now Try It

Create a test repo locally: git init git-practice && cd git-practice, add a file notes.txt with one line of text, commit it on main. Now create two branches off that same commit, branch-a and branch-b. On branch-a, change line 1 of notes.txt and commit. On branch-b, change that same line 1 differently and commit. Merge branch-a into main (should be clean), then try merging branch-b into main — you should get a real conflict. Resolve it by hand, combining both edits sensibly, and complete the merge commit.

Success looks like: git log --oneline --graph --all shows a merge commit with two parents, and notes.txt contains content from both branches with no leftover <<<<<<< markers.

Frequently asked about Branching, Merging, and Collaboration with GitHub

You'll learn how Git branches let you work on changes in isolation, how merging and rebasing bring that work back together, and how pull requests turn this into a team workflow on GitHub. You'll see exactly what a merge conflict looks like and how to resolve one by hand. Read the full notes above for the details.

Branching, Merging, and Collaboration with GitHub is a core topic in Make notes on docker an GitHub. Most exam papers test it via a mix of definitions, worked examples, and applied problems. The notes above cover the high-yield sub-topics, common pitfalls, and the kind of questions examiners typically set.

Yes — every note in the StudyAI Campus Hub is free to read in full, right here on this page, with no account needed. If you clone the plan into your own dashboard, the free plan shows a preview of each note there; Basic and above unlock the full notes in your dashboard, along with practice quizzes, flashcards and offline study. You can always come back here to read the complete note for free.
Continue with
Introduction to Docker and Containerization

Study this next


Get the full Make notes on docker an GitHub curriculum

Clone the complete plan to your dashboard for unlimited AI-generated notes, practice quizzes, and a personalised revision schedule.

Save this course free