Merge pull request #6869 from coollabsio/allow-at-sign-in-git-urls
fix(git): handle Git redirects and improve URL parsing for tangled.sh and other Git hosts
This commit is contained in:
commit
6d3c996ef3
3 changed files with 115 additions and 17 deletions
|
|
@ -1897,9 +1897,27 @@ private function check_git_if_build_needed()
|
|||
);
|
||||
}
|
||||
if ($this->saved_outputs->get('git_commit_sha') && ! $this->rollback) {
|
||||
$this->commit = $this->saved_outputs->get('git_commit_sha')->before("\t");
|
||||
$this->application_deployment_queue->commit = $this->commit;
|
||||
$this->application_deployment_queue->save();
|
||||
// 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)
|
||||
$lsRemoteOutput = $this->saved_outputs->get('git_commit_sha');
|
||||
|
||||
// Find the part containing a tab (the actual ls-remote result)
|
||||
// Handle cases where warning is on the same line as the result
|
||||
if ($lsRemoteOutput->contains("\t")) {
|
||||
// Get everything from the last occurrence of a valid commit SHA pattern before the tab
|
||||
// A valid commit SHA is 40 hex characters
|
||||
$output = $lsRemoteOutput->value();
|
||||
|
||||
// Extract the line with the tab (actual ls-remote result)
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
|
||||
if (isset($matches[1])) {
|
||||
$this->commit = $matches[1];
|
||||
$this->application_deployment_queue->commit = $this->commit;
|
||||
$this->application_deployment_queue->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->set_coolify_variables();
|
||||
|
||||
|
|
@ -1914,7 +1932,7 @@ private function clone_repository()
|
|||
{
|
||||
$importCommands = $this->generate_git_import_commands();
|
||||
$this->application_deployment_queue->addLogEntry("\n----------------------------------------");
|
||||
$this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}.");
|
||||
$this->application_deployment_queue->addLogEntry("Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->commit}) to {$this->basedir}.");
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->application_deployment_queue->addLogEntry("Checking out tag pull/{$this->pull_request_id}/head.");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1003,29 +1003,30 @@ public function dirOnServer()
|
|||
public function setGitImportSettings(string $deployment_uuid, string $git_clone_command, bool $public = false)
|
||||
{
|
||||
$baseDir = $this->generateBaseDir($deployment_uuid);
|
||||
$escapedBaseDir = escapeshellarg($baseDir);
|
||||
$isShallowCloneEnabled = $this->settings?->is_git_shallow_clone_enabled ?? false;
|
||||
|
||||
if ($this->git_commit_sha !== 'HEAD') {
|
||||
// If shallow clone is enabled and we need a specific commit,
|
||||
// we need to fetch that specific commit with depth=1
|
||||
if ($isShallowCloneEnabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$this->git_commit_sha} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git fetch --depth=1 origin {$this->git_commit_sha} && git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
} else {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git -c advice.detachedHead=false checkout {$this->git_commit_sha} >/dev/null 2>&1";
|
||||
}
|
||||
}
|
||||
if ($this->settings->is_git_submodules_enabled) {
|
||||
// Check if .gitmodules file exists before running submodule commands
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && if [ -f .gitmodules ]; then";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && if [ -f .gitmodules ]; then";
|
||||
if ($public) {
|
||||
$git_clone_command = "{$git_clone_command} sed -i \"s#git@\(.*\):#https://\\1/#g\" {$baseDir}/.gitmodules || true &&";
|
||||
$git_clone_command = "{$git_clone_command} sed -i \"s#git@\(.*\):#https://\\1/#g\" {$escapedBaseDir}/.gitmodules || true &&";
|
||||
}
|
||||
// Add shallow submodules flag if shallow clone is enabled
|
||||
$submoduleFlags = $isShallowCloneEnabled ? '--depth=1' : '';
|
||||
$git_clone_command = "{$git_clone_command} git submodule sync && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git submodule update --init --recursive {$submoduleFlags}; fi";
|
||||
}
|
||||
if ($this->settings->is_git_lfs_enabled) {
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull";
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null\" git lfs pull";
|
||||
}
|
||||
|
||||
return $git_clone_command;
|
||||
|
|
@ -1130,7 +1131,8 @@ public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_
|
|||
|
||||
if ($this->deploymentType() === 'other') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$base_command = "{$base_command} {$customRepository}";
|
||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||
$base_command = "{$base_command} {$escapedCustomRepository}";
|
||||
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, $base_command));
|
||||
|
|
@ -1272,7 +1274,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
|||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
|
|
@ -1280,14 +1282,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
|||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1305,7 +1307,8 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
|||
}
|
||||
if ($this->deploymentType() === 'other') {
|
||||
$fullRepoUrl = $customRepository;
|
||||
$git_clone_command = "{$git_clone_command} {$customRepository} {$baseDir}";
|
||||
$escapedCustomRepository = escapeshellarg($customRepository);
|
||||
$git_clone_command = "{$git_clone_command} {$escapedCustomRepository} {$escapedBaseDir}";
|
||||
$git_clone_command = $this->setGitImportSettings($deployment_uuid, $git_clone_command, public: true);
|
||||
|
||||
if ($pull_request_id !== 0) {
|
||||
|
|
@ -1316,7 +1319,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
|||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'github' || $git_type === 'gitea') {
|
||||
$branch = "pull/{$pull_request_id}/head:$pr_branch_name";
|
||||
if ($exec_in_docker) {
|
||||
|
|
@ -1324,14 +1327,14 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
|||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $branch && ".$this->buildGitCheckoutCommand($pr_branch_name);
|
||||
} elseif ($git_type === 'bitbucket') {
|
||||
if ($exec_in_docker) {
|
||||
$commands->push(executeInDocker($deployment_uuid, "echo 'Checking out $branch'"));
|
||||
} else {
|
||||
$commands->push("echo 'Checking out $branch'");
|
||||
}
|
||||
$git_clone_command = "{$git_clone_command} && cd {$baseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
$git_clone_command = "{$git_clone_command} && cd {$escapedBaseDir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" ".$this->buildGitCheckoutCommand($commit);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
77
tests/Unit/GitLsRemoteParsingTest.php
Normal file
77
tests/Unit/GitLsRemoteParsingTest.php
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
|
||||
uses(\Tests\TestCase::class);
|
||||
|
||||
it('extracts commit SHA from git ls-remote output without warnings', function () {
|
||||
$output = "196d3df7665359a8c8fa3329a6bcde0267e550bf\trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('extracts commit SHA from git ls-remote output with redirect warning on separate line', function () {
|
||||
$output = "warning: redirecting to https://tangled.org/@tangled.org/core/\n196d3df7665359a8c8fa3329a6bcde0267e550bf\trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('extracts commit SHA from git ls-remote output with redirect warning on same line', function () {
|
||||
// This is the actual format from tangled.sh - warning and result on same line, no newline
|
||||
$output = "warning: redirecting to https://tangled.org/@tangled.org/core/196d3df7665359a8c8fa3329a6bcde0267e550bf\trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('extracts commit SHA from git ls-remote output with multiple warning lines', function () {
|
||||
$output = "warning: redirecting to https://example.org/repo/\ninfo: some other message\n196d3df7665359a8c8fa3329a6bcde0267e550bf\trefs/heads/main";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('handles git ls-remote output with extra whitespace', function () {
|
||||
$output = " 196d3df7665359a8c8fa3329a6bcde0267e550bf \trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('extracts commit SHA with uppercase letters and normalizes to lowercase', function () {
|
||||
$output = "196D3DF7665359A8C8FA3329A6BCDE0267E550BF\trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
// Git SHAs are case-insensitive, so we normalize to lowercase for comparison
|
||||
expect(strtolower($commit))->toBe('196d3df7665359a8c8fa3329a6bcde0267e550bf');
|
||||
});
|
||||
|
||||
it('returns null when no commit SHA is present in output', function () {
|
||||
$output = "warning: redirecting to https://example.org/repo/\nError: repository not found";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when output has tab but no valid SHA', function () {
|
||||
$output = "invalid-sha-format\trefs/heads/master";
|
||||
|
||||
preg_match('/\b([0-9a-fA-F]{40})(?=\s*\t)/', $output, $matches);
|
||||
$commit = $matches[1] ?? null;
|
||||
|
||||
expect($commit)->toBeNull();
|
||||
});
|
||||
Loading…
Reference in a new issue