Learning Objectives

By the end of this module, you will be able to:

  1. Explain the difference between fast-forward merges and three-way merges
  2. Read and manually resolve conflict markers in files
  3. Use git merge --abort to safely back out of a failed merge
  4. Configure and use visual merge tools (VS Code, vimdiff, etc.)
  5. Understand merge strategies and when each applies

1. What Is a Merge?

A merge brings the work from one branch into another. You're on branch A and you say "bring in the changes from branch B." Git examines the two branches, finds their common ancestor (the commit where they diverged), and combines the changes.

git switch main
git merge feature-login

This means: "Take the changes on feature-login and integrate them into main."

Git decides how to merge based on the shape of the commit graph. There are two fundamentally different cases.


2. Fast-Forward Merges

A fast-forward merge happens when the branch you're merging into hasn't diverged — there's a straight line from it to the branch you're merging.

Before

    ○ ◄── ○ ◄── ○ ◄── ○ ◄── ○
    C1     C2     C3     C4     C5
                   ▲              ▲
                  main          feature
                   ▲
                  HEAD

main is at C3. feature is at C5. There are no commits on main after C3 — feature is simply "ahead." The history is a straight line.

The Merge

git switch main
git merge feature
# Updating abc1234..def5678
# Fast-forward
#  login.py | 45 +++++++++++++++++++++++
#  1 file changed, 45 insertions(+)

Git says "Fast-forward" — it just moved the main pointer to where feature points. No new commit was created.

After

    ○ ◄── ○ ◄── ○ ◄── ○ ◄── ○
    C1     C2     C3     C4     C5
                                  ▲
                           main, feature
                                  ▲
                                 HEAD

The pointer moved. That's it.

When Fast-Forward Happens

Only when the target branch has no commits that the source branch doesn't have — meaning the source is a direct descendant. There can never be conflicts in a fast-forward merge because there's nothing to reconcile.

Why You Might NOT Want Fast-Forward

Fast-forward merges erase the visual record that a branch existed. In a team setting, you often want to preserve the fact that a set of commits came from a feature branch. Use --no-ff to force a merge commit:

git merge --no-ff feature -m "Merge feature-login into main"

Before (same starting point)

    ○ ◄── ○ ◄── ○ ◄── ○ ◄── ○
    C1     C2     C3     C4     C5
                   ▲              ▲
                  main          feature

After --no-ff

    ○ ◄── ○ ◄── ○ ◄── ○ ◄── ○
    C1     C2     C3     C4     C5
                    ╲              ╱
                     ○ ◄──────── ○    ← merge commit C6
                                  ▲
                                 main
                                  ▲
                                 HEAD

Wait — that diagram is misleading for --no-ff with a straight line. Let me show it properly:

                  ○ ◄── ○          feature
                 C4     C5
                ╱         ╲
    ○ ◄── ○ ◄── ○          ○      ← merge commit C6 (--no-ff)
    C1     C2     C3         ▲
                            main

Even though Git could have fast-forwarded, --no-ff created a merge commit with two parents. Now git log --graph will show that C4 and C5 came from a side branch.

Convention: Many teams and platforms (GitHub, GitLab) use --no-ff for all PR/MR merges. GitHub's "Merge pull request" button creates a --no-ff merge by default. This preserves branch history in the graph.


3. Three-Way Merges (True Merges)

When both branches have diverged — each has commits the other doesn't — Git must perform a three-way merge. This is the "real" merge.

The Setup

                  ○ ◄── ○          feature
                 C4     C5
                ╱
    ○ ◄── ○ ◄── ○
    C1     C2     C3 ◄── ○          main ← HEAD
                          C6

main has C6. feature has C4 and C5. They diverged after C3. Git can't just move a pointer — it needs to combine the changes.

The Three-Way Comparison

Git uses three inputs to compute the merge:

        Common Ancestor (C3)
              ╱         ╲
         Ours (C6)    Theirs (C5)
         (main)       (feature)
FileAncestor (C3)Ours (main/C6)Theirs (feature/C5)Result
README.mdv1v1v1v1 (no change)
app.pyv1v2v1v2 (only we changed it)
login.pyNEWNEW (only they added it)
config.pyv1v2v3CONFLICT (both changed it)

The rules:

  • If only one side changed a file → take that change (auto-resolved)
  • If neither side changed a file → keep as-is
  • If both sides changed a file in different places → Git can often auto-merge the changes
  • If both sides changed a file in the same placeconflict — human must decide

The Merge Commit

After resolution, Git creates a merge commit with two parents:

                  ○ ◄── ○
                 C4     C5
                ╱         ╲
    ○ ◄── ○ ◄── ○          ○    ← merge commit C7 (two parents)
    C1     C2     C3 ◄── ○  ▲
                          C6 main

The merge commit's tree is the resolved result. Its first parent is C6 (the branch you were on), its second parent is C5 (the branch you merged).


4. Merge Conflicts

A conflict occurs when both branches modified the same lines of the same file and Git can't determine which version should win.

What Triggers a Conflict

Ancestor (line 5):    result = calculate(x)
Ours (main):          result = calculate(x, precision=2)
Theirs (feature):     result = compute(x)

Both sides changed line 5. Git doesn't know which change is correct, so it stops and asks you.

What Does NOT Trigger a Conflict

Ancestor:          Ours:              Theirs:
line 1: aaa        line 1: AAA        line 1: aaa
line 2: bbb        line 2: bbb        line 2: bbb
line 3: ccc        line 3: ccc        line 3: CCC

Ours changed line 1. Theirs changed line 3. Git auto-merges this — no conflict.

Conflict Markers

When Git encounters a conflict, it writes special markers directly into the file:

Some clean code above the conflict...

<<<<<<< HEAD
result = calculate(x, precision=2)
=======
result = compute(x)
>>>>>>> feature

Some clean code below the conflict...
MarkerMeaning
<<<<<<< HEADStart of YOUR version (the branch you're on)
=======Separator between the two versions
>>>>>>> featureEnd of THEIR version (the branch being merged)

Everything between <<<<<<< and ======= is your current branch's version. Everything between ======= and >>>>>>> is the incoming branch's version.

Resolving a Conflict

You must:

  1. Open the file and find the conflict markers
  2. Decide which version to keep (yours, theirs, both, or a manual combination)
  3. Remove all conflict markers (<<<<<<<, =======, >>>>>>>)
  4. Stage the resolved file with git add
  5. Commit to finalize the merge

After editing, the file should contain only the final correct code — no markers.

# Resolved: kept the function name from feature, added precision from main
result = compute(x, precision=2)

Then:

git add config.py
git commit -m "Merge feature into main"

If Git opens your editor for the merge commit message, it pre-fills a default message like Merge branch 'feature' into main. You can accept it or customize it.


5. Aborting a Merge

If you start a merge and things go wrong — or you just want to back out — you can abort cleanly:

git merge --abort

This resets your working directory, staging area, and HEAD to the state before the merge began. No changes are lost (your branches remain as they were).

Important: git merge --abort works cleanly when your working directory was clean before starting the merge. If you had uncommitted changes, the abort may not restore them perfectly. Always commit or stash before merging.

Checking if You're Mid-Merge

git status
# On branch main
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#   (use "git merge --abort" to abort the merge)

The .git/MERGE_HEAD file also exists during a merge — its presence tells Git (and your tools) that a merge is in progress.


6. Resolving Conflicts with Visual Tools

While you can resolve conflicts in any text editor by editing the markers manually, visual tools make it much easier — especially with complex conflicts spanning many lines.

VS Code (Built-In)

VS Code detects conflict markers and adds clickable buttons above each conflict:

<<<<<<< HEAD
  Accept Current Change | Accept Incoming Change | Accept Both Changes | Compare Changes
result = calculate(x, precision=2)
=======
result = compute(x)
>>>>>>> feature
  • Accept Current Change — keep your version (HEAD)
  • Accept Incoming Change — keep their version (feature)
  • Accept Both Changes — keep both (stacked, yours on top)
  • Compare Changes — open a side-by-side diff

After clicking, VS Code removes the markers and applies your choice. You still need to git add and git commit.

VS Code Merge Editor (3-Way View)

VS Code also has a dedicated merge editor. When you have conflicts:

  1. Open the Source Control panel (Ctrl+Shift+G)
  2. Under "Merge Changes," click on a conflicted file
  3. Click "Open in Merge Editor"

This opens a three-panel view:

┌─────────────────┬─────────────────┐
│  Incoming       │  Current        │
│  (theirs)       │  (yours/HEAD)   │
├─────────────────┴─────────────────┤
│            Result                  │
│  (what will be committed)         │
└───────────────────────────────────┘

You can check/uncheck changes from each side, and the result updates in real-time.

git mergetool (Command-Line Interface to External Tools)

Git can launch an external merge tool for each conflicted file:

git mergetool

Git will open each conflicted file in the configured tool one by one.

Configuring a Merge Tool

# VS Code
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
 
# vimdiff (built-in)
git config --global merge.tool vimdiff
 
# meld (Linux — graphical)
git config --global merge.tool meld
 
# kdiff3
git config --global merge.tool kdiff3
 
# Beyond Compare
git config --global merge.tool bc
git config --global mergetool.bc.path '/usr/local/bin/bcomp'

Preventing .orig Backup Files

By default, git mergetool creates .orig backup files. To disable:

git config --global mergetool.keepBackup false

vimdiff Layout

When using vimdiff, the default layout shows three windows:

┌──────────────┬──────────────┬──────────────┐
│   LOCAL      │   BASE       │   REMOTE     │
│   (yours)    │   (ancestor) │   (theirs)   │
├──────────────┴──────────────┴──────────────┤
│                  MERGED                      │
│           (result you'll commit)             │
└──────────────────────────────────────────────┘

Navigate between windows with Ctrl-W + arrow keys. Edit the MERGED buffer. Save and quit all with :wqa.


7. Merge Strategies

Git uses different algorithms depending on the situation. You rarely need to specify these explicitly, but understanding them helps when things go wrong.

recursive (default for two-branch merges)

The standard three-way merge. Handles renames, can deal with criss-cross merge histories (multiple common ancestors) by recursively merging the ancestors first.

git merge feature                          # uses recursive by default
git merge -s recursive feature             # explicit
git merge -s recursive -X patience feature # with strategy option

Strategy options for recursive:

OptionEffect
-X oursOn conflict, auto-pick our version
-X theirsOn conflict, auto-pick their version
-X patienceUse patience diff algorithm (better for moved blocks)
-X ignore-space-changeIgnore whitespace conflicts

Note: -X ours and -X theirs only affect conflicts. Non-conflicting changes from both sides are still merged normally. This is different from -s ours (see below).

ort (default in Git 2.34+)

A rewrite of recursive that is faster and handles edge cases better. It's the new default and behaves the same as recursive in most cases. You don't need to do anything different.

ours (strategy, not option)

Takes everything from our side and ignores the other branch entirely. The merge commit is created (preserving the graph history) but no changes from the other branch are incorporated.

git merge -s ours obsolete-branch

Use case: recording that you intentionally discarded another branch's changes.

Warning: -s ours (strategy) ignores everything from the other branch. -X ours (strategy option) only resolves conflicts in your favor but still merges non-conflicting changes. They are very different.

octopus

Merges more than two branches at once. Used internally by Git when you specify multiple branches:

git merge feature-a feature-b feature-c

Octopus merges can't handle conflicts — they abort if any conflict is detected. Useful for bringing together several non-conflicting feature branches simultaneously.


8. Common Merge Scenarios

Scenario 1: Clean Merge (No Conflicts)

git switch main
git merge feature
# Auto-merging app.py
# Merge made by the 'ort' strategy.
#  app.py | 10 ++++++++++
#  1 file changed, 10 insertions(+)

Git auto-resolved everything. A merge commit was created. Done.

Scenario 2: Conflicts in One File

git switch main
git merge feature
# Auto-merging app.py
# CONFLICT (content): Merge conflict in app.py
# Automatic merge failed; fix conflicts and then commit the result.

Fix the conflict in app.py, then:

git add app.py
git commit

Scenario 3: Conflicts in Multiple Files

git merge feature
# CONFLICT (content): Merge conflict in app.py
# CONFLICT (content): Merge conflict in config.py
# CONFLICT (content): Merge conflict in tests.py

Resolve each file, stage each one, then commit once:

# Resolve all files...
git add app.py config.py tests.py
git commit -m "Merge feature: resolve 3 conflicts"

Or resolve one at a time with git mergetool, which iterates through conflicted files.

Scenario 4: Merge with Auto-Resolve Using Theirs

When you know the other branch is correct for all conflicts:

git merge -X theirs feature

Conflicts are auto-resolved in favor of the incoming branch. Non-conflicting changes from both sides are still merged.


Command Reference

CommandDescription
git merge <branch>Merge a branch into the current branch
git merge --no-ff <branch>Force a merge commit even if fast-forward is possible
git merge --ff-only <branch>Only merge if fast-forward is possible; abort otherwise
git merge --abortAbort a merge in progress, return to pre-merge state
git merge --continueContinue merge after resolving conflicts (same as git commit)
git merge -X ours <branch>Auto-resolve conflicts in favor of current branch
git merge -X theirs <branch>Auto-resolve conflicts in favor of incoming branch
git merge -s ours <branch>Merge but discard all changes from the other branch
git merge --no-commit <branch>Merge but don't auto-commit (lets you inspect the result)
git merge --squash <branch>Squash all commits into a single change set (no merge commit)
git mergetoolOpen configured merge tool for each conflicted file
git log --mergeShow commits that caused conflicts
git diffDuring merge: show combined diff of conflicted files
git merge-base main featureFind the common ancestor of two branches

Hands-On Lab: Merging and Conflict Resolution

This lab creates merge conflicts deliberately, then resolves them using multiple techniques.

Setup

mkdir ~/git-merge-lab
cd ~/git-merge-lab
git init

Create a base file:

cat > app.py << 'EOF'
# Calculator App v1.0
 
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
def main():
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
 
if __name__ == "__main__":
    main()
EOF
 
git add app.py
git commit -m "Base version: add and subtract"

Checkpoint:

git log --oneline
# abc1234 Base version: add and subtract

Step 1: Fast-Forward Merge

Create a branch and add a feature:

git switch -c feature/multiply
 
cat > app.py << 'EOF'
# Calculator App v1.0
 
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
def multiply(a, b):
    return a * b
 
def main():
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
    print(f"4 * 3 = {multiply(4, 3)}")
 
if __name__ == "__main__":
    main()
EOF
 
git add app.py
git commit -m "Add multiply function"

Merge back into main:

git switch main
git merge feature/multiply

Checkpoint:

git log --oneline
# def5678 Add multiply function    ← no merge commit, just pointer move
# abc1234 Base version: add and subtract
 
# Check for merge commit? There isn't one:
git cat-file -p HEAD
# Only one parent line — this is NOT a merge commit

The message says "Fast-forward." No merge commit was created.

Step 2: --no-ff Merge

Create another feature:

git switch -c feature/divide
 
cat >> app.py << 'EOF'
 
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
EOF
 
# Also add to main():
# (We'll use sed-like approach by rewriting the file)
cat > app.py << 'EOF'
# Calculator App v1.0
 
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
def multiply(a, b):
    return a * b
 
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
 
def main():
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
    print(f"4 * 3 = {multiply(4, 3)}")
    print(f"10 / 3 = {divide(10, 3):.2f}")
 
if __name__ == "__main__":
    main()
EOF
 
git add app.py
git commit -m "Add divide function with zero-division guard"

Now merge with --no-ff:

git switch main
git merge --no-ff feature/divide -m "Merge feature/divide into main"

Checkpoint:

git log --oneline --graph
# *   111aaa Merge feature/divide into main
# |\
# | * 222bbb Add divide function with zero-division guard
# |/
# * def5678 Add multiply function
# * abc1234 Base version: add and subtract
 
git cat-file -p HEAD
# TWO parent lines — this IS a merge commit

The graph shows the branch and merge structure.

Step 3: Create a Real Conflict

Now we'll create a situation where both branches modify the same lines.

# Create the conflicting branch
git switch -c feature/power
 
# Modify the comment and main() on the feature branch
cat > app.py << 'EOF'
# Calculator App v2.0 - Power Edition
 
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
def multiply(a, b):
    return a * b
 
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
 
def power(a, b):
    return a ** b
 
def main():
    print("=== Power Calculator ===")
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
    print(f"4 * 3 = {multiply(4, 3)}")
    print(f"10 / 3 = {divide(10, 3):.2f}")
    print(f"2 ^ 8 = {power(2, 8)}")
 
if __name__ == "__main__":
    main()
EOF
 
git add app.py
git commit -m "Add power function, update header"

Now go back to main and make a conflicting change:

git switch main
 
# Modify the SAME lines (comment and main()) differently
cat > app.py << 'EOF'
# Calculator App v2.0 - Scientific Edition
 
def add(a, b):
    return a + b
 
def subtract(a, b):
    return a - b
 
def multiply(a, b):
    return a * b
 
def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b
 
def modulo(a, b):
    return a % b
 
def main():
    print("=== Scientific Calculator ===")
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
    print(f"4 * 3 = {multiply(4, 3)}")
    print(f"10 / 3 = {divide(10, 3):.2f}")
    print(f"10 % 3 = {modulo(10, 3)}")
 
if __name__ == "__main__":
    main()
EOF
 
git add app.py
git commit -m "Add modulo function, update header"

Checkpoint:

git log --oneline --graph --all
# * 444ddd (HEAD -> main) Add modulo function, update header
# | * 333ccc (feature/power) Add power function, update header
# |/
# *   111aaa Merge feature/divide into main
# ...

The branches have diverged. Both changed the header comment and the main() function.

Step 4: Attempt the Merge

git merge feature/power

Output:

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

Checkpoint:

git status
# On branch main
# You have unmerged paths.
#   (fix conflicts and run "git commit")
#   (use "git merge --abort" to abort the merge)
#
# Unmerged paths:
#   both modified:   app.py

Step 5: Read the Conflict Markers

cat app.py

Look for sections like:

<<<<<<< HEAD
# Calculator App v2.0 - Scientific Edition
=======
# Calculator App v2.0 - Power Edition
>>>>>>> feature/power

There will be multiple conflict blocks — one for the header, one for the function definitions area, and one for the main() function.

Step 6: Resolve Manually

Open app.py in your editor. For each conflict:

  1. Decide what the final version should be
  2. Remove all <<<<<<<, =======, and >>>>>>> markers
  3. Write the correct code

For example, combine both features:

# Calculator App v2.0 - Full Edition
 
# ... (keep all functions: add, subtract, multiply, divide, power, modulo) ...
 
def main():
    print("=== Full Calculator ===")
    print(f"2 + 3 = {add(2, 3)}")
    print(f"5 - 2 = {subtract(5, 2)}")
    print(f"4 * 3 = {multiply(4, 3)}")
    print(f"10 / 3 = {divide(10, 3):.2f}")
    print(f"2 ^ 8 = {power(2, 8)}")
    print(f"10 % 3 = {modulo(10, 3)}")

Step 7: Stage and Commit

git add app.py
git commit -m "Merge feature/power: combine power and modulo functions"

Checkpoint:

git log --oneline --graph --all
# *   555eee (HEAD -> main) Merge feature/power: combine power and modulo functions
# |\
# | * 333ccc (feature/power) Add power function, update header
# * | 444ddd Add modulo function, update header
# |/
# *   111aaa Merge feature/divide into main
# ...

Step 8: Practice Aborting

Let's redo the conflict to practice aborting. First, undo the merge:

git reset --hard HEAD~1    # go back to before the merge

Now merge again:

git merge feature/power
# CONFLICT...
 
git status
# You have unmerged paths...
 
git merge --abort
git status
# On branch main — clean, as if nothing happened

Step 9: Merge with -X theirs

When you know the incoming branch should win all conflicts:

git merge -X theirs feature/power

Git auto-resolves every conflict in favor of feature/power. Check the result:

cat app.py | head -1
# # Calculator App v2.0 - Power Edition   ← theirs won
 
git log --oneline --graph -5
# Merge commit created automatically

Challenge

  1. Reset back to before the merge (git reset --hard HEAD~1). Merge again without -X, and this time use git mergetool to resolve. If you have VS Code configured, use: git config merge.tool vscode && git config mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'

  2. Create a scenario with three branches diverging from the same point, each modifying the same file. Merge them one at a time into main and resolve the cascading conflicts.

  3. Try git merge --no-commit feature/power to merge without auto-committing. Inspect the result with git diff --staged, then decide whether to git commit or git merge --abort.

Cleanup

rm -rf ~/git-merge-lab

Common Pitfalls & Troubleshooting

PitfallExplanation
Conflict markers left in committed codeAlways search for <<<<<<< in your files before committing a merge. Accidentally committing conflict markers is a common mistake. Some teams add a pre-commit hook to check for this.
Merging with a dirty working directoryIf your working directory has uncommitted changes when you start a merge, aborting may not fully restore. Always commit or stash before merging.
Confusing -X ours with -s ours-X ours (strategy option) resolves only conflicts in your favor, still merging non-conflicting changes. -s ours (strategy) discards the other branch's changes entirely.
"Already up to date"The branch you're merging has already been merged (or is an ancestor). There's nothing new to bring in.
Fear of merge conflictsConflicts are normal and expected. They just mean two people edited the same lines. The resolution is always the same: read both versions, pick the right one, remove markers, stage, commit.
Forgetting to git add after resolvingEditing the file removes markers, but Git still considers it unmerged until you git add it. Staging marks the conflict as resolved.
.orig files cluttering the repogit mergetool creates backup .orig files. Add *.orig to .gitignore or disable with git config --global mergetool.keepBackup false.

Pro Tips

  1. Always check git status during a merge. It tells you which files are conflicted, which are resolved, and reminds you of your options (--abort, --continue).

  2. Preview before merging. See what will happen before you commit to it:

    git log main..feature --oneline   # commits that will be merged
    git diff main...feature           # changes that will be merged (three-dot)
    git merge --no-commit feature     # merge without committing — inspect, then abort or commit
  3. Use git merge-base to find the common ancestor.

    git merge-base main feature
    # Returns the commit hash where the branches diverged
    git show $(git merge-base main feature)
  4. Small, frequent merges beat large, infrequent ones. The more two branches diverge, the worse the conflicts. Merge (or rebase) regularly.

  5. After a conflict, verify the result works. Run tests, compile, or at least read through the merged file. Git's auto-merge is textual, not semantic — it can combine two changes that individually make sense but together introduce a bug.

  6. Configure merge.conflictStyle for more context.

    git config --global merge.conflictStyle diff3

    This adds a third section showing the common ancestor version:

    <<<<<<< HEAD
    result = calculate(x, precision=2)
    ||||||| merged common ancestors
    result = calculate(x)
    =======
    result = compute(x)
    >>>>>>> feature
    

    The middle section shows what the line looked like before either branch changed it. This is invaluable for understanding what each side intended.


Quiz / Self-Assessment

1. What is a fast-forward merge?

Answer
A merge where the target branch has no commits that the source branch doesn't have — the source is a direct descendant. Git simply moves the branch pointer forward. No merge commit is created unless --no-ff is used.

2. What triggers a merge conflict?

Answer
When both branches modify the same lines of the same file, and Git's auto-merge algorithm can't determine which version is correct. If changes are in different parts of the file, Git usually resolves them automatically.

3. What are the three inputs Git uses for a three-way merge?

Answer
The common ancestor (merge base), the current branch's version (ours/HEAD), and the incoming branch's version (theirs). Git compares both sides against the ancestor to determine what changed and how to combine.

4. How do you resolve a merge conflict?

Answer
1) Open the conflicted file. 2) Find the conflict markers (<<<<<<<, =======, >>>>>>>). 3) Edit the file to contain the correct final version. 4) Remove all conflict markers. 5) Stage the file with git add. 6) Commit with git commit.

5. What does git merge --abort do?

Answer
It cancels a merge in progress and returns the working directory, staging area, and HEAD to the state before the merge began. The branches are unchanged.

6. What's the difference between --no-ff and a regular merge?

Answer
A regular merge fast-forwards when possible (just moves the pointer, no merge commit). --no-ff forces a merge commit even when fast-forward is possible, preserving the visual record of the branch in the commit graph.

7. What does -X theirs do?

Answer
It's a merge strategy option that automatically resolves conflicts in favor of the incoming branch's version. Non-conflicting changes from both sides are still merged normally. Only conflicts are auto-resolved.

8. How is -X ours different from -s ours?

Answer
-X ours (strategy option) resolves only conflicts in your favor — non-conflicting changes from both sides still merge. -s ours (strategy) discards ALL changes from the other branch — the merge commit's tree is identical to HEAD.

9. What does merge.conflictStyle diff3 show you?

Answer
In addition to the normal two-way conflict markers (HEAD and incoming), it shows a third section with the common ancestor's version. This helps you understand what each branch changed relative to the original.

10. Can a fast-forward merge ever have conflicts?

Answer
No. A fast-forward merge only occurs when the target branch has no divergent commits — the source branch is a direct descendant. Since there's nothing to reconcile, conflicts are impossible.