From 3f953d1dce0130ffe3d840a80d7466cd3eb025f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=8F=94=EF=B8=8F=20Peak?= <122374094+peaklabs-dev@users.noreply.github.com> Date: Thu, 8 Oct 2026 18:10:07 +0200 Subject: [PATCH] revert(git): stop forcing HTTP/1.1 for repository imports (#12080) --- app/Models/Application.php | 66 ++------ .../Feature/GitHttpTransportCommandsTest.php | 158 ------------------ .../Feature/GitSubmoduleRemoteRewriteTest.php | 46 +++++ .../GitHubAppSubmoduleCredentialsTest.php | 6 +- .../GitlabAppSubmoduleCredentialsTest.php | 2 +- tests/Unit/GitlabSourceCommandsTest.php | 4 +- 6 files changed, 67 insertions(+), 215 deletions(-) delete mode 100644 tests/Feature/GitHttpTransportCommandsTest.php create mode 100644 tests/Feature/GitSubmoduleRemoteRewriteTest.php diff --git a/app/Models/Application.php b/app/Models/Application.php index a1146059d5..d7020d9d80 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -1791,28 +1791,6 @@ class Application extends BaseModel return $commands; } - private function withGitHttpTransportConfig(?string $gitConfigOptions = null): string - { - return trim(($gitConfigOptions ? "{$gitConfigOptions} " : '').'-c http.version=HTTP/1.1'); - } - - private function isHttpGitRepository(string $repository): bool - { - return str_starts_with($repository, 'https://') || str_starts_with($repository, 'http://'); - } - - private function applyGitConfigOptionsToCloneCommand(string $gitCloneCommand, string $gitConfigOptions): string - { - $configuredCommand = preg_replace( - "/^git(?:\s+-c\s+(?:'[^']*'|\S+))*\s+clone\b/", - "git {$gitConfigOptions} clone", - $gitCloneCommand, - 1 - ); - - return $configuredCommand ?: $gitCloneCommand; - } - public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null) { $branch = $this->git_branch; @@ -1856,10 +1834,8 @@ class Application extends BaseModel $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; $escapedRepoUrl = escapeshellarg("{$this->source->html_url}/{$customRepository}"); $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; - $gitConfigOptions = $this->withGitHttpTransportConfig(); - $git_clone_command = $this->applyGitConfigOptionsToCloneCommand($git_clone_command, $gitConfigOptions); if (! $only_checkout) { - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, gitConfigOptions: $gitConfigOptions, baseDir: $baseDir, onlyCheckout: $only_checkout); + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, baseDir: $baseDir, onlyCheckout: $only_checkout); } if ($exec_in_docker) { $commands->push($this->gitCommand(executeInDocker($deployment_uuid, $git_clone_command))); @@ -1872,7 +1848,6 @@ class Application extends BaseModel // Rewrite same-host HTTPS URLs only for these git commands so submodules can authenticate without persisting credentials. $gitConfigOption = '-c '.escapeshellarg("url.{$source_html_url_scheme}://x-access-token:{$encodedToken}@{$source_html_url_host}/.insteadOf={$source_html_url_scheme}://{$source_html_url_host}/"); - $gitConfigOptions = $this->withGitHttpTransportConfig($gitConfigOption); $git_clone_command = str_replace('git clone', "git {$gitConfigOption} clone", $git_clone_command); if ($exec_in_docker) { @@ -1886,9 +1861,8 @@ class Application extends BaseModel $git_clone_command = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; $fullRepoUrl = $repoUrl; } - $git_clone_command = $this->applyGitConfigOptionsToCloneCommand($git_clone_command, $gitConfigOptions); if (! $only_checkout) { - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false, commit: $commit, gitConfigOptions: $gitConfigOptions, baseDir: $baseDir, onlyCheckout: $only_checkout); + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: false, commit: $commit, gitConfigOptions: $gitConfigOption, baseDir: $baseDir, onlyCheckout: $only_checkout); } if ($exec_in_docker) { $commands->push($this->gitCommand(executeInDocker($deployment_uuid, $git_clone_command))); @@ -1899,13 +1873,12 @@ class Application extends BaseModel if ($pull_request_id !== 0) { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; - $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOptions ?? null); - $gitCommand = isset($gitConfigOptions) ? "git {$gitConfigOptions}" : 'git'; + $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOption ?? null); $escapedPrBranch = escapeshellarg($branch); if ($exec_in_docker) { - $commands->push($this->gitCommand(executeInDocker($deployment_uuid, "cd {$escapedBaseDir} && {$gitCommand} fetch origin {$escapedPrBranch} && $git_checkout_command"))); + $commands->push($this->gitCommand(executeInDocker($deployment_uuid, "cd {$escapedBaseDir} && git fetch origin {$escapedPrBranch} && $git_checkout_command"))); } else { - $commands->push($this->gitCommand("cd {$escapedBaseDir} && {$gitCommand} fetch origin {$escapedPrBranch} && $git_checkout_command")); + $commands->push($this->gitCommand("cd {$escapedBaseDir} && git fetch origin {$escapedPrBranch} && $git_checkout_command")); } } @@ -1927,16 +1900,16 @@ class Application extends BaseModel // Rewrite same-host HTTPS submodule URLs to auth with the OAuth token (mirrors the GitHub path) without persisting credentials. $gitConfigOption = '-c '.escapeshellarg("url.{$source_html_url_scheme}://oauth2:{$encodedToken}@{$source_html_url_host}{$pathPrefix}/.insteadOf={$source_html_url_scheme}://{$source_html_url_host}{$pathPrefix}/"); - $gitConfigOptions = $this->withGitHttpTransportConfig($gitConfigOption); + $git_clone_command = str_replace('git clone', "git {$gitConfigOption} clone", $git_clone_command); $repoUrl = "{$source_html_url_scheme}://oauth2:{$encodedToken}@{$source_html_url_host}{$pathPrefix}/{$customRepository}.git"; $escapedRepoUrl = escapeshellarg($repoUrl); $fullRepoUrl = $repoUrl; - $git_clone_command_base = $this->applyGitConfigOptionsToCloneCommand("{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}", $gitConfigOptions); + $git_clone_command_base = "{$git_clone_command} {$escapedRepoUrl} {$escapedBaseDir}"; if ($only_checkout) { $git_clone_command = $git_clone_command_base; } else { - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, gitConfigOptions: $gitConfigOptions, baseDir: $baseDir, onlyCheckout: $only_checkout); + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command_base, commit: $commit, gitConfigOptions: $gitConfigOption, baseDir: $baseDir, onlyCheckout: $only_checkout); } if ($pull_request_id !== 0) { @@ -1946,9 +1919,9 @@ class Application extends BaseModel } else { $commands->push("echo 'Checking out {$branch}'"); } - $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOptions); + $git_checkout_command = $this->buildGitCheckoutCommand($pr_branch_name, gitConfigOptions: $gitConfigOption); $escapedPrBranch = escapeshellarg($branch); - $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && git {$gitConfigOptions} fetch origin {$escapedPrBranch} && {$git_checkout_command}"; + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && git {$gitConfigOption} fetch origin {$escapedPrBranch} && {$git_checkout_command}"; } if ($exec_in_docker) { @@ -2007,11 +1980,7 @@ class Application extends BaseModel $fullRepoUrl = $customRepository; $escapedCustomRepository = escapeshellarg($customRepository); $git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; - $gitConfigOptions = $this->isHttpGitRepository($customRepository) ? $this->withGitHttpTransportConfig() : null; - if ($gitConfigOptions) { - $git_clone_command = $this->applyGitConfigOptionsToCloneCommand($git_clone_command, $gitConfigOptions); - } - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, gitConfigOptions: $gitConfigOptions, baseDir: $baseDir, onlyCheckout: $only_checkout); + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, baseDir: $baseDir, onlyCheckout: $only_checkout); if ($exec_in_docker) { $commands->push($this->gitCommand(executeInDocker($deployment_uuid, $git_clone_command))); @@ -2086,15 +2055,10 @@ class Application extends BaseModel $fullRepoUrl = $customRepository; $escapedCustomRepository = escapeshellarg($customRepository); $git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}"; - $gitConfigOptions = $this->isHttpGitRepository($customRepository) ? $this->withGitHttpTransportConfig() : null; - if ($gitConfigOptions) { - $git_clone_command = $this->applyGitConfigOptionsToCloneCommand($git_clone_command, $gitConfigOptions); - } - $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, gitConfigOptions: $gitConfigOptions, baseDir: $baseDir, onlyCheckout: $only_checkout); + $git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true, commit: $commit, baseDir: $baseDir, onlyCheckout: $only_checkout); $otherSshCommand = "ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa"; if ($pull_request_id !== 0) { - $gitCommand = isset($gitConfigOptions) ? "git {$gitConfigOptions}" : 'git'; if ($git_type === 'gitlab') { $branch = "merge-requests/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -2102,7 +2066,7 @@ class Application extends BaseModel } else { $commands->push($this->gitCommand("echo 'Checking out $branch'")); } - $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" {$gitCommand} fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand, $gitConfigOptions); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand); } elseif ($git_type === 'github' || $git_type === 'gitea') { $branch = "pull/{$pull_request_id}/head:$pr_branch_name"; if ($exec_in_docker) { @@ -2110,14 +2074,14 @@ class Application extends BaseModel } else { $commands->push($this->gitCommand("echo 'Checking out $branch'")); } - $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" {$gitCommand} fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand, $gitConfigOptions); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name, $otherSshCommand); } elseif ($git_type === 'bitbucket') { if ($exec_in_docker) { $commands->push($this->gitCommand(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"))); } else { $commands->push($this->gitCommand("echo 'Checking out $branch'")); } - $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" ".$this->buildGitCheckoutCommand($commit, $otherSshCommand, $gitConfigOptions); + $git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"{$otherSshCommand}\" ".$this->buildGitCheckoutCommand($commit, $otherSshCommand); } } diff --git a/tests/Feature/GitHttpTransportCommandsTest.php b/tests/Feature/GitHttpTransportCommandsTest.php deleted file mode 100644 index 3d0295d078..0000000000 --- a/tests/Feature/GitHttpTransportCommandsTest.php +++ /dev/null @@ -1,158 +0,0 @@ -forceFill([ - 'uuid' => 'test-app-uuid', - 'git_repository' => 'coollabsio/private-app', - 'git_branch' => 'main', - 'git_commit_sha' => 'HEAD', - ]); - - $settings = new ApplicationSetting; - $settings->is_git_shallow_clone_enabled = $shallow; - $settings->is_git_submodules_enabled = false; - $settings->is_git_lfs_enabled = false; - $application->setRelation('settings', $settings); - - return $application; -} - -it('uses http 1 transport for public https source clones', function () { - $application = applicationWithGitSettings(); - - $source = new GithubApp; - $source->forceFill([ - 'html_url' => 'https://github.com', - 'api_url' => 'https://api.github.com', - 'is_public' => true, - ]); - $application->setRelation('source', $source); - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - exec_in_docker: false, - ); - - expect($result['commands']) - ->toContain("git -c http.version=HTTP/1.1 clone --depth=1 -b 'main' 'https://github.com/coollabsio/private-app' '/artifacts/test-deployment'") - ->not->toContain('Primary repository import failed, retrying with HTTP/1.1') - ->not->toContain('mktemp') - ->not->toContain('git_retry_dir'); -}); - -it('applies http 1 transport to https fetches after clone', function () { - $application = applicationWithGitSettings(); - $application->git_commit_sha = 'abc123def456abc123def456abc123def456abc1'; - - $source = new GithubApp; - $source->forceFill([ - 'html_url' => 'https://github.com', - 'api_url' => 'https://api.github.com', - 'is_public' => true, - ]); - $application->setRelation('source', $source); - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - exec_in_docker: false, - ); - - expect($result['commands']) - ->toContain("git -c http.version=HTTP/1.1 fetch --depth=1 origin 'abc123def456abc123def456abc123def456abc1'") - ->toContain("git -c http.version=HTTP/1.1 -c advice.detachedHead=false checkout 'abc123def456abc123def456abc123def456abc1'"); -}); - -it('rewrites generic ssh submodule remotes to https for public clones', function () { - $application = applicationWithGitSettings(shallow: false); - $application->settings->is_git_submodules_enabled = true; - - $source = new GithubApp; - $source->forceFill([ - 'html_url' => 'https://github.com', - 'api_url' => 'https://api.github.com', - 'is_public' => true, - ]); - $application->setRelation('source', $source); - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - exec_in_docker: false, - ); - - expect($result['commands']) - ->toContain('sed -i "s#[A-Za-z0-9._-]*@\(.*\):#https://\\1/#g"') - ->not->toContain('s#git@\(.*\):#https://\\1/#g'); -}); - -it('does not add http transport config to ssh deploy key clones', function () { - $application = applicationWithGitSettings(); - $application->private_key_id = 1; - $application->setRelation('private_key', new class extends PrivateKey - { - public function getAttribute($key) - { - if ($key === 'private_key') { - return 'fake-private-key'; - } - - return parent::getAttribute($key); - } - }); - $application->git_repository = 'git@github.com:coollabsio/private-app.git'; - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - exec_in_docker: false, - ); - - expect($result['commands']) - ->not->toContain('http.version=HTTP/1.1') - ->not->toContain('Primary repository import failed, retrying with HTTP/1.1'); -}); - -it('supports dedicated checkout directories for compose file loading', function () { - $application = applicationWithGitSettings(); - - $source = new GithubApp; - $source->forceFill([ - 'html_url' => 'https://github.com', - 'api_url' => 'https://api.github.com', - 'is_public' => true, - ]); - $application->setRelation('source', $source); - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - only_checkout: true, - exec_in_docker: false, - custom_base_dir: 'checkout', - ); - - expect($result['commands']) - ->toContain("git -c http.version=HTTP/1.1 clone --depth=1 --no-checkout -b 'main' 'https://github.com/coollabsio/private-app' 'checkout'") - ->not->toContain('mktemp') - ->not->toContain('git_retry_dir'); -}); - -it('applies http 1 transport to custom bitbucket pull request checkout', function () { - $application = applicationWithGitSettings(); - $application->git_repository = 'https://bitbucket.org/coollabsio/private-app.git'; - - $result = $application->generateGitImportCommands( - deployment_uuid: 'test-deployment', - pull_request_id: 123, - git_type: 'bitbucket', - exec_in_docker: false, - commit: 'abc123def456abc123def456abc123def456abc1', - ); - - expect($result['commands']) - ->toContain("git -c http.version=HTTP/1.1 checkout 'abc123def456abc123def456abc123def456abc1'"); -}); diff --git a/tests/Feature/GitSubmoduleRemoteRewriteTest.php b/tests/Feature/GitSubmoduleRemoteRewriteTest.php new file mode 100644 index 0000000000..9ba6d11b78 --- /dev/null +++ b/tests/Feature/GitSubmoduleRemoteRewriteTest.php @@ -0,0 +1,46 @@ +forceFill([ + 'uuid' => 'test-app-uuid', + 'git_repository' => 'coollabsio/private-app', + 'git_branch' => 'main', + 'git_commit_sha' => 'HEAD', + ]); + + $settings = new ApplicationSetting; + $settings->is_git_shallow_clone_enabled = $shallow; + $settings->is_git_submodules_enabled = false; + $settings->is_git_lfs_enabled = false; + $application->setRelation('settings', $settings); + + return $application; +} + +it('rewrites generic ssh submodule remotes to https for public clones', function () { + $application = applicationWithGitSettings(shallow: false); + $application->settings->is_git_submodules_enabled = true; + + $source = new GithubApp; + $source->forceFill([ + 'html_url' => 'https://github.com', + 'api_url' => 'https://api.github.com', + 'is_public' => true, + ]); + $application->setRelation('source', $source); + + $result = $application->generateGitImportCommands( + deployment_uuid: 'test-deployment', + exec_in_docker: false, + ); + + expect($result['commands']) + ->toContain('sed -i "s#[A-Za-z0-9._-]*@\(.*\):#https://\\1/#g"') + ->not->toContain('s#git@\(.*\):#https://\\1/#g'); +}); diff --git a/tests/Unit/GitHubAppSubmoduleCredentialsTest.php b/tests/Unit/GitHubAppSubmoduleCredentialsTest.php index 36266fce65..48f18d2a59 100644 --- a/tests/Unit/GitHubAppSubmoduleCredentialsTest.php +++ b/tests/Unit/GitHubAppSubmoduleCredentialsTest.php @@ -42,8 +42,8 @@ namespace { expect($result['commands']) ->not->toContain('git config --global') - ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' -c http.version=HTTP/1.1 clone --recurse-submodules -b 'main'") - ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' -c http.version=HTTP/1.1 submodule sync") - ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' -c http.version=HTTP/1.1 submodule update --init --recursive"); + ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' clone --recurse-submodules -b 'main'") + ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' submodule sync") + ->toContain("git -c 'url.https://x-access-token:review%20token%2Fwith%2Bsymbols@github.com/.insteadOf=https://github.com/' submodule update --init --recursive"); }); } diff --git a/tests/Unit/GitlabAppSubmoduleCredentialsTest.php b/tests/Unit/GitlabAppSubmoduleCredentialsTest.php index bf713070f7..183c5419d9 100644 --- a/tests/Unit/GitlabAppSubmoduleCredentialsTest.php +++ b/tests/Unit/GitlabAppSubmoduleCredentialsTest.php @@ -46,7 +46,7 @@ test('connected gitlab oauth submodule credentials use per command git config', exec_in_docker: false, ); - $expectedConfig = "git -c 'url.https://oauth2:gl-token%2Fwith%2Bsym@gitlab.example.test/.insteadOf=https://gitlab.example.test/' -c http.version=HTTP/1.1"; + $expectedConfig = "git -c 'url.https://oauth2:gl-token%2Fwith%2Bsym@gitlab.example.test/.insteadOf=https://gitlab.example.test/'"; expect($result['commands']) ->not->toContain('git config --global') diff --git a/tests/Unit/GitlabSourceCommandsTest.php b/tests/Unit/GitlabSourceCommandsTest.php index 320eaa388f..ca95f50561 100644 --- a/tests/Unit/GitlabSourceCommandsTest.php +++ b/tests/Unit/GitlabSourceCommandsTest.php @@ -210,8 +210,8 @@ it('applies OAuth git config to GitLab merge-request fetch and submodule checkou // The MR-ref fetch and submodule update must run through the OAuth-rewritten git, or private same-host submodules fail. expect($commands) ->toContain('oauth2:gitlab-access-token@gitlab.example.test') - ->toContain("http.version=HTTP/1.1 fetch origin 'merge-requests/2/head:pr-2-coolify'") - ->toContain('http.version=HTTP/1.1 submodule update --init --recursive'); + ->toContain(".insteadOf=https://gitlab.example.test/' fetch origin 'merge-requests/2/head:pr-2-coolify'") + ->toContain(".insteadOf=https://gitlab.example.test/' submodule update --init --recursive"); }); it('uses a GitLab source private key only when it belongs to the source team', function (int $keyTeamId, bool $expectKey) {