mirror of
https://github.com/Heretek-AI/GDevelop-Heretek.git
synced 2026-10-11 21:09:52 +00:00
fix(ci): pin fork-divergence guard to a baseline commit
The fork-divergence job has been red since upstream pushed on 2026-09-23:
191 modified paths vs 126 allowlisted, 65 "unexpected", plus one unexpected
upstreamMissing entry.
Root cause is not fork divergence. The guard compared the fork against the
live refs/remotes/upstream/master (or the API's master tip), so every
upstream push that touched a file the fork also carries read as newly
introduced divergence. All 66 reported paths were byte-identical to the
fork's previous upstream snapshot — the fork had never modified them. The
one upstreamMissing entry came from an upstream commit that touched it in
that same window.
Compare against a baseline commit instead:
* fork-divergence.json records `baselineCommit` (here the merge base of
the last upstream sync, b9109cfbaa). The reported set now only moves
when the fork's own content moves, which is the only signal a reviewer
can act on.
* Resolution falls back to `git merge-base HEAD upstream/master` when the
field is absent, and the baseline tree is read either from a local
commit or the GitHub trees API by SHA — still no ~1.7 GB clone in CI.
* A baseline that is recorded but unreachable is a hard error (exit 2)
rather than a silent fallback to live upstream, which would reintroduce
exactly the false positives the baseline exists to remove.
Re-anchoring also drops entries that have converged: 10 paths the fork once
modified but which now match the baseline (upstream fixes adopted by the
merge) are no longer divergence. Verified all 10 are byte-identical to the
baseline and none is touched by a non-merge fork commit, so nothing is lost.
Also updates MAINTENANCE.md, scripts/README.md, AGENTS.md, docs and the
ci.yml comment to the corrected semantics and counts (180 paths: 116/57/7).
Verified: guard exits 0 locally against upstream at a later HEAD; exits 0 in
a depth-1 clone with no upstream ref against the API-by-SHA path; still exits
1 on a new modification, a stale entry, and a restored upstreamMissing file;
exits 2 with an actionable message when no baseline can be resolved.
This commit is contained in:
@@ -53,8 +53,12 @@ jobs:
|
||||
# listed path no longer diverges (stale entry). Without it, divergence only
|
||||
# surfaces as a merge conflict on the next upstream sync.
|
||||
#
|
||||
# The upstream tree is read through the GitHub trees API rather than a
|
||||
# clone: 4ian/GDevelop is ~1.7 GB and this job runs on every PR.
|
||||
# The comparison is against the manifest's `baselineCommit`, not live
|
||||
# `upstream/master` — otherwise every upstream push that touched a shared
|
||||
# file turned this job red with no fork change to review.
|
||||
#
|
||||
# The baseline tree is read through the GitHub trees API by commit SHA
|
||||
# rather than a clone: 4ian/GDevelop is ~1.7 GB and this job runs on every PR.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -350,10 +350,12 @@ clang-tidy, ASan/UBSan, visual tests).
|
||||
|
||||
## Fork Divergence
|
||||
|
||||
**This fork differs from upstream in 164 paths out of ~6,700.** Expect merge conflicts there.
|
||||
**This fork differs from upstream in 180 paths out of ~6,700.** Expect merge conflicts there.
|
||||
|
||||
**`fork-divergence.json` is the authoritative allowlist** of every path the fork intentionally diverges
|
||||
on, in three buckets (`modified` 110, `forkOnly` 54, `upstreamMissing` 7). It is generated and enforced by
|
||||
on, in three buckets (`modified` 116, `forkOnly` 57, `upstreamMissing` 7), measured against the
|
||||
`baselineCommit` the manifest records — not against live `upstream/master`, so upstream's own pushes do
|
||||
not read as fork divergence. It is generated and enforced by
|
||||
`scripts/check-fork-divergence.js`: the `fork-divergence` job in `.github/workflows/ci.yml` fails when a
|
||||
path diverges that is not listed, or when a listed path stops diverging. Run
|
||||
`node scripts/check-fork-divergence.js` after any change that touches upstream-owned files, and
|
||||
|
||||
+38
-6
@@ -14,14 +14,15 @@ rather than trusting them if the tree has moved.
|
||||
|
||||
### What diverges
|
||||
|
||||
The fork diverges on **164 paths** out of ~6,700. `fork-divergence.json` is the
|
||||
checked-in allowlist, in three buckets:
|
||||
The fork diverges on **180 paths** out of ~6,700. `fork-divergence.json` is the
|
||||
checked-in allowlist, in three buckets, measured against the baseline commit the
|
||||
manifest records:
|
||||
|
||||
| Bucket | Count | Meaning |
|
||||
|---|---:|---|
|
||||
| `modified` | 110 | Path exists upstream but the fork's content differs |
|
||||
| `forkOnly` | 54 | Path does not exist upstream at all |
|
||||
| `upstreamMissing` | 7 | Path exists upstream but the fork deliberately does not carry it |
|
||||
| `modified` | 116 | Existed upstream at the baseline and the fork's content differs |
|
||||
| `forkOnly` | 57 | The fork created it; upstream had no such path at the baseline |
|
||||
| `upstreamMissing` | 7 | Existed upstream at the baseline and the fork deliberately does not carry it |
|
||||
|
||||
`upstreamMissing` is the dangerous bucket: an upstream merge re-introduces those
|
||||
files, and nothing else notices. The 7 are:
|
||||
@@ -59,6 +60,36 @@ git fetch --depth=1 upstream master
|
||||
The comparison includes uncommitted work, so `--update` describes the tree you
|
||||
actually have, not just the last commit.
|
||||
|
||||
### The baseline commit
|
||||
|
||||
The guard measures against **the baseline commit recorded in the manifest**
|
||||
(`baselineCommit`), not against live `upstream/master`. That distinction is what
|
||||
makes it usable:
|
||||
|
||||
- Measured against live upstream, every upstream push that touched a file the
|
||||
fork also carries read as "new divergence" — a locale file upstream
|
||||
regenerates, a platformer behavior upstream edits. On 2026-09-23 upstream
|
||||
pushed 70 such files in one afternoon and the guard went red without the fork
|
||||
having changed anything. A guard that fires on upstream's own activity gets
|
||||
ignored, which is exactly when real divergence slips through.
|
||||
- Measured against a fixed baseline, the reported set only moves when **the
|
||||
fork's own content** moves. The 66 files above produced zero actionable
|
||||
signal; they are upstream movement, not fork divergence.
|
||||
|
||||
`baselineCommit` is the last upstream point the fork reconciled with — normally
|
||||
the merge base of the upstream sync, so it advances when a sync PR merges and the
|
||||
reported set re-derives from the newly-merged tree. Resolution order when the
|
||||
field is absent: `git merge-base HEAD upstream/master`. Both paths are stable
|
||||
across upstream pushing between syncs, which is the property the guard needs.
|
||||
|
||||
`--update` records the resolved baseline, and additionally drops entries that
|
||||
have *converged*: a path the fork once modified but which now matches the
|
||||
baseline (an upstream fix adopted by the merge, say) is no longer divergence and
|
||||
must leave the manifest, or the next `--update` keeps re-listing it. If the
|
||||
guard reports the manifest baseline differing from the comparison baseline, a
|
||||
bucket may look stale because the two describe different upstream points — run
|
||||
`--update` to re-anchor.
|
||||
|
||||
### Why the manifest excludes itself
|
||||
|
||||
`fork-divergence.json` and `.sonarcloud-drift-baseline` are filtered out of the
|
||||
@@ -73,7 +104,8 @@ game, including files the fork added.
|
||||
commit message. A future sync reviewer reads that manifest, not the PR.
|
||||
|
||||
Do not add an entry to silence the guard for something accidental. Revert it
|
||||
instead — that is the entire point.
|
||||
instead — that is the entire point. Note that `--update` regenerates the whole
|
||||
manifest: review the diff for entries it *removed*, not just those it added.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ each phase below cites them.
|
||||
| **Engine (culling / instancing)** | Scene-level 3D frustum culling, `Model3DInstancePool` + `useInstancing`, TileMap culling for non-Simple maps (nested maps fail safe to full draw) |
|
||||
| **Unlocked client features** | No watermark/splash clamp, network preview, debugger, Heretek release feed |
|
||||
| **Editor UX landed** | Delete key in Instances list, drag-drop image → Sprite on canvas |
|
||||
| **Divergence hygiene** | 164 intentional paths allowlisted; `fork-divergence.json` + CI guard + daily PR-only upstream sync |
|
||||
| **Divergence hygiene** | 180 intentional paths allowlisted against a pinned baseline; `fork-divergence.json` + CI guard + daily PR-only upstream sync |
|
||||
|
||||
Direction A (own AI/BYOK) is substantially delivered as of cycle 34 / `ae815f489`. The work
|
||||
below is what remains to make it *trustworthy* and *maintained*.
|
||||
|
||||
+2
-11
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$comment": "Checked-in allowlist of every path this fork intentionally diverges from 4ian/GDevelop on. Regenerate with `node scripts/check-fork-divergence.js --update`. Enforced by `node scripts/check-fork-divergence.js` in CI. See MAINTENANCE.md.",
|
||||
"updatedAt": "2026-09-23",
|
||||
"upstreamSource": "refs/remotes/upstream/master",
|
||||
"baselineCommit": "b9109cfbaaaf19c31b5af4d315317fb67d8903c3",
|
||||
"upstreamSource": "b9109cfbaaaf19c31b5af4d315317fb67d8903c3",
|
||||
"modified": [
|
||||
".github/workflows/build-storybook.yml",
|
||||
".github/workflows/extract-translations.yml",
|
||||
@@ -99,25 +100,15 @@
|
||||
"newIDE/app/src/Profile/Subscription/SubscriptionChecker.js",
|
||||
"newIDE/app/src/Profile/Subscription/SubscriptionContext.js",
|
||||
"newIDE/app/src/ProjectManager/LoadingScreenEditor.js",
|
||||
"newIDE/app/src/PropertiesEditor/index.js",
|
||||
"newIDE/app/src/ResourcesEditor/ResourcePropertiesEditor/index.js",
|
||||
"newIDE/app/src/ResourcesEditor/Toolbar.js",
|
||||
"newIDE/app/src/ResourcesEditor/index.js",
|
||||
"newIDE/app/src/ResourcesList/index.js",
|
||||
"newIDE/app/src/SceneEditor/EditorsDisplay.flow.js",
|
||||
"newIDE/app/src/SceneEditor/MosaicEditorsDisplay/index.js",
|
||||
"newIDE/app/src/SceneEditor/SwipeableDrawerEditorsDisplay/index.js",
|
||||
"newIDE/app/src/SceneEditor/index.js",
|
||||
"newIDE/app/src/UI/InlineCheckbox.js",
|
||||
"newIDE/app/src/UI/List.js",
|
||||
"newIDE/app/src/UI/SortableVirtualizedItemList/ItemRow.js",
|
||||
"newIDE/app/src/UI/SortableVirtualizedItemList/index.js",
|
||||
"newIDE/app/src/Utils/Analytics/EventSender.js",
|
||||
"newIDE/app/src/Utils/GDevelopServices/ApiConfigs.js",
|
||||
"newIDE/app/src/Utils/GDevelopServices/Authentication.js",
|
||||
"newIDE/app/src/Utils/GDevelopServices/Generation.js",
|
||||
"newIDE/app/src/Utils/GDevelopServices/Usage.js",
|
||||
"newIDE/app/src/stories/componentStories/ResourcesList/ResourcesList.stories.js",
|
||||
"newIDE/electron-app/app/main.js",
|
||||
"newIDE/electron-app/app/package-lock.json",
|
||||
"newIDE/electron-app/app/package.json",
|
||||
|
||||
+9
-6
@@ -8,12 +8,15 @@ Repository-wide tooling. Per-package scripts live in each package's own
|
||||
See [MAINTENANCE.md](../MAINTENANCE.md) for how these fit together.
|
||||
|
||||
- **check-fork-divergence.js**: keeps the fork's divergence from upstream
|
||||
`4ian/GDevelop` bounded. Compares this tree against upstream and checks the
|
||||
result against the checked-in `fork-divergence.json` allowlist; exits 1 on a
|
||||
path that diverges without being listed or a listed path that no longer
|
||||
diverges. `--update` rewrites the allowlist, `--list` prints the divergence
|
||||
set. Runs in `ci.yml` (`fork-divergence` job) and, informationally, in
|
||||
`upstream-sync.yml`.
|
||||
`4ian/GDevelop` bounded. Compares this tree against the **baseline commit**
|
||||
recorded in `fork-divergence.json` (falling back to
|
||||
`git merge-base HEAD upstream/master`) and checks the result against the
|
||||
checked-in allowlist; exits 1 on a path that diverges without being listed
|
||||
or a listed path that no longer diverges. Measuring against a fixed baseline
|
||||
rather than live `upstream/master` is what keeps upstream's own pushes from
|
||||
reading as fork divergence. `--update` rewrites the allowlist and records the
|
||||
baseline, `--list` prints the divergence set. Runs in `ci.yml`
|
||||
(`fork-divergence` job) and, informationally, in `upstream-sync.yml`.
|
||||
- **sonarcloud-drift.js**: compares the project's open SonarCloud issue count
|
||||
against `.sonarcloud-drift-baseline` and fails when growth exceeds the
|
||||
threshold. Self-seeds the baseline when it is missing and writes `count` /
|
||||
|
||||
@@ -25,12 +25,26 @@
|
||||
* node scripts/check-fork-divergence.js --update # rewrite the allowlist
|
||||
* node scripts/check-fork-divergence.js --list # print the diff, exit 0
|
||||
*
|
||||
* Upstream tree lookup, in order of preference:
|
||||
* 1. `git ls-tree` of a local upstream ref (`upstream/master`, then the
|
||||
* `origin/upstream-sync-*` branches the sync workflow pushes), when one
|
||||
* exists and is reachable;
|
||||
* 2. the GitHub trees API (`GH_TOKEN` / `GITHUB_TOKEN`), which needs no
|
||||
* clone of the ~1.7 GB upstream repository.
|
||||
* The comparison is against the *baseline commit* recorded in the manifest,
|
||||
* not against live `upstream/master`. Comparing against a moving ref made the
|
||||
* guard fire on every upstream push that touched a shared file — upstream
|
||||
* advancing a file the fork had never modified reads as "new divergence",
|
||||
* which is not something a reviewer can act on. The baseline is the point the
|
||||
* fork last reconciled with upstream, so the reported set only changes when
|
||||
* the fork's own content does.
|
||||
*
|
||||
* Baseline resolution, in order of preference:
|
||||
* 1. the manifest's `baselineCommit`, when set (the normal case);
|
||||
* 2. `git merge-base HEAD upstream/master` — the fork merges upstream
|
||||
* rather than rebasing, so this is the last reconciled point. Used once
|
||||
* to seed a manifest that predates the field.
|
||||
* When the baseline cannot be resolved, the guard falls back to live upstream
|
||||
* and says so, rather than failing.
|
||||
*
|
||||
* Baseline tree lookup, in order of preference:
|
||||
* 1. `git ls-tree` of the local commit, when it is available locally;
|
||||
* 2. the GitHub trees API by commit SHA (`GH_TOKEN` / `GITHUB_TOKEN`), which
|
||||
* needs no clone of the ~1.7 GB upstream repository.
|
||||
*
|
||||
* Exit codes: 0 clean, 1 divergence mismatch, 2 fatal error.
|
||||
*/
|
||||
@@ -106,38 +120,58 @@ function localTree() {
|
||||
return map;
|
||||
}
|
||||
|
||||
/** Upstream tree as { path: blobSha }, via a local ref when possible. */
|
||||
function upstreamTree() {
|
||||
const candidates = ['refs/remotes/upstream/master', 'refs/heads/upstream/master'];
|
||||
for (const ref of candidates) {
|
||||
try {
|
||||
git('rev-parse', '--verify', '--quiet', ref);
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
const out = git('ls-tree', '-r', '-z', ref);
|
||||
const map = new Map();
|
||||
for (const entry of out.split('\0')) {
|
||||
if (!entry) continue;
|
||||
const tab = entry.indexOf('\t');
|
||||
if (tab === -1) continue;
|
||||
const meta = entry.slice(0, tab).split(' ');
|
||||
if (meta[1] === 'blob') map.set(entry.slice(tab + 1), meta[2]);
|
||||
}
|
||||
return { source: ref, tree: map };
|
||||
function revParse(ref) {
|
||||
try {
|
||||
return git('rev-parse', '--verify', '--quiet', `${ref}^{commit}`).trim();
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to the GitHub trees API. No upstream clone required.
|
||||
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
||||
if (!token) {
|
||||
throw new Error(
|
||||
'No local upstream ref and no GH_TOKEN/GITHUB_TOKEN. Either run\n' +
|
||||
' git remote add upstream https://github.com/4ian/GDevelop.git\n' +
|
||||
' git fetch --depth=1 upstream master\n' +
|
||||
'or export GH_TOKEN.'
|
||||
);
|
||||
/** Local tree of a commit-ish as { path: blobSha }, or null when absent. */
|
||||
function treeOfCommit(ref) {
|
||||
// Probe first: `git ls-tree` on a missing object writes "fatal: not a tree
|
||||
// object" to stderr, which is noise in CI where the baseline is expected to
|
||||
// be absent and the API is the intended path.
|
||||
if (!revParse(ref)) return null;
|
||||
let out;
|
||||
try {
|
||||
out = git('ls-tree', '-r', '-z', ref);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
const url = `https://api.github.com/repos/${UPSTREAM_REPO}/git/trees/${UPSTREAM_REF}?recursive=1`;
|
||||
const map = new Map();
|
||||
for (const entry of out.split('\0')) {
|
||||
if (!entry) continue;
|
||||
const tab = entry.indexOf('\t');
|
||||
if (tab === -1) continue;
|
||||
const meta = entry.slice(0, tab).split(' ');
|
||||
if (meta[1] === 'blob') map.set(entry.slice(tab + 1), meta[2]);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/** The upstream commit the fork is being compared against. */
|
||||
function mergeBaseCommit() {
|
||||
for (const ref of ['refs/remotes/upstream/master', 'refs/heads/upstream/master']) {
|
||||
const refSha = revParse(ref);
|
||||
if (!refSha) continue;
|
||||
try {
|
||||
const base = git('merge-base', 'HEAD', refSha).trim();
|
||||
if (base) return base;
|
||||
} catch (_) {
|
||||
// No shared history locally (shallow clone) — a baselineCommit in the
|
||||
// manifest is the way out.
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** GitHub trees API by commit SHA. No upstream clone required. */
|
||||
function apiTree(ref) {
|
||||
const token = process.env.GH_TOKEN || process.env.GITHUB_TOKEN;
|
||||
if (!token) return null;
|
||||
const url = `https://api.github.com/repos/${UPSTREAM_REPO}/git/trees/${ref}?recursive=1`;
|
||||
const body = execFileSync(
|
||||
'curl',
|
||||
['-fsSL', '-H', `Authorization: Bearer ${token}`, '-H', 'Accept: application/vnd.github+json', url],
|
||||
@@ -146,19 +180,100 @@ function upstreamTree() {
|
||||
const json = JSON.parse(body);
|
||||
if (json.truncated) {
|
||||
throw new Error(
|
||||
`Upstream tree listing was truncated by the GitHub API; use a local upstream ref instead.`
|
||||
`Upstream tree listing was truncated by the GitHub API; use a local upstream commit instead.`
|
||||
);
|
||||
}
|
||||
const map = new Map();
|
||||
for (const entry of json.tree) {
|
||||
if (entry.type === 'blob') map.set(entry.path, entry.sha);
|
||||
}
|
||||
return { source: `api:${UPSTREAM_REPO}@${UPSTREAM_REF}`, tree: map };
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the baseline commit and its tree.
|
||||
*
|
||||
* `manifest` may be null while seeding (`--update` on a manifest that has no
|
||||
* baseline yet). The returned `source` is for reporting, and `note` carries
|
||||
* any caveat worth surfacing (fallback to live upstream, missing baseline).
|
||||
*/
|
||||
function baselineTree(manifest) {
|
||||
const wanted = (manifest && manifest.baselineCommit) || null;
|
||||
|
||||
const commit = wanted || mergeBaseCommit();
|
||||
if (!commit) {
|
||||
// Nothing to anchor on. Compare against live upstream so the guard still
|
||||
// reports something, but be explicit that the number is upstream churn.
|
||||
const refSha = revParse('refs/remotes/upstream/master');
|
||||
if (!refSha) {
|
||||
throw new Error(
|
||||
'Cannot resolve an upstream baseline. Either set `baselineCommit` in\n' +
|
||||
`${MANIFEST_NAME}, or provide upstream locally:\n` +
|
||||
' git remote add upstream https://github.com/4ian/GDevelop.git\n' +
|
||||
' git fetch --depth=1 upstream master\n' +
|
||||
'or export GH_TOKEN.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
source: `live:${UPSTREAM_REPO}@${UPSTREAM_REF}`,
|
||||
commit: refSha,
|
||||
tree: treeOfCommit(refSha),
|
||||
note:
|
||||
'no baseline commit — compared against live upstream/master, so upstream\n' +
|
||||
'pushes that touch shared files appear below',
|
||||
};
|
||||
}
|
||||
|
||||
const local = treeOfCommit(commit);
|
||||
if (local) return { source: commit, commit, tree: local };
|
||||
|
||||
const viaApi = apiTree(commit);
|
||||
if (viaApi) return { source: `api:${commit}`, commit, tree: viaApi };
|
||||
|
||||
// Baseline recorded but unreachable. Falling back to live upstream would
|
||||
// silently reintroduce the false-positive churn this baseline exists to
|
||||
// avoid, so a recorded baseline that cannot be read is a hard error.
|
||||
if (wanted) {
|
||||
throw new Error(
|
||||
`Baseline commit ${wanted} from ${MANIFEST_NAME} is not available locally\n` +
|
||||
'and the GitHub API could not be read. Fetch it or set GH_TOKEN.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
source: `live:${UPSTREAM_REPO}@${UPSTREAM_REF}`,
|
||||
commit,
|
||||
tree: treeOfCommit(revParse('refs/remotes/upstream/master')),
|
||||
note: `baseline ${commit} unreachable — compared against live upstream/master`,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Fork divergence, measured against the baseline commit.
|
||||
*
|
||||
* For each path, three trees matter: the baseline `B`, the fork `L`, and the
|
||||
* current upstream `U`.
|
||||
*
|
||||
* L == U converged: the fork and upstream agree. Nothing to reconcile,
|
||||
* and reporting it is what broke the guard (upstream moving a
|
||||
* file the fork never touched).
|
||||
* path absent from B the fork created it -> forkOnly
|
||||
* path in B, absent from L the fork deleted it, still upstream -> upstreamMissing
|
||||
* otherwise the fork changed an upstream-owned file -> modified
|
||||
*/
|
||||
function computeDivergence() {
|
||||
const local = localTree();
|
||||
const { source, tree: upstream } = upstreamTree();
|
||||
let manifest = null;
|
||||
try {
|
||||
manifest = readManifest();
|
||||
} catch (_) {
|
||||
// A malformed manifest should not stop `--list`; verification reports it
|
||||
// through the normal unexpected/stale diff.
|
||||
}
|
||||
const {
|
||||
source,
|
||||
commit: baseline,
|
||||
tree: upstream,
|
||||
note,
|
||||
} = baselineTree(manifest);
|
||||
|
||||
const modified = [];
|
||||
const forkOnly = [];
|
||||
@@ -172,8 +287,12 @@ function computeDivergence() {
|
||||
|
||||
for (const [filePath, sha] of local) {
|
||||
if (selfReferential.has(filePath)) continue;
|
||||
if (!upstream.has(filePath)) forkOnly.push(filePath);
|
||||
else if (upstream.get(filePath) !== sha) modified.push(filePath);
|
||||
if (upstream.get(filePath) === sha) continue; // converged; nothing to reconcile
|
||||
if (!upstream.has(filePath)) {
|
||||
forkOnly.push(filePath);
|
||||
} else if (sha !== upstream.get(filePath)) {
|
||||
modified.push(filePath);
|
||||
}
|
||||
}
|
||||
for (const filePath of upstream.keys()) {
|
||||
if (selfReferential.has(filePath)) continue;
|
||||
@@ -183,7 +302,7 @@ function computeDivergence() {
|
||||
modified.sort();
|
||||
forkOnly.sort();
|
||||
upstreamMissing.sort();
|
||||
return { source, modified, forkOnly, upstreamMissing };
|
||||
return { source, baseline, note, modified, forkOnly, upstreamMissing };
|
||||
}
|
||||
|
||||
function readManifest() {
|
||||
@@ -209,9 +328,13 @@ function main() {
|
||||
const list = argv.includes('--list');
|
||||
|
||||
const actual = computeDivergence();
|
||||
const baselineLine = `Baseline commit: ${actual.baseline}`;
|
||||
const noteLine = actual.note ? `Note: ${actual.note}` : null;
|
||||
|
||||
if (list) {
|
||||
console.log(`Upstream source: ${actual.source}`);
|
||||
console.log(baselineLine);
|
||||
if (noteLine) console.log(noteLine);
|
||||
console.log(`modified: ${actual.modified.length}`);
|
||||
console.log(`forkOnly: ${actual.forkOnly.length}`);
|
||||
console.log(`upstreamMissing: ${actual.upstreamMissing.length}`);
|
||||
@@ -229,6 +352,7 @@ function main() {
|
||||
'4ian/GDevelop on. Regenerate with `node scripts/check-fork-divergence.js --update`. ' +
|
||||
'Enforced by `node scripts/check-fork-divergence.js` in CI. See MAINTENANCE.md.',
|
||||
updatedAt: new Date().toISOString().slice(0, 10),
|
||||
baselineCommit: actual.baseline,
|
||||
upstreamSource: actual.source,
|
||||
modified: actual.modified,
|
||||
forkOnly: actual.forkOnly,
|
||||
@@ -244,12 +368,15 @@ function main() {
|
||||
}
|
||||
console.log(
|
||||
`Wrote ${path.relative(REPO_ROOT, MANIFEST_PATH)} ` +
|
||||
`(${actual.modified.length} modified, ${actual.forkOnly.length} fork-only, ` +
|
||||
`(baseline ${actual.baseline}, ${actual.modified.length} modified, ` +
|
||||
`${actual.forkOnly.length} fork-only, ` +
|
||||
`${actual.upstreamMissing.length} upstream-missing)` +
|
||||
(Object.keys(counts).length ? ` delta vs previous: ${JSON.stringify(counts)}` : '')
|
||||
);
|
||||
} else {
|
||||
console.log(`Wrote ${path.relative(REPO_ROOT, MANIFEST_PATH)}`);
|
||||
console.log(
|
||||
`Wrote ${path.relative(REPO_ROOT, MANIFEST_PATH)} (baseline ${actual.baseline})`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -264,6 +391,16 @@ function main() {
|
||||
}
|
||||
|
||||
console.log(`Upstream source: ${actual.source}`);
|
||||
console.log(baselineLine);
|
||||
if (noteLine) console.log(noteLine);
|
||||
if (manifest.baselineCommit && manifest.baselineCommit !== actual.baseline) {
|
||||
console.error(
|
||||
` NOTE: manifest baseline ${manifest.baselineCommit} differs from the ` +
|
||||
`comparison baseline ${actual.baseline}.\n` +
|
||||
` A bucket may look stale because the two describe different ` +
|
||||
`upstream points.`
|
||||
);
|
||||
}
|
||||
const buckets = ['modified', 'forkOnly', 'upstreamMissing'];
|
||||
let failed = false;
|
||||
|
||||
@@ -298,7 +435,11 @@ function main() {
|
||||
'\nDivergence from upstream changed. Two acceptable resolutions:\n' +
|
||||
' 1. The new divergence is intended -> record it:\n' +
|
||||
' node scripts/check-fork-divergence.js --update && git add fork-divergence.json\n' +
|
||||
' 2. It was accidental -> revert it, so the file stays in sync with upstream.'
|
||||
' 2. It was accidental -> revert it, so the file stays in sync with upstream.\n' +
|
||||
'\n' +
|
||||
'`--update` also re-anchors the baseline and drops allowlist entries that\n' +
|
||||
'have converged with upstream; review the paths it removes, not just those\n' +
|
||||
'it adds.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user