Skip to content

Commit

Permalink
Merge pull request #244 from dxw/feature/warn-user-on-archived-github…
Browse files Browse the repository at this point in the history
…-repos

Feature/warn user on archived GitHub repos
  • Loading branch information
snim2 authored May 30, 2024
2 parents bf78cd5 + 08b4673 commit 19d6be6
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 5 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [2.4.2] - 2024-05-28

### Changed
- Users are now warned if they pull from an archived GitHub repository

## [2.4.1] - 2024-03-11

### Changed
Expand Down
65 changes: 65 additions & 0 deletions src/Git/Git.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,69 @@ public function is_repo()
return file_exists("{$this->repo_path}/.git");
}

private function is_github_repository($repository)
{
$pos = strpos($repository, 'github.com');
return $pos !== false;
}

/** Issue a warning to the user if a GitHub repository is archived.
*
* Note that we specifically ignore any non-GitHub repository for now,
* which is why we have not factored this code into its own class structure.
*
* See: https://docs.github.com/en/rest/repos/repos?get-a-repository
*/
public function check_is_archived_github_repository($repository)
{
if (!$this->is_github_repository($repository)) {
return;
}
$baseurl = 'https://api.github.com/repos'; # Must not have a trailing slash.
$substrings = explode('/', $repository);
$num_substrings = count($substrings);
# If the URL is http formatted: ['https', 'github.com', 'org', 'repo']
# If the URL is ssh formatted: ['[email protected]:org', 'repo']
if ($num_substrings < 2) {
return false;
}
$repo = $substrings[$num_substrings - 1];
if (false !== strpos($repo, '.git')) { # repo.git
$repo = str_replace('.git', '', $repo);
}

if (false !== strpos($repository, '@')) {
# ssh formatted...
$org = explode(':', $substrings[$num_substrings - 2])[1];
} else {
# http formatted...
$org = $substrings[$num_substrings - 2];
}
$api_url = join('/', [$baseurl, $org, $repo]);

$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $api_url);
curl_setopt($curl, CURLOPT_USERAGENT, 'Whippet');
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$raw_json = curl_exec($curl);
$json = json_decode($raw_json);
curl_close($curl);
if (!is_null($json) && property_exists($json, 'archived') && $json->archived) {
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
echo "!! WARNING: GitHub repo is archived. This dependency !!\n";
echo "!! should be replaced before the repo is removed. !!\n";
echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n";
}
}

public function checkout($revision)
{
list($output, $return) = $this->run_command(['git', 'remote', 'get-url', 'origin']);
if ($return === 0) {
$this->check_is_archived_github_repository($output[0]);
}

list($output, $return) = $this->run_command(['git', 'fetch', '-a', '--force', '&&', 'git', 'checkout', $revision]);

return $this->check_git_return('Checkout failed', $return, $output);
Expand All @@ -62,6 +123,8 @@ public function mixed_reset($revision = 'HEAD')

public function clone_repo($repository)
{
$this->check_is_archived_github_repository($repository);

list($output, $return) = $this->run_command(['git', 'clone', $repository, $this->repo_path], false);

if (!$this->check_git_return('Clone failed', $return, $output)) {
Expand All @@ -73,6 +136,8 @@ public function clone_repo($repository)

public function clone_no_checkout($repository)
{
$this->check_is_archived_github_repository($repository);

$tmpdir = $this->get_tmpdir();

list($output, $return) = $this->run_command(['git', 'clone', '--no-checkout', $repository, $tmpdir], false);
Expand Down
34 changes: 29 additions & 5 deletions tests/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,22 @@ private function getWhippetLock(/* string */ $hash, array $dependencyMap)
return $whippetLock;
}

private function getGit($isRepo, $cloneRepo, $checkout)
private function getArchivedWarning()
{
$warning = <<<'EOT'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! WARNING: GitHub repo is archived. This dependency !!
!! should be replaced before the repo is removed. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

EOT;
return $warning;
}

private function getGit($isRepo, $cloneRepo, $checkout, $isArchived = false)
{
$archived_warning = $this->getArchivedWarning();

$git = $this->getMockBuilder('\\Dxw\\Whippet\\Git\\Git')
->disableOriginalConstructor()
->getMock();
Expand All @@ -42,6 +56,11 @@ private function getGit($isRepo, $cloneRepo, $checkout)

if ($cloneRepo !== null) {
$return = true;
$output = "git clone output\n";

if ($isArchived) {
$output = $archived_warning . $output;
}

if (is_array($cloneRepo)) {
$return = $cloneRepo['return'];
Expand All @@ -51,15 +70,20 @@ private function getGit($isRepo, $cloneRepo, $checkout)
$git->expects($this->exactly(1))
->method('clone_repo')
->with($cloneRepo)
->will($this->returnCallback(function () use ($return) {
echo "git clone output\n";
->will($this->returnCallback(function () use ($output, $return) {
echo $output;

return $return;
}));
}

if ($checkout !== null) {
$return = true;
$output = "git checkout output\n";

if ($isArchived) {
$output = $archived_warning . $output;
}

if (is_array($checkout)) {
$return = $checkout['return'];
Expand All @@ -69,8 +93,8 @@ private function getGit($isRepo, $cloneRepo, $checkout)
$git->expects($this->exactly(1))
->method('checkout')
->with($checkout)
->will($this->returnCallback(function () use ($return) {
echo "git checkout output\n";
->will($this->returnCallback(function () use ($output, $return) {
echo $output;

return $return;
}));
Expand Down
120 changes: 120 additions & 0 deletions tests/dependencies/installer_test.php
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,65 @@ public function testInspectionsApiUnavailable()
$this->assertEquals($expectedOutput, $output);
}

public function testInstallArchiveRepo()
{
$dir = $this->getDir();
file_put_contents($dir.'/whippet.json', 'foobar');
file_put_contents($dir.'/whippet.lock', 'foobar');

$my_theme = [
'name' => 'my-theme',
'src' => '[email protected]:wordpress-themes/my-theme',
'revision' => '27ba906',
];

$whippetLock = $this->getWhippetLock(sha1('foobar'), [
'themes' => [
$my_theme,
],
'plugins' => [],
]);
$this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock));

$gitMyTheme = $this->getGit(false, '[email protected]:wordpress-themes/my-theme', '27ba906', true);
$this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/themes/my-theme', $gitMyTheme);

$inspection_check_results = function ($type, $dep) {
return [
'themes' => [
'my-theme' => \Result\Result::ok('')
],
][$type][$dep['name']];
};

$dependencies = new \Dxw\Whippet\Dependencies\Installer(
$this->getFactory(),
$this->getProjectDirectory($dir),
$this->fakeInspectionCheckerWithResults($inspection_check_results)
);

ob_start();
$result = $dependencies->installAll();
$output = ob_get_clean();

$this->assertFalse($result->isErr());
$expectedOutput = <<<'EOT'
[Adding themes/my-theme]
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! WARNING: GitHub repo is archived. This dependency !!
!! should be replaced before the repo is removed. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
git clone output
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! WARNING: GitHub repo is archived. This dependency !!
!! should be replaced before the repo is removed. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
git checkout output


EOT;
$this->assertEquals($expectedOutput, $output);
}

public function testInstallAllThemeAlreadyCloned()
{
Expand Down Expand Up @@ -468,6 +527,67 @@ public function testInstallSingleAlreadyCloned()
$this->assertFalse($result->isErr());
}

public function testInstallSingleAlreadyClonedAndArchived()
{
$dir = $this->getDir();
file_put_contents($dir.'/whippet.json', 'foobar');
file_put_contents($dir.'/whippet.lock', 'foobar');

$whippetLock = $this->getWhippetLock(sha1('foobar'), [
'themes' => [
[
'name' => 'my-theme',
'src' => '[email protected]:wordpress-themes/my-theme',
'revision' => '27ba906',
],
],
'plugins' => [
[
'name' => 'my-plugin',
'src' => '[email protected]:wordpress-plugins/my-plugin',
'revision' => '123456',
],
[
'name' => 'another-plugin',
'src' => '[email protected]:wordpress-plugins/another-plugin',
'revision' => '789abc',
],
],
]);
$this->addFactoryCallStatic('\\Dxw\\Whippet\\Files\\WhippetLock', 'fromFile', $dir.'/whippet.lock', \Result\Result::ok($whippetLock));

$gitMyPlugin = $this->getGit(false, '[email protected]:wordpress-plugins/my-plugin', '123456', true);
$this->addFactoryNewInstance('\\Dxw\\Whippet\\Git\\Git', $dir.'/wp-content/plugins/my-plugin', $gitMyPlugin);

$dependencies = new \Dxw\Whippet\Dependencies\Installer(
$this->getFactory(),
$this->getProjectDirectory($dir),
$this->fakeInspectionChecker()
);
ob_start();
$result = $dependencies->installSingle('plugins/my-plugin');
$output = ob_get_clean();
$expectedOutput = <<<'EOT'
[Adding plugins/my-plugin]
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! WARNING: GitHub repo is archived. This dependency !!
!! should be replaced before the repo is removed. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
git clone output
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
!! WARNING: GitHub repo is archived. This dependency !!
!! should be replaced before the repo is removed. !!
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
git checkout output


EOT;


$this->assertEquals($expectedOutput, $output);
$this->assertFalse($result->isErr());
}

public function testInstallSingleCloneFails()
{
$dir = $this->getDir();
Expand Down

0 comments on commit 19d6be6

Please sign in to comment.