feat: add merge-report command that sweeps the branch topology for unpropagated fixes - #39
Merged
Merged
Conversation
Detects commits on a source branch (e.g. support/1.x) whose patch content has no equivalent on one or more target branches (develop, or a peer support/* branch) -- catching the case where a bugfix lands on a maintenance branch but never reaches develop, which becomes a regression for a customer upgrading past that branch. Uses git cherry -v (patch-id comparison) rather than ancestry diffing, so a cherry-picked/re-landed commit under a new sha is correctly recognized as already propagated. Filters candidates against the same exclude-labels config and cached, event-sourced Driver entries the changelog command already reads (dependencies, wontfix, etc. -- no second API surface), plus a checked-in, sha-keyed .gitflow-changelog-merge-ignore.yml for residual human-judgment cases. Reports everything, unfiltered, sorted oldest-first; fail-on-outstanding-after-days (default 14) governs pass/fail without touching what's shown. Ships as a sibling action (merge-report/action.yml) and a `merge-report` CLI subcommand, sharing git.ts/cache/Driver infrastructure with the existing changelog generator. Closes #38.
jfallows
force-pushed
the
claude/gitflow-merge-report-66igkl
branch
from
July 20, 2026 20:21
c5cb975 to
a06244e
Compare
…ce/targets pair Replace the source/targets shape with auto-discovery: list every branch matching the mainline branch (default develop) or a configured support-branch pattern (default support/(\d+)\.x), then derive each branch's sources purely from naming/version convention -- mainline's sources are every support branch that exists, support/N.x's sources are every support/M.x with M < N, and the lowest surviving support branch has none at all and is trivially clean by definition. This makes the report self-adjusting: cutting a new support branch from develop needs no config change anywhere, and a scheduled run and a manual workflow_dispatch run produce identical results since neither requires the caller to supply the current topology. An explicit target (+ optional sources override) still exists for on-demand narrowing. Adds src/topology.ts (pure computeTopology, no git involved) and git.ts's listBranches/resolveRef (prefers origin/<name>, falls back to the bare name for a plain local clone). The renderer now produces one section per discovered branch, including an explicit "nothing to check"/"nothing outstanding" state rather than omitting a clean branch. Per the GitHub Actions constraint that a schedule trigger always runs the workflow file on the repo's default branch (no branch-selection equivalent to workflow_dispatch), this only works as one workflow living on the mainline branch -- README updated with the required `git fetch origin '+refs/heads/*:refs/remotes/origin/*'` step, since actions/checkout's default fetch refspec only brings full history for the one ref it checks out, even at fetch-depth 0. Updates #38 to match.
commitDate's %cI format renders a UTC commit date as trailing "Z" on the CI runner's git version and "+00:00" locally — both are valid ISO 8601, so the assertion should accept either instead of assuming one.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #38.
Adds
merge-report, a sibling command to the existing changelog generator, that detects commits on one branch whose patch content has no equivalent on another that should have it. This is the reverse of a "backport": a fix that lands on a maintenance branch but never reachesdevelopbecomes a regression for a customer upgrading past that branch.Rather than taking an explicit source/targets pair, it discovers the branch topology itself and sweeps it in one pass:
develop) or a configured support-branch pattern (defaultsupport/(\d+)\.x)support/N.x's sources are everysupport/M.xwithM < N; the lowest surviving support branch has none at all and is trivially clean by definitiontarget(+ optionalsourcesoverride) still exists for on-demand narrowing, but the default, parameter-free invocation is always the full sweepThis makes the report self-adjusting — cutting a new support branch from
developneeds no config change anywhere — and makes a scheduled run and a manualworkflow_dispatchrun produce identical results, since neither requires the caller to supply the current topology.git cherry -v <target> <source>(patch-id comparison), not ancestry diffing — a cherry-picked/re-landed commit under a new sha is correctly recognized as already propagated, avoiding the false-positive flood a plaingit log target..sourceproduces.exclude-labelsconfig and cached, event-sourced Driver entries the changelog command already reads — no second API surface.excludedShas()answers "is this sha's PR/issue labeled to be skipped" independent of enhancement/bug categorization..gitflow-changelog-merge-ignore.ymlfor residual cases the label sweep can't resolve — sha-keyed rather than a commit-message convention, since message conventions aren't consistent enough to filter on reliably.fail-on-outstanding-after-days(default 14) governs pass/fail only, never what's rendered.scheduletrigger always runs the workflow file on the repo's default branch — no branch-selection equivalent toworkflow_dispatch. So this has to be one workflow living on the mainline branch, not duplicated per branch.git cherrydoesn't need a branch checked out, only present locally, so a singlefetch-depth: 0checkout plus an explicitgit fetch origin '+refs/heads/*:refs/remotes/origin/*'(actions/checkout's default fetch refspec only brings full history for the one ref it checks out, even atfetch-depth: 0) is enough to sweep the whole topology in one job.merge-report/action.yml+src/merge-report-action.ts, built tomerge-report/index.jsthe same waydist/index.jsis) and amerge-reportCLI subcommand added to the existingcli.tswithout changing its default (no-subcommand) behavior.Changes
src/topology.ts: purecomputeTopology()— no git involved, just branch names/versions in,{target, sources}[]outsrc/git.ts:unmatchedCommits(),commitDate(),listBranches(),resolveRef()(prefersorigin/<name>, falls back to the bare name for a plain local clone)src/drivers/github.ts:excludedShas()src/merge-ignore.ts: sha-keyed ignore-list loadersrc/merge-report.ts: discovers topology, sweeps every (target, source) pair, filters, age-sortssrc/render/merge-report.ts: one markdown section per branchsrc/repo-config.ts+src/merge-report-config.ts:mainline-branch/support-branch-patternpolicy (alongside the sharedexclude-labels)src/merge-report-cli.ts+src/cli.ts:merge-reportsubcommandsrc/merge-report-action.ts: writesoutput-path, writes a GitHub Actions job summary, fails the run past the age thresholdmerge-report/action.yml: sibling action manifestpackage.json,.gitignore,.github/workflows/release.yml: build + release wiring formerge-report/index.js, mirroringdist/README.md:## merge-reportsection, including the requiredgit fetchstepTest plan
npm run typecheckpassesnpm test— 195 tests passing across 18 files, including:test/topology.test.ts:computeTopology— mainline gets every support branch,support/N.xgets only lower-numbered peers, lowest branch gets none, numeric (not lexicographic) version sort, self-adjusts when a branch is removedtest/git-cherry.test.ts:unmatchedCommits/commitDate(from the original commit) +listBranches/resolveRef— dedupes local vs.origin/-prefixed, prefersorigin/<name>when present, falls back to the bare name otherwisetest/github-driver.test.ts:excludedShaslabel-intersection behaviortest/merge-ignore.test.ts: sha-keyed ignore-file parsingtest/merge-report.test.ts: end-to-end against real git fixtures — genuine gap vs. already-cherry-picked, full-topology sweep (mainline sees every support branch,support/N.xsees only lower peers), lowest branch trivially clean,targetalone vs.target+sourcesoverride, exclude-label filtering, merge-ignore filtering, oldest-first sortnpm run buildsucceeds for all three entrypoints (dist/cli.js,dist/index.js,merge-report/index.js)npm run lintfails on this branch the same way it fails ondevelop(missingeslint.config.js, an ESLint 9 migration gap) — confirmed pre-existing, unrelated to this change, left out of scope.