In the previous lesson, every time Git created a merge commit it told us the same thing: Merge made by the 'ort' strategy. We walked straight past that word, but it hides an important part of how Git works.
A merge strategy is the algorithm Git uses to combine two (or more) lines of work. There is not just one: Git ships several, picks one by default depending on the situation, and lets you impose a different one. On top of that, each strategy accepts options that tweak its behaviour in specific details, such as what to do about a conflict or whether to ignore whitespace changes.
In practice, 95 % of the merges you do will use the default strategy without you ever having to think about it. But the remaining 5 % — a merge with hundreds of trivial conflicts, an abandoned branch that has to be formally "closed", a feature built from twenty chaotic commits you do not want anywhere near main — is exactly where knowing the alternatives saves you hours.
This lesson also covers a decision that goes beyond the algorithm: which form of integration to choose, based on the history you want to end up with. A normal merge, --no-ff, a squash… or a rebase, which we will see in module 5 but which is worth placing on the map now.
Contents
- What a merge strategy actually is
ort: the default strategyresolve: the veteranoctopus: several branches at once- The
oursstrategy: discard the content, keep the history subtree: merging nested projects- Strategy options with
-X - The trap: the
oursstrategy versus the-X oursoption - The squash merge
--no-commit: reviewing before you seal it- Decision table: which form of integration to choose
- What a merge strategy actually is
When git merge has to genuinely combine work — that is, when it is not a fast-forward — it has to answer one question for every file and every line: given what was in the common ancestor and what is on each side, what do I put here?
The set of rules that answers that question is the strategy. You choose it with -s or --strategy:
And its internal options with -X or --strategy-option:
The two can be combined, and -X can be repeated:
Let us compare all the available strategies first, and then look at each one:
| Strategy | Branches it accepts | When Git uses it | What it is for |
|---|---|---|---|
ort |
Two | The default since Git 2.34 | General-purpose merging; the one you want almost always |
recursive |
Two | The default from Git 2.0 to 2.33 | Predecessor of ort; today an alias for ort |
resolve |
Two | Never the default | Old, simple algorithm; a single common ancestor |
octopus |
Three or more | The default when merging 3+ branches | Integrating several trivial branches in one go |
ours |
Two or more | Never the default | Recording the merge while discarding the other branch's content |
subtree |
Two | Never the default | Merging a project that lives in a subdirectory |
ort: the default strategy
ort: the default strategyort stands for Ostensibly Recursive's Twin, a name with a sense of humour that gives away its origin: it is a from-scratch rewrite of the recursive strategy, designed to give the same results but much faster and with fewer odd corner cases.
It has been the default strategy since Git 2.34 (November 2021). Before that it was recursive. Today, if you type -s recursive, Git uses ort anyway.
What it does, in short:
- It works out the common ancestor of the two branches.
- It performs the three-way merge file by file and line by line, with the table of rules we saw in lesson 03-03.
- It detects renames: if you moved
styles.csstocss/styles.csson one branch and modified it on the other, it applies the changes to the file in its new location instead of handing you an absurd conflict. - If there are several common ancestors, it merges them together recursively until it has a virtual one, and uses that as the base.
Point 4 is the one that gives the family its name, and it deserves a moment. There can be more than one common ancestor when the history contains criss-cross merges:
graph RL
A["A"]
B["B"] --> A
C["C"] --> A
D["D"] --> B
D --> C
E["E"] --> C
E --> B
F["F<br/>branch-1"] --> D
G["G<br/>branch-2"] --> E
Here both D and E are common ancestors of branch-1 and branch-2, and neither is an ancestor of the other. A simple strategy would have to pick one arbitrarily, at the risk of producing false conflicts. ort merges them together to build a virtual base that combines the information from both.
You do not need to engineer this situation to get on with your work: you only need to know it exists and that Git handles it on its own. It is the reason merges in complex histories work surprisingly well.
resolve: the veteran
resolve: the veteranresolve is the classic strategy: a three-way merge with a single common ancestor, picked if there are several. It does no recursion and its rename detection is far more limited.
When should you use it? Practically never. Its niche is the rare case where ort produces a result you are not happy with in a history full of criss-cross merges and you want to try a different base. It is more of a diagnostic resource than a working tool.
It is on the syllabus because you will see it mentioned in old documentation, and because knowing it helps you understand why ort does what it does.
octopus: several branches at once
octopus: several branches at oncegit merge is not limited to one argument. You can name several branches:
When there are three or more, Git automatically switches to the octopus strategy, which creates a single merge commit with as many parents as there are branches, plus one.
Suppose the team has piled up three tiny, independent fixes, each on its own branch:
Trying simple merge with fix/readme-typo Trying simple merge with fix/favicon Trying simple merge with fix/html-lang Merge made by the 'octopus' strategy. README.md | 2 +- index.html | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-)
tree 5f8b2e1c9a3d7f4b6e2a8c1d5f9b3e7a4c6d2f8b parent f7a3e92b5d1c8f4a6e3b7d9f2a5c1e8b4d6f3a9c parent 2f6a3c8e9b1d5f7a3c2e8b4d6f1a9c5e7b3d2f4a parent 7d2f9a1c5e8b3d6f2a4c9e1b7d5f3a8c2e6b4d9f parent 3c9d4a2f8e1b5d7c3a9f2e6b4d8c1a5f7e3b9d2c
Four parents. The first is main and the other three are the tips of the merged branches.
gitGraph commit id: "f7a3e92" branch readme-typo commit id: "2f6a3c8" checkout main branch favicon commit id: "7d2f9a1" checkout main branch html-lang commit id: "3c9d4a2" checkout main merge readme-typo merge favicon merge html-lang id: "octopus"
The crucial limitation of octopus: it refuses to work if there is any conflict at all.
There is no resolution to attempt, no markers in the files, nothing to fix: the whole operation is aborted. The reason is a design decision: resolving conflicts between four simultaneous versions would be unmanageable for a human being.
That is why its realistic use is very specific: integrating in one go several branches that do not step on each other. If any of them conflicts, you will have to merge them one at a time. In the day-to-day life of a small team you will rarely see it; in projects with a great many independent contributions (the Linux kernel is the canonical example, and no coincidence, since it was the project Git was written for) it makes complete sense.
- The
ours strategy: discard the content, keep the history
ours strategy: discard the content, keep the historyThis is the oddest strategy of the lot and the most widely misunderstood.
What it does: it creates a merge commit with the two normal parents, but the resulting tree is exactly the one from the current branch. The other branch's content is discarded entirely.
It sounds absurd. Why merge something you are going to throw away? To record in the history that the branch has already been considered, without bringing in its code.
The real use case in Ana's project: a month ago the team opened the branch experiment/indexeddb-storage to try out a different storage system. The experiment was dropped — in the end they stuck with localStorage — but the branch is still there and every so often somebody asks whether it needs integrating.
git switch main
git merge -s ours experiment/indexeddb-storage -m "Drop the IndexedDB experiment
We tried IndexedDB for task storage. Dropping it: the added
complexity is not worth it for the volume of data we handle.
We keep localStorage. The branch is recorded as integrated."Notice that it lists no changed files: nothing has changed.
Zero differences from the previous commit on main. The code is identical. But:
The branch counts as merged, so it will show up in the lists of branches you can delete without losing anything (lesson 03-06), it will stop appearing in "branches still to integrate" warnings, and the history holds a permanent explanation of why it was dropped.
Another classic use: when two branches have diverged so far that reconciling them by hand is unrealistic and you want one of them to win outright, leaving a record of the decision.
An important warning: -s ours discards all of the other branch's content without asking and without warning. There are no conflicts, no review, no automatic way back. It is a deliberate decision, not a shortcut out of a difficult merge. And do not confuse it with -X ours, which is something completely different and which we will see in section 8.
subtree: merging nested projects
subtree: merging nested projectssubtree is a variant of ort for the case where one branch contains a project that on the other branch lives inside a subdirectory.
Suppose the team develops a small component library separately, in its own repository, and that in task-manager that library lives in vendor/components/. When merging the library's branch, a normal strategy would try to match its files against the project root and produce a disaster. subtree detects the path shift and applies the changes where they belong.
You can also give the prefix by hand, which is more reliable than letting Git guess:
This mechanism is the basis of the git subtree command, one of the two ways of nesting repositories. The other is submodules, which get a lesson of their own in module 6. For now it is enough to know that the strategy exists and which scenario it was designed for.
- Strategy options with
-X
-XOptions do not change the algorithm: they adjust it. These are the ones worth knowing:
| Option | Effect |
|---|---|
-X ours |
Faced with a conflict, resolves it automatically in favour of the current branch |
-X theirs |
Faced with a conflict, resolves it automatically in favour of the merged branch |
-X ignore-space-change |
Ignores changes in the amount of whitespace when comparing |
-X ignore-all-space |
Ignores all whitespace |
-X ignore-space-at-eol |
Ignores whitespace at the end of a line |
-X ignore-cr-at-eol |
Ignores the trailing carriage return (handy between Windows and Unix) |
-X renormalize |
Normalises line endings before comparing, according to .gitattributes |
-X find-renames=<n> |
Adjusts the similarity threshold for detecting renames |
-X no-renames |
Turns rename detection off |
-X patience |
Uses the "patience" diff algorithm, which sometimes lines things up better |
-X diff-algorithm=histogram |
Picks a different comparison algorithm |
The whitespace case
This is the one that will save you most often. A real situation: Bruno has set his editor to reindent automatically on save, and it has reindented the whole of app.js on his branch. Ana has modified three lines of the same file on hers.
Auto-merging app.js CONFLICT (content): Merge conflict in app.js Automatic merge failed; fix conflicts and then commit the result.
Eighty conflicts, and not one of them real: they are all indentation differences. The fix:
Auto-merging app.js Merge made by the 'ort' strategy. app.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
The eighty false conflicts vanish and only the real changes are left.
Two important caveats:
- These options affect how lines are compared, not the text that gets written. The final file keeps the indentation of one of the versions (the
oursside in ambiguous stretches), so review the result. - If the indentation problem keeps recurring in the team, the real fix is not
-Xbut.gitattributesplus a shared automatic formatter. That is the subject of lesson 08-04.
-X theirs in practice
A typical example: an automatically generated file (a bundle.js, a dependency lock file) that conflicts every single time and where the incoming branch's version is the correct one:
It is convenient, but use -X ours/-X theirs with your brain switched on: they resolve every conflict on the same side, without distinguishing which ones mattered. If the result matters to you, it is better to resolve by hand (lesson 03-05) or to limit the shortcut to the specific files that deserve it, which is also possible and which we will see there.
- The trap: the
ours strategy versus the -X ours option
ours strategy versus the -X ours optionThey are written almost identically and they do radically different things. This is one of the most expensive confusions in Git, so let us nail it down.
-s ours (strategy) |
-X ours (option) |
|
|---|---|---|
| What it is | A complete merge algorithm | A tweak to the ort strategy |
| What it does | Discards all of the other branch's content | Merges normally, and picks your side only when there is a conflict |
| Conflict-free changes from the other branch | All lost | Brought in as usual |
| Result | The tree is identical to your branch's | The tree combines both branches |
| When to use it | Formally closing a branch you have dropped | Resolving a block of unimportant conflicts |
A concrete example. The branch feature/pending-filter brings two things: a new file filters.js (which conflicts with nothing) and a modification to line 12 of app.js (which does conflict with a change of Ana's).
Result: filters.js does not appear and line 12 is Ana's. Everything has been discarded, even what was bothering nobody.
Result: filters.js does appear, because there was no conflict over it, and line 12 is Ana's, because there was one there. A real merge with an automatic tie-break.
Mnemonic:
-sis the strategy, and a strategy decides everything.-Xis an option, and an option only decides the ties.
And one point about the vocabulary, because it confuses people too: in a merge, ours is always the branch you are standing on and theirs is the one you named in the command. It sounds obvious, but during a rebase the roles are swapped compared with what you would expect, and that is one of the reasons rebasing deserves a lesson of its own (05-01).
- The squash merge
Time for something different. A squash merge is not a strategy: it is a different form of integration, and in many teams it is the most used of them all.
The situation in the project: Bruno has built CSV export for the task list on the branch feature/csv-export. It works, but the branch's history is a mess:
e9a2c5f Now it works 7e2a9c4 Remove the console.log 1d6b4f8 wip 2 5a8e2b9 Fix the separator 9f3a2c1 wip 4a1c7e3 First attempt at exporting
Six commits, four of which give nothing at all to anyone reading the history six months from now. Bruno does not want that in main.
Updating f7a3e92..e9a2c5f Fast-forward Squash commit -- not updating HEAD app.js | 34 ++++++++++++++++++++++++++++++++++ index.html | 2 ++ 2 files changed, 36 insertions(+)
Read it carefully: Squash commit -- not updating HEAD. Git has worked out the result of the merge and left it in the staging area, but it has committed nothing.
On branch main Changes to be committed: (use "git restore --staged <file>..." to unstage) modified: app.js modified: index.html
Now Bruno writes a decent message and commits:
git commit -m "Add CSV export of the task list
Generates a CSV file with the title, the status and the creation
date of every task. It downloads from the new button in the
header."How it differs from a normal merge
graph TB
subgraph squash["SQUASH MERGE"]
S1["f7a3e92<br/>main"] --> S2["3b9e7d1<br/>main (a single commit,<br/>ONE parent)"]
S3["4a1c7e3 → e9a2c5f<br/>csv-export<br/>(disconnected)"]
end
subgraph normal["NORMAL MERGE"]
N1["f7a3e92"] --> N3["merge<br/>main (TWO parents)"]
N2["4a1c7e3 → e9a2c5f<br/>csv-export"] --> N3
end
The essential difference:
| Normal merge | Squash merge | |
|---|---|---|
New commits on main |
1 (a merge commit) | 1 (a normal one) |
| Parents of that commit | Two | One |
The branch's commits in main's history |
Yes, all six | No, none |
| Is the relationship with the branch recorded? | Yes | No |
What does git branch --merged say? |
That it is merged | That it is not |
| Authorship of the original commits | Preserved | Lost (the committer is credited) |
That "the relationship is not recorded" has one very concrete practical consequence. After a squash, main is not a descendant of feature/csv-export:
Bruno's branch does not appear, even though its content is entirely in main. If you try to delete it:
error: The branch 'feature/csv-export' is not fully merged. If you are sure you want to delete it, run 'git branch -D feature/csv-export'.
Git is not lying: from the graph's point of view, those six commits are not in main. You will have to delete it with -D, knowing what you are doing. We will come back to this in lesson 03-06.
When to use squash
In favour:
maingets one commit per feature, clean and with a message written calmly.- The working branch's mess (the
wips, the "now it works") does not contaminate the project. git bisect(lesson 06-02) works better: every commit onmainis a complete, working state.- It is the default model behind the "Squash and merge" button on hosting platforms, and therefore the most widespread today in teams that work with change proposals.
Against:
- The detail of the development is lost. If the feature is 40 well-written commits, squashing them destroys useful information.
- Individual authorship is lost: a commit built from three people's contributions ends up in the committer's name.
- Branches have to be deleted with
-D, without the safety net of-d. - If you carry on working on the branch after the squash and merge again, Git does not know that work was already integrated and can replay conflicts you had already resolved.
Rule of thumb: squash for short, single-person branches with untidy commits; a merge with --no-ff for large features whose history deserves keeping.
--no-commit: reviewing before you seal it
--no-commit: reviewing before you seal itgit merge --no-commit does the merge for real but stops just before creating the commit:
On branch main All conflicts fixed but you are still merging. (use "git commit" to conclude merge) Changes to be committed: modified: app.js modified: styles.css modified: index.html
At this point you can:
# See exactly what is about to go in
git diff --cached
# Run the tests against the combined result
npm test
# Adjust something before committingAnd afterwards, commit or abort:
git commit # seals the merge (with its two parents)
# or else
git merge --abort # undoes the merge and goes back to the previous stateThe difference from --squash is fundamental and often confused, because both of them "stop before committing":
--no-commit |
--squash |
|
|---|---|---|
| Is the merge recorded? | Yes: committing produces a commit with two parents | No: it produces a normal commit |
| Can it be aborted? | Yes, with git merge --abort |
No: there is no merge in progress to abort |
State of .git/MERGE_HEAD |
It exists | It does not exist |
| What it is for | Reviewing or testing before sealing | Squashing a branch into a single commit |
That MERGE_HEAD file is the technical detail that explains everything: with --no-commit there is a merge in progress that Git remembers, which is why the resulting commit will have two parents and --abort works. With --squash there is no merge in progress at all: there are just some changes staged in the index.
Common uses of --no-commit:
- Running the test suite against the combined result before leaving it on
main. - Reviewing a large merge file by file.
- Adjusting minor details (a duplicated
import, a stray space) and folding them into the merge commit itself.
- Decision table: which form of integration to choose
Let us gather everything from this module, plus the rebase coming in module 5, into a single decision table:
| I want to… | I use | Resulting history |
|---|---|---|
| Integrate with no sign that there was ever a branch | git merge (fast-forward) |
Linear, no merge node |
| Leave a record that a feature was integrated | git merge --no-ff |
With a merge node and a bubble of commits |
| One single clean commit per feature | git merge --squash + git commit |
Linear, one commit per branch |
Forbid merge nodes on main |
git merge --ff-only |
Linear, or it fails |
| Test the result before sealing it | git merge --no-commit |
Whatever you decide afterwards |
| Integrate several trivial branches at once | git merge a b c (octopus) |
One node with many parents |
| Close a dropped branch without bringing its code | git merge -s ours |
With a merge node, no changes |
Bring my branch up to date with main without a merge node |
git rebase main → 05-01 |
Linear, with rewritten commits |
About that last row, just enough to place it: a rebase does not merge; it rewrites your branch's commits as if you had made them on top of main's current state. The result is a linear history with no merge nodes, at the price of the commits changing hash and therefore being new commits. That has serious implications once the work has been shared, which is why it gets a lesson of its own. Here you only need to know that it is the third alternative alongside merging and squashing.
And a starting recommendation
If you are just getting going and have no team policy to follow:
- A normal merge for everything, letting Git decide between fast-forward and three-way.
--no-ffwhen you integrate something you want to be able to identify as a unit in the future.--squashwhen the branch is yours, short and untidy.- Leave
-sand-Xalone until a concrete problem asks you for one of those options.
Above all, have the whole team do the same thing. A consistent history is worth more than an optimal but erratic one. The named workflow conventions — Git Flow, GitHub Flow, Trunk Based Development — are precisely that: bundles of decisions somebody has already made for you. They have their place in module 7.
Common Mistakes and Tips
Mistake 1: confusing -s ours with -X ours. This is the most expensive mistake in this lesson. -s ours silently discards all of the other branch's work, including files that were causing no trouble at all. If what you wanted was to break the tie on conflicts, the option is -X ours. If you are ever in doubt, git diff HEAD~1 HEAD after the merge will tell you straight away whether you brought in anything or nothing.
Mistake 2: using -X theirs as a universal shortcut. It resolves every conflict on the same side, including the ones where your version was the right one. Work disappears without a single warning. Keep these options for conflicts you know are irrelevant.
Mistake 3: believing that --squash merges. It does not: it stages the changes and walks away. If you forget the git commit afterwards, you are left with a pile of staged changes on main and an odd feeling that "the merge did not work". And even once you commit, the branch will still show up as unmerged.
Mistake 4: squashing a branch and carrying on working on it. Since the resulting commit has no relationship with the originals, the next merge of that branch will bring everything back and replay conflicts you had already resolved. After a squash, the branch is closed: you delete it and open another.
Mistake 5: hunting for the perfect strategy for a difficult conflict. There is no such thing. If ort gives you conflicts, it is because there is a human decision to be made. Changing strategy only moves the problem or hides it. The right answer is the next lesson.
Tip 1: -X ignore-space-change is the one you will use most. When a merge blows up with dozens of conflicts that turn out to be nothing but indentation, abort and try again with that option. The improvement is spectacular.
Tip 2: check what an unusual merge has done. After any merge with -s or -X, look at the result before moving on:
Tip 3: --no-commit is your safety net. On large or delicate merges, stop before committing, run the tests and review. It costs thirty seconds and stops you putting a combination nobody has checked into main.
Tip 4: write down the team policy. Put in the project's README.md or CONTRIBUTING.md how work gets integrated into main. It is the kind of decision that, left unwritten, every person interprets differently.
Exercises
Exercise 1: demonstrating the difference between -s ours and -X ours
Set up a test repository where a branch brings two things: a new file that does not conflict and a modification that does conflict with the main branch. Merge it both ways (in two independent attempts) and show with commands what is left in each case.
Exercise 2: squash versus a normal merge
Create a branch with three commits and integrate it into main both ways, either in two different repositories or by undoing in between. Then answer:
- How many commits does
mainhave in each case? - How many parents does
main's last commit have in each case? - What does
git branch --mergedsay in each case? - Can the branch be deleted with
-din each case?
Exercise 3: choosing the form of integration
For each situation, choose between a normal merge, --no-ff, --squash, -s ours, octopus or --no-commit, and justify it:
- A colleague's branch with 25 well-written commits and careful messages, implementing the reporting module.
- Your own branch with 7 commits, four of which are called
wip. - Five translation branches, each touching a different language file.
- A branch for an experiment that has been dropped, and which you want on the record.
- A merge that affects the billing logic and that you want to validate with the tests before it goes into
main.
Solutions
Solution 1:
mkdir /tmp/practice-ours && cd /tmp/practice-ours
git init -b main
echo "original line" > common.txt
git add . && git commit -m "Base"
# The branch brings a new file AND a conflicting change
git switch -c contribution
echo "new content" > exclusive.txt
echo "branch version" > common.txt
git add . && git commit -m "Add exclusive.txt and change common.txt"
# main changes the SAME line of common.txt
git switch main
echo "main version" > common.txt
git commit -am "Change common.txt on main"Attempt A, with the strategy:
exclusive.txt is not there. All of the branch's content has been discarded, including the file that was causing nobody any trouble.
Zero changes relative to main. That is the unmistakable signature of -s ours.
Attempt B, with the option:
git reset --hard HEAD~1 # we undo attempt A
git merge -X ours contribution -m "Merge with the -X ours option"
ls
cat common.txtexclusive.txt is there, because there was no conflict over it. And common.txt holds main's version because there was one there and -X ours broke the tie in our favour.
Conclusion: -s ours discards everything; -X ours genuinely merges and only breaks ties.
Solution 2:
mkdir /tmp/practice-squash && cd /tmp/practice-squash
git init -b main
echo "base" > f.txt && git add . && git commit -m "Base"
git switch -c feature
echo "a" >> f.txt && git commit -am "Step 1"
echo "b" >> f.txt && git commit -am "Step 2"
echo "c" >> f.txt && git commit -am "Step 3"
git switch main
echo "change on main" > other.txt && git add . && git commit -m "Advance main"Normal merge:
Squash (rebuilding the setup from scratch or undoing with git reset --hard HEAD~1):
A single parent.
Answers:
- 6 commits with a normal merge (base + 3 from the branch + main's own commit + the merge); 3 with squash (base + main's own commit + the squashed commit).
- Two parents with a normal merge; one with squash.
- With a normal merge the branch appears as merged; with squash it does not.
- With a normal merge,
-dworks; with squash, you have to use-D.
Solution 3:
-
A merge with
--no-ff. Twenty-five well-written commits are valuable information that should not be destroyed, and the merge node documents the module's integration as an identifiable unit. -
--squash. A short, personal, untidy branch: the textbook case. One clean commit onmainwith a message written calmly is worth more than seven commits of which four say nothing. -
octopus(git merge trans-es trans-ca trans-en trans-fr trans-de). Five branches touching different files and therefore incapable of conflicting: exactly the scenario it exists for. If any of them did conflict,octopuswould fail and they would have to be integrated one by one. -
-s ours. It records in the history that the branch was considered and dropped, without bringing in its code, and it stops appearing as pending integration. Use the commit message to explain why it was dropped. -
--no-commit. It lets you run the test suite against the already-combined result and, if something fails, walk away cleanly withgit merge --abortwithoutmainever containing anything.
Conclusion
You now know that "merging" is not one single thing:
- A strategy is the algorithm that combines the branches, and you choose it with
-s.orthas been the default since Git 2.34: a three-way merge with rename detection and recursive resolution when there are several common ancestors.resolveis its simple predecessor;octopusmerges three or more branches at once but fails on any conflict;subtreematches up projects that live in subdirectories. - The
oursstrategy (-s ours) creates a merge commit and discards all the content of the other branch. It exists to formally close a branch you have decided not to integrate. - The
-Xoptions adjust the strategy, they do not replace it.-X ours/-X theirsbreak ties only on conflicts;-X ignore-space-changewipes out false indentation conflicts at a stroke. -s oursand-X ourshave nothing to do with each other. The strategy decides everything; the option decides only the ties.- The squash merge (
--squash) leaves the result staged without committing and without recording the merge: the final commit has a single parent and the branch does not count as merged. Ideal for short, untidy branches; bad for features whose history deserves keeping. --no-commitdoes the merge for real but stops before committing, so you can review and test. Unlike--squash, the merge is still in progress and--abortworks.- Choosing how to integrate is a team decision about the history you want to have, not a technical question.
What comes next
Throughout this lesson we have taken for granted something that does not always hold in the real world: that the merge goes well. But Ana and Bruno will not be so lucky next time. They are both about to touch the same lines of the function that renders the task list in app.js, each on their own branch and for different reasons.
When that happens, Git cannot decide. It stops, writes some strange markers inside the file and passes the ball to you. In the next lesson, Resolving Merge Conflicts, we will provoke a real conflict and resolve it step by step: you will learn to read the <<<<<<<, ======= and >>>>>>> markers (and the diff3 style, which also shows you what was in the common ancestor), to find your bearings with git status and git diff while the merge is half done, to use the --ours/--theirs shortcuts, to abort with git merge --abort when things go wrong, and to set up a graphical tool for the hard conflicts.
Mastering Git: From Beginner to Advanced
Module 1: Introduction to Git
- What Is Git?
- Installing Git
- Basic Git Terminology
- The Git Data Model
- Configuring Git
- Initial Configuration
Module 2: Basic Git Operations
- Creating a Repository
- Cloning a Repository
- The Basic Git Workflow
- Staging and Committing Changes
- Inspecting Changes with git diff
- Viewing Commit History
Module 3: Branching and Merging
- Understanding Branches
- Creating and Switching Branches
- Merging Branches
- Merge Strategies
- Resolving Merge Conflicts
- Branch Management
Module 4: Working with Remote Repositories
- Understanding Remote Repositories
- Adding a Remote Repository
- Authenticating with Remote Repositories
- Fetching and Pulling Changes
- Pushing Changes
- Tracking Branches
Module 5: Advanced Git Operations
Module 6: Git Tools and Techniques
- Using Git Hooks
- Git Bisect
- Git Blame
- Git Log and Aliases
- Git Submodules
- Multiple Working Copies with git worktree
Module 7: Collaboration and Workflow Strategies
- Forks and Pull Requests
- Code Reviews with Git
- The Git Flow Workflow
- GitHub Flow
- Trunk Based Development
- Continuous Integration with Git
Module 8: Git Best Practices and Tips
- Writing Good Commit Messages
- Keeping a Clean History
- Ignoring Files with .gitignore
- File Attributes with .gitattributes
- Security Best Practices
- Performance Tips
Module 9: Troubleshooting and Debugging
- Common Git Problems
- Undoing Changes
- Resolving Divergence with the Remote
- Recovering Lost Commits
- Dealing with Corrupted Repositories
- Advanced Debugging Techniques
