diff --git a/app/Models/Application.php b/app/Models/Application.php index 9ab89e8442..65f4732ca6 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1589,9 +1589,18 @@ class Application extends BaseModel if ($commitToUse !== 'HEAD') { $escapedCommit = escapeshellarg($commitToUse); // If shallow clone is enabled and we need a specific commit, - // we need to fetch that specific commit with depth=1 + // we need to fetch that specific commit with depth=1. + // A remote cannot fetch a short SHA (Bitbucket sends one for pull requests), + // so fetch the tip of each branch and let the checkout expand it. When the + // commit is not a branch tip, fetch the full history of the branches. if ($isShallowCloneEnabled) { - $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && echo 'Fetching the commit.' && {$sshCommand} {$gitCommand} fetch --depth=1 origin {$escapedCommit} && {$gitCommand} -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; + $checkoutCommand = "{$gitCommand} -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; + if (preg_match('/\A[0-9a-fA-F]{7,39}\z/', $commitToUse) === 1) { + $branchesRefspec = escapeshellarg('+refs/heads/*:refs/remotes/origin/*'); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && echo 'Fetching the commit.' && {$sshCommand} {$gitCommand} fetch --depth=1 origin {$branchesRefspec} && ({$checkoutCommand} || (echo 'The commit is not a branch tip. Fetching the full history.' && {$sshCommand} {$gitCommand} fetch --unshallow origin {$branchesRefspec} && {$checkoutCommand}))"; + } else { + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && echo 'Fetching the commit.' && {$sshCommand} {$gitCommand} fetch --depth=1 origin {$escapedCommit} && {$checkoutCommand}"; + } } else { $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && {$sshCommand} {$gitCommand} -c advice.detachedHead=false checkout {$escapedCommit} >/dev/null 2>&1"; } diff --git a/tests/Unit/GitImportStepsTest.php b/tests/Unit/GitImportStepsTest.php index e429ce37b9..8db5cf80fc 100644 --- a/tests/Unit/GitImportStepsTest.php +++ b/tests/Unit/GitImportStepsTest.php @@ -82,3 +82,39 @@ test('git ssh command stops a stalled connection instead of waiting for the depl ->and(gitSshCommand(22))->not->toContain(' -i ') ->and(gitSshCommand(22, '/root/.ssh/id_rsa', identitiesOnly: false))->toEndWith('-i /root/.ssh/id_rsa'); }); + +test('a shallow clone checks out a short commit sha', function (string $commitName) { + $dir = sys_get_temp_dir().'/coolify-short-sha-'.bin2hex(random_bytes(4)); + $git = 'git -c user.email=test@example.com -c user.name=test'; + exec("git init -q -b main {$dir}/source && cd {$dir}/source && {$git} commit -q --allow-empty -m main" + ." && git checkout -q -b feature && {$git} commit -q --allow-empty -m first && {$git} commit -q --allow-empty -m second" + ." && git checkout -q main && git clone -q --bare {$dir}/source {$dir}/remote.git"); + $commit = trim(shell_exec("git -C {$dir}/source rev-parse {$commitName}")); + + $application = new Application; + $application->forceFill(['uuid' => 'test-app-uuid']); + $settings = new ApplicationSetting; + $settings->is_git_shallow_clone_enabled = true; + $settings->is_git_submodules_enabled = false; + $settings->is_git_lfs_enabled = false; + $application->setRelation('settings', $settings); + + // Bitbucket sends the pull request commit as a 12-character hash. + $command = $application->setGitImportSettings( + deployment_uuid: 'test-uuid', + git_clone_command: "git clone -q --depth=1 -b main file://{$dir}/remote.git {$dir}/checkout", + commit: substr($commit, 0, 12), + baseDir: "{$dir}/checkout", + ); + $process = Process::fromShellCommandline($command); + $process->run(); + $checkedOutCommit = trim((string) shell_exec("git -C {$dir}/checkout rev-parse HEAD 2>/dev/null")); + + exec('rm -rf '.escapeshellarg($dir)); + + expect($process->getExitCode())->toBe(0, $process->getErrorOutput()) + ->and($checkedOutCommit)->toBe($commit); +})->with([ + 'tip of a branch' => ['feature'], + 'older commit of a branch' => ['feature~1'], +]);