Appendix 1: Advanced Git Workflows and Conflict Management
October 2025 (6711 Words, 38 Minutes)
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:
- “Oops, I committed to main instead of creating a feature branch!”
- “I made a typo in my commit message - can I fix it?”
- “My feature branch is outdated - main has moved forward!”
- “Git says my branches have diverged - what do I do?”
- “I have merge conflicts - how do I resolve them?”
This lecture covers three critical Git workflows that will help you:
- Recover from accidentally committing to main
- Understand and use
git commit --amendandgit rebase - Keep your feature branch synchronized with main using merge or rebase strategies
- Resolve conflicts effectively
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:
- Create a feature branch
- Make your commits there
- Create a Pull Request (PR) / Merge Request (MR)
- Get code review
- Merge to main after approval
Benefits:
- Code review before merging
- Easy to revert if something goes wrong
- Clear separation of work-in-progress vs. stable code
- Better collaboration with team members
- CI/CD can run tests before merging
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:
- Moves the branch pointer (main) back to the specified commit
- Updates the working directory to match
- Warning: Discards any uncommitted changes (but our commits are safe on the feature branch!)
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 assignment requires using the feature branch workflow (25 points)
- CI checks won’t run properly on main
- You can’t create a pull request from main to main
- Branch protection rules prevent pushing to main
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:
- Continue refactoring (extract
road.py,visualization.py) - Make incremental commits on your feature branch
- Push to remote:
git push origin feature/refactor-to-modules - Create a Pull Request on GitHub
- CI checks will run automatically
- Request peer review
- Merge after approval
1.5 Why This Works
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)
git reset --hard origin/main- Moves the
mainbranch pointer back to matchorigin/main - The
--hardflag also resets your working directory - Your commit is no longer on main…
- But it’s still safe! The feature branch still points to it
- Moves the
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:
- Require pull requests before merging
- Require status checks to pass
- Prevent direct pushes to 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:
- Initial State: We made changes to files and committed them with a descriptive message
- The Commit: Successfully pushed commit to the remote branch
- Small Change Needed: Made a minor edit (fixing a typo or small formatting issue)
- The Amend: Instead of creating a new commit, we used
git commit --amendto modify the previous commit - New Commit Created: Git created a new commit with the same message but different content
- 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:
- Same commit message
- Same files (with your additional changes)
- Different commit hashes (different SHA-1 identifiers)
- Different history (Git treats them as completely separate commits)
2.3 What –amend Does
git commit --amend allows you to modify the most recent commit by:
- Adding forgotten files
- Fixing typos in the commit message
- Including small changes that logically belong to the previous commit
- Keeping the commit history clean (avoiding “oops” commits)
The Technical Reality
When you amend:
git add file.txt
git commit --amend
Git actually:
- Takes the current staging area
- Takes the tree from the previous commit
- Combines them into a new commit object
- Updates HEAD to point to this new commit
- 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:
- Rebase strategy instead of merge strategy
- Clean, linear history instead of merge commits
- Atomic commits (one logical change = one commit)
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:
- Make a commit:
git commit -m "Add feature X" - Realize you forgot something:
git add forgotten_file.js git commit --amend --no-edit - 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):
- Checks if remote branch is still at expected commit
- Fails if someone else pushed changes
- Protects against accidentally overwriting others’ work
--force (dangerous):
- Unconditionally overwrites remote branch
- No safety checks
- Use only when you’re certain
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:
- Temporary
- Your personal workspace
- Safe to rewrite history
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:
- ✅ Am I the only one working on this branch?
- ✅ Is this a feature/personal branch (not main)?
- ✅ Do I really need to rewrite history?
- ✅ Have I communicated with the team?
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:
- reword: Change commit messages
- edit: Modify commit contents
- squash: Combine multiple commits into one
- fixup: Like squash but discard the commit message
- drop: Remove a commit entirely
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:
- Your feature branch was created from commit C
- Main has moved to commit G (colleagues’ work)
- You have commits D, H, I (your work)
- Files overlap - potential conflicts!
- What’s the best strategy?
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:
- Daily updates = small incremental conflicts
- Context still fresh in your mind
- Integration issues caught early
- PR review easier (already compatible with latest main)
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:
- Your commits (D, H, I) stay unchanged
- Git creates new merge commit (M)
- M has two parents: I (your work) and G (main)
- History shows the merge happened
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:
- Your commits (D, H, I) are rewritten as (D’, H’, I’)
- Same changes, but new commit hashes
- Commits now have G as ancestor (not C)
- History is linear (no merge commits)
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:
- Same file modified in both branches
- Same lines (or nearby lines) changed differently
- 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:
- Conflicts are small (only 1 day of changes)
- Both strategies work well
- Rebase preferred for clean history
- Merge preferred for simplicity
Without daily updates:
- Conflicts are large (weeks of changes)
- Rebase becomes painful (many commits)
- Merge preferred (resolve once)
3.5 Practical Workflow Examples
Example 1: Solo Developer (Rebase Recommended)
Situation:
- Working alone on feature branch
- Want clean, linear history
- Comfortable with force push
- Branch short-lived (< 2 weeks)
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:
- Feature branch always up-to-date
- History stays linear
- PR review easy (clean diff)
- Conflicts resolved daily (small)
Example 2: Team Feature Branch (Merge Recommended)
Situation:
- Multiple developers on same feature
- Can’t force push (would break teammates’ work)
- Branch might be longer-lived
- History preservation important
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:
- Safe for multiple developers
- No force push needed
- Teammates not disrupted
- Conflicts resolved once per merge
Example 3: Long-Running Feature (Hybrid Approach)
Situation:
- Feature takes 3+ weeks
- Main moving rapidly
- Want to stay synchronized
- Want relatively clean history
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:
- Safe merges during development
- Clean history for PR review
- Best of both worlds
3.6 Decision Matrix
When to Use Rebase
✅ Use rebase when:
- Working alone on the feature branch
- No one else affected by history rewriting
- Force push is safe
- Short-lived branch (< 1-2 weeks)
- Limited opportunity for conflicts
- Rebase manageable
- Want clean history for PR review
- Linear history easier to review
- Shows logical progression of work
- Updating frequently (daily or more)
- Conflicts stay small
- Rewriting few commits each time
- 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:
- Multiple people work on the branch
- Force push would break their work
- Shared history must be preserved
- Long-lived branch (> 2 weeks)
- Many commits accumulated
- Rebase would be painful
- Don’t want to force push
- Safer, more conservative approach
- Less risk of mistakes
- Want to preserve exact history
- See when integrations happened
- Understand development timeline
- 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?
- Conflicts stay small (1 day of changes)
- Context fresh (remember what you did yesterday)
- Integration issues caught early
- PR ready anytime
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:
- Am I the only one working on this branch?
- Yes → Consider rebase
- No → Use merge
- Is this branch short-lived (< 2 weeks)?
- Yes → Consider rebase
- No → Use merge
- Am I comfortable with force pushing?
- Yes → Consider rebase
- No → Use merge
- 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
Recommended Workflows by Experience Level
Beginner (0-6 months with Git)
Use merge:
git fetch --prune
git merge origin/main
git push
Why:
- Safer (no force push)
- Simpler mental model
- Easier to undo mistakes
- Industry-standard approach
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:
- Understand tradeoffs
- Can choose based on situation
- Comfortable with both approaches
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:
- Best of both worlds
- Safe during development
- Clean history for PR
- Professional workflow
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:
- Small conflicts vs. large conflicts
- Early integration vs. late surprises
- Fresh context vs. forgotten details
- 5 minutes/day vs. days at PR time
Key Takeaways
- “A main merge/rebase a day keeps the conflicts away”
- Most important practice
- Works with either strategy
- Prevents conflict buildup
- Commits are not stuck on branches - branches point to commits
- Creating a branch before reset preserves commits
- Understanding this makes Git less scary
git commit --amendrewrites history by creating a new commit- Use it freely on local commits
- Never on shared/pushed commits (unless you know what you’re doing)
- Merge is the safer default
- Works for teams
- No force push needed
- Easier to understand
- Can’t go wrong
- Rebase creates cleaner history
- But requires force push
- Only for solo branches
- Requires more Git knowledge
- Worth learning eventually
- Both strategies work fine with daily updates
- Conflicts stay small
- Choose based on team/preference
- Consistency matters more than choice
- Test after resolving conflicts
- Every single time
- No exceptions
- Prevents broken builds
- Use
--force-with-leasenot--force- Safer force pushing
- Prevents data loss
- Always prefer it
- 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! 🚀