Warmup Guide — Apache Contribution Mechanics
Zero-to-expert primer for Phase 09: the file formats and state machines that contribution workflows run on — unified diffs, semantic versioning, and issue lifecycles — learned by building parsers and engines for each.
Table of Contents
- Chapter 1: Why Build Tooling for the Contribution Process
- Chapter 2: The Unified Diff Format, Byte by Byte
- Chapter 3: Patch Metrics — What Reviewability Looks Like in Numbers
- Chapter 4: Semantic Versioning as a Contract Language
- Chapter 5: Version Comparison — The Deceptively Tricky Part
- Chapter 6: Issue Lifecycles as State Machines
- Chapter 7: Encoding Process in Types
- Lab Walkthrough Guidance
- Success Criteria
- Interview Q&A
- References
Chapter 1: Why Build Tooling for the Contribution Process
This phase looks meta — parsing diffs instead of writing them — for two deliberate reasons. First, tooling understanding is mechanism understanding: after writing a diff parser you read patches structurally (hunks, context, boundaries) rather than visually; after building a semver engine, compatibility discussions become precise. Second, process tooling is real committer work: release scripts, JIRA bots, compat checkers, patch-quality dashboards are maintained by the same committers who maintain the engine — and they're a classic early-contribution surface (small blast radius, gratefully reviewed).
Chapter 2: The Unified Diff Format, Byte by Byte
The format every git diff/PR/patch email speaks (Lab 01 parses it):
diff --git a/src/Foo.java b/src/Foo.java ← per-file header
--- a/src/Foo.java ← old side ("/dev/null" = file added)
+++ b/src/Foo.java ← new side ("/dev/null" = file deleted)
@@ -14,7 +14,9 @@ public class Foo { ← hunk header
context line (leading space)
-removed line (leading minus)
+added line (leading plus)
The parts people misread:
- The hunk header
@@ -start,count +start,count @@: start is 1-based first line of the hunk including context lines; count is total lines shown on that side (context + changes).countomitted means 1;startof 0 with count 0 appears for pure additions to empty files. The trailing text after the second@@is a free-form "section heading" (the enclosing function) — informational only. - Consistency invariants a parser must check (and Lab 01 tests): old-count = number
of
+-lines in the hunk; new-count =++lines; hunks are ordered and non-overlapping. Real-world wrinkles:\ No newline at end of filemarkers, renames (similarity indexheaders), binary file stanzas, and mode changes. - Why context lines exist at all: they let
patchapply hunks at shifted offsets when the file has drifted — fuzzy matching anchored on context. That's also why hand-edited patches break: the counts stop matching the content.
Chapter 3: Patch Metrics — What Reviewability Looks Like in Numbers
Once parsed, a patch yields metrics that predict review outcomes (Lab 01's
PatchSummary):
- Size and spread: total ±lines, files touched, hunks per file. Defect-detection effectiveness collapses as patches grow (the classic industry observation: beyond ~400 changed lines, reviewers approve on faith) — the quantitative backbone of Phase 06's "small patches" norm.
- Test ratio: changed lines in test paths vs main paths. Zero test delta on a behavior change is an automatic review question; tooling can flag it before a human does (many Apache CI bots do).
- Scope coherence: one logical change per patch shows up as low file-type spread (not: code + reformat + rename in one diff — the Phase 12 mega-patch scenario, detectable mechanically).
- The point of the lab's tool: these checks run in CI as advice, converting review norms from tribal knowledge into feedback newcomers get in seconds.
Chapter 4: Semantic Versioning as a Contract Language
Semver (MAJOR.MINOR.PATCH[-prerelease][+build]) is a promise protocol, not a
numbering aesthetic:
- PATCH: bug fixes only — no API surface change. Consumers auto-upgrade.
- MINOR: additive, backward-compatible surface (new methods, new configs with safe defaults). Consumers upgrade with reading.
- MAJOR: anything breaking — removals, signature changes, behavioral contract changes. Consumers migrate.
The deep content is what "breaking" means — the four compatibility dimensions from
Phases 03/04 (source, binary, behavioral, wire). A return-type narrowing is
source-compatible but binary-breaking → MAJOR even though code "still compiles."
Changing a default value breaks behavioral compat → at minimum MINOR-with-loud-notes,
arguably MAJOR for load-bearing defaults. Lab 02's CompatibilityLevel engine encodes
exactly this mapping: given a change classification, emit the minimum required bump —
the same logic japicmp + release policy implement in production projects (and the
foundation Phase 10's checker builds on).
Apache reality check: big projects approximate semver with documented deviations —
Spark guarantees within a major line per its versioning policy; Hadoop's
audience/stability annotations scope the promise per-interface. The annotation system
is semver's scoping mechanism: @Private surface is exempt from the contract.
Chapter 5: Version Comparison — The Deceptively Tricky Part
Ordering versions correctly is a minefield of off-by-one-intuition bugs (Lab 02 tests all of them):
- Numeric, not lexicographic, per segment:
1.10.0 > 1.9.0(string compare says otherwise — the classic bug). - Pre-release precedence:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0. A pre-release is lower than its release. Identifier-by-identifier comparison: numeric identifiers compare numerically and rank below alphanumeric ones; a longer identifier list wins ties of equal prefixes. - Build metadata (
+sha.5114f85) is ignored for precedence — versions differing only in build metadata are equal (semver §10). - Missing segments normalize to zero (
1.2=1.2.0) in most ecosystems — but Maven'sComparableVersionhas its own additional rules (1.0-SNAPSHOT < 1.0; qualifiers likealpha/beta/milestone/rc/gahave defined order) — know that ecosystems diverge and the spec you implement must be stated.
Chapter 6: Issue Lifecycles as State Machines
A JIRA issue's workflow is a finite state machine, and treating it as one (Lab 03) is both a modeling exercise and process literacy:
- States:
OPEN → IN_PROGRESS → PATCH_AVAILABLE → IN_REVIEW → RESOLVED → CLOSED, withREOPENEDas the loop-back. (Projects customize; the shape is universal.) - Transitions carry guards: you can't move to
PATCH_AVAILABLEwithout an attached patch/PR link;RESOLVEDrequires a resolution type (Fixed / Won't Fix / Duplicate / Cannot Reproduce — each routes differently for release notes!); only the assignee or a committer may resolve. - Why projects enforce this: the issue tracker is the release ledger — "what's in
3.5.1" is a JIRA query (
fixVersion = 3.5.1 AND resolution = Fixed), so state hygiene is release correctness, not bureaucracy. A bug resolved "Fixed" without a fixVersion is invisible to the release notes generator; aREOPENEDissue that kept its old fixVersion lies to users. - Illegal transitions are the interesting design space:
CLOSED → IN_PROGRESS(must reopen first — preserves the audit trail),OPEN → RESOLVED:Fixed(fixed without a patch? — projects differ deliberately).
Chapter 7: Encoding Process in Types
The software-design lesson the three labs share — make illegal states unrepresentable:
- Enums for states; transitions as an explicit table/switch (
EnumMap<State, Set<State>>), not scatteredifs — adding a state then forces a decision at every transition site (exhaustive switch +MissingSwitchDefaultfrom Phase 05). - Guard conditions as predicates attached to transitions, throwing a domain exception
(
IllegalTransitionException) with which guard failed — error messages as documentation. - Value objects (
SemanticVersion,VoteEntry) are immutable, parsed once at the boundary (parse-don't-validate): once constructed, they're valid by construction, and the whole downstream pipeline drops its defensive checks. - This trio — explicit state machines, guarded transitions, parse-don't-validate — is the same design language you'll find inside YARN's RM state machines and Kafka's controller; the labs are deliberately scaled-down practice of production patterns.
Lab Walkthrough Guidance
Order: Lab 01 (diff parser) → Lab 02 (semver engine) → Lab 03 (issue state machine) — independent topics; this order matches increasing design (vs parsing) content.
- Lab 01: write the hunk-header parser first with the count-invariant checks from
Chapter 2; collect oddball real diffs (
git diffwith renames, binary files, no-EOF) as test inputs; then build the metrics layer of Chapter 3. - Lab 02: implement comparison against semver §11's algorithm literally, then run the spec's own example chain as a test; add the compatibility-level engine and feed it the Phase 04 Lab 03 changes as cases.
- Lab 03: draw the state diagram before coding; implement the transition table + guards; tests should include every illegal transition (the negative space is the spec) and the audit-trail property (history append-only).
Success Criteria
You are ready for Phase 10 when you can, from memory:
- Parse a hunk header by hand and verify a hunk's count invariants on sight.
- Name three patch metrics that predict review outcomes and the norm each enforces.
- Map all four compatibility dimensions onto minimum semver bumps with one example each.
- Order
1.0.0-alpha.1,1.0.0-rc.1,1.0.0,1.0.0+build5,1.0.1-SNAPSHOTcorrectly and justify each comparison. - Sketch the issue FSM with guards, and explain why resolution-type hygiene is release correctness.
- State the parse-don't-validate principle and where each lab applies it.
Interview Q&A
Q: Why is a giant diff a problem if every line is correct?
Reviewer effectiveness collapses with size (approval-on-faith past a few hundred
lines), git blame/bisect granularity degrades, revert blast radius grows, and merge
conflicts multiply for everyone in flight. Correct-but-unreviewable still costs the
project — reviewability is a property of the change, not the code. (Mechanically
detectable: that's the Lab 01 dashboard's point.)
Q: We renamed a public method's parameter and changed its Javadoc'd behavior on null. What's the version bump? Parameter name is neither source- nor binary-breaking in Java (PATCH-eligible alone). The null-behavior change is a behavioral contract break — the dimension tools don't catch. If the old behavior was documented and plausibly relied upon: MAJOR (or MINOR with a compat flag and loud notes, per project policy). The trap in the question is answering from signatures alone.
Q: Design the state machine for a release vote thread. States: DRAFT → VOTING(72h floor) → {PASSED | FAILED | CANCELLED}; PASSED → PUBLISHED. Guards: VOTING requires artifacts+sigs staged; PASSED requires ≥3 binding +1 and binding +1 > -1 and clock ≥72h; CANCELLED from VOTING on RC-sinking defect (records reason). Events append-only for the audit trail; tally recomputed from events, never stored mutable. — Which is exactly Phase 10's Lab 03, so building it twice is the point.
References
- GNU diffutils: Unified Format — the format spec
git help diff— the git-specific extensions (renames, binary, mode)- Semantic Versioning 2.0.0 — read §9–11 (pre-release/build/precedence) twice
- Maven version ordering (ComparableVersion) — where ecosystems diverge from semver
- Spark Versioning Policy and Hadoop's InterfaceClassification
- Alexis King, Parse, don't validate — the Chapter 7 design principle, canonically stated
- A real workflow: Apache JIRA workflow documentation for Hadoop — see the PATCH_AVAILABLE conventions