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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ On next release:
- [ ] update src/Kernel.php (REPMAN_VERSION)
- [ ] update docker-compose.yml (image tags)

### Fixed
- Prefer VCS tag/branch name over hardcoded `version` in composer.json when syncing packages (tags were silently skipped, webhook/sync looked successful but the release never appeared)

## [1.3.4] - 2021-06-25
### Security
- Upgrade flysystem to 1.1.4 - fix [CVE-2021-32708](https://github.com/thephpleague/flysystem/security/advisories/GHSA-9f46-5r25-5wfm)
Expand Down
33 changes: 30 additions & 3 deletions src/Service/PackageSynchronizer/ComposerPackageSynchronizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ public function synchronize(Package $package): void
{
try {
$io = $this->createIO($package);
/** @var RepositoryInterface $repository */
$repository = current(RepositoryFactory::defaultRepos($io, $this->createConfig($package, $io)));
$repository = $this->createRepository($package, $io);
$json = ['packages' => []];
$packages = $repository->getPackages();

Expand Down Expand Up @@ -271,14 +270,32 @@ private function accessToken(Package $package): string
return $package->oauthToken()->accessToken($this->tokenRefresher);
}

private function createRepository(Package $package, IOInterface $io): RepositoryInterface
{
$config = $this->createConfig($package, $io);
$repoType = $this->repositoryType($package);

if ($this->isVcsRepositoryType($repoType)) {
return new PreferTagVcsRepository([
'type' => $repoType,
'url' => $package->repositoryUrl(),
], $io, $config);
}

/** @var RepositoryInterface $repository */
$repository = current(RepositoryFactory::defaultRepos($io, $config));

return $repository;
}

private function createConfig(Package $package, IOInterface $io): Config
{
unset(Config::$defaultRepositories['packagist.org']);
$config = Factory::createConfig($io);
$config->merge([
'repositories' => [
[
'type' => strpos($package->type(), '-oauth') !== false ? 'vcs' : $package->type(),
'type' => $this->repositoryType($package),
'url' => $package->repositoryUrl(),
],
],
Expand All @@ -293,4 +310,14 @@ private function createConfig(Package $package, IOInterface $io): Config

return $config;
}

private function repositoryType(Package $package): string
{
return strpos($package->type(), '-oauth') !== false ? 'vcs' : $package->type();
}

private function isVcsRepositoryType(string $type): bool
{
return \in_array($type, ['vcs', 'git', 'github', 'gitlab', 'bitbucket'], true);
}
}
135 changes: 135 additions & 0 deletions src/Service/PackageSynchronizer/IgnoreComposerJsonVersionVcsDriver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<?php

declare(strict_types=1);

namespace Buddy\Repman\Service\PackageSynchronizer;

use Composer\Config;
use Composer\IO\IOInterface;
use Composer\Repository\Vcs\VcsDriverInterface;

/**
* Drops composer.json "version" so Composer uses the VCS tag/branch name instead.
*
* Without this, Composer silently skips tags when the hardcoded version does not match
* the tag (sync succeeds, webhook returns 202, but the new release never appears).
*/
final class IgnoreComposerJsonVersionVcsDriver implements VcsDriverInterface
{
private VcsDriverInterface $driver;

public function __construct(VcsDriverInterface $driver)
{
$this->driver = $driver;
}

public function initialize(): void
{
$this->driver->initialize();
}

/**
* @param string $identifier
*
* @return mixed[]|null
*/
public function getComposerInformation($identifier)
{
$data = $this->driver->getComposerInformation($identifier);

if (\is_array($data)) {
unset($data['version']);
}

return $data;
}

/**
* @param string $file
* @param string $identifier
*
* @return string|null
*/
public function getFileContent($file, $identifier)
{
return $this->driver->getFileContent($file, $identifier);
}

/**
* @param string $identifier
*
* @return \DateTime|null
*/
public function getChangeDate($identifier)
{
return $this->driver->getChangeDate($identifier);
}

public function getRootIdentifier(): string
{
return $this->driver->getRootIdentifier();
}

/**
* @return array<string, string>
*/
public function getBranches(): array
{
return $this->driver->getBranches();
}

/**
* @return array<string, string>
*/
public function getTags(): array
{
return $this->driver->getTags();
}

/**
* @param string $identifier
*
* @return mixed[]|null
*/
public function getDist($identifier)
{
return $this->driver->getDist($identifier);
}

/**
* @param string $identifier
*
* @return mixed[]
*/
public function getSource($identifier)
{
return $this->driver->getSource($identifier);
}

public function getUrl(): string
{
return $this->driver->getUrl();
}

/**
* @param string $identifier
*/
public function hasComposerFile($identifier): bool
{
return $this->driver->hasComposerFile($identifier);
}

public function cleanup(): void
{
$this->driver->cleanup();
}

/**
* @param string $url
* @param bool $deep
*/
public static function supports(IOInterface $io, Config $config, $url, $deep = false): bool
{
return false;
}
}
27 changes: 27 additions & 0 deletions src/Service/PackageSynchronizer/PreferTagVcsRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Buddy\Repman\Service\PackageSynchronizer;

use Composer\Repository\Vcs\VcsDriverInterface;
use Composer\Repository\VcsRepository;

final class PreferTagVcsRepository extends VcsRepository
{
private ?VcsDriverInterface $wrappedDriver = null;

public function getDriver(): ?VcsDriverInterface
{
if ($this->wrappedDriver !== null) {
return $this->wrappedDriver;
}

$driver = parent::getDriver();
if ($driver === null) {
return null;
}

return $this->wrappedDriver = new IgnoreComposerJsonVersionVcsDriver($driver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace Buddy\Repman\Tests\Unit\Service\PackageSynchronizer;

use Buddy\Repman\Service\PackageSynchronizer\IgnoreComposerJsonVersionVcsDriver;
use Composer\Repository\Vcs\VcsDriverInterface;
use PHPUnit\Framework\TestCase;

final class IgnoreComposerJsonVersionVcsDriverTest extends TestCase
{
public function testRemovesVersionFromComposerInformation(): void
{
$inner = $this->createMock(VcsDriverInterface::class);
$inner->method('getComposerInformation')->with('abc123')->willReturn([
'name' => 'buddy-works/example',
'version' => '1.0.0',
'description' => 'Example package',
]);

$driver = new IgnoreComposerJsonVersionVcsDriver($inner);

self::assertSame([
'name' => 'buddy-works/example',
'description' => 'Example package',
], $driver->getComposerInformation('abc123'));
}

public function testReturnsNonArrayComposerInformationUnchanged(): void
{
$inner = $this->createMock(VcsDriverInterface::class);
$inner->method('getComposerInformation')->willReturn(null);

$driver = new IgnoreComposerJsonVersionVcsDriver($inner);

self::assertNull($driver->getComposerInformation('abc123'));
}

public function testDelegatesTagsAndUrl(): void
{
$inner = $this->createMock(VcsDriverInterface::class);
$inner->method('getTags')->willReturn(['1.0.0' => 'aaa', '1.0.1' => 'bbb']);
$inner->method('getUrl')->willReturn('https://example.com/repo.git');

$driver = new IgnoreComposerJsonVersionVcsDriver($inner);

self::assertSame(['1.0.0' => 'aaa', '1.0.1' => 'bbb'], $driver->getTags());
self::assertSame('https://example.com/repo.git', $driver->getUrl());
}
}