Chapter 06Lesson 03~85 minutes

Branches, Fast-Forward and Three-Way Merges, and Branching Strategies: Configuration, Design Choices, and Tradeoffs

Design branch and merge policy around merge.ff, pull.ff, branch-specific merge options, branch lifetime, server-side protection boundaries, and merge/squash/rebase history tradeoffs.

merge.ff / pull.ffBranch policySquash vs mergeTradeoffs

Learning objectives

  • Explain merge.ff, pull.ff, and branch..mergeOptions with scope/precedence implications.
  • Design branch naming and lifecycle rules that are explicit and reproducible.
  • Separate local Git mechanics from hosted branch-protection and review controls.
  • Compare merge commits, squash integration, and rebased linear history as history-design choices.
  • Choose branch lifetime and integration policy from deployment cadence and support constraints.

1. Branch mechanics are local; branching strategy is governance

Git core gives you refs, commits, merge bases, fast-forward rules, merge commits, squash mode, and rebase machinery. A team decides which of those shapes is acceptable for each integration path. Hosting products can then enforce server-side controls such as protected branches, required review, and merge queues; those are not local branch objects.

2. merge.ff controls ordinary merge behavior

git config --show-origin --show-scope --get merge.ff
git config --local merge.ff only

Current Git supports three practical modes: the normal fast-forward-when-possible behavior, false (equivalent to --no-ff for ordinary merges), and only (equivalent to --ff-only). A global setting can surprise repositories whose policy expects a different graph shape, so shared workflow rules should be explicit in documentation/automation rather than depending on an invisible developer default.

3. pull.ff belongs to pull/integration policy

git pull fetches and then integrates. pull.ff controls fast-forward behavior during that integration and can override merge.ff for pull. A team that wants pull never to create an accidental merge commit can choose pull.ff=only; a team using rebase on pull needs to define that separately.

git config --show-origin --show-scope --get pull.ff
git config --show-origin --show-scope --get pull.rebase

Chapter 07 teaches fetch/pull/remotes deeply. Here the key lesson is that a “surprise merge from pull” is often a configuration/policy issue, not mysterious Git behavior.

4. branch.<name>.mergeOptions can hide defaults in one branch

git config --local branch.trunk.mergeOptions --ff-only
git config --show-origin --show-scope --get branch.trunk.mergeOptions

This can make merges into a particular branch inherit options. It is powerful but easy to forget. Prefer obvious CI/runbook commands for critical integration constraints when hidden local configuration would make another clone behave differently.

5. Branch naming and lifecycle are discoverability policies

Names should tell humans what a ref represents without pretending the name supplies authorization. A small team might use feature/<topic>, fix/<incident>, and release/<line>. Lifecycle rules matter more than punctuation: who creates it, expected lifetime, integration target, who may delete it, and whether CI must pass before integration.

6. Protected integration is server-side, not a special Git branch type

A local trunk branch is structurally the same kind of ref as any other local branch. A hosting service may reject pushes, require reviews, require CI, or serialize merges. Those controls live at the remote service boundary and will be revisited in platform-specific courses. Local labs can still model the underlying ancestry and merge shape without an account.

7. Merge commit, squash, and rebased linear history answer different history-design questions

Integration shape What history preserves Main tradeoff
True merge commit Feature commit ancestry plus explicit integration event Graph is non-linear; reviewers must understand parents
Squash merge Final combined content as one new commit Original feature commits are not ancestors of the target branch
Rebased linear history Individual logical commits replayed on a new base Commit IDs change; unsafe as a casual rewrite of shared history

git merge --squash topic updates the working tree/index with the merge result but does not create a merge commit or record the topic as a merge parent; a subsequent normal commit creates one new single-parent commit. Rebase is covered deeply in Chapter 09 and should be limited to unpublished/local work unless a coordinated policy says otherwise.

8. Long-lived branches increase integration cost nonlinearly

The longer two branches evolve independently, the more code, configuration, tests, infrastructure assumptions, and deployment behavior can diverge. Even when Git reports no textual conflict, the combined result can be semantically wrong. Long-lived branches are justified when support/release constraints outweigh that integration cost—not because a diagram looks professional.

9. Decision table for common team constraints

Constraint Likely policy Why
Deploy many times per day, one supported line Trunk + very short branches, fast integration Minimize divergence and feedback delay
Review required before trunk Short-lived feature branches + controlled server integration Review gate without long isolation
Maintain v1 and v2 simultaneously Release/support branches plus targeted fixes Separate supported baselines are real operational constraints
Monthly release train with stabilization window Possibly a temporary release branch Allows stabilization while trunk moves, but adds backport cost
No requirement for multiple long-lived lanes Avoid Git Flow complexity Every long-lived ref adds synchronization work

10. Worked scenario — a six-person DevOps product team

The team deploys daily, supports only the currently deployed major version, requires review plus CI, and can hide incomplete features behind flags. A reasonable policy is: trunk is always releasable; feature/fix branches should normally live hours to a few days; integration is controlled on the server; hotfixes branch only long enough for review/validation; release branches are created only if a future support requirement appears.

That policy is maintainable because it follows the delivery model. Adopting permanent develop, release, and hotfix lanes without a matching support problem would increase operational cost without adding capability.

11. Knowledge check

Question 1. What does merge.ff=only enforce?

Question 2. Why can a global merge.ff=false surprise a team?

Question 3. Does a squash merge preserve the feature commits as parents of the target history?

Question 4. When is a long-lived release branch justified?

12. Summary

Fast-forward and pull settings are policy levers, not universal best practices. Hidden global defaults can make clones behave differently. Merge, squash, and rebase shapes preserve different information, and branch lifetime should be chosen from deployment/support constraints rather than fashion.

Next

Diagnose wrong-branch merges, deleted refs, semantic conflicts, and pull-created history

Lesson 4 applies an evidence-first runbook and shows why destructive history surgery is usually the wrong first response to branching mistakes.

Authoritative references

 git-config
 git-merge
 git-pull
 git-branch

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.