fix(git): check out short commit SHAs with shallow clone

Closes #9926
This commit is contained in:
Andras Bacsai committed 2026-10-27 17:09:55 +01:00
1 parent bb1fada600
commit c6b52a1d36
2 files changed
+47 -2

No files matched your search

+11 -2
View File
@@ -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";
}
+36
View File
@@ -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'],
]);