diff --git a/src/parser-includes.ts b/src/parser-includes.ts index 9919267c6..171b516fb 100644 --- a/src/parser-includes.ts +++ b/src/parser-includes.ts @@ -301,7 +301,7 @@ export class ParserIncludes { if (this._cache.sha === undefined) { if (this.isLocal) { this._cache.sha = this.gitData.commit.SHA; - } else if (/^[0-9a-f]{40}$/.test(this.effectiveRef)) { + } else if (/^[0-9a-f]{40}$/i.test(this.effectiveRef)) { // effectiveRef may already be a sha, if so return it directly this._cache.sha = this.effectiveRef; } else { @@ -377,13 +377,15 @@ export class ParserIncludes { await fs.mkdirp(path.dirname(`${cwd}/${target}/${normalizedFile}`)); tmpDir = `${cwd}/${target}.${ext}`; - const gitCloneBranch = (ref === "HEAD") ? "" : `--branch ${ref}`; + const isCommitSha = /^[0-9a-f]{40}$/i.test(ref); + const gitCloneBranch = (ref === "HEAD" || isCommitSha) ? "" : `--branch ${ref}`; await Utils.bashMulti([ `cd ${cwd}/${stateDir}`, `git clone ${gitCloneBranch} -n --depth=1 --filter=tree:0 ${remote.schema}://${remote.host}:${remote.port}/${project}.git ${tmpDir}`, `cd ${tmpDir}`, + ...isCommitSha ? [`git fetch --depth=1 --filter=tree:0 origin ${ref}`] : [], `git sparse-checkout set --no-cone ${normalizedFile}`, - "git checkout", + isCommitSha ? "git checkout FETCH_HEAD" : "git checkout", `cd ${cwd}/${stateDir}`, `cp ${tmpDir}/${normalizedFile} ${cwd}/${target}/${normalizedFile}`, ], cwd); @@ -418,13 +420,15 @@ export class ParserIncludes { await fs.mkdirp(path.dirname(`${cwd}/${target}/templates`)); tmpDir = `${cwd}/${target}.${ext}`; - const gitCloneBranch = (ref === "HEAD") ? "" : `--branch ${ref}`; + const isCommitSha = /^[0-9a-f]{40}$/i.test(ref); + const gitCloneBranch = (ref === "HEAD" || isCommitSha) ? "" : `--branch ${ref}`; await Utils.bashMulti([ `cd ${cwd}/${stateDir}`, `git clone ${gitCloneBranch} -n --depth=1 --filter=tree:0 ${remote.schema}://${remote.host}:${remote.port}/${project}.git ${tmpDir}`, `cd ${tmpDir}`, + ...isCommitSha ? [`git fetch --depth=1 --filter=tree:0 origin ${ref}`] : [], `git sparse-checkout set --no-cone ${files[0]} ${files[1]}`, - "git checkout", + isCommitSha ? "git checkout FETCH_HEAD" : "git checkout", `cd ${cwd}/${stateDir}`, `mkdir -p ${tmpDir}/templates`, // create templates subdir (if it doesn't exist), as the check out may not create it `cp -r ${tmpDir}/templates ${cwd}/${target}`, diff --git a/tests/test-cases/include-project-file/.gitlab-ci-4.yml b/tests/test-cases/include-project-file/.gitlab-ci-4.yml new file mode 100644 index 000000000..1dc0d86de --- /dev/null +++ b/tests/test-cases/include-project-file/.gitlab-ci-4.yml @@ -0,0 +1,7 @@ +--- +include: + # https://gitlab.com/ANGkeith/gitlab-ci-local-test/-/tree/dev?ref_type=heads + - project: angkeith/gitlab-ci-local-test + ref: 126db0b30c40344e135a0335fdfd3d5f26af3b6d + file: + - .gitlab-ci.yml diff --git a/tests/test-cases/include-project-file/integration.test.ts b/tests/test-cases/include-project-file/integration.test.ts index 22b7d1a1a..91a3f082d 100644 --- a/tests/test-cases/include-project-file/integration.test.ts +++ b/tests/test-cases/include-project-file/integration.test.ts @@ -66,3 +66,19 @@ test.concurrent("include:project should respect rules specified in included proj expect(writeStreams.stdoutLines.join("\n")).toEqual(expected.join("\n")); }); + +test.concurrent("include:project be able to target a commit sha via ref", async () => { + const writeStreams = new WriteStreamsMock(); + + await handler({ + file: ".gitlab-ci-4.yml", + cwd: "tests/test-cases/include-project-file", + noColor: true, + stateDir: ".gitlab-ci-local-include-project-file-sha", + }, writeStreams); + + const expected = "job > hello world from dev branch"; + + const filteredStdout = writeStreams.stdoutLines.filter(f => f.startsWith("job >")).join("\n"); + expect(filteredStdout).toEqual(expected); +});