Foundations: Python, Git and toolsLoading progress…

Conflicts

Trace commits and branches, choose an appropriate undo operation, and resolve a small merge conflict.

What a conflict is

A conflict is not an error in Git; it is a question Git cannot answer. Two branches changed the same lines in different ways, and only a person knows which result is intended. Git merges everything it can, leaves the file with conflict markers, and pauses the merge.

Step 1 of 4

Git stops and writes both sides into the file

$ git merge legal

Refund policy
<<<<<<< HEAD
Refunds are accepted within 30 days.
=======
Refunds are accepted within 7 days for sale items.
>>>>>>> legal
Contact: [email protected]

Between <<<<<<< and ======= is what the branch you are on (HEAD) has. Between ======= and >>>>>>> is what the other branch has. Everything else merged cleanly.

Walk through a whole conflict: markers, decision, staging, commit.
<<<<<<< HEAD
what the branch you are on has
=======
what the other branch has
>>>>>>> legal

Resolving is editing plus telling Git

  1. git status lists Unmerged paths ("both modified").
  2. Open each file and edit it into the final content. Remove all three marker lines.
  3. git add <file> marks it resolved. git commit finishes the merge.
  4. Or give up cleanly with git merge --abort.

Removing the markers is necessary, not sufficient. Try five resolutions of the same conflict:

Policy file: main says 30 days, legal says 7 days for sale items. Pick a resolution

Which results are acceptable?

Predict first. You delete just the three marker lines and keep everything else. Is the merge correct?

Do

Search for <<<<<<< before staging, then run the program or its tests.

Don't

Stage every conflicted file blindly just to make git status go quiet.

Chapter context

Git records snapshots in a graph of commits. Branch names are movable references to commits. Treat the working tree, staging area and recorded history as distinct states, and many apparently mysterious commands become predictable.

Chapter reference: math, worked example & code

This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.

Math and notation

Model history as a directed acyclic graph. A commit is reachable from a branch when following parent edges reaches it. A merge preserves both ancestry paths; rebasing creates a different path with new commit identities.

Worked example

Let B have parent A, C have parent B, and D have parent B. A merge M with parents C and D makes both C and D reachable. Moving a branch from C to M is different from copying D's files over C: the graph also records why those histories meet.

Reference code

The following example uses the Python standard library and does not call a remote service.

parents = {'A': [], 'B': ['A'], 'C': ['B'], 'D': ['B'], 'M': ['C', 'D']}
def ancestors(commit):
    seen, todo = set(), [commit]
    while todo:
        node = todo.pop()
        if node not in seen:
            seen.add(node)
            todo.extend(parents[node])
    return seen
assert ancestors('M') == {'A', 'B', 'C', 'D', 'M'}

Python examples are provided for study and local execution; this page does not execute Python.

Common mistakes

Git commit does not automatically upload data. Rebase changes commit identities. Reset modes have different effects on uncommitted work.