fix: carry a job's !reference through extends at every depth (#1970)
build / yamllint (push) Has been cancelled
build / smoke-test (1.2.23, 24) (push) Has been cancelled
build / smoke-test (1.2.23, 25) (push) Has been cancelled
build / smoke-test (1.3.10, 24) (push) Has been cancelled
build / smoke-test (1.3.10, 25) (push) Has been cancelled
build / typecheck (push) Has been cancelled
build / eslint (push) Has been cancelled
build / unused-deps (push) Has been cancelled
build / test (push) Has been cancelled
build / CodeQL (push) Has been cancelled

Co-authored-by: tran4774 <59108692+tran4774@users.noreply.github.com>
This commit is contained in:
Mads Jon Nielsenandtran4774 authored and GitHub committed 2026-10-07 13:12:52 +02:00
1 parent 86ba8e23aa
commit 74778ea8a9
3 files changed
+114 -10

No files matched your search

+11 -8
View File
@@ -1,10 +1,18 @@
import chalk from "chalk-template";
import deepExtend from "deep-extend";
import assert, {AssertionError} from "node:assert";
import {Job, Need} from "./job.js";
import {getNodeByPath, traverse} from "object-traversal";
import {traverse} from "object-traversal";
import {Utils} from "./utils.js";
const isMergeable = (value: any) => value != null && Utils.isObject(value) && value.referenceData == null;
const extendsMerge = (target: any, source: any): any => {
if (!isMergeable(target) || !isMergeable(source)) return structuredClone(source);
for (const [key, value] of Object.entries(source)) {
target[key] = extendsMerge(target[key], value);
}
return target;
};
const extendsMaxDepth = 11;
const extendsRecurse = (gitlabData: any, jobName: string, jobData: any, parents: any[], depth: number) => {
assert(depth < extendsMaxDepth, chalk`{blueBright ${jobName}}: circular dependency detected in \`extends\``);
@@ -27,12 +35,7 @@ export function jobExtends (gitlabData: any) {
if (Job.illegalJobNames.has(jobName)) continue;
if (!Utils.isObject(jobData)) continue;
const parentDatas = extendsRecurse(gitlabData, jobName, jobData, [], 0);
const mergedData = deepExtend({}, ...parentDatas, jobData);
traverse(jobData, ({key, value, meta}) => {
if (key == null || value?.referenceData == null) return;
getNodeByPath(mergedData, meta.nodePath!.slice(0, -key.length - 1))[key] = value;
});
gitlabData[jobName] = mergedData;
gitlabData[jobName] = [...parentDatas, jobData].reduce((mergedData, data) => extendsMerge(mergedData, data), {});
}
for (const [jobName, jobData] of Object.entries<any>(gitlabData)) {
@@ -0,0 +1,41 @@
---
.src:
cache:
key: src
paths: [a, b, jib]
.setup:
cache:
key: setup
paths: [a, b]
holder:
extends: .setup
script:
- echo "Heya"
cache: !reference [.src, cache]
inherits:
extends: holder
overrides:
extends: holder
cache:
key: own
paths: [own]
.key-src:
key:
files: [k.lock]
.key-setup:
cache:
key:
files: [setup.lock]
prefix: setup
paths: [a, b]
nested:
extends: [holder, .key-setup]
cache:
key: !reference [.key-src, key]
+62 -2
View File
@@ -159,7 +159,7 @@ test.concurrent("should not support 11 level deep", async () => {
throw new Error("Error is expected but not thrown/caught");
});
it("should merge values", async () => {
it("should let a job's own value replace an inherited reference", async () => {
const writeStreams = new WriteStreamsMock();
await handler({
preview: true,
@@ -177,7 +177,6 @@ stages:
- .post
test-job:
variables:
HESTHEST: ponypony
NICENESS: byrdalos
script:
- echo \${NICENESS}
@@ -274,3 +273,64 @@ job:
expect(writeStreams.stdoutLines.join("\n")).toEqual(expected.trim());
});
it("should carry a reference through a job that extends the one holding it", async () => {
const writeStreams = new WriteStreamsMock();
await handler({
preview: true,
file: ".gitlab-ci-inherited-reference.yml",
cwd: "tests/test-cases/reference",
}, writeStreams);
const expected = `
---
stages:
- .pre
- build
- test
- deploy
- .post
holder:
cache:
- key: src
paths: &ref_0
- a
- b
- jib
policy: pull-push
when: on_success
script:
- echo "Heya"
inherits:
cache:
- key: src
paths: *ref_0
policy: pull-push
when: on_success
script:
- echo "Heya"
overrides:
cache:
- key: own
paths:
- own
policy: pull-push
when: on_success
script:
- echo "Heya"
nested:
cache:
- key:
files:
- k.lock
paths:
- a
- b
policy: pull-push
when: on_success
script:
- echo "Heya"
`;
expect(writeStreams.stdoutLines.join("\n")).toEqual(expected.trim());
});