A Gerrit & JGit maintainer's perspective

JGit Servlet-4 Divergence: Four Maintenance Options Compared

Honest ranked assessment of the four approaches to bridging Gerrit stable-3.13's JGit dependency through the jakarta.servlet / Jetty 12 transition.

The Problem

Why this question exists in the first place.

JGit master has moved to jakarta.servlet 6.1.0 on Jetty 12, because of the alignment with the Eclipse project lifecycle. Gerrit stable-3.13 still runs on javax.servlet 4.0.1, and is subject to Google's policy on global dependencies updates.

The above situation is caused by the fact that JGit and Gerrit belong to two different organizations with different policies:

  • JGit is part of the Eclipse Foundation and the global release cycle. Matthias Sohn is the project leader and can approve the dependency updates. Google is one of the committers, but not the owner of the project.
  • Gerrit is a project that belongs to Google and follows the company-wide dependencies system. Google has veto on the dependency updates that are used in the *.googlesource.com fork of Gerrit and requires a Library-Compliance* vote from a Googler.

NOTE: This document refers to the Servlet upgrade issue; however, the same problem also exists for many other dependencies that are misaligned between JGit and Gerrit, and for future security or maintenance dependency bumps.

To support consumers that haven't yet migrated, JGit maintains a special servlet-4 branch that tracks master but holds back the jakarta.servlet namespace move — using Jetty 12's ee8 compatibility layer to keep its HTTP/LFS modules (org.eclipse.jgit.http.server, http.test, junit.http, lfs.server, lfs.server.test) on the javax.servlet 4 API surface Gerrit needs to compile.

Scope is bounded: three reverts are the entire set of JGit-side changes needed, and no future additions to the fork are expected.

Why not consume the servlet-4 branch directly? It's designed exactly for our servlet stack — but it tracks JGit master, which has migrated to bzlmod. Gerrit stable-3.13 still uses WORKSPACE-mode builds. That single divergence — bzlmod vs WORKSPACE — rules out direct consumption.

Why stable-7.4 + three reverts instead? stable-7.4 predates JGit's bzlmod migration (still on WORKSPACE, matching Gerrit stable-3.13), so the bzlmod axis requires zero JGit-side work. The three reverts undo the jakarta.servlet / Jetty 12 bumps that stable-7.4 inherits from master — restoring the javax.servlet 4 API surface that the servlet-4 branch already provides via Jetty 12 ee8.

Once stable-3.13 ages out and aligns on the modern stack, the fork branch goes away entirely.

Four approaches address that gap. Each parks the divergence in a different place, with different long-term costs.

Four Options at a Glance

Where the divergence lives, who maintains it, and the impact on Gerrit's tree.

# Option Divergence lives in Maintained by Gerrit-tree impact
1 http_archive + patches in tools/jgit/ Gerrit change 589501 Gerrit's tree Gerrit team tools/jgit/ directory + WORKSPACE patches= field
2 Fork SHA with reverts pre-applied Gerrit change 591581 Fork branch on GitHub Gerrit team (rebases on fork) Single WORKSPACE SHA pin
3 Backport bzlmod to stable-3.13 Gerrit change 588622 Gerrit stable-3.13 branch (bzlmod backport) Gerrit team Massive — bzlmod backport on a stable branch
4 New servlet-4/stable-7.4 upstream branch Upstream JGit JGit team (parallel branch maintenance) None
Companion proposal for Options 1 & 2: both approaches remove JGit as a tracked submodule, which regresses Eclipse/IntelliJ source navigation, debugger breakpoints, and git bisect across JGit history — capabilities the submodule layout gave for free. Gerrit change 591441 ("Add developer story for build-time-patched JGit workflow") closes that gap with a documented IDE workflow against the Bazel-materialized JGit source: tools/eclipse/project.py points the generated .classpath entries directly at $(bazel info output_base)/external/jgit, so Eclipse and Bazel read the same physical files. It lands alongside whichever of Options 1 or 2 is accepted.

Per-Perspective Views

How the trade-offs shift depending on which hat you're wearing.

🌿 JGit Maintainer

Rejects Option 4 — parallel servlet-4/stable-7.N branches per Gerrit stable release scale poorly. Merge-up discipline across all of them is real ongoing work that JGit gets no direct value from. Indifferent to Options 1, 2, and 3 (those are Gerrit's problem).

⚙️ Gerrit Maintainer

Rejects Option 3 — backporting bzlmod to a stable branch is a high-risk infrastructure change on the line you're supposed to be keeping conservative. Stable-3.13 exists to not take large changes. Indifferent-to-favorable on 1, 2, and 4.

🎩 Both Hats Together

This is the position I actually hold — I wear both maintainer hats and have to weigh the trade-offs accordingly. The real choice is between Options 1 and 2. Option 3 is out (wrong cost-benefit on a stable branch). Option 4 is out (unfair externality on the JGit team for a problem that's structurally Gerrit's). Which leaves the question: patches in Gerrit's tree, or on a fork branch?

My Ranked Preference

If I were wearing both maintainer hats, here's how I'd order them — with reasoning.

🥇

Fork SHA with Pre-Applied Reverts

Option 2
Recommended

Why first

  • Semantic cleanness. Gerrit consumes JGit as code, full stop. The divergence isn't a Gerrit concern; it's a fork-of-JGit concern. The boundary between projects is honest.
  • Smallest Gerrit-tree footprint. One WORKSPACE SHA. No tools/jgit/ directory, no patches= declaration, no patch_tool, no patch-file maintenance. Looking at Gerrit's tree, you can't tell JGit is patched at all — and arguably you shouldn't be able to.
  • Established precedent. The rules_nodejs approach on stable-3.14 already does exactly this pattern and is accepted. Consistency with an existing precedent is itself a maintainer value.
  • Maintenance shape is identical to Option 1. Three reverts to rebase per JGit SHA bump. Same work, different location — and the location matters.

Concerns to address before landing

  • Fork ownership. Currently a personal fork. For long-term sustainability the fork should sit under a Gerrit-team org, not an individual's GitHub account. If the maintainer leaves, the fork shouldn't disappear with them.
  • Discoverability. A developer reading WORKSPACE sees a SHA pointing at eclipse-jgit/jgit via fork-network reachability, with no obvious signal that "this is patched." A short comment in WORKSPACE pointing at the fork branch mitigates this.
  • End-of-life cleanup. When stable-3.13 ages out, who removes the fork? Needs to be on the same checklist as removing tools/jgit/.
🥈

Build-Time Patches in tools/jgit/

Option 1
Defensible Fallback

Why second

Strictly auditable — patches are in Gerrit's git history, reviewable by Gerrit reviewers as part of normal change review, no external dependency. If the fork option had to be ruled out (org policy, fork-hosting concerns, supply-chain considerations), this is the next-best choice.

Why not first

  • Creates the "is this Gerrit code or JGit code?" tree confusion. The patches live in Gerrit's repository but they're not Gerrit's code.
  • Build-time patching is a real ongoing maintenance vector — patch_tool quirks, patch context drift, WORKSPACE hunk stripping. Every WORKSPACE bump requires a patch refresh PR.
  • The directory persists indefinitely in Gerrit's tree. Death by a thousand small things.
🥉

New JGit Upstream Branch (servlet-4/stable-7.4)

Option 4
Ideal but Unfair

Why third

Cleanest in the abstract — divergence lives where it semantically belongs, on the project that knows JGit best. No Gerrit-side patches at all. Reviewable on JGit's own gerrit. Standard "maintenance branch" pattern that the open-source ecosystem understands well.

Why not first

  • Externalises the cost onto the JGit team for a problem that's structurally a Gerrit-stable-line-mismatch concern. JGit doesn't gain anything from carrying servlet-4/stable-7.4; only Gerrit's old stable lines benefit.
  • Scales badly. Every Gerrit stable line that needs this gets its own servlet-4/stable-7.N branch with merge-up discipline. Per-consumer maintenance branches don't compose.
  • Asking another project to take on maintenance for our backwards-compat needs is poor citizenship unless absolutely necessary.
🚫

Backport bzlmod to stable-3.13

Option 3
Reject

Why last

Wrong cost-benefit on a stable branch. Stable-3.13 exists to be conservative — to receive security fixes and minor bumps, not infrastructure overhauls. Backporting bzlmod is the kind of change that historically causes regressions you don't catch for months.

Compounding problems

  • Even with bzlmod, JGit's servlet-4 branch tracks master, so stable-3.13 would pick up master-only feature drift in a stable dependency graph. Version-skew risk is real even if CI is green today.
  • Per-stable-branch backports scale poorly — 3.13 today, 3.12 tomorrow, the precedent compounds.
  • If bzlmod really needed to land on stable-3.13, that should be an independent decision driven by tooling reasons — not a JGit-divergence workaround.
TL;DR

Pick Option 2 — Fork SHA with pre-applied reverts.

It's structurally cleanest, precedent-aligned with rules_nodejs on stable-3.14, and bounded by stable-3.13's lifecycle.

Option 1 is a defensible fallback if there's an objection to the fork model. Option 4 would be the platonic ideal but is unfair to JGit. Option 3 is overkill.

The one mitigation worth landing alongside Option 2: move the fork to a team-owned org (not a personal account) and add a single explanatory comment in WORKSPACE pointing at the fork branch. Those two things turn "ad-hoc personal patch" into "documented Gerrit-team-maintained variant" — which is what makes Option 2 sustainable across maintainer turnover.