From 923b9fd2ec28c55ab57094fa24832842a28f8058 Mon Sep 17 00:00:00 2001 From: Lucas Pluchon <55061318+lucaspluchon@users.noreply.github.com> Date: Sun, 29 Mar 2026 23:49:20 +0000 Subject: [PATCH] fix(deployment): respect pinned commit SHA for deployments Read git_commit_sha from the application model when queuing a deployment so the pinned SHA is used instead of defaulting to HEAD. Prevent git ls-remote from overwriting the pinned SHA in the deployment job. Rollback and PR deployments are unaffected. Fixes #9204 Related: #6881 --- app/Jobs/ApplicationDeploymentJob.php | 12 ++++- bootstrap/helpers/applications.php | 9 ++++ tests/Unit/PinnedCommitDeploymentTest.php | 63 +++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/PinnedCommitDeploymentTest.php diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index dc8bc4374f..0f1067b9c2 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -2189,7 +2189,17 @@ private function check_git_if_build_needed() ], ); } - if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { + // Skip overwriting the commit when the application has a pinned SHA + $pinnedCommit = str(data_get($this->application, 'git_commit_sha'))->trim()->value(); + $hasPinnedCommit = $this->pull_request_id === 0 && ! $this->rollback && $pinnedCommit !== '' && $pinnedCommit !== 'HEAD'; + + if ($hasPinnedCommit) { + $this->commit = $pinnedCommit; + if ($this->application_deployment_queue->commit !== $pinnedCommit) { + $this->application_deployment_queue->commit = $pinnedCommit; + $this->application_deployment_queue->save(); + } + } elseif ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) { // Extract commit SHA from git ls-remote output, handling multi-line output (e.g., redirect warnings) // Expected format: "commit_sha\trefs/heads/branch" possibly preceded by warning lines // Note: Git warnings can be on the same line as the result (no newline) diff --git a/bootstrap/helpers/applications.php b/bootstrap/helpers/applications.php index fbcedf2772..84eb744fe4 100644 --- a/bootstrap/helpers/applications.php +++ b/bootstrap/helpers/applications.php @@ -29,6 +29,15 @@ function queue_application_deployment(Application $application, string $deployme $destination_id = $destination->id; } + // Use the application's pinned commit SHA when no specific commit was requested + // and this is not a PR or rollback deployment + if ($pull_request_id === 0 && ! $rollback) { + $pinnedCommit = str($application->git_commit_sha ?? '')->trim()->value(); + if ($pinnedCommit !== '' && $pinnedCommit !== 'HEAD') { + $commit = $pinnedCommit; + } + } + // Check if the deployment queue is full for this server $serverForQueueCheck = $server ?? Server::find($server_id); $queue_limit = $serverForQueueCheck->settings->deployment_queue_limit ?? 25; diff --git a/tests/Unit/PinnedCommitDeploymentTest.php b/tests/Unit/PinnedCommitDeploymentTest.php new file mode 100644 index 0000000000..372602c12b --- /dev/null +++ b/tests/Unit/PinnedCommitDeploymentTest.php @@ -0,0 +1,63 @@ +application = new Application; + $this->application->forceFill([ + 'uuid' => 'test-app-uuid', + 'git_commit_sha' => 'HEAD', + ]); + + $settings = new ApplicationSetting; + $settings->is_git_shallow_clone_enabled = false; + $settings->is_git_submodules_enabled = false; + $settings->is_git_lfs_enabled = false; + $this->application->setRelation('settings', $settings); + }); + + test('setGitImportSettings uses pinned commit when no explicit commit is passed', function () { + $pinnedSha = str_repeat('a', 40); + $this->application->git_commit_sha = $pinnedSha; + + $result = $this->application->setGitImportSettings( + deployment_uuid: 'test-uuid', + git_clone_command: 'git clone', + public: true, + ); + + expect($result)->toContain($pinnedSha); + }); + + test('setGitImportSettings prefers explicit commit over pinned commit', function () { + $pinnedSha = str_repeat('a', 40); + $explicitSha = str_repeat('b', 40); + $this->application->git_commit_sha = $pinnedSha; + + $result = $this->application->setGitImportSettings( + deployment_uuid: 'test-uuid', + git_clone_command: 'git clone', + public: true, + commit: $explicitSha, + ); + + expect($result) + ->toContain($explicitSha) + ->not->toContain($pinnedSha); + }); + + test('setGitImportSettings does not checkout when git_commit_sha is HEAD', function () { + $this->application->git_commit_sha = 'HEAD'; + + $result = $this->application->setGitImportSettings( + deployment_uuid: 'test-uuid', + git_clone_command: 'git clone', + public: true, + ); + + expect($result)->not->toContain('advice.detachedHead=false checkout'); + }); + +});