12
12
namespace Symfony \Flex \Command ;
13
13
14
14
use Composer \Command \BaseCommand ;
15
+ use Composer \Downloader \TransportException ;
15
16
use Symfony \Component \Console \Input \InputArgument ;
16
17
use Symfony \Component \Console \Input \InputInterface ;
17
18
use Symfony \Component \Console \Output \OutputInterface ;
18
19
use Symfony \Flex \InformationOperation ;
19
20
use Symfony \Flex \Lock ;
21
+ use Symfony \Flex \ParallelDownloader ;
20
22
use Symfony \Flex \Recipe ;
21
23
22
24
/**
@@ -27,13 +29,14 @@ class RecipesCommand extends BaseCommand
27
29
/** @var \Symfony\Flex\Flex */
28
30
private $ flex ;
29
31
30
- /** @var Lock */
31
32
private $ symfonyLock ;
33
+ private $ downloader ;
32
34
33
- public function __construct (/* cannot be type-hinted */ $ flex , Lock $ symfonyLock )
35
+ public function __construct (/* cannot be type-hinted */ $ flex , Lock $ symfonyLock, ParallelDownloader $ downloader )
34
36
{
35
37
$ this ->flex = $ flex ;
36
38
$ this ->symfonyLock = $ symfonyLock ;
39
+ $ this ->downloader = $ downloader ;
37
40
38
41
parent ::__construct ();
39
42
}
@@ -128,9 +131,11 @@ protected function execute(InputInterface $input, OutputInterface $output)
128
131
129
132
private function displayPackageInformation (Recipe $ recipe )
130
133
{
134
+ $ io = $ this ->getIO ();
131
135
$ recipeLock = $ this ->symfonyLock ->get ($ recipe ->getName ());
132
136
133
137
$ lockRef = $ recipeLock ['recipe ' ]['ref ' ] ?? null ;
138
+ $ lockRepo = $ recipeLock ['recipe ' ]['repo ' ] ?? null ;
134
139
$ lockFiles = $ recipeLock ['files ' ] ?? null ;
135
140
136
141
$ status = '<comment>up to date</comment> ' ;
@@ -142,16 +147,63 @@ private function displayPackageInformation(Recipe $recipe)
142
147
$ status = '<comment>update available</comment> ' ;
143
148
}
144
149
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 );
148
170
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
+ }
150
203
}
151
- $ io ->write ('<info>status</info> : ' .$ status );
152
204
153
205
if (null !== $ lockFiles ) {
154
- $ io ->write ('<info>files</info> : ' );
206
+ $ io ->write ('<info>files</info> : ' );
155
207
$ io ->write ('' );
156
208
157
209
$ tree = $ this ->generateFilesTree ($ lockFiles );
@@ -195,7 +247,8 @@ private function addNode(array $node): array
195
247
*/
196
248
private function displayFilesTree (array $ tree )
197
249
{
198
- $ endKey = array_key_last ($ tree );
250
+ end ($ tree );
251
+ $ endKey = key ($ tree );
199
252
foreach ($ tree as $ dir => $ files ) {
200
253
$ treeBar = '├ ' ;
201
254
$ total = \count ($ files );
@@ -252,4 +305,59 @@ private function writeTreeLine($line)
252
305
253
306
$ io ->write ($ line );
254
307
}
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
+ }
255
363
}
0 commit comments