diff --git a/packages/core/src/evaluation/workspace/repo-manager.ts b/packages/core/src/evaluation/workspace/repo-manager.ts index 66006f57..1d597051 100644 --- a/packages/core/src/evaluation/workspace/repo-manager.ts +++ b/packages/core/src/evaluation/workspace/repo-manager.ts @@ -39,6 +39,10 @@ function getSourceUrl(source: RepoSource): string { return source.type === 'git' ? source.url : source.path; } +function isFullCommitSha(ref: string | undefined): boolean { + return typeof ref === 'string' && /^[0-9a-f]{40}$/i.test(ref); +} + async function git(args: string[], opts?: { cwd?: string; timeout?: number }): Promise { const { stdout } = await execFileAsync('git', args, { cwd: opts?.cwd, @@ -169,9 +173,12 @@ export class RepoManager { // Resolve ref const ref = getRepoCheckoutRef(repo.checkout); const resolve = repo.checkout?.resolve ?? 'remote'; + const baseCommit = repo.checkout?.base_commit; + const shouldResolveLocally = + resolve === 'local' || (repo.source.type === 'git' && isFullCommitSha(baseCommit)); let resolvedSha: string; - if (resolve === 'remote' && repo.source.type === 'git') { + if (!shouldResolveLocally && repo.source.type === 'git') { // Resolve via ls-remote for remote refs const url = getSourceUrl(repo.source); try { diff --git a/packages/core/test/evaluation/workspace/repo-manager.test.ts b/packages/core/test/evaluation/workspace/repo-manager.test.ts index 6d5744a4..d55e0b3c 100644 --- a/packages/core/test/evaluation/workspace/repo-manager.test.ts +++ b/packages/core/test/evaluation/workspace/repo-manager.test.ts @@ -99,6 +99,34 @@ describe('RepoManager', () => { expect(existsSync(path.join(targetDir, 'third.txt'))).toBe(false); }, 30_000); + it('checks out raw base_commit SHAs from git sources without resolve: local', async () => { + const repoDir = path.join(tmpDir, 'source-repo'); + createTestRepo(repoDir); + writeFileSync(path.join(repoDir, 'second.txt'), 'second'); + execSync('git add -A && git commit -m "second"', { cwd: repoDir, ...EXEC_OPTS }); + const secondSha = gitExec('git rev-parse HEAD', repoDir); + writeFileSync(path.join(repoDir, 'third.txt'), 'third'); + execSync('git add -A && git commit -m "third"', { cwd: repoDir, ...EXEC_OPTS }); + + const remoteDir = path.join(tmpDir, 'remote.git'); + execSync(`git clone --bare "${repoDir}" "${remoteDir}"`, { env: cleanGitEnv() }); + + await manager.materialize( + { + path: './my-repo', + source: { type: 'git', url: remoteDir }, + checkout: { base_commit: secondSha }, + }, + workspaceDir, + ); + + const targetDir = path.join(workspaceDir, 'my-repo'); + const headSha = gitExec('git rev-parse HEAD', targetDir); + expect(headSha).toBe(secondSha); + expect(existsSync(path.join(targetDir, 'second.txt'))).toBe(true); + expect(existsSync(path.join(targetDir, 'third.txt'))).toBe(false); + }, 30_000); + it('walks ancestor commits', async () => { const repoDir = path.join(tmpDir, 'source-repo'); const firstSha = createTestRepo(repoDir);