Home

Appendix 1: Advanced Git Workflows and Conflict Management

appendix git workflow rebase merge conflict-resolution feature-branch best-practices

Introduction: Git Challenges in Real Feature Development

As you work on your first real GitHub Classroom assignment implementing features using the feature branch workflow, you’re encountering Git challenges that every professional developer faces. This appendix lecture addresses the most common Git issues you’ll encounter and provides practical solutions.

You’re likely experiencing one or more of these situations:

  1. “Oops, I committed to main instead of creating a feature branch!”
  2. “I made a typo in my commit message - can I fix it?”
  3. “My feature branch is outdated - main has moved forward!”
  4. “Git says my branches have diverged - what do I do?”
  5. “I have merge conflicts - how do I resolve them?”

This lecture covers three critical Git workflows that will help you:

Let’s turn these challenges into learning opportunities!


Part 1: Fixing “Oops, I Committed to Main!”

1.1 The Problem

You’ve been working on a new feature and made several commits. Everything seems fine until you realize: you committed directly to main instead of creating a feature branch first!

Even worse - you’ve already pushed to the remote repository, so other people might have seen your commits.

main branch:
A---B---C---D---E---F  (commits D, E, F should be on a feature branch!)

This is a common mistake, especially when you’re focused on the code and forget to create a branch first.

1.2 Why This Matters

Best Practice: Feature Branch Workflow

In professional development, you should never commit directly to main (or master). Instead:

  1. Create a feature branch
  2. Make your commits there
  3. Create a Pull Request (PR) / Merge Request (MR)
  4. Get code review
  5. Merge to main after approval

Benefits:

1.3 The Solution: Move Commits to a Feature Branch

Let’s fix this situation step by step. We’ll move the commits from main to a new feature branch.

Scenario: You have 3 commits (D, E, F) that should be on a feature branch called feature/my-awesome-feature:

Current state:
main: A---B---C---D---E---F (D, E, F are the mistake)

Desired state:
main:                   A---B---C
                             \
feature/my-awesome-feature:   D---E---F

Step-by-Step Fix

Step 1: Create the feature branch (preserve the commits)

git branch feature/my-awesome-feature

This creates a new branch pointing at your current HEAD (commit F), but doesn’t switch to it yet.

Important: This doesn’t remove anything - it just creates a pointer to the current state.

Step 2: Move main back to before your commits

git reset --hard C_COMMIT_HASH

Replace C_COMMIT_HASH with the hash of the last good commit (the one before your feature work started).

What git reset --hard does:

How to find the correct commit hash:

git log --oneline -10

Look for the commit right before your feature work began.

Step 3: Switch to the feature branch

git checkout feature/my-awesome-feature

Step 4: Push the feature branch to remote

git push origin feature/my-awesome-feature

This uploads your feature branch with all the commits to the remote repository.

Step 5: Force push main to remote (if you already pushed)

git push --force-with-lease origin main

⚠️ Warning: --force-with-lease overwrites the remote history. Only use this when you’re sure!

Why force push is needed: Your local main is now “behind” the remote main (you removed commits). Git won’t let you push normally because it would lose commits. Since we want exactly that, we need --force-with-lease.

Complete Command Sequence

# 1. Create feature branch from current position
git branch feature/my-awesome-feature

# 2. Move main back (replace abc123 with the correct hash)
git reset --hard abc123

# 3. Switch to the feature branch
git checkout feature/my-awesome-feature

# 4. Push the feature branch
git push origin feature/my-awesome-feature

# 5. Force push main (only if you already pushed to remote)
git push --force-with-lease origin main

1.4 Real-World Example: Road Profile Viewer Assignment

The situation:

You’re excited to start the refactoring assignment. You’ve just read the requirements - you need to extract the geometry module with the ray intersection functions from the monolithic main.py file.

You create the new road_profile_viewer/geometry.py file, extract the find_intersection() and project_point_to_line() functions, and make your first commit. But then…

# You thought you were on a feature branch, but...
git status
# On branch main
# Your branch is ahead of 'origin/main' by 1 commit.
#
#   (use "git push" to publish your local commits)

# Oh no! The assignment requires a branch named 'feature/refactor-to-modules'!
# You committed directly to main instead!

Why this is a problem:

The recovery:

# 1. Check the situation
git log --oneline -3
# a7f3e21 Extract geometry module with intersection functions
# 8b5c742 Initial commit: monolithic road profile viewer
# c4d9a31 Add README and assignment instructions

# 2. Create the required feature branch at the current position (keeps your commit)
git branch feature/refactor-to-modules

# 3. Reset main back to match origin/main (removes commit from main)
git reset --hard origin/main
# HEAD is now at 8b5c742 Initial commit: monolithic road profile viewer

# 4. Switch to the feature branch (your commit is safe here!)
git checkout feature/refactor-to-modules

# 5. Verify your commit is now on the feature branch
git log --oneline -3
# a7f3e21 Extract geometry module with intersection functions
# 8b5c742 Initial commit: monolithic road profile viewer
# c4d9a31 Add README and assignment instructions

git status
# On branch feature/refactor-to-modules
# nothing to commit, working tree clean

# 6. Push the feature branch and continue with the assignment
git push -u origin feature/refactor-to-modules

Visual representation:

Initial state (BAD):
    origin/main: Initial commit---Monolithic viewer
    local main:  Initial commit---Monolithic viewer---Extract geometry ❌
                                                      ↑
                                                  You are here
                                             (Wrong branch!)

After recovery (GOOD):
    origin/main:              Initial commit---Monolithic viewer
    local main:               Initial commit---Monolithic viewer
    feature/refactor-to-modules: Initial commit---Monolithic viewer---Extract geometry ✅
                                                                       ↑
                                                                   You are here
                                                            (Correct branch!)

What you can do next:

Now you can continue with the assignment workflow:

  1. Continue refactoring (extract road.py, visualization.py)
  2. Make incremental commits on your feature branch
  3. Push to remote: git push origin feature/refactor-to-modules
  4. Create a Pull Request on GitHub
  5. CI checks will run automatically
  6. Request peer review
  7. Merge after approval

1.5 Why This Works

  1. git branch feature/my-feature
    • Creates a new branch at your current position
    • The commit is now referenced by both main and the feature branch
    • Does NOT switch branches (you’re still on main)
  2. git reset --hard origin/main
    • Moves the main branch pointer back to match origin/main
    • The --hard flag also resets your working directory
    • Your commit is no longer on main…
    • But it’s still safe! The feature branch still points to it
  3. git checkout feature/my-feature
    • Switches to the feature branch
    • Your commit is now on the correct branch
    • You can proceed with normal PR workflow

Key insight: Git’s branch pointers are just references to commits. By creating a new branch before resetting main, you ensure the commit remains accessible. Commits don’t belong to branches—branches point to commits!

1.6 Prevention: How to Avoid This

Check your current branch

git branch
# * main  ← Red flag!

# Or use git status
git status
# On branch main  ← Warning!

Always create your feature branch BEFORE starting work

# Start new feature work
git checkout -b feature/my-awesome-feature

The -b flag creates the branch and switches to it in one command.

Configure your shell prompt

Many shell prompts can show your current Git branch. This gives you a constant visual reminder.

Example (oh-my-zsh, bash-git-prompt, etc.):

user@computer ~/project (main) $        # On main - be careful!
user@computer ~/project (feature/xyz) $ # On feature branch - safe!

Use branch protection rules

On GitHub/GitLab/Bitbucket, enable branch protection for main:

This prevents accidental commits to main in the first place.


Part 2: Understanding git commit –amend and Rebase Strategy

2.1 The Scenario: What Happens When You Amend

The issue we often experience:

  1. Initial State: We made changes to files and committed them with a descriptive message
  2. The Commit: Successfully pushed commit to the remote branch
  3. Small Change Needed: Made a minor edit (fixing a typo or small formatting issue)
  4. The Amend: Instead of creating a new commit, we used git commit --amend to modify the previous commit
  5. New Commit Created: Git created a new commit with the same message but different content
  6. Push Failed: When trying to push, Git rejected it with the message:
    Your branch and 'origin/feature/branch-name' have diverged,
    and have 1 and 1 different commits each, respectively.
    

2.2 Why This Happened

The critical insight: git commit --amend doesn’t modify an existing commit—it creates an entirely new commit.

Before amend:
    A---B---C (12d3002) ← HEAD, origin/feature/branch

After amend:
    A---B---C (12d3002) ← origin/feature/branch
         \
          C' (71db7cf) ← HEAD (local)

The commits have:

2.3 What –amend Does

git commit --amend allows you to modify the most recent commit by:

The Technical Reality

When you amend:

git add file.txt
git commit --amend

Git actually:

  1. Takes the current staging area
  2. Takes the tree from the previous commit
  3. Combines them into a new commit object
  4. Updates HEAD to point to this new commit
  5. The old commit becomes “dangling” (still exists but not referenced)

2.4 Why It’s Powerful

Example: Messy History (without –amend)

* fix typo in README
* oops forgot to add file
* actually add the feature
* fix formatting in feature
* Feature: Add user authentication

Clean History (with –amend)

* Feature: Add user authentication

All those intermediate “oops” commits are combined into one logical unit.

2.5 The Rebase Strategy and –amend

In modern Git workflows (especially in professional environments), teams often use:

Goal: Keep the project history as a clean, linear story of changes.

Merge Strategy (creates merge commits):
    A---B---C (main)
         \   \
          D---E---M (feature merged)

Rebase Strategy (linear history):
    A---B---C---D'---E' (main with feature)

How –amend Fits In

Before pushing to the remote, you can freely rewrite your local history:

  1. Make a commit:
    git commit -m "Add feature X"
    
  2. Realize you forgot something:
    git add forgotten_file.js
    git commit --amend --no-edit
    
  3. Fix a typo in the commit message:
    git commit --amend -m "Add feature X with proper error handling"
    

Result: One clean commit instead of multiple “fix” commits.

2.6 The Golden Rule: When Rewriting History is Dangerous

⚠️ Critical Warning

Never rewrite commits that have been pushed to a shared branch that others are working on!

Safe to Amend/Rebase:

Local commits (not pushed yet) ✅ Feature branches where you’re the only developer ✅ After force-pushing to your own feature branch (what we did)

Dangerous to Amend/Rebase:

Main/master branch with team members ❌ Shared feature branches with multiple developers ❌ Public releases or tags

Why It’s Dangerous

If someone based their work on commit 12d3002 and you force-push 71db7cf:

Their situation:
    A---B---C (12d3002, their work is based here)
             \
              D---E (their new commits)

After your force-push:
    A---B---C' (71db7cf, different commit!)

    Their commits D and E are now orphaned!

2.7 The Solution: Force Push (With Care)

After amending, the local and remote histories diverged. To update the remote:

git push --force-with-lease

–force-with-lease vs –force

--force-with-lease (safer):

--force (dangerous):

What Happened in Our Case

# Before force-push
Local:  A---B---C' (71db7cf) ← HEAD
Remote: A---B---C (12d3002) ← origin/feature/branch

# After force-push
Local:  A---B---C' (71db7cf) ← HEAD
Remote: A---B---C' (71db7cf) ← origin/feature/branch

# The old commit 12d3002 is now unreferenced on remote

2.8 Best Practices

Amend Early, Amend Often (Locally)

While working on a feature:

# Initial work
git add .
git commit -m "WIP: Add feature"

# Add more work
git add .
git commit --amend --no-edit

# Keep refining before push
git add .
git commit --amend

Result: One polished commit when you finally push.

Use Descriptive Branch Names

feature/user-authentication
bugfix/login-timeout
refactor/database-layer

This makes it clear that the branch is:

Communicate Force-Pushes

If others might be watching your branch:

# Slack/Discord message
"⚠️ Force-pushing to feature/branch-name
Please pull with --rebase if you have local changes!"

The Pre-Push Checklist

Before git push --force-with-lease:

2.9 Interactive Rebase: The Power Tool

--amend only works on the most recent commit. For older commits, use interactive rebase:

git rebase -i HEAD~3  # Edit last 3 commits

You can:

Example:

# Your messy history
git log --oneline
a1b2c3d Fix typo
b2c3d4e Add feature part 3
c3d4e5f Fix bug in part 2
d4e5f6g Add feature part 2
e5f6g7h Add feature part 1

# Start interactive rebase
git rebase -i HEAD~5

# Editor opens with:
pick e5f6g7h Add feature part 1
pick d4e5f6g Add feature part 2
pick c3d4e5f Fix bug in part 2
pick b2c3d4e Add feature part 3
pick a1b2c3d Fix typo

# Change to:
pick e5f6g7h Add feature part 1
squash d4e5f6g Add feature part 2
fixup c3d4e5f Fix bug in part 2
squash b2c3d4e Add feature part 3
fixup a1b2c3d Fix typo

# Result: One clean commit
git log --oneline
f7g8h9i Add complete user profile feature

2.10 Quick Reference

Amend the last commit

git commit --amend                    # Edit message and add staged changes
git commit --amend --no-edit          # Just add staged changes
git commit --amend -m "New message"   # Change message only

Force push safely

git push --force-with-lease           # Safe force push
git push --force                      # Dangerous force push

Interactive rebase

git rebase -i HEAD~3                  # Edit last 3 commits
git rebase -i main                    # Rebase entire branch on main

Undo an amend (emergency!)

git reflog                            # Find previous commit
git reset --soft HEAD@{1}             # Undo amend, keep changes staged

Part 3: Git Rebase vs Merge - Workflow Strategies and Conflict Management

3.1 The Common Scenario

You’re working on a feature branch, but main doesn’t stand still:

Day 1: Create feature branch
main:    A---B---C
              \
feature:       D

Day 3: Colleagues merge their work
main:    A---B---C---E---F---G  (moved forward!)
              \
feature:       D---H---I         (your work continues)

Question: How do you stay synchronized with main?

The challenge:

3.2 Why Staying Synchronized Matters

Problem 1: Conflicts grow over time

Week 1: Main has 2 new commits
- Small conflicts, easy to resolve

Week 2: Main has 10 new commits
- Large conflicts, hard to resolve
- You forgot context of your old code

Problem 2: Integration issues discovered late

Your branch:
- Works perfectly in isolation
- All tests pass

Main branch:
- API changed 3 days ago
- Your code now incompatible

If you wait until PR time:
- Big surprise! Nothing works
- Takes days to fix
- Blocks other work

Problem 3: Difficult code review

PR with outdated branch:
Reviewer: "Wait, this conflicts with the new API"
You: "Let me rebase..."
Reviewer: "The diff changed, I need to review again"

PR with up-to-date branch:
Reviewer: "Looks good, already works with latest main"
Quick approval!

The Golden Rule

“A main merge/rebase a day keeps the conflicts away!”

Why this works:

3.3 The Two Strategies

Strategy 1: Merge Main into Feature (Merge Strategy)

The workflow:

# Daily routine (on feature branch)
git fetch --prune
git merge origin/main
# Resolve conflicts if any
git push

What happens:

Before merge:
main:    A---B---C---E---F---G
              \
feature:       D---H---I

After git merge origin/main:
main:    A---B---C---E---F---G
              \             \
feature:       D---H---I---M  (M = merge commit)

Visual explanation:

Advantages:

✅ Simple mental model (just combine branches)
✅ Preserves exact history (shows when integration happened)
✅ Each conflict resolved only once (in merge commit)
✅ No force push needed (safe!)
✅ Safe if multiple people work on same branch
✅ Can undo easily (git reset to before merge)

Disadvantages:

❌ Creates merge commits (clutters history)
❌ Non-linear history (harder to read git log)
❌ PR diff might include unrelated changes from main
❌ "Messy" looking git graph

Strategy 2: Rebase Feature onto Main (Rebase Strategy)

The workflow:

# Daily routine (on feature branch)
git fetch --prune
git rebase origin/main
# Resolve conflicts if any
git push --force-with-lease

What happens:

Before rebase:
main:    A---B---C---E---F---G
              \
feature:       D---H---I

After git rebase origin/main:
main:    A---B---C---E---F---G
                           \
feature:                    D'---H'---I'  (commits rewritten!)

Visual explanation:

Advantages:

✅ Clean, linear history
✅ No merge commits cluttering log
✅ Easier git log to read (straight line)
✅ PR review shows only your changes
✅ Easier to cherry-pick or revert specific commits
✅ Professional-looking history

Disadvantages:

❌ Rewrites history (changes commit hashes)
❌ Requires force push (dangerous if not careful!)
❌ Conflicts might appear multiple times (per commit)
❌ Loses information about when integration happened
❌ More complex to understand (for beginners)
❌ Dangerous if others work on same branch

3.4 Conflicts - When and How

How Conflicts Happen

A conflict occurs when:

  1. Same file modified in both branches
  2. Same lines (or nearby lines) changed differently
  3. Git can’t automatically decide which change to keep

Example conflict:

Scenario:
- You changed line 10 in main.py
- Colleague changed line 10 in main.py differently
- Both changes based on old version

When you merge/rebase:
Git says: "I don't know which line 10 is correct!"

Merge Strategy Conflicts

How conflicts appear:

$ git merge origin/main
Auto-merging main.py
CONFLICT (content): Merge conflict in main.py
Automatic merge failed; fix conflicts and then commit the result.

What the conflict looks like:

# main.py
def calculate_total(items):
<<<<<<< HEAD  (your branch)
    return sum(item.price * item.quantity for item in items)
=======  (main branch)
    return sum(item.final_price() * item.quantity for item in items)
>>>>>>> origin/main

Resolving the conflict:

# 1. Open main.py, edit to resolve conflict
# Choose one version or combine them:
def calculate_total(items):
    return sum(item.final_price() * item.quantity for item in items)

# 2. Mark as resolved
git add main.py

# 3. Complete the merge
git commit  # Git auto-fills merge commit message

Key point: You resolve the conflict once in the merge commit, and you’re done!

Rebase Strategy Conflicts

How conflicts appear:

$ git rebase origin/main
Applying: Add feature X
CONFLICT (content): Merge conflict in main.py
error: could not apply 7ea41f8... Add feature X
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".

The difference: Rebase applies commits one by one.

If you have 3 commits (D, H, I):

Step 1: Apply D on top of G
- Might conflict → resolve → continue

Step 2: Apply H on top of (G + D')
- Might conflict → resolve → continue

Step 3: Apply I on top of (G + D' + H')
- Might conflict → resolve → continue

The problem: Same conflict multiple times!

Commit D: Changed line 10 in main.py
Commit H: Changed line 10 in main.py again
Commit I: Changed line 10 in main.py again

Main also changed line 10.

Result:
- Conflict when applying D → resolve
- Conflict when applying H → resolve (same area!)
- Conflict when applying I → resolve (same area again!)

Resolving rebase conflicts:

# For each commit that conflicts:

# 1. Open main.py, resolve conflict
vim main.py

# 2. Mark as resolved
git add main.py

# 3. Continue rebase
git rebase --continue

# Repeat for each conflicting commit!

Escape hatches:

# If conflicts too complex:
git rebase --abort  # Start over, branch unchanged

# If want to skip a commit:
git rebase --skip  # Dangerous! Only if commit is redundant

Comparing Conflict Resolution

Aspect Merge Rebase
Frequency Once (in merge commit) Per conflicting commit
Complexity Resolve all at once Resolve incrementally
Same conflict Solved once Might appear multiple times
Context See both branches' changes See commit by commit
Abort git merge --abort git rebase --abort
After resolving git commit git rebase --continue

Which Is Easier?

For small conflicts: Rebase can be easier (incremental)

Example: 3 commits, each with tiny conflict
Rebase: Resolve 3 small conflicts one-by-one
Merge: Resolve 1 medium conflict all at once

Winner: Rebase (bite-sized)

For large conflicts: Merge is usually easier

Example: 10 commits, same file touched in each
Rebase: Resolve SAME conflict 10 times!
Merge: Resolve conflict ONCE

Winner: Merge (no repetition)

The key factor: Daily updates!

With daily updates:

Without daily updates:

3.5 Practical Workflow Examples

Situation:

Daily workflow:

# Morning: Start of workday
git checkout feature/my-feature
git fetch --prune
git rebase origin/main
# Resolve any conflicts
git push --force-with-lease

# During day: Regular commits
git add .
git commit -m "Progress on feature"
git push

# Evening: Check if main moved
git fetch --prune
# If main updated, rebase again
git rebase origin/main
git push --force-with-lease

Benefits:

Situation:

Daily workflow:

# Morning: Start of workday
git checkout feature/team-feature
git pull  # Get teammates' work
git fetch --prune
git merge origin/main
# Resolve any conflicts
git push

# During day: Regular commits
git add .
git commit -m "My part of feature"
git push

# Before end of day: Sync again
git pull  # Get teammates' afternoon work

Benefits:

Example 3: Long-Running Feature (Hybrid Approach)

Situation:

Strategy: Merge during development, rebase before PR

# During development (daily):
git merge origin/main  # Safe, no force push
# Work continues...

# Before creating PR (final cleanup):
git rebase -i origin/main  # Interactive rebase
# Squash commits, clean up history
git push --force-with-lease

# Create PR with clean history

Benefits:

3.6 Decision Matrix

When to Use Rebase

Use rebase when:

  1. Working alone on the feature branch
    • No one else affected by history rewriting
    • Force push is safe
  2. Short-lived branch (< 1-2 weeks)
    • Limited opportunity for conflicts
    • Rebase manageable
  3. Want clean history for PR review
    • Linear history easier to review
    • Shows logical progression of work
  4. Updating frequently (daily or more)
    • Conflicts stay small
    • Rewriting few commits each time
  5. Feature hasn’t been pushed yet
    • No remote history to worry about
    • Can rewrite freely

Example command:

git rebase origin/main
git push --force-with-lease

When to Use Merge

Use merge when:

  1. Multiple people work on the branch
    • Force push would break their work
    • Shared history must be preserved
  2. Long-lived branch (> 2 weeks)
    • Many commits accumulated
    • Rebase would be painful
  3. Don’t want to force push
    • Safer, more conservative approach
    • Less risk of mistakes
  4. Want to preserve exact history
    • See when integrations happened
    • Understand development timeline
  5. Large, complex conflicts expected
    • Resolve once vs. multiple times
    • Less repetitive conflict resolution

Example command:

git merge origin/main
git push  # No force needed!

Decision Flowchart

START: Need to update feature branch with latest main
  ↓
Working alone on this branch?
  ├─ Yes → Branch short-lived (< 2 weeks)?
  │         ├─ Yes → Comfortable with rebase?
  │         │         ├─ Yes → USE REBASE ✅
  │         │         └─ No  → USE MERGE ✅
  │         └─ No  → USE MERGE ✅
  └─ No  → USE MERGE ✅

Summary:
- Default to MERGE (safer)
- Use REBASE only if:
  * Solo developer
  * Short branch
  * Comfortable with force push

3.7 Best Practices for Daily Workflow

The Daily Sync Ritual

Choose one and stick to it:

Option A: Rebase (clean history):

# Every morning
git checkout feature/my-feature
git fetch --prune
git rebase origin/main
git push --force-with-lease

Option B: Merge (safe):

# Every morning
git checkout feature/my-feature
git fetch --prune
git merge origin/main
git push

Why daily?

Use --force-with-lease Not --force

Bad:

git push --force  # Blindly overwrites remote

Good:

git push --force-with-lease  # Checks remote hasn't changed

Why --force-with-lease is safer:

Scenario:
1. You rebase locally
2. Teammate pushes to same branch (you don't know!)
3. You force push

--force:
→ Teammate's work LOST! 💥

--force-with-lease:
→ Push rejected! Git says "Remote changed since you last fetched"
→ You fetch, see teammate's work, coordinate
→ No data loss ✅

Communicate with Your Team

When rebasing shared branch:

# In team chat:
"Hey team, I'm about to rebase feature/team-feature onto latest main.
Please push any pending work now, then re-clone after I'm done."

# Wait for responses...

# Then rebase
git rebase origin/main
git push --force-with-lease

# Notify team:
"Rebase done! Please run: git fetch && git reset --hard origin/feature/team-feature"

When merging (no communication needed):

git merge origin/main
git push  # Team can pull anytime, no disruption

Resolve Conflicts Carefully

During merge:

# 1. See which files conflict
git status

# 2. Open each file, look for:
<<<<<<< HEAD
your code
=======
their code
>>>>>>> origin/main

# 3. Choose correct version or combine

# 4. Test! Make sure it works
npm test  # or whatever your test command is

# 5. Mark resolved and commit
git add .
git commit

During rebase:

# Same process, but after each commit:
git add .
git rebase --continue

# Repeat until done

Golden rule: Always test after resolving conflicts!

Know Your Escape Hatches

Undo a merge:

git merge --abort  # Before committing
git reset --hard HEAD~1  # After committing (last commit)

Undo a rebase:

git rebase --abort  # During rebase

# After rebase completed:
git reflog  # Find commit before rebase
git reset --hard HEAD@{5}  # Or whatever reflog entry

Undo a force push:

# Find old commit
git reflog
# or on GitHub: Check branch history before force push

# Force push back to old state
git reset --hard <old-commit-sha>
git push --force-with-lease

3.8 Common Pitfalls and Solutions

Pitfall 1: Waiting Too Long to Sync

Problem:

Week 1: Branch created from commit C
Week 2: Main is at commit G (4 commits ahead)
Week 3: Main is at commit M (10 commits ahead)
Week 4: Main is at commit Z (20 commits ahead)

Time to merge PR:
- Massive conflicts
- Days to resolve
- Everything broken
- Start over? 😢

Solution:

# Daily sync (choose one):
git merge origin/main  # or
git rebase origin/main

# Takes 5 minutes per day vs. days at the end

Pitfall 2: Force Pushing to Shared Branch

Problem:

Developer A:
feature: A---B---C---D---E

Developer B:
feature: A---B---C---D---E---F (pushed)

Developer A rebases:
feature: A---B---C'---D'---E' (force push)

Developer B:
→ Git says "diverged"
→ Their work (F) is orphaned!
→ 😢

Solution:

# If multiple people: DON'T REBASE!
git merge origin/main  # Safe for shared branches

Pitfall 3: Forgetting to Test After Resolving Conflicts

Problem:

Resolve conflict:
- Pick "your" version
- git add, git commit
- Push

Later:
- Build broken
- Tests fail
- Oops! 😬

Solution:

# After resolving conflicts:
git add .

# BEFORE committing:
npm test  # or pytest, or whatever
npm run build

# Only if tests pass:
git commit

Pitfall 4: Losing Work with git reset --hard

Problem:

git reset --hard origin/main  # Oops! Lost my work!

Solution:

# If just did it:
git reflog  # Find your lost commits
git reset --hard HEAD@{3}  # Restore

# Prevention: Check what you're about to lose
git log HEAD..origin/main  # "What will I lose?"
git diff HEAD origin/main   # "What changes will I lose?"

Pitfall 5: Rebase Conflict Fatigue

Problem:

git rebase origin/main
→ Conflict in commit D... resolve, continue
→ Conflict in commit E... resolve, continue
→ Conflict in commit F... resolve, continue
→ Conflict in commit G... resolve, continue
→ "I give up!" 😩

Solution:

# Abort the rebase
git rebase --abort

# Use merge instead
git merge origin/main  # Resolve once, done!

# Or: Interactive rebase to squash commits first
git rebase -i HEAD~10  # Combine commits
git rebase origin/main  # Fewer commits = fewer conflicts

Conclusion: Summary and Recommendations

Quick Decision Guide

Ask yourself these questions:

  1. Am I the only one working on this branch?
    • Yes → Consider rebase
    • No → Use merge
  2. Is this branch short-lived (< 2 weeks)?
    • Yes → Consider rebase
    • No → Use merge
  3. Am I comfortable with force pushing?
    • Yes → Consider rebase
    • No → Use merge
  4. Do I want clean, linear history?
    • Yes → Consider rebase
    • No (or don’t care) → Use merge

If 3-4 answers favor rebase → Use rebase If 0-2 answers favor rebase → Use merge

Beginner (0-6 months with Git)

Use merge:

git fetch --prune
git merge origin/main
git push

Why:

Intermediate (6-24 months with Git)

Use rebase for solo branches:

git fetch --prune
git rebase origin/main
git push --force-with-lease

Use merge for team branches:

git fetch --prune
git merge origin/main
git push

Why:

Advanced (24+ months with Git)

Use hybrid approach:

# During development:
git merge origin/main  # Safe, frequent syncs

# Before PR:
git rebase -i origin/main  # Clean up history
git push --force-with-lease

Why:

The Universal Rule (All Levels)

Update your branch daily (or at least every 2-3 days):

# Pick one and do it DAILY:

# Option A: Merge (safer)
git fetch --prune && git merge origin/main

# Option B: Rebase (cleaner)
git fetch --prune && git rebase origin/main

# The key is: DO IT REGULARLY!

Why this matters more than rebase vs. merge:

Key Takeaways

  1. “A main merge/rebase a day keeps the conflicts away”
    • Most important practice
    • Works with either strategy
    • Prevents conflict buildup
  2. Commits are not stuck on branches - branches point to commits
    • Creating a branch before reset preserves commits
    • Understanding this makes Git less scary
  3. git commit --amend rewrites history by creating a new commit
    • Use it freely on local commits
    • Never on shared/pushed commits (unless you know what you’re doing)
  4. Merge is the safer default
    • Works for teams
    • No force push needed
    • Easier to understand
    • Can’t go wrong
  5. Rebase creates cleaner history
    • But requires force push
    • Only for solo branches
    • Requires more Git knowledge
    • Worth learning eventually
  6. Both strategies work fine with daily updates
    • Conflicts stay small
    • Choose based on team/preference
    • Consistency matters more than choice
  7. Test after resolving conflicts
    • Every single time
    • No exceptions
    • Prevents broken builds
  8. Use --force-with-lease not --force
    • Safer force pushing
    • Prevents data loss
    • Always prefer it
  9. Communicate when rebasing shared branches
    • Coordinate with team
    • Minimize disruption
    • Or just use merge instead

Command Reference

Daily Sync Commands

Merge strategy:

git fetch --prune
git merge origin/main
git push

Rebase strategy:

git fetch --prune
git rebase origin/main
git push --force-with-lease

Conflict Resolution

During merge:

# See conflicts
git status

# After resolving:
git add <file>
git commit

During rebase:

# See conflicts
git status

# After resolving:
git add <file>
git rebase --continue

Escape Hatches

Abort merge:

git merge --abort

Abort rebase:

git rebase --abort

Undo last commit (local only):

git reset --hard HEAD~1

Recover from reflog:

git reflog
git reset --hard HEAD@{N}

Safe Force Push

Always use:

git push --force-with-lease

Never use (unless you know exactly what you’re doing):

git push --force  # ⚠️ Dangerous!

Remember: The best strategy is the one your team agrees on and uses consistently. When in doubt, merge is the safer choice! With great Git power comes great responsibility - use these tools wisely! 🚀

Additional Resources

Official Documentation

Tutorials and Guides

Best Practices

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk