Git Conflict Resolution Guide
A practical guide to understanding and systematically resolving code conflicts during Git merge/rebase. Covers conflict markers, mergetool, and rebase strategies.
Problem
Required Tools
A distributed version control system. Conflicts can occur during merge, rebase, and cherry-pick commands, and status, diff, and log help assess the situation.
VS Code's 3-way merge editor displays Current Change, Incoming Change, and common ancestor (Base) side by side to help resolve conflicts.
A Git command that invokes external merge tools. Can be configured to use vimdiff, meld, kdiff3, Beyond Compare, and more.
Visually displays branch history to identify where branches diverged and which commits caused the conflicts.
Solution Steps
Check conflict status (git status)
After running merge or rebase, when conflicts occur, Git enters a "MERGING" or "REBASING" state. Use git status to check the list of files with conflicts. Files shown as "both modified" are conflict files where both sides made changes. Before resolving conflicts, it's helpful to understand the context by checking which commits caused the conflicts with git log.
# Check current status
git status
# Example output:
# On branch feature/user-auth
# You have unmerged paths.
# (fix conflicts and run "git commit")
#
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: src/auth/login.ts
# both modified: src/api/users.ts
# both modified: src/config/database.ts
# Filter only conflict files
git diff --name-only --diff-filter=U
# Check branch divergence history (where they split)
git log --oneline --graph --all --decorate -20
# Check commits that caused the conflict
git log --oneline main..feature/user-auth # My branch commits
git log --oneline feature/user-auth..main # Commits added to mainUnderstand conflict markers (<<<< ==== >>>>)
Git marks conflicting sections with special markers. <<<<<<< HEAD (or current branch name): Code from the current branch =======: Separator >>>>>>> main (or merge target branch name): Code from the incoming branch In a 3-way merge, checking the common ancestor (Base) code helps understand the intent behind each side's changes. You can individually check the differences with git diff --base, git diff --ours, and git diff --theirs.
# Example content of a conflicted file (src/auth/login.ts)
# ============================================
async function login(email: string, password: string) {
<<<<<<< HEAD
// Code from the current branch (feature/user-auth)
const user = await db.users.findByEmail(email);
if (!user) throw new AuthError('USER_NOT_FOUND');
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) throw new AuthError('INVALID_PASSWORD');
const token = generateJWT(user, { expiresIn: '7d' });
=======
// Code from the main branch
const user = await UserRepository.findOne({ email });
if (!user) throw new NotFoundException('User not found');
const isValid = await argon2.verify(user.password, password);
if (!isValid) throw new UnauthorizedException('Invalid credentials');
const token = this.jwtService.sign({ sub: user.id });
>>>>>>> main
return { user, token };
}
# Decisions to make in this case:
# 1. DB access pattern: db.users vs UserRepository (architecture decision)
# 2. Hash algorithm: bcrypt vs argon2 (check team standard)
# 3. Error classes: AuthError vs NotFoundException (need to unify)
# 4. JWT generation: generateJWT vs jwtService.sign (framework dependency)Resolve conflicts manually and use VS Code Merge Editor
There are 3 main ways to resolve conflicts: 1. Accept Current Change: Keep only your code 2. Accept Incoming Change: Keep only the other person's code 3. Accept Both Changes or edit directly: Combine both changes or write new code When you open a conflicted file in VS Code, buttons appear above each conflict region: "Accept Current Change | Accept Incoming Change | Accept Both Changes | Compare Changes". For complex conflicts, directly editing the code to reflect the intent of both changes is the best approach.
# Resolve conflicts in VS Code
code src/auth/login.ts # Open the conflicted file in VS Code
# Launch VS Code 3-way Merge Editor (Git 2.38+)
# Click "Resolve in Merge Editor" button at the top of the file
# - Left: Current (my branch)
# - Right: Incoming (other branch)
# - Bottom: Result (final result)
# Example of manually resolved final code:
# (Remove all conflict markers and keep only the correct code)
async function login(email: string, password: string) {
// Result combining both changes
const user = await UserRepository.findOne({ email }); // Adopted Repository pattern from main
if (!user) throw new AuthError('USER_NOT_FOUND'); // Kept error class from feature
const isValid = await argon2.verify(user.password, password); // Adopted argon2 from main
if (!isValid) throw new AuthError('INVALID_PASSWORD'); // Kept error code from feature
const token = generateJWT(user, { expiresIn: '7d' }); // Kept expiry setting from feature
return { user, token };
}
# Use external mergetool (vimdiff, meld, etc.)
git config merge.tool vimdiff # Configure tool
git mergetool # Open conflicted files sequentially
git mergetool src/auth/login.ts # Specific file onlyComplete the merge with git add + commit after resolution
After resolving all conflicted files, stage them with git add and create a merge commit with git commit. Briefly recording what conflicts existed and how they were resolved in the commit message is useful for tracing history later. If mergetool was used, .orig backup files may have been created; verify and delete them.
# 1. Stage the resolved files
git add src/auth/login.ts
git add src/api/users.ts
git add src/config/database.ts
# Or stage all resolved conflict files at once
git add -u
# 2. Check if any unresolved files remain
git status
# "Unmerged paths" should be empty
# 3. Create the merge commit
git commit
# When the editor opens, review the default merge message and save
# Or specify the message directly:
git commit -m "Merge main into feature/user-auth
Resolved conflicts:
- auth/login.ts: adopted argon2 from main, kept custom error codes
- api/users.ts: merged both pagination and filtering changes
- config/database.ts: used main's connection pool settings"
# 4. Delete .orig backup files (created when using mergetool)
git clean -f -n # Preview files to be deleted (dry run)
git clean -f # Actually delete
# 5. Verify the merge result
git log --oneline --graph -10
git diff main..feature/user-auth --statHandle rebase conflicts with continue/abort
git rebase reapplies commits one by one, so conflicts occur on a per-commit basis. After resolving each commit's conflicts, proceed to the next commit with git rebase --continue. If resolution is complex or done incorrectly, you can fully revert to the pre-rebase state with git rebase --abort. Always create a backup branch before rebasing for safety.
# Create a backup branch before rebase (safety measure)
git branch backup/feature-user-auth
# Start rebase
git checkout feature/user-auth
git rebase main
# When a conflict occurs:
# CONFLICT (content): Merge conflict in src/auth/login.ts
# error: could not apply abc1234... Add user authentication
# Resolve all conflicts manually, mark them as resolved with
# "git add <pathspec>" then run "git rebase --continue"
# 1. Resolve conflicts
code src/auth/login.ts # Resolve the conflict
# 2. Stage the resolved file
git add src/auth/login.ts
# 3. Proceed to the next commit
git rebase --continue
# If the next commit also has conflicts, repeat steps 1-3
# Check current progress during rebase
git status
# interactive rebase in progress; onto def5678
# Last commands done (3 commands done):
# pick abc1234 Add user authentication
# pick def5678 Add password reset
# Next commands to do (2 remaining commands):
# pick ghi9012 Add email verification
# pick jkl3456 Add OAuth integration
# If something went wrong, cancel the entire rebase (restore original state)
git rebase --abort
# Skip a specific commit (if that change already exists in main)
git rebase --skip
# After rebase completion, verify
git log --oneline -10
# Clean linear history confirmedCore Code
All Git commands needed for merge and rebase conflict resolution. Create a backup branch first, then resolve conflicts safely.
# ========================================
# Git Conflict Resolution Command Checklist
# ========================================
# === Merge conflict resolution ===
git merge main # Attempt merge
git status # Check conflicted files
git diff --name-only --diff-filter=U # List only conflicted files
# Resolve conflicted files (VS Code or editor)
# <<<<<<< HEAD (my code)
# =======
# >>>>>>> main (their code)
# -> Remove markers and keep only the correct code
git add <resolved-files> # Stage resolved files
git commit # Create merge commit
# === Rebase conflict resolution ===
git branch backup/my-branch # Backup first!
git rebase main # Start rebase
# When conflict occurs:
git add <resolved-files> # Stage after resolving
git rebase --continue # Proceed to next commit
git rebase --abort # Cancel entirely (emergency)
# === Conflict inspection/comparison ===
git diff --base <file> # Compare with common ancestor
git diff --ours <file> # View my changes
git diff --theirs <file> # View their changes
git log --merge --oneline # Commits related to conflicts
# === Conflict prevention strategies ===
git pull --rebase origin main # Frequently sync with main
git fetch origin && git rebase origin/main # Can check state before pullingCommon Mistakes
Committing without removing conflict markers (<<<<<<< ======= >>>>>>>)
Leftover conflict markers will break the code and cause syntax errors. Before committing, check the staged content with git diff --staged, and always verify that no markers remain with grep -rn "<<<<<<" .. You can also add a pre-commit hook for automatic detection.
Overwriting a colleague's work with git push --force
If push is rejected after a rebase, use --force-with-lease instead of --force. This option rejects the push if there are new remote commits you don't know about, protecting your colleague's work. On shared branches, it's safer to use merge instead of rebase.
Unnecessary conflicts due to not running git pull before merging
While working on a feature branch, regularly (daily or every 2-3 days) pull changes from the main branch to stay synchronized. Habitually running git fetch origin && git rebase origin/main reduces the scope of conflicts and makes them easier to resolve.
Performing other operations without aborting during a rebase in progress
During a rebase, Git is in a "REBASING" state. Running other branch commands in this state can corrupt the working tree. Always complete or cancel the rebase with git rebase --continue or git rebase --abort before doing anything else.