Skip to content

Commit 1aa8816

Browse files
feature #588 Adding the current recipe URL to recipes <recipe> output (weaverryan)
This PR was squashed before being merged into the 1.5-dev branch. Discussion ---------- Adding the current recipe URL to recipes <recipe> output Hi! This replaces #585 and builds off of #562. Basically, I *thought* that it would be nearly impossible to get the *exact* git sha for when a recipe is installed. But with a few API calls to GitHub, it's quite possible :). This gives us the ability to (at least) give the URL to the *exact* state of the recipe when it was installed in their app. This can help them determine what's changed and if they need to update. Giving them a "diff" URL would be even more awesome - but as the directory often changes - I don't believe there is any URL we can give to visually compare across directories. Output: <img width="1278" alt="Screen Shot 2019-12-12 at 12 06 54 PM" src="https://user-images.githubusercontent.com/121003/70733308-13dd3800-1cd8-11ea-85c8-61c3283dc1b4.png"> The "installed recipe" is the line that this enables. The "new commits" line (unfortunately) can only be shown when the version/directory of the recipe hasn't changed. There's just not good way (that I can think of) to show a decent history if they are upgrading from (for example) `symfony/framework/bundle/3.3` to `symfony/framework-bundle/4.2`. Cheers! Commits ------- a761365 Adding the current recipe URL to recipes <recipe> output
2 parents 77a0cc9 + a761365 commit 1aa8816

File tree

2 files changed

+118
-10
lines changed

2 files changed

+118
-10
lines changed

src/Command/RecipesCommand.php

Lines changed: 117 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
namespace Symfony\Flex\Command;
1313

1414
use Composer\Command\BaseCommand;
15+
use Composer\Downloader\TransportException;
1516
use Symfony\Component\Console\Input\InputArgument;
1617
use Symfony\Component\Console\Input\InputInterface;
1718
use Symfony\Component\Console\Output\OutputInterface;
1819
use Symfony\Flex\InformationOperation;
1920
use Symfony\Flex\Lock;
21+
use Symfony\Flex\ParallelDownloader;
2022
use Symfony\Flex\Recipe;
2123

2224
/**
@@ -27,13 +29,14 @@ class RecipesCommand extends BaseCommand
2729
/** @var \Symfony\Flex\Flex */
2830
private $flex;
2931

30-
/** @var Lock */
3132
private $symfonyLock;
33+
private $downloader;
3234

33-
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock)
35+
public function __construct(/* cannot be type-hinted */ $flex, Lock $symfonyLock, ParallelDownloader $downloader)
3436
{
3537
$this->flex = $flex;
3638
$this->symfonyLock = $symfonyLock;
39+
$this->downloader = $downloader;
3740

3841
parent::__construct();
3942
}
@@ -128,9 +131,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
128131

129132
private function displayPackageInformation(Recipe $recipe)
130133
{
134+
$io = $this->getIO();
131135
$recipeLock = $this->symfonyLock->get($recipe->getName());
132136

133137
$lockRef = $recipeLock['recipe']['ref'] ?? null;
138+
$lockRepo = $recipeLock['recipe']['repo'] ?? null;
134139
$lockFiles = $recipeLock['files'] ?? null;
135140

136141
$status = '<comment>up to date</comment>';
@@ -142,16 +147,63 @@ private function displayPackageInformation(Recipe $recipe)
142147
$status = '<comment>update available</comment>';
143148
}
144149

145-
$io = $this->getIO();
146-
$io->write('<info>name</info> : '.$recipe->getName());
147-
$io->write('<info>version</info> : '.$recipeLock['version']);
150+
$branch = $recipeLock['recipe']['branch'] ?? 'master';
151+
$gitSha = null;
152+
$commitDate = null;
153+
if (null !== $lockRef && null !== $lockRepo) {
154+
try {
155+
list($gitSha, $commitDate) = $this->findRecipeCommitDataFromTreeRef(
156+
$recipe->getName(),
157+
$lockRepo,
158+
$branch,
159+
$recipeLock['version'],
160+
$lockRef
161+
);
162+
} catch (TransportException $exception) {
163+
$io->writeError('Error downloading exact git sha for installed recipe.');
164+
}
165+
}
166+
167+
$io->write('<info>name</info> : '.$recipe->getName());
168+
$io->write('<info>version</info> : '.$recipeLock['version']);
169+
$io->write('<info>status</info> : '.$status);
148170
if (!$recipe->isAuto()) {
149-
$io->write('<info>repo</info> : '.sprintf('https://%s/tree/master/%s/%s', $recipeLock['recipe']['repo'], $recipe->getName(), $recipeLock['version']));
171+
$recipeUrl = sprintf(
172+
'https://%s/tree/%s/%s/%s',
173+
$lockRepo,
174+
// if something fails, default to the branch as the closest "sha"
175+
$gitSha ?? $branch,
176+
$recipe->getName(),
177+
$recipeLock['version']
178+
);
179+
180+
$io->write('<info>installed recipe</info> : '.$recipeUrl);
181+
}
182+
183+
if ($lockRef !== $recipe->getRef()) {
184+
$io->write('<info>latest recipe</info> : '.$recipe->getURL());
185+
186+
// if the version is the same, point them to the history
187+
if ($recipe->getVersion() === $recipeLock['version'] && null !== $gitSha) {
188+
$historyUrl = sprintf(
189+
'https://%s/commits/%s/%s/%s',
190+
$lockRepo,
191+
$branch,
192+
$recipe->getName(),
193+
$recipe->getVersion()
194+
);
195+
196+
// show commits since one second after the currently-installed recipe
197+
if (null !== $commitDate) {
198+
$historyUrl .= '?since='.(new \DateTime($commitDate))->modify('+1 seconds')->format('c\Z');
199+
}
200+
201+
$io->write('<info>new commits</info> : '.$historyUrl);
202+
}
150203
}
151-
$io->write('<info>status</info> : '.$status);
152204

153205
if (null !== $lockFiles) {
154-
$io->write('<info>files</info> : ');
206+
$io->write('<info>files</info> : ');
155207
$io->write('');
156208

157209
$tree = $this->generateFilesTree($lockFiles);
@@ -195,7 +247,8 @@ private function addNode(array $node): array
195247
*/
196248
private function displayFilesTree(array $tree)
197249
{
198-
$endKey = array_key_last($tree);
250+
end($tree);
251+
$endKey = key($tree);
199252
foreach ($tree as $dir => $files) {
200253
$treeBar = '';
201254
$total = \count($files);
@@ -252,4 +305,59 @@ private function writeTreeLine($line)
252305

253306
$io->write($line);
254307
}
308+
309+
/**
310+
* Attempts to find the original git sha when the recipe was installed.
311+
*/
312+
private function findRecipeCommitDataFromTreeRef(string $package, string $repo, string $branch, string $version, string $lockRef)
313+
{
314+
// only supports public repository placement
315+
if (0 !== strpos($repo, 'github.com')) {
316+
return [null, null];
317+
}
318+
319+
$parts = explode('/', $repo);
320+
if (3 !== \count($parts)) {
321+
return [null, null];
322+
}
323+
324+
$recipePath = sprintf('%s/%s', $package, $version);
325+
$commitsData = $this->requestGitHubApi(sprintf(
326+
'https://api.github.com/repos/%s/%s/commits?path=%s&sha=%s',
327+
$parts[1],
328+
$parts[2],
329+
$recipePath,
330+
$branch
331+
));
332+
333+
foreach ($commitsData as $commitData) {
334+
// go back the commits one-by-one
335+
$treeUrl = $commitData['commit']['tree']['url'].'?recursive=true';
336+
337+
// fetch the full tree, then look for the tree for the package path
338+
$treeData = $this->requestGitHubApi($treeUrl);
339+
foreach ($treeData['tree'] as $treeItem) {
340+
if ($treeItem['path'] !== $recipePath) {
341+
continue;
342+
}
343+
344+
if ($treeItem['sha'] === $lockRef) {
345+
// shorten for brevity
346+
return [
347+
substr($commitData['sha'], 0, 7),
348+
$commitData['commit']['committer']['date'],
349+
];
350+
}
351+
}
352+
}
353+
354+
return [null, null];
355+
}
356+
357+
private function requestGitHubApi(string $path)
358+
{
359+
$contents = $this->downloader->getContents('api.github.com', $path, false);
360+
361+
return json_decode($contents, true);
362+
}
255363
}

src/Flex.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ class_exists(__NAMESPACE__.str_replace('/', '\\', substr($file, \strlen(__DIR__)
243243
$app->add(new Command\UpdateCommand($resolver));
244244
$app->add(new Command\RemoveCommand($resolver));
245245
$app->add(new Command\UnpackCommand($resolver));
246-
$app->add(new Command\RecipesCommand($this, $this->lock));
246+
$app->add(new Command\RecipesCommand($this, $this->lock, $this->rfs));
247247
$app->add(new Command\InstallRecipesCommand($this, $this->options->get('root-dir')));
248248
$app->add(new Command\GenerateIdCommand($this));
249249
$app->add(new Command\DumpEnvCommand($this->config, $this->options));

0 commit comments

Comments
 (0)