These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace PHPSemVerCheckerGit\Console\Command; |
||
4 | |||
5 | use Gitter\Client; |
||
6 | use Gitter\Repository; |
||
7 | use PHPSemVerChecker\Analyzer\Analyzer; |
||
8 | use PHPSemVerChecker\Finder\Finder; |
||
9 | use PHPSemVerChecker\Reporter\Reporter; |
||
10 | use PHPSemVerChecker\Scanner\Scanner; |
||
11 | use PHPSemVerChecker\SemanticVersioning\Level; |
||
12 | use PHPSemVerCheckerGit\Filter\SourceFilter; |
||
13 | use RuntimeException; |
||
14 | use Symfony\Component\Console\Helper\ProgressBar; |
||
15 | use Symfony\Component\Console\Input\InputInterface; |
||
16 | use Symfony\Component\Console\Input\InputOption; |
||
17 | use Symfony\Component\Console\Output\OutputInterface; |
||
18 | use vierbergenlars\SemVer\expression as SemanticExpression; |
||
19 | use vierbergenlars\SemVer\SemVerException as SemanticVersionException; |
||
20 | use vierbergenlars\SemVer\version as SemanticVersion; |
||
21 | |||
22 | class SuggestCommand extends BaseCommand |
||
23 | { |
||
24 | /** |
||
25 | * @return void |
||
26 | */ |
||
27 | protected function configure() |
||
28 | { |
||
29 | $this->setName('suggest')->setDescription('Compare a semantic versioned tag against a commit and provide a semantic version suggestion')->setDefinition([ |
||
30 | new InputOption('include-before', null, InputOption::VALUE_REQUIRED, 'List of paths to include <info>(comma separated)</info>'), |
||
31 | new InputOption('include-after', null, InputOption::VALUE_REQUIRED, 'List of paths to include <info>(comma separated)</info>'), |
||
32 | new InputOption('exclude-before', null, InputOption::VALUE_REQUIRED, 'List of paths to exclude <info>(comma separated)</info>'), |
||
33 | new InputOption('exclude-after', null, InputOption::VALUE_REQUIRED, 'List of paths to exclude <info>(comma separated)</info>'), |
||
34 | new InputOption('tag', 't', InputOption::VALUE_REQUIRED, 'A tag to test against (latest by default)'), |
||
35 | new InputOption('against', 'a', InputOption::VALUE_REQUIRED, 'What to test against the tag (HEAD by default)'), |
||
36 | new InputOption('allow-detached', 'd', InputOption::VALUE_NONE, 'Allow suggest to start from a detached HEAD'), |
||
37 | new InputOption('details', null, InputOption::VALUE_NONE, 'Report the changes on which the suggestion is based'), |
||
38 | new InputOption('config', null, InputOption::VALUE_REQUIRED, 'A configuration file to configure php-semver-checker-git'), |
||
39 | ]); |
||
40 | } |
||
41 | |||
42 | /** |
||
43 | * @param \Symfony\Component\Console\Input\InputInterface $input |
||
44 | * @param \Symfony\Component\Console\Output\OutputInterface $output |
||
45 | */ |
||
46 | protected function execute(InputInterface $input, OutputInterface $output) |
||
47 | { |
||
48 | $startTime = microtime(true); |
||
49 | |||
50 | $targetDirectory = getcwd(); |
||
51 | $tag = $this->config->get('tag'); |
||
52 | $against = $this->config->get('against') ?: 'HEAD'; |
||
53 | |||
54 | $includeBefore = $this->config->get('include-before'); |
||
55 | $excludeBefore = $this->config->get('exclude-before'); |
||
56 | |||
57 | $includeAfter = $this->config->get('include-after'); |
||
58 | $excludeAfter = $this->config->get('exclude-after'); |
||
59 | |||
60 | $client = new Client(); |
||
61 | |||
62 | $repository = $client->getRepository($targetDirectory); |
||
63 | |||
64 | if ($tag === null) { |
||
65 | $tag = $this->findLatestTag($repository); |
||
66 | } else { |
||
67 | $tag = $this->findTag($repository, $tag); |
||
68 | } |
||
69 | |||
70 | if ($tag === null) { |
||
71 | $output->writeln('<error>No tags to suggest against</error>'); |
||
72 | return; |
||
73 | } |
||
74 | |||
75 | $output->writeln('<info>Testing ' . $against . ' against tag: ' . $tag . '</info>'); |
||
76 | |||
77 | $finder = new Finder(); |
||
78 | $sourceFilter = new SourceFilter(); |
||
79 | $beforeScanner = new Scanner(); |
||
80 | $afterScanner = new Scanner(); |
||
81 | |||
82 | $modifiedFiles = $repository->getModifiedFiles($tag, $against); |
||
83 | $modifiedFiles = array_filter($modifiedFiles, function ($modifiedFile) { |
||
84 | return substr($modifiedFile, -4) === '.php'; |
||
85 | }); |
||
86 | |||
87 | $initialBranch = $repository->getCurrentBranch(); |
||
88 | |||
89 | if ( ! $this->config->get('allow-detached') && ! $initialBranch) { |
||
90 | $output->writeln('<error>You are on a detached HEAD, aborting.</error>'); |
||
91 | $output->writeln('<info>If you still wish to run against a detached HEAD, use --allow-detached.</info>'); |
||
92 | return -1; |
||
93 | } |
||
94 | |||
95 | // Start with the against commit |
||
96 | $repository->checkout($against . ' --'); |
||
97 | |||
98 | $sourceAfter = $finder->findFromString($targetDirectory, $includeAfter, $excludeAfter); |
||
99 | $sourceAfterMatchedCount = count($sourceAfter); |
||
100 | $sourceAfter = $sourceFilter->filter($sourceAfter, $modifiedFiles); |
||
101 | $progress = new ProgressBar($output, count($sourceAfter)); |
||
102 | foreach ($sourceAfter as $file) { |
||
103 | $afterScanner->scan($file); |
||
104 | $progress->advance(); |
||
105 | } |
||
106 | |||
107 | $progress->clear(); |
||
108 | |||
109 | // Finish with the tag commit |
||
110 | $repository->checkout($tag . ' --'); |
||
111 | |||
112 | $sourceBefore = $finder->findFromString($targetDirectory, $includeBefore, $excludeBefore); |
||
113 | $sourceBeforeMatchedCount = count($sourceBefore); |
||
114 | $sourceBefore = $sourceFilter->filter($sourceBefore, $modifiedFiles); |
||
115 | $progress = new ProgressBar($output, count($sourceBefore)); |
||
116 | foreach ($sourceBefore as $file) { |
||
117 | $beforeScanner->scan($file); |
||
118 | $progress->advance(); |
||
119 | } |
||
120 | |||
121 | $progress->clear(); |
||
122 | |||
123 | // Reset repository to initial branch |
||
124 | if ($initialBranch) { |
||
125 | $repository->checkout($initialBranch); |
||
126 | } |
||
127 | |||
128 | $registryBefore = $beforeScanner->getRegistry(); |
||
129 | $registryAfter = $afterScanner->getRegistry(); |
||
130 | |||
131 | $analyzer = new Analyzer(); |
||
132 | $report = $analyzer->analyze($registryBefore, $registryAfter); |
||
133 | |||
134 | $tag = new SemanticVersion($tag); |
||
135 | $newTag = new SemanticVersion($tag); |
||
136 | |||
137 | $suggestedLevel = $report->getSuggestedLevel(); |
||
138 | |||
139 | if ($suggestedLevel !== Level::NONE) { |
||
140 | if ($newTag->getPrerelease()) { |
||
141 | $newTag->inc('prerelease'); |
||
142 | } else { |
||
143 | if ($newTag->getMajor() < 1 && $suggestedLevel === Level::MAJOR) { |
||
144 | $newTag->inc('minor'); |
||
145 | } else { |
||
146 | $newTag->inc(strtolower(Level::toString($suggestedLevel))); |
||
147 | } |
||
148 | } |
||
149 | } |
||
150 | |||
151 | $output->writeln(''); |
||
152 | $output->writeln('<info>Initial semantic version: ' . $tag . '</info>'); |
||
153 | $output->writeln('<info>Suggested semantic version: ' . $newTag . '</info>'); |
||
154 | |||
155 | if ($this->config->get('details')) { |
||
156 | $reporter = new Reporter($report); |
||
157 | $reporter->output($output); |
||
158 | } |
||
159 | |||
160 | $duration = microtime(true) - $startTime; |
||
161 | $output->writeln(''); |
||
162 | $output->writeln('[Scanned files] Before: ' . count($sourceBefore) . ' (' . $sourceBeforeMatchedCount . ' unfiltered), After: ' . count($sourceAfter) . ' (' . $sourceAfterMatchedCount . ' unfiltered)'); |
||
163 | $output->writeln('Time: ' . round($duration, 3) . ' seconds, Memory: ' . round(memory_get_peak_usage() / 1024 / 1024, 3) . ' MB'); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * @param \Gitter\Repository $repository |
||
168 | * @return string|null |
||
169 | */ |
||
170 | protected function findLatestTag(Repository $repository) |
||
171 | { |
||
172 | return $this->findTag($repository, '*'); |
||
173 | } |
||
174 | |||
175 | /** |
||
176 | * @param \Gitter\Repository $repository |
||
177 | * @param string $tag |
||
178 | * @return string|null |
||
179 | */ |
||
180 | protected function findTag(Repository $repository, $tag) |
||
181 | { |
||
182 | $tags = (array)$repository->getTags(); |
||
183 | $tags = $this->filterTags($tags); |
||
184 | |||
185 | $tagExpression = new SemanticExpression($tag); |
||
186 | |||
187 | try { |
||
188 | // Throws an exception if it cannot find a matching version |
||
189 | $satisfyingTag = $tagExpression->maxSatisfying($tags); |
||
190 | } catch (SemanticVersionException $e) { |
||
191 | return null; |
||
192 | } |
||
193 | |||
194 | return $this->getMappedVersionTag($tags, $satisfyingTag); |
||
195 | } |
||
196 | |||
197 | private function filterTags(array $tags) |
||
198 | { |
||
199 | $filteredTags = []; |
||
200 | foreach ($tags as $tag) { |
||
201 | try { |
||
202 | new SemanticVersion($tag); |
||
203 | $filteredTags[] = $tag; |
||
204 | } catch (SemanticVersionException $e) { |
||
205 | // Do nothing |
||
206 | } |
||
207 | } |
||
208 | return $filteredTags; |
||
209 | } |
||
210 | |||
211 | /** |
||
212 | * @param string[] $tags |
||
213 | * @param \vierbergenlars\SemVer\version|string|null $versionTag |
||
214 | * @return string|null |
||
215 | */ |
||
216 | private function getMappedVersionTag(array $tags, $versionTag) |
||
217 | { |
||
218 | foreach ($tags as $tag) { |
||
219 | try { |
||
220 | if (SemanticVersion::eq($versionTag, $tag)) { |
||
0 ignored issues
–
show
|
|||
221 | return $tag; |
||
222 | } |
||
223 | } catch (RuntimeException $e) { |
||
224 | // Do nothing |
||
225 | } |
||
226 | } |
||
227 | return null; |
||
228 | } |
||
229 | } |
||
230 |
This check looks at variables that have been passed in as parameters and are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.