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

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). count omitted means 1; start of 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 file markers, renames (similarity index headers), binary file stanzas, and mode changes.
  • Why context lines exist at all: they let patch apply 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's ComparableVersion has its own additional rules (1.0-SNAPSHOT < 1.0; qualifiers like alpha/beta/milestone/rc/ga have 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, with REOPENED as the loop-back. (Projects customize; the shape is universal.)
  • Transitions carry guards: you can't move to PATCH_AVAILABLE without an attached patch/PR link; RESOLVED requires 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; a REOPENED issue 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 scattered ifs — adding a state then forces a decision at every transition site (exhaustive switch + MissingSwitchDefault from 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 diff with 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:

  1. Parse a hunk header by hand and verify a hunk's count invariants on sight.
  2. Name three patch metrics that predict review outcomes and the norm each enforces.
  3. Map all four compatibility dimensions onto minimum semver bumps with one example each.
  4. Order 1.0.0-alpha.1, 1.0.0-rc.1, 1.0.0, 1.0.0+build5, 1.0.1-SNAPSHOT correctly and justify each comparison.
  5. Sketch the issue FSM with guards, and explain why resolution-type hygiene is release correctness.
  6. 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