Trunk-based development and delivery metrics
Merge small changes into trunk at least daily, so each day's edits start from everyone else's; DORA's five metrics show whether delivery is improving.
Predict first
Before you read: predict
Checkout team. Ana and Ben both edit checkout/fees.js, which starts as:
Their edits this week, each made in the copy of the file that developer has at the time:
Mon Ana SHIPPING_FEE = 399
Tue Ben FREE_SHIPPING_FROM = 4000
Tue Ana FREE_SHIPPING_FROM = 4500
Wed Ben SHIPPING_FEE = 349
The week is run twice from the same start. Branch week: each works on their own branch from Monday; on Friday Ana merges hers into main, then Ben merges his. Daily week: each evening, whoever edited that day merges into main, and each morning both start from main.
Over each week, how many constants does git stop on for someone to settle?
// checkout fees, all in pence// standard deliveryexport const SHIPPING_FEE = 499;// orders at or above this ship freeexport const FREE_SHIPPING_FROM = 5000;// per wrapped itemexport const GIFT_WRAP_FEE = 250;// next-day deliveryexport const EXPRESS_FEE = 999;The 30-second version
Integrating means your commits reach trunk. Two changes clash when both touched the same lines since their copies last met there, so integrating daily keeps that window to a day. A clean merge can still break the build, so test merged trunk. DORA tracks delivery through throughput (lead time, deployment frequency, recovery time) and instability (change fail rate, rework rate).
Merge small changes into trunk at least daily, so each day's edits start from everyone else's; DORA's five metrics show whether delivery is improving.
How it works
Step 1 of 14
main), the copy everyone starts from. Merging main into your branch updates only your copy.In the kitchen
- each family's own copy of the chart → a developer's branch (step 1)
- a family that reads the master each morning but hands its own copy in only at the end has integrated nothing → merging
maininto your branch is not integration (step 1)
Analogy: Two families keeping one wedding seating plan
Each family edits its own copy of the seating chart, and the planner folds the copies into the master chart.
| At the planner's table | In the repository |
|---|---|
| each family's own copy of the chart | a developer's branch (step 1) |
| a guest both families moved since they last swapped copies is a clash the planner must settle | a line both sides changed since the merge base (step 2) |
| the planner folds both copies in every evening and hands out fresh ones, so Wednesday's copy already shows Monday's move; only two moves made the same day can still clash | daily integration: each edit lands on the other's earlier ones (step 3) |
| a family that reads the master each morning but hands its own copy in only at the end has integrated nothing | merging main into your branch is not integration (step 1) |
| two copies fold together with no double-booked seat, yet put two feuding uncles at one table | a clean merge that fails the build (step 5) |
In real terms: integrating means your commits reach trunk; integrate daily and each change is written on top of everyone else's, so few of them ever meet as a clash.
Breaks down when: the planner sees the whole room and spots the feuding uncles; git compares lines only, so only the build catches a clash of meaning.
Contrasting case: a family that keeps its copy to itself until the wedding morning: every move both families made meets at once, which is the long-lived branch of step 4.
Examples, run for real
4.1 ExamHall: the proctor-console redesign, replayed
EXH-2060: the proctor-console redesign lived on redesign/proctor-v2 for six weeks and broke on merge. Rui renamed the tile renderer in week 1; Lee, on main, added an alerts banner calling the old name in week 3. MR !2360 replays the same seven changes as daily merges, with unfinished screens switched off behind proctor_v2.
4.1 six-week branch · the redesign merged into main 1 time · git stopped on TILE_COLUMNS and REFRESH_SECONDS · tests on merged main: fail (alerts.mjs imports renderTile; tiles.mjs no longer exports it)
4.1 daily merges · the redesign merged into main 4 times · git stopped on nothing · tests on main after each of the 7 merges: pass 7 of 7
The six-week merge stops twice, because both sides changed the same two layout constants (steps 2, 4). Then the merge looks clean and fails the check anyway: the rename and the new caller never touched the same line (step 5). In the daily replay Lee writes the banner on a copy that already has the rename, so the caller uses the new name and every merged main passes (steps 3, 6).
4.2 Proof domain, checkout: measuring the practice
A checkout repository on a Friday afternoon, read with git for-each-ref, git merge-base and the deploy tags:
4.2 branches not merged into main: 4 (DORA's goal: three or fewer) · left main more than 2 days ago: feature/loyalty 19 days, feature/apple-pay 11 days
4.2 deployments Mon–Fri: 2 · 1 · 0 · 3 · 1 · deployment frequency 7 this week
Four branches are open where DORA's goal is three or fewer, and two left main far more than a couple of days ago (steps 7, 10). Deployment frequency is a count of deployments over a period (step 11).
Try it
Answer first, then check. Nothing is saved.
Question 1 of 5
Carla (feature/gift-wrap) and Dev (feature/express) both edit checkout/extras.js, which starts as:
Their edits, each made in the copy that developer has at the time:
Mon Carla GIFT_WRAP_FEE = 300
Wed Dev GIFT_WRAP_FEE = 200
Thu Carla EXPRESS_FEE = 899
Thu Dev EXPRESS_FEE = 1099
Every morning each runs git merge main on their own branch to stay up to date. On Friday Carla merges into main, then Dev, and git stops on 2 constants in Dev's merge.
Which one change to the week leaves the fewest constants to settle?
// checkout extras, in pence unless stated// per wrapped itemexport const GIFT_WRAP_FEE = 250;// longest gift message, in charactersexport const GIFT_NOTE_MAX = 120;// next-day deliveryexport const EXPRESS_FEE = 999;// last order hour for next-day deliveryexport const EXPRESS_CUTOFF_HOUR = 14;Question 2 of 5
A catalog team of nine merges through pull requests and calls its workflow trunk-based. Four habits from this sprint are below.
Which one goes against how its short-lived branches are meant to be used?
Question 3 of 5
The checkout service's deploy log for September:
deploy plan trigger afterwards
d1 planned release fine
d2 planned release checkout 500s; rolled back by d3
d3 unplanned incident from d2: roll back fine
d4 planned release fine
d5 unplanned marketing: sale banner by noon fine
d6 unplanned incident: payment provider cert expired fine
d7 planned release tax wrong on baskets; fixed by d8
d8 unplanned incident from d7: hotfix fine
d9 planned release fine
d10 planned release fine
What are the service's change fail rate and deployment rework rate for the month, to the nearest whole percent?
Question 4 of 5
A box-office team cut feature/seat-map-v2 from main at 2026-08-12 10:00 and later merged it with git merge --no-ff. deploy-117 reached production at 2026-09-27 10:00 and deploy-118 at 2026-09-29 10:00. git log --date=format:'%Y-%m-%d %H:%M' --format='%ad %s' deploy-117..deploy-118 prints:
2026-09-28 10:00 Merge branch 'feature/seat-map-v2'
2026-09-24 10:00 seat-map: review fixes
2026-09-22 10:00 seat-map: hold timer
2026-09-09 10:00 seat-map: accessible seats
2026-08-28 10:00 seat-map: price bands
2026-08-17 10:00 seat-map: seat model
What is the longest change lead time among the changes that deploy-118 shipped, in whole days?
Question 5 of 5
ExamHall's engineering leads want delivery to improve across its twelve product teams. Their plan: a monthly league table of each team's deployment frequency, and 30 deployments a month written into every team's objectives.
Which response fits how these metrics are meant to be used, as of 2026-10-01?
Go deeper
Pro tips
- Before you push, ask git whether your branch would merge cleanly into
main, without touching your working tree:
When: checking a branch against today'sgit merge-tree --write-tree --name-only main feature/gift-cardsmainbefore a pull request (steps 2–3). It prints a tree id and exits 0 when the merge is clean; on a clash it exits 1 and names each conflicted file. Verify:merge-tree main feature/gift-cards -> exit 1, conflicted: checkout/giftcards.js · merge-tree main feature/gift-value -> exit 0, tree 798d87c · working tree and HEAD unchanged: yes. Status: RAN. - List the branches that have not reached
main, oldest last commit first:
When: a weekly look at how many branches are open and which ones have gone quiet (step 10). Verify:git for-each-ref --no-merged main --sort=committerdate --format='%(committerdate:short) %(refname:short)' refs/headsfor-each-ref --no-merged main -> 2026-09-14 feature/wishlist | 2026-09-29 feature/klarna | 2026-10-01 fix/vat-label (feature/done, merged on 2026-09-21, is not listed). Status: RAN.
Source
DORA's metric names, their grouping and the trunkbaseddevelopment.com rules can change; every claim below is as of 2026-10-01.
- DORA, Capabilities: Trunk-based development: https://dora.dev/capabilities/trunk-based-development/ (
dora-tbd.txt). Quoted: "Have three or fewer active branches in the application’s code repository." "Merge branches to trunk at least once a day." "Don’t have code freezes and don’t have integration phases." Supports steps 7 and 10. - DORA, Capabilities: Continuous integration: https://dora.dev/capabilities/continuous-integration/ (
dora-ci.txt). Quoted: "If the build can’t be fixed in a few minutes, the change that caused the build to break should be identified and reverted." Supports step 6. - DORA, DORA's software delivery performance metrics (page last updated January 5, 2026): https://dora.dev/guides/dora-metrics/ (
dora-metrics.txt). Quoted: "Change lead time: The amount of time it takes for a change to go from committed to version control to deployed in production." "Deployment frequency: The number of deployments over a given period or the time between deployments." "Change fail rate: The ratio of deployments that require immediate intervention following a deployment." "Likely resulting in a rollback of the changes or a “hotfix” to quickly remediate any issues." "Deployment rework rate: The ratio of deployments that are unplanned but happen as a result of an incident in production." The page lists failed deployment recovery time under throughput. - Paul Hammant, Trunk Based Development: https://trunkbaseddevelopment.com/ (
tbd-home.txt). Quoted: "You can either do a direct to trunk commit/push (v small teams) or a Pull-Request workflow as long as those feature branches are short-lived". - Short-Lived Feature Branches: https://trunkbaseddevelopment.com/short-lived-feature-branches/ (
tbd-short-lived.txt). Quoted: "Simply put, the branch should only last a couple of days." and "These short-lived feature branches are not shared within a team for general development activity." - Committing straight to the trunk: https://trunkbaseddevelopment.com/committing-straight-to-the-trunk/ (
tbd-straight.txt). Quoted: "Two changes can auto-merge with zero VCS conflict and still break each other in meaning". - Continuous Integration (CI): https://trunkbaseddevelopment.com/continuous-integration/ (
tbd-ci.txt), and Alternative branching models: https://trunkbaseddevelopment.com/alternative-branching-models/ (tbd-alternatives.txt). Quoted: "Git Flow is incompatible with Trunk-Based Development." Background for step 4 and the curriculum's GitFlow question; no item is keyed on them. tbd-flags.txtandtbd-release.txt(https://trunkbaseddevelopment.com/feature-flags/, https://trunkbaseddevelopment.com/branch-for-release/) were fetched for the lane check againstarch-feature-flagsanddevops-deploy-strategies; nothing here rests on them.
Related lessons
Checked against the tools and docs as of 2026-10-01.