Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 24 additions & 18 deletions src/parser-includes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,11 @@ type GitRemoteInfoContext = {

export class ParserIncludes {
private static count: number = 0;
private static gitRemoteInfoCache: Record<string, string> = {};

@firecow firecow Jul 31, 2026

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never cleared, though resetCount() sits right beside it for count. Stale git ls-remote results could leak between runs in the test suite. Broke nothing I ran, so your call whether it matters for a single-shot CLI.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I can reinit the remote info cache in the resetCount method. That would be good enough I think.


static resetCount (): void {
this.count = 0;
this.gitRemoteInfoCache = {};
}

private static normalizeTriggerInclude (gitlabData: any, opts: ParserIncludesInitOptions) {
Expand Down Expand Up @@ -114,13 +116,13 @@ export class ParserIncludes {
promises.push(this.downloadIncludeRemote(cwd, stateDir, value["remote"], fetchIncludes, writeStreams));
} else if (value["component"]) {
const component = this.parseIncludeComponent(value["component"], gitData);
componentParseCache.set(index, component);
if (!component.isLocal)
{
promises.push(this.downloadIncludeComponent(opts, component.projectPath, component.effectiveRef, component.componentPath));
}
promises.push((async () => {
if (!component.isLocal) {
await this.downloadIncludeComponent(opts, component.projectPath, component.effectiveRef, component.componentPath);
}
componentParseCache.set(index, component);
})());
}

}

await Promise.all(promises);
Expand Down Expand Up @@ -251,6 +253,20 @@ export class ParserIncludes {
};
}

static getGitRemoteInfo (ctx: GitRemoteInfoContext, ...args: string[]): string {
const cmdArgs = ["git", "ls-remote", ...args];
if (ctx.gitData.remote.schema == "git" || ctx.gitData.remote.schema == "ssh") {
cmdArgs.push(`git@${ctx.domain}:${ctx.projectPath}`);
} else {
cmdArgs.push(`${ctx.gitData.remote.schema}://${ctx.domain}:${ctx.port ?? 443}/${ctx.projectPath}.git`);
}
const cmdStr = cmdArgs.join(" ");
if (!(cmdStr in this.gitRemoteInfoCache)) {
this.gitRemoteInfoCache[cmdStr] = Utils.syncSpawn(cmdArgs).stdout;
}
return this.gitRemoteInfoCache[cmdStr];
};
Comment on lines +263 to +268

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const cmdStr = cmdArgs.join(" ");
let info = this.gitRemoteInfoCache[cmdStr];
if (!(cmdStr in this.gitRemoteInfoCache)) {
info = Utils.syncSpawn(cmdArgs).stdout;
this.gitRemoteInfoCache[cmdStr] = info;
}
return info;
};
const cmdStr = cmdArgs.join(" ");
if (!(cmdStr in this.gitRemoteInfoCache)) {
this.gitRemoteInfoCache[cmdStr] = Utils.syncSpawn(cmdArgs).stdout;
}
return this.gitRemoteInfoCache[cmdStr];
}


static parseIncludeComponent (component: string, gitData: GitData): ParsedComponent {
assert(!component.includes("://"), `This GitLab CI configuration is invalid: component: \`${component}\` should not contain protocol`);
const pattern = /(?<domain>[^/:\s]+)(:(?<port>\d+))?\/(?<projectPath>.+)\/(?<componentName>[^@]+)@(?<ref>.+)/; // https://regexr.com/7v7hm
Expand Down Expand Up @@ -279,7 +295,7 @@ export class ParserIncludes {
const semanticVersionRangesPattern = /^\d+(\.\d+)?$/;
if (this.reference == "~latest" || semanticVersionRangesPattern.test(this.reference)) {
// https://docs.gitlab.com/ci/components/#semantic-version-ranges
const stdout = getGitRemoteInfo(this, "--tags");
const stdout = ParserIncludes.getGitRemoteInfo(this, "--tags");
const tags = stdout.split("\n").map(line => line.split("\t")[1].split("/")[2]);
Comment thread
jrd marked this conversation as resolved.
const version = resolveSemanticVersionRange(this.reference, tags);
assert(version ?? tags.includes(this.reference), `This GitLab CI configuration is invalid: component: \`${this.name}\` - The reference (${this.reference}) is invalid`);
Expand All @@ -305,7 +321,7 @@ export class ParserIncludes {
// effectiveRef may already be a sha, if so return it directly
this._cache.sha = this.effectiveRef;
} else {
const stdout = getGitRemoteInfo(this);
const stdout = ParserIncludes.getGitRemoteInfo(this);
const lines = stdout.split("\n");
// annotated tags: prefer the deref'd commit sha (refs/tags/x^{})
const match = lines.find(line => line.endsWith(`refs/tags/${this.effectiveRef}^{}`)) ??
Expand Down Expand Up @@ -509,13 +525,3 @@ export async function resolveIncludeLocal (pattern: string, cwd: string) {
const re2js = RE2JS.compile(`^${pattern}`);
return repoFiles.filter((f: any) => re2js.matches(f));
}

export function getGitRemoteInfo (ctx: GitRemoteInfoContext, ...args: string[]) {
const cmdArgs = ["git", "ls-remote", ...args];
if (ctx.gitData.remote.schema == "git" || ctx.gitData.remote.schema == "ssh") {
cmdArgs.push(`git@${ctx.domain}:${ctx.projectPath}`);
} else {
cmdArgs.push(`${ctx.gitData.remote.schema}://${ctx.domain}:${ctx.port ?? 443}/${ctx.projectPath}.git`);
}
return Utils.syncSpawn(cmdArgs).stdout;
}