Back to CI/CD Pipeline
Q29Question 29 of 56

Resolving merge conflicts and undoing a bad commit already pushed to a shared branch (revert vs reset + force-push)

Key Concepts
  • A merge conflict occurs when two branches changed the same lines differently; Git pauses and marks the file with conflict markers
  • Resolve by understanding both changes, editing to the correct content, removing markers, staging, and completing the merge
  • git revert creates a new commit that inverts the bad change, preserving history; safe for shared branches
  • git reset moves the branch pointer backward and rewrites history; only safe for private, unshared branches
  • Never reset + force-push a shared branch: it causes divergence, conflicts, and can silently lose teammates' work
  • Use --force-with-lease instead of --force to prevent clobbering a teammate's push, and disable force-push on protected branches

Definition

Resolve conflicts by choosing or combining the clashing lines; undo a pushed commit with git revert (adds an inverting commit), never with reset + force-push on shared branches because that rewrites history others already have.
  • A merge conflict occurs when Git cannot reconcile two branches because the same lines were changed differently on each side
  • Resolution means editing the conflicted file to the correct final content, removing markers, staging, and completing the merge
  • git revert creates a new commit that inverts the bad change, preserving history and safe for shared branches
  • git reset moves the branch pointer backward, rewriting history; only safe for private, unshared branches
  • Never force-push a shared branch because it causes divergence, conflicts, and can lose teammates' work

A merge conflict is like two tellers at the National Bank of India both editing the same line of an account's instructions: one writes "limit 50000" and the other writes "limit 75000." The system stops and asks a human to decide. Undoing a shared mistake is the important part: if a wrong entry was already copied into every branch's ledger, you do not secretly erase it because chaos follows. Instead you make a new correcting entry that cancels the mistake: everyone stays in sync.

Understanding the Concepts

How do merge conflicts happen?

A conflict happens when two branches changed the same region of a file in incompatible ways. Git pauses and marks the file with conflict markers.

TEXT
# Conflict markers in the file:
<<<<<<< HEAD
private static final BigDecimal TRANSFER_FEE = new BigDecimal("25.00");
=======
private static final BigDecimal TRANSFER_FEE = new BigDecimal("30.00");
>>>>>>> feature/new-fee-structure

# Between <<<<<<< and ======= is YOUR branch's version
# Between ======= and >>>>>>> is the INCOMING branch's version

How do you resolve a merge conflict?

Resolve by understanding both changes, editing to the correct content, removing markers, staging, and completing.

BASH
# Step 1: Attempt the merge (conflict occurs)
git merge feature/new-fee-structure
# CONFLICT (content): Merge conflict in TransferService.java

# Step 2: Open the file and resolve
# Choose one side, the other, or combine them
# Remove the <<<<<<<, =======, and >>>>>>> markers

# Step 3: Stage the resolved file
git add src/main/java/com/bank/service/TransferService.java

# Step 4: Complete the merge
git commit

# Step 5: Run tests! A syntactically clean resolution can be logically wrong
mvn test

# If the merge goes badly, abort and start over:
git merge --abort
JAVA
// Resolved file (combining both changes):
private static final BigDecimal TRANSFER_FEE = new BigDecimal("30.00");
// Chose the new fee structure, confirmed with the product team

How do you undo a commit on a private branch?

If the bad commit is only in your local, unshared branch, you can rewrite history freely with git reset.

BASH
# Soft reset: undo the commit, keep changes staged
git reset --soft HEAD~1

# Mixed reset (default): undo the commit, keep changes unstaged
git reset HEAD~1

# Hard reset: undo the commit, discard all changes
git reset --hard HEAD~1
# WARNING: hard reset loses uncommitted work

How do you undo a commit on a shared branch?

The moment a commit is pushed to a shared branch, use git revert. It creates a new commit that applies the exact inverse.

BASH
# Revert a single bad commit
git revert abc1234
# Opens editor with: Revert "Add broken fee calculation"
# Creates a NEW commit that undoes abc1234

# Revert a merge commit (specify which parent to keep)
git revert -m 1 abc1234
# -m 1 means keep the first parent (usually main)

# Everyone can pull normally -- no divergence
git push origin develop

How is revert different from reset? (comparison table)

| Aspect | git revert | git reset + force-push |
|---|---|---|
| History | Preserved (new inverting commit) | Rewritten (commit removed) |
| Safe for shared branches | Yes | No |
| Teammates affected | No divergence | Divergence for everyone |
| Audit trail | Shows mistake and correction | Mistake hidden |
| Compliance | Meets audit requirements | May violate audit rules |
| Use when | Commit is on a shared branch | Commit is on a private branch |

Why is reset + force-push dangerous on shared branches?

When you reset and force-push, you rewrite history that other clones depend on. Every teammate who already pulled the old commits is now divergent.

TEXT
# Timeline of disaster:
# 1. You push commit X to shared develop
# 2. Teammates Rajesh and Priya pull, their clones have X
# 3. You reset to before X and force-push
# 4. Remote develop no longer has X
# 5. Rajesh's next pull: conflict or auto-merge that re-introduces X
# 6. Priya pushes her work: X comes back from her clone
# 7. Work can be silently lost

# The "deleted" commit keeps coming back because
# other clones still have it
BASH
# WRONG: Reset and force-push a shared branch
git reset --hard HEAD~1
git push --force origin develop
# Every teammate is now divergent!

# CORRECT: Revert adds an inverting commit
git revert abc1234
git push origin develop
# Everyone pulls normally, no divergence

When is force-push acceptable?

Force-push is acceptable only on genuinely private branches, and even then use --force-with-lease.

BASH
# Safe: force-push your own feature branch
# --force-with-lease refuses if someone else pushed
git push --force-with-lease origin feature/my-branch

# Protected branches should disable force-push entirely
# GitHub: Settings > Branches > Branch protection rules
# [x] Do not allow force pushes

What are common mistakes?

The most common mistake is resolving a conflict syntactically but not logically: the code compiles but is wrong.

JAVA
// WRONG: Syntactically resolved but logically broken
// Branch A added: validateBalance(fromAccount, amount);
// Branch B added: applyFee(amount);
// Resolved as:
validateBalance(fromAccount, amount);
applyFee(amount);
// But the fee should be applied BEFORE validation!
// Always run tests after resolving conflicts.

What should a senior engineer know?

The guiding principle is: rewrite private history freely, but on shared history only ever move forward with new commits. Protect main and release branches with server-side rules that disable force-push entirely.

What are common interview follow-up questions?

  • Can you revert a revert? Yes, git revert re-applies the original change.
  • What is --force-with-lease? It refuses to force-push if someone else has pushed since your last fetch.
  • How do you prevent conflicts? Keep branches short-lived, merge frequently, communicate about shared files.

Visual Overview

DIAGRAM
  Merge Conflict Resolution
  ┌──────────────┐     ┌──────────────┐     ┌──────────────┐
  │ Both branches│────▶│ Git pauses   │────▶│ Human edits  │
  │ changed same │     │ marks file   │     │ correct      │
  │ lines        │     │ with <<<<    │     │ content      │
  └──────────────┘     └──────────────┘     └──────┬───────┘
                                                    │
                                                    ▼
                                            ┌──────────────┐
                                            │ git add +    │
                                            │ git commit + │
                                            │ run tests    │
                                            └──────────────┘

  Revert vs Reset
  ┌─────────────────────────────────────────┐
  │  Shared branch: use REVERT              │
  │  A ── B ── bad ── revert(bad)           │
  │  History preserved, everyone in sync    │
  ├─────────────────────────────────────────┤
  │  Private branch: use RESET              │
  │  A ── B ── bad  →  A ── B              │
  │  History rewritten, nobody affected     │
  └─────────────────────────────────────────┘

Key Takeaway

A merge conflict happens when two branches changed the same region of a file in incompatible ways, so Git pauses and marks the file with conflict markers. You resolve by understanding both changes, editing to the correct content, removing the markers, staging, and completing the merge. Always run tests afterward because a resolution that compiles can still be logically wrong.

Undoing commits depends entirely on whether the commit is shared. On a private branch, reset freely. On a shared branch that teammates have pulled, never reset and force-push because it rewrites history that other clones depend on, causing divergence, conflicts, and silently lost work. The correct tool for shared history is git revert, which creates a new commit that applies the exact inverse, preserving the full audit trail.

In the National Bank of India project, a teammate pushed a commit to shared develop that broke transfer fee rounding for Rajesh and Priya, and four team members had already pulled it. Resetting would have broken everyone's clones, so the team used git revert to add an inverting commit that everyone pulled cleanly, preserving the full history showing both the mistake and its correction.

Remember one sentence: rewrite private history freely, but on shared history only ever move forward with new commits.

---

Share
Use ← → arrow keys to navigate