Merging is the process of combining the changes from one branch into another. Git’s merge algorithm Is one of its most sophisticated features — it can automatically resolve many cases where both Branches have modified different files or different parts of the same file.
A merge takes two (or more) commit pointers — branch tips — and produces a new merge Commit that has both as parents:
gitGraph
commit id: "C (base)"
checkout main
commit id: "D (ours)"
checkout feature-auth
commit id: "E (theirs)"
checkout main
merge feature-auth id: "F (merge commit)" The merge commit F has two parents: D (main’s previous position) and E (feature-auth’s tip). Its tree is the result of combining the changes from both branches.
Git uses the three-way merge algorithm, which requires three snapshots:
Base (common ancestor): The most recent commit that is an ancestor of both branches.Ours : The tip of the branch you are merging into (the current branch).Theirs : The tip of the branch you are merging from.flowchart TD
BASE["Base commit<br/>(common ancestor)"]
OURS["Ours<br/>(current branch)"]
THEIRS["Theirs<br/>(branch to merge)"]
RESULT["Merge result"]
BASE -->|"What changed?"| OURS
BASE -->|"What changed?"| THEIRS
OURS -->|"Our changes"| RESULT
THEIRS -->|"Their changes"| RESULT
style BASE fill:#fff3e0
style OURS fill:#e8f5e9
style THEIRS fill:#e3f2fd
style RESULT fill:#f3e5f5 The algorithm works file by file, hunk by hunk :
Base Ours Theirs Result Explanation xxxxNo change — keep as-is xyxyOnly we changed — take ours xxyyOnly they changed — take theirs xyzConflict Both changed differently — manual resolution required
The critical case is the last row: when both branches modify the same region of the same file. This Is a merge conflict .
Git finds the base commit by computing the lowest common ancestor (LCA) of the two branch tips In the commit DAG. This is not trivial when the history contains multiple merge bases (criss-cross Merges):
gitGraph
commit id: "A"
branch left
checkout left
commit id: "B"
checkout main
commit id: "C"
merge left id: "D"
checkout left
merge main id: "E"
commit id: "F"
checkout main
commit id: "G" In criss-cross situations like this, there are two possible merge bases (B and C). Git’s Default recursive strategy recursively merges these bases first to create a virtual base, then Performs the three-way merge against it.
When the current branch has no new commits since the branch point (i.e., the current branch is an Ancestor of the branch being merged), Git can perform a fast-forward merge. This moves The branch pointer forward — no merge commit is created.
gitGraph
commit id: "A"
commit id: "B"
checkout feature
commit id: "C"
commit id: "D"
checkout main
merge feature id: "D (fast-forward)" Updating a3f2b1c..b7e9d4f
src/auth.c | 42 +++++++++++++++
1 file changed, 42 insertions ( + )
Sometimes you want a merge commit even when a fast-forward is possible — to preserve a record of the Merge event:
$ git merge --no-ff feature-auth
gitGraph
commit id: "A"
commit id: "B"
checkout feature
commit id: "C"
commit id: "D"
checkout main
merge feature id: "E (--no-ff)" noff This is common in release workflows where the merge commit serves as a “release marker” that can be identified in the history.
It easy to see when a feature was merged, revert the entire feature with one command (`git revert -m 1`), and understand the project history.Git supports several merge strategies, selectable with -s:
Strategy Description When to Use recursive (default)Three-way merge with recursive base resolution General purpose, handles criss-cross merges ort (new default, Git 2.34+)Modern rewrite of recursive with better conflict markers and performance General purpose (replacing recursive) resolveSimple three-way merge with one base Simple histories without criss-cross merges octopusMerge more than two branches at once Very rare; most merge tools handle only two oursDiscard all changes from the other branch, keep ours Rare; git merge -s ours is an anti-pattern subtreeAdjust subtree merge paths When managing subtree merges
The ort (“Ostensibly Recursive’s Twin”) strategy is a from-scratch rewrite of the recursive Strategy. It produces identical merge results but with significant improvements:
Performance : 2 × 2\times 2 × –10 × 10\times 10 × faster on large repositories (Chromium, Android).Conflict markers : Clearer conflict markers with section headers (<<<<<<< HEAD``======= >>>>>>> branch).Rename detection : More accurate rename detection.Memory usage : Lower peak memory consumption.## Explicitly use ort (it's the default in Git 2.34+)
$ git merge -s ort feature-auth
When both branches modify the same region of a file, Git cannot automatically resolve the conflict. It marks the conflicted regions in the file:
function authenticate(user) {
return verify_password(user.password, user.stored_hash);
function authenticate(user) {
const token = jwt.sign(user);
return { token, expiresIn: 3600 };
The markers are:
Marker Meaning <<<<<<< HEADStart of the conflict. Our version begins here. =======Separator between our version and their version. >>>>>>> feature-authEnd of the conflict. Their version ends here.
Open the conflicted file and edit it to contain the correct resolution.Stage the resolved file with git add.Complete the merge with git commit (or git merge --continue for ongoing merges).## See which files are conflicted
both modified: src/auth.c
# Edit the file to resolve the conflict, then:
# Or: git merge --continue
There are several ways to resolve a conflict, depending on the situation:
# Accept our version entirely
$ git checkout --ours src/auth.c
# Accept their version entirely
$ git checkout --theirs src/auth.c
If you cannot resolve a conflict, you can abort the merge entirely:
# Restores HEAD and the index to their pre-merge state
Scenario Recommended Resolution One side added a feature, other side refactored Manually integrate the feature into the refactored code Both sides fixed the same bug differently Choose the better fix, verify with tests One side deleted a file, other side modified it Discuss with the team: delete or keep with modifications Rename conflicts (file renamed differently on each branch) Manually resolve: pick one name, apply changes from both sides Large-scale conflicts (hundreds of files) Consider rebasing instead, or git merge --abort and re-evaluate
```bash $ git config --global merge.tool vscode $ git config --global mergetool.vscode.cmd 'code --wait $MERGED' ```Popular options: meld (Linux), vscode (cross-platform), kdiff3 (cross-platform), opendiff (macOS).
The fundamental trade-off between merge and rebase is history topology :
gitGraph
commit id: "A"
commit id: "B"
checkout feature
commit id: "C"
commit id: "D"
checkout main
merge feature id: "E (merge commit)" Merge : Preserves the exact history. The DAG shows that C and D were developed in parallel. Non-linear history.
gitGraph
commit id: "A"
commit id: "B"
commit id: "C (rebased)"
commit id: "D (rebased)" Rebase : Rewrites history to create a linear sequence. The original commits C and D are Replaced by new commits C' and D' with different hashes.
See Rebasing for the complete treatment.
The longer you wait between merges, the more likely conflicts become, and the harder they are to Resolve. A good practice is to merge main into your feature branch daily:
$ git switch feature-auth
Or use rebase (see Rebasing ):
Long-lived branches accumulate conflicts. A feature branch should ideally exist for no more than a Few days. If a feature is large, break it into smaller, independently mergeable pieces.
$ git merge --no-ff feature-auth
This creates a merge commit even when a fast-forward is possible, preserving the branch topology and Making the feature’s scope visible in the history.
Run your test suite before and after merging:
$ git merge --no-commit feature-auth # Stage the merge without committing
$ git commit # Commit only if tests pass
$ git merge --no-ff feature-auth -m " Merge feature/auth: JWT authentication
Implements JWT-based authentication with refresh token rotation.
- Token validation middleware
- Password hashing with bcrypt
Confusing an algorithm with a program. An algorithm is a step-by-step procedure, not its implementation in code.
Forgetting that O ( n log n ) O(n \log n) O ( n log n ) average-case for quicksort becomes O ( n 2 ) O(n^2) O ( n 2 ) worst-case on already sorted input.
Neglecting to normalise database designs, leading to data redundancy and update anomalies.
Writing pseudocode that is too language-specific rather than using standard algorithmic constructs.
The key principles covered in this topic are linked in the sub-pages above. Focus on understanding the definitions, applying the formulas or frameworks, and evaluating strengths and limitations of each approach.
Worked examples demonstrating the application of key concepts are covered in the detailed sub-pages linked above.
Merging combines two branches’ histories into one. A fast-forward merge is a simple pointer move when the target branch has no new commits. A three-way merge creates a new commit that combines both branches, with a common ancestor as the reference. Conflicts happen when both branches modify the same lines: Git asks you to choose which version to keep. Think of merging as reconciling two drafts of a document: you keep the best parts from each and resolve any contradictions.