Skip to content

1.2.4 Branches and Collaboration

Git branch collaboration flowchart

This lesson explains why Git supports safe collaboration. You’ll understand how branches let you try new ideas without breaking the main codebase, and you’ll get a first look at Pull Requests and merge conflicts, preparing you for team projects and open-source contributions later.

  • Understand the concept of branches and when to use them
  • Master creating, switching, and merging branches
  • Learn the collaboration workflow of Pull Requests
  • Learn how to resolve simple merge conflicts

Imagine you live in an apartment (main branch = the home you are currently living in). You want to try a new interior design style, but you’re not sure whether it will turn out well.

You have two choices:

  1. Make changes directly in your home — if you mess it up, you can’t really live there anymore
  2. Rent an identical apartment first (a new branch) and try things there — if it looks good, move the changes back; if not, just end the lease

A branch is option 2. You can make changes freely on a new branch. If it works, merge it back into main. If it doesn’t, delete the branch, and main stays completely unaffected.

You are working on an AI image classification project, and the code on the main branch is running normally.

What you want to try:

  • Replace the model from CNN to Vision Transformer.
  • Check whether the result is actually better.
  • Spend several days on changes that may touch many files.

If you change main directly, half-finished code may stop running, an urgent bug fix becomes hard to ship, and reverting a failed ViT attempt may involve dozens of files.

If you use a branch, you can work slowly on feature/vit, switch back to main for urgent fixes, and delete the experiment if it does not work.


Terminal window
# View local branches (the current branch has a * in front)
git branch
# Output:
# * main
# View all branches (including remote branches)
git branch -a
Terminal window
# Create a new branch
git branch feature/data-augmentation
# Switch to the new branch
git checkout feature/data-augmentation
# Or do it in one step: create and switch (more common)
git checkout -b feature/data-augmentation

Example: Developing a New Feature on a Branch

Section titled “Example: Developing a New Feature on a Branch”

Let’s do a real example. Continue using the previous ai-image-classifier project:

Terminal window
cd ai-image-classifier
# Confirm that we are on the main branch
git branch
# * main
# Create and switch to a new branch: add data augmentation
git checkout -b feature/data-augmentation

Now you are on the new branch. Start writing code:

Terminal window
# Create the data augmentation module
cat > src/augmentation.py << 'EOF'
import torchvision.transforms as T
def get_train_transforms():
"""Augmentation strategy for training data"""
return T.Compose([
T.RandomHorizontalFlip(p=0.5), # 50% chance of horizontal flip
T.RandomRotation(degrees=15), # Random rotation of ±15 degrees
T.ColorJitter( # Color jitter
brightness=0.2,
contrast=0.2,
saturation=0.2
),
T.ToTensor(),
T.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
])
def get_test_transforms():
"""Test data only gets normalized, no augmentation"""
return T.Compose([
T.ToTensor(),
T.Normalize(
mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225]
),
])
EOF
# Update train.py to use data augmentation
cat >> src/train.py << 'EOF'
# Added: use data augmentation
from augmentation import get_train_transforms, get_test_transforms
train_transform = get_train_transforms()
test_transform = get_test_transforms()
print("Data augmentation strategy loaded")
EOF
# Commit to the current branch
git add .
git commit -m "feat: add data augmentation module (random flip, rotation, color jitter)"

Now check the status of the two branches:

Terminal window
# View the history of the current branch
git log --oneline -3
# Output:
# aaa1111 feat: add data augmentation module (random flip, rotation, color jitter)
# bbb2222 Improve README: add project overview and usage
# ccc3333 Add .gitignore
# Switch back to main and take a look
git checkout main
# main does not have augmentation.py!
ls src/
# model.py train.py utils.py (no augmentation.py)
# Switch back to the feature branch
git checkout feature/data-augmentation
ls src/
# augmentation.py model.py train.py utils.py (it’s there!)

That’s the power of branches—two timelines that do not affect each other.


When the feature on your branch is finished and passes tests, you can merge it back into main.

Terminal window
# Step 1: switch back to the main branch
git checkout main
# Step 2: merge the feature branch into main
git merge feature/data-augmentation

Output:

Updating bbb2222..aaa1111
Fast-forward
src/augmentation.py | 25 +++++++++++++++++++++++++
src/train.py | 5 +++++
2 files changed, 30 insertions(+)
create mode 100644 src/augmentation.py

Now the main branch also has the data augmentation code:

Terminal window
ls src/
# augmentation.py model.py train.py utils.py ✅
Terminal window
# The feature branch has been merged, so you can delete it (to keep the repo tidy)
git branch -d feature/data-augmentation
# View branches — only main remains
git branch
# * main

Git merge conflict resolution flow

When two branches modify the same location in the same file, Git does not know which version to keep, so a conflict occurs.

Terminal window
# Create two branches from main to simulate two people working at the same time
git checkout -b alice/update-model
cat > src/model.py << 'EOF'
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # Engineer A: change to 32 filters
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(32 * 16 * 16, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = x.view(-1, 32 * 16 * 16)
return self.fc1(x)
EOF
git add . && git commit -m "alice: increase filter count to 32"
# Switch back to main and create Engineer B's branch
git checkout main
git checkout -b bob/update-model
cat > src/model.py << 'EOF'
import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # Engineer B: change to 64 filters, 5x5 kernel
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = x.view(-1, 64 * 16 * 16)
return self.fc1(x)
EOF
git add . && git commit -m "bob: switch to 64 filters and a 5x5 kernel"

Now merge Engineer A’s changes:

Terminal window
git checkout main
git merge alice/update-model # ✅ Success, no conflict

Then merge Engineer B’s changes:

Terminal window
git merge bob/update-model
# Output:
# CONFLICT (content): Merge conflict in src/model.py
# Automatic merge failed; fix conflicts and then commit the result.

A conflict occurred! Because Engineer A and Engineer B both modified the same line in model.py.

Open src/model.py, and you will see Git marking the conflict like this:

class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
# Engineer A keeps a 3x3 kernel and raises filters to 32.
self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
self.fc1 = nn.Linear(32 * 16 * 16, 10)
# Engineer B changes both filter count and kernel size.
self.conv1 = nn.Conv2d(3, 64, 5, padding=2)
self.fc1 = nn.Linear(64 * 16 * 16, 10)
  • In a real conflict, Git shows <<<<<<< HEAD, then the current branch version, then =======, then the incoming branch version, and finally >>>>>>> branch-name.
  • The example above uses CONFLICT_MARKER_* placeholders so repository checks do not mistake this teaching sample for an unresolved merge conflict.

You need to manually decide what to keep. For example, let’s choose Engineer B’s version:

import torch
import torch.nn as nn
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 64, 5, padding=2) # Use Engineer B's version
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 16 * 16, 10)
def forward(self, x):
x = self.pool(torch.relu(self.conv1(x)))
x = x.view(-1, 64 * 16 * 16)
return self.fc1(x)

Delete all the <<<<<<<, =======, and >>>>>>> markers, and keep only the code you want. Then:

Terminal window
git add src/model.py
git commit -m "merge: merge Engineer A and Engineer B's changes, using Engineer B's 64-filter design"

The conflict is resolved.

Terminal window
# Clean up branches
git branch -d alice/update-model
git branch -d bob/update-model

In team collaboration, you usually do not merge directly into main. Instead, you use a Pull Request (PR) so someone else can review your code first and merge it only after confirming it looks good.

1. You create a feature branch and write code
2. Push it to GitHub
3. Create a Pull Request on GitHub
4. A teammate reviews your code and gives feedback
5. You make changes based on the feedback and push new commits
6. The teammate clicks "Approve"
7. The code gets merged into the main branch
Terminal window
# 1. Create a branch and write code
git checkout -b feature/add-evaluation
echo "def evaluate(model, dataloader): pass" > src/evaluate.py
git add . && git commit -m "Add model evaluation module"
# 2. Push the branch to GitHub
git push -u origin feature/add-evaluation

Then open GitHub, and you’ll see a prompt:

feature/add-evaluation had recent pushes — Compare & pull request

Click that button, fill in the PR title and description, and click Create pull request to finish.

For a personal project, you can review it yourself and then click Merge pull request on the GitHub page to merge it directly.


Complete the following checks to confirm you understand Git basics:

  • Can create a Git repository from scratch
  • Can use addcommit to save code changes
  • Can use git diff to see what changed
  • Know how to write a .gitignore file
  • Can push code to GitHub
  • Can use git clone to download someone else’s project
  • Understand branches, and can create and merge them
  • Stay calm when merge conflicts happen, and know how to solve them
Check reasoning and explanation
  1. You pass this check when you can create a repo, make a commit, inspect a diff, write .gitignore, push or clone, and create/merge a branch.
  2. A clean Git trace includes git status --short before and after risky operations.
  3. When a conflict happens, keep both versions only if both are needed, then remove all conflict markers before committing.
  4. Use git restore for uncommitted mistakes and new commits for shared history. Use hard reset only in disposable practice repos.
  5. Good evidence can be a branch graph, a short PR, or a terminal transcript showing branch creation, merge, and clean status.

Keep this page’s proof of learning as a small evidence card:

Repo State
git status before and after the operation
Operation
init, add, commit, branch, merge, remote, pull, or push command used
History
git log or branch graph showing what changed
Failure Check
untracked files, wrong branch, merge conflict, or remote/auth issue
Expected Output
a clean Git trace that another learner can replay safely