Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 106 additions & 18 deletions src/Command/Push/PushArtifactCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@
]);

$this->checklist->addItem('Preparing artifact directory');
$this->cloneSourceBranch($outputCallback, $artifactDir, $destinationGitUrls[0], $sourceGitBranch);
$this->cloneSourceBranch($outputCallback, $artifactDir, $destinationGitUrls, $sourceGitBranch);
$this->checklist->completePreviousItem();
}

Expand Down Expand Up @@ -175,8 +175,10 @@

/**
* Prepare a directory to build the artifact.
*
* @param string[] $vcsUrls
*/
private function cloneSourceBranch(Closure $outputCallback, string $artifactDir, string $vcsUrl, string $vcsPath): void
private function cloneSourceBranch(Closure $outputCallback, string $artifactDir, array $vcsUrls, string $vcsPath): void
{
$fs = $this->localMachineHelper->getFilesystem();

Expand All @@ -185,39 +187,63 @@

$outputCallback('out', "Initializing Git in $artifactDir");
$this->localMachineHelper->checkRequiredBinariesExist(['git']);
$printOutput = $this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL;
$process = $this->localMachineHelper->execute([
'git',
'clone',
'--depth=1',
$vcsUrl,
$vcsUrls[0],
$artifactDir,
], $outputCallback, null, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL));
], $outputCallback, null, $printOutput);
if (!$process->isSuccessful()) {
throw new AcquiaCliException('Failed to clone repository from the Cloud Platform: {message}', ['message' => $process->getErrorOutput()]);
}
$process = $this->localMachineHelper->execute([
'git',
'fetch',
'--depth=1',
'--update-head-ok',
$vcsUrl,
$vcsPath . ':' . $vcsPath,
], $outputCallback, $artifactDir, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL));
if (!$process->isSuccessful()) {
// Remote branch does not exist. Just create it locally. This will create
// the new branch off of the current commit.

// Fetch the branch tip from every destination so out-of-sync remotes
// are detected before the artifact is built and pushed.
$tips = [];
foreach ($vcsUrls as $vcsUrl) {
$outputCallback('out', "Fetching $vcsPath from $vcsUrl");

Check warning on line 206 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "FunctionCallRemoval": @@ @@ // are detected before the artifact is built and pushed. $tips = []; foreach ($vcsUrls as $vcsUrl) { - $outputCallback('out', "Fetching $vcsPath from $vcsUrl"); + $process = $this->localMachineHelper->execute([ 'git', 'fetch',
$process = $this->localMachineHelper->execute([
'git',
'fetch',
'--depth=1',
$vcsUrl,
$vcsPath,
], $outputCallback, $artifactDir, $printOutput);
if (!$process->isSuccessful()) {
// The branch does not exist on this remote yet. The push will
// create it.
continue;
}
$process = $this->localMachineHelper->execute([
'git',
'rev-parse',
'FETCH_HEAD',
], null, $artifactDir, false);
if ($process->isSuccessful() && trim($process->getOutput()) !== '') {

Check warning on line 224 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "LogicalAnd": @@ @@ 'rev-parse', 'FETCH_HEAD', ], null, $artifactDir, false); - if ($process->isSuccessful() && trim($process->getOutput()) !== '') { + if ($process->isSuccessful() || trim($process->getOutput()) !== '') { $tips[$vcsUrl] = trim($process->getOutput()); } }

Check warning on line 224 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "UnwrapTrim": @@ @@ 'rev-parse', 'FETCH_HEAD', ], null, $artifactDir, false); - if ($process->isSuccessful() && trim($process->getOutput()) !== '') { + if ($process->isSuccessful() && $process->getOutput() !== '') { $tips[$vcsUrl] = trim($process->getOutput()); } }
$tips[$vcsUrl] = trim($process->getOutput());
}
Comment on lines +219 to +226
}

if ($tips === []) {
// The branch does not exist on any remote. Just create it locally.
// This will create the new branch off of the current commit.
$process = $this->localMachineHelper->execute([
'git',
'checkout',
'-b',
$vcsPath,
], $outputCallback, $artifactDir, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL));
], $outputCallback, $artifactDir, $printOutput);
} else {
$baseTip = $this->determineBaseTip($tips, $vcsPath, $artifactDir);
$process = $this->localMachineHelper->execute([
'git',
'checkout',
'-B',
$vcsPath,
], $outputCallback, $artifactDir, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL));
$baseTip,
], $outputCallback, $artifactDir, $printOutput);
}
if (!$process->isSuccessful()) {
throw new AcquiaCliException("Could not checkout $vcsPath branch locally: {message}", ['message' => $process->getErrorOutput() . $process->getOutput()]);
Expand Down Expand Up @@ -392,12 +418,69 @@
return "Automated commit by Acquia CLI (source commit: $commitHash)";
}

/**
* Pick the branch tip to base the artifact on.
*
* Ensures every destination can fast-forward to the artifact commit. If
* the tips differ but are ancestor-related, the most advanced tip wins.
* Truly diverged tips abort the push before anything is built.
*
* @param array<string, string> $tips
*/
private function determineBaseTip(array $tips, string $vcsPath, string $artifactDir): string
{
$uniqueTips = array_values(array_unique($tips));
if (count($uniqueTips) === 1) {
return $uniqueTips[0];
}

// The tips differ. Deepen the shallow history so ancestry between
// them can be established.
foreach (array_keys($tips) as $vcsUrl) {
$this->localMachineHelper->execute([
'git',
'fetch',
'--deepen=50',
$vcsUrl,
$vcsPath,
], null, $artifactDir, false);
}
Comment on lines +439 to +447
foreach ($uniqueTips as $candidate) {
foreach ($uniqueTips as $other) {
if ($other === $candidate) {
continue;
}
$process = $this->localMachineHelper->execute([
'git',
'merge-base',
'--is-ancestor',
$other,
$candidate,
], null, $artifactDir, false);
if (!$process->isSuccessful()) {
continue 2;
}
Comment on lines +453 to +462
}
return $candidate;
}

$remoteTips = [];
foreach ($tips as $vcsUrl => $tip) {

Check warning on line 468 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "Foreach_": @@ @@ } $remoteTips = []; - foreach ($tips as $vcsUrl => $tip) { + foreach ([] as $vcsUrl => $tip) { $remoteTips[] = "$vcsUrl ($tip)"; } throw new AcquiaCliException('The destination git repositories are out of sync for the {branch} branch: {tips}. Reconcile them (e.g. delete the stale artifact branch from the out-of-date remote or push the desired tip to it) and try again.', [
$remoteTips[] = "$vcsUrl ($tip)";
}
throw new AcquiaCliException('The destination git repositories are out of sync for the {branch} branch: {tips}. Reconcile them (e.g. delete the stale artifact branch from the out-of-date remote or push the desired tip to it) and try again.', [

Check warning on line 471 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "ArrayItemRemoval": @@ @@ $remoteTips[] = "$vcsUrl ($tip)"; } throw new AcquiaCliException('The destination git repositories are out of sync for the {branch} branch: {tips}. Reconcile them (e.g. delete the stale artifact branch from the out-of-date remote or push the desired tip to it) and try again.', [ - 'branch' => $vcsPath, 'tips' => implode(', ', $remoteTips), ]); }
'branch' => $vcsPath,
'tips' => implode(', ', $remoteTips),
]);
}

/**
* Push the artifact.
*/
private function pushArtifact(Closure $outputCallback, string $artifactDir, array $vcsUrls, string $destGitBranch): void
{
$this->localMachineHelper->checkRequiredBinariesExist(['git']);
$failures = [];
foreach ($vcsUrls as $vcsUrl) {
$outputCallback('out', "Pushing changes to Acquia Git ($vcsUrl)");
$args = [
Expand All @@ -408,9 +491,14 @@
];
$process = $this->localMachineHelper->execute($args, $outputCallback, $artifactDir, ($this->output->getVerbosity() > OutputInterface::VERBOSITY_NORMAL));
if (!$process->isSuccessful()) {
throw new AcquiaCliException("Unable to push artifact: {message}", ['message' => $process->getOutput() . $process->getErrorOutput()]);
// Keep pushing to the remaining remotes so a single failure
// does not leave them further out of sync.
$failures[] = "$vcsUrl: " . $process->getOutput() . $process->getErrorOutput();

Check warning on line 496 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "ConcatOperandRemoval": @@ @@ if (!$process->isSuccessful()) { // Keep pushing to the remaining remotes so a single failure // does not leave them further out of sync. - $failures[] = "$vcsUrl: " . $process->getOutput() . $process->getErrorOutput(); + $failures[] = "$vcsUrl: " . $process->getOutput(); } } if ($failures !== []) {

Check warning on line 496 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "ConcatOperandRemoval": @@ @@ if (!$process->isSuccessful()) { // Keep pushing to the remaining remotes so a single failure // does not leave them further out of sync. - $failures[] = "$vcsUrl: " . $process->getOutput() . $process->getErrorOutput(); + $failures[] = "$vcsUrl: " . $process->getErrorOutput(); } } if ($failures !== []) {

Check warning on line 496 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "Concat": @@ @@ if (!$process->isSuccessful()) { // Keep pushing to the remaining remotes so a single failure // does not leave them further out of sync. - $failures[] = "$vcsUrl: " . $process->getOutput() . $process->getErrorOutput(); + $failures[] = "$vcsUrl: " . $process->getErrorOutput() . $process->getOutput(); } } if ($failures !== []) {

Check warning on line 496 in src/Command/Push/PushArtifactCommand.php

View workflow job for this annotation

GitHub Actions / Mutation Testing

Escaped Mutant for Mutator "Concat": @@ @@ if (!$process->isSuccessful()) { // Keep pushing to the remaining remotes so a single failure // does not leave them further out of sync. - $failures[] = "$vcsUrl: " . $process->getOutput() . $process->getErrorOutput(); + $failures[] = $process->getOutput() . "$vcsUrl: " . $process->getErrorOutput(); } } if ($failures !== []) {
}
}
if ($failures !== []) {
throw new AcquiaCliException("Unable to push artifact: {message}", ['message' => implode(PHP_EOL, $failures)]);
}
}

/**
Expand Down
Loading
Loading