From 6233293ae65ea97747d670ac844f68f9c6ab3554 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:57:35 +0100 Subject: [PATCH] buildx(build): support git context subdir and other query options Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- __tests__/buildx/build.test.ts | 26 +++++++++++++++++++-- src/buildx/build.ts | 41 ++++++++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/__tests__/buildx/build.test.ts b/__tests__/buildx/build.test.ts index 5746c475..3d44fa72 100644 --- a/__tests__/buildx/build.test.ts +++ b/__tests__/buildx/build.test.ts @@ -64,6 +64,9 @@ describe('gitContext', () => { prHeadRef: boolean; sendGitQueryAsInput: boolean; buildxQuerySupport: boolean; + subdir?: string; + keepGitDir?: boolean; + submodules?: boolean; }; // prettier-ignore @@ -79,28 +82,47 @@ describe('gitContext', () => { [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/merge&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/pull/15/merge', format: undefined, prHeadRef: true, sendGitQueryAsInput: true, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/head&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: true, buildxQuerySupport: false}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, keepGitDir: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&keep-git-dir=true'], + [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: false, keepGitDir: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&keep-git-dir=true'], + [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, submodules: false}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&submodules=false'], + [{ref: 'refs/heads/master', format: undefined, prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: false, submodules: false}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&submodules=false'], // query format [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/pull/15/merge', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/merge&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/tags/v1.0.0', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/tags/v1.0.0&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/pull/15/merge', format: 'query', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/pull/15/head&checksum=860c1904a1ce19322e91ac35af1ab07466440c37'], + [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&subdir=subdir'], + [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir', keepGitDir: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&subdir=subdir&keep-git-dir=true'], + [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, submodules: true}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&submodules=true'], + [{ref: 'refs/heads/master', format: 'query', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, submodules: false}, 'https://github.com/docker/actions-toolkit.git?ref=refs/heads/master&checksum=860c1904a1ce19322e91ac35af1ab07466440c37&submodules=false'], // fragment format [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/merge'], [{ref: 'refs/tags/v1.0.0', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37'], [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head'], + [{ref: 'refs/heads/master', format: 'fragment', prHeadRef: false, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git#860c1904a1ce19322e91ac35af1ab07466440c37:subdir'], + [{ref: 'refs/pull/15/merge', format: 'fragment', prHeadRef: true, sendGitQueryAsInput: false, buildxQuerySupport: true, subdir: 'subdir'}, 'https://github.com/docker/actions-toolkit.git#refs/pull/15/head:subdir'], ]; test.each(gitContextCases)('given %o should return %o', async (input: GitContextTestCase, expected: string) => { - const {ref, format, prHeadRef, sendGitQueryAsInput, buildxQuerySupport} = input; + const {ref, format, prHeadRef, sendGitQueryAsInput, buildxQuerySupport, subdir, keepGitDir, submodules} = input; process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF = prHeadRef ? 'true' : ''; process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT = sendGitQueryAsInput ? 'true' : ''; const buildx = new Buildx(); vi.spyOn(buildx, 'versionSatisfies').mockResolvedValue(buildxQuerySupport); const build = new Build({buildx}); - expect(await build.gitContext(ref, '860c1904a1ce19322e91ac35af1ab07466440c37', format)).toEqual(expected); + expect( + await build.gitContext({ + ref, + checksum: '860c1904a1ce19322e91ac35af1ab07466440c37', + format, + subdir, + keepGitDir, + submodules + }) + ).toEqual(expected); }); }); diff --git a/src/buildx/build.ts b/src/buildx/build.ts index a3a60976..aeb9abc4 100644 --- a/src/buildx/build.ts +++ b/src/buildx/build.ts @@ -38,6 +38,15 @@ export interface ResolveSecretsOpts { redact?: boolean; } +export interface GitContextOpts { + ref?: string; + checksum?: string; + subdir?: string; + keepGitDir?: boolean; + submodules?: boolean; + format?: GitContextFormat; +} + export class Build { private readonly buildx: Buildx; private readonly iidFilename: string; @@ -49,31 +58,45 @@ export class Build { this.metadataFilename = `build-metadata-${Util.generateRandomString()}.json`; } - public async gitContext(ref?: string, sha?: string, format?: GitContextFormat): Promise { + public async gitContext(opts?: GitContextOpts): Promise { const setPullRequestHeadRef = Util.parseBoolOrDefault(process.env.DOCKER_DEFAULT_GIT_CONTEXT_PR_HEAD_REF); - ref = ref || github.context.ref; - sha = sha || github.context.sha; + const gitChecksum = opts?.checksum || github.context.sha; + let ref = opts?.ref || github.context.ref; if (!ref.startsWith('refs/')) { ref = `refs/heads/${ref}`; } else if (ref.startsWith(`refs/pull/`) && setPullRequestHeadRef) { ref = ref.replace(/\/merge$/g, '/head'); } const baseURL = `${GitHub.serverURL}/${github.context.repo.owner}/${github.context.repo.repo}.git`; + let format = opts?.format; if (!format) { const sendGitQueryAsInput = Util.parseBoolOrDefault(process.env.BUILDX_SEND_GIT_QUERY_AS_INPUT); - if (sendGitQueryAsInput && (await this.buildx.versionSatisfies('>=0.29.0'))) { + if (opts?.keepGitDir || typeof opts?.submodules !== 'undefined') { + format = 'query'; + } else if (sendGitQueryAsInput && (await this.buildx.versionSatisfies('>=0.29.0'))) { format = 'query'; } else { format = 'fragment'; } } if (format === 'query') { - return `${baseURL}?ref=${ref}${sha ? `&checksum=${sha}` : ''}`; - } - if (sha && !ref.startsWith(`refs/pull/`)) { - return `${baseURL}#${sha}`; + const query = [`ref=${ref}`]; + if (gitChecksum) { + query.push(`checksum=${gitChecksum}`); + } + if (opts?.subdir) { + query.push(`subdir=${opts.subdir}`); + } + if (typeof opts?.keepGitDir !== 'undefined') { + query.push(`keep-git-dir=${opts.keepGitDir}`); + } + if (typeof opts?.submodules !== 'undefined') { + query.push(`submodules=${opts.submodules}`); + } + return `${baseURL}?${query.join('&')}`; } - return `${baseURL}#${ref}`; + const fragmentRef = gitChecksum && !ref.startsWith(`refs/pull/`) ? gitChecksum : ref; + return `${baseURL}#${fragmentRef}${opts?.subdir ? `:${opts.subdir}` : ''}`; } public getImageIDFilePath(): string {