bdelespierre /
gitstats
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace Bdelespierre\GitStats\Commands; |
||||
| 4 | |||||
| 5 | use Bdelespierre\GitStats\Interfaces\GitServiceInterface; |
||||
| 6 | use Bdelespierre\GitStats\Interfaces\ProcessServiceInterface; |
||||
| 7 | use Bdelespierre\GitStats\Task; |
||||
| 8 | use Symfony\Component\Console\Command\Command; |
||||
| 9 | use Symfony\Component\Console\Input\InputArgument; |
||||
| 10 | use Symfony\Component\Console\Input\InputInterface; |
||||
| 11 | use Symfony\Component\Console\Input\InputOption; |
||||
| 12 | use Symfony\Component\Console\Output\OutputInterface; |
||||
| 13 | |||||
| 14 | class Run extends Command |
||||
| 15 | { |
||||
| 16 | protected GitServiceInterface $git; |
||||
| 17 | protected ProcessServiceInterface $process; |
||||
| 18 | |||||
| 19 | public function __construct(GitServiceInterface $git, ProcessServiceInterface $process) |
||||
| 20 | { |
||||
| 21 | $this->git = $git; |
||||
| 22 | $this->process = $process; |
||||
| 23 | |||||
| 24 | parent::__construct(); |
||||
| 25 | } |
||||
| 26 | |||||
| 27 | protected function configure() |
||||
| 28 | { |
||||
| 29 | $this->setName('run') |
||||
| 30 | ->setDescription('Iterate through git commits to gather statistics') |
||||
| 31 | ->addArgument('branch', InputArgument::OPTIONAL, "The branch to use.", "master") |
||||
| 32 | ->addArgument('tasks', InputArgument::OPTIONAL, "The task file to use.", ".gitstats.php"); |
||||
| 33 | } |
||||
| 34 | |||||
| 35 | protected function execute(InputInterface $input, OutputInterface $output) |
||||
| 36 | { |
||||
| 37 | try { |
||||
| 38 | $this->checkEnvironment(); |
||||
| 39 | |||||
| 40 | $branch = $this->getBranch($input); |
||||
| 41 | $data = $this->runTasks( |
||||
| 42 | $this->getTasks($input), |
||||
| 43 | $this->git->getCommits($branch) |
||||
| 44 | ); |
||||
| 45 | |||||
| 46 | $output->write($this->format($data)); |
||||
| 47 | } catch (\Exception $e) { |
||||
| 48 | $output->writeln("<error>{$e->getMessage()}</error>"); |
||||
| 49 | return self::FAILURE; |
||||
| 50 | } finally { |
||||
| 51 | if (isset($branch)) { |
||||
| 52 | $this->git->checkout($branch); |
||||
| 53 | } |
||||
| 54 | } |
||||
| 55 | |||||
| 56 | return self::SUCCESS; |
||||
| 57 | } |
||||
| 58 | |||||
| 59 | private function checkEnvironment(): void |
||||
| 60 | { |
||||
| 61 | if (! $this->git->isGitAvailable()) { |
||||
| 62 | throw new \RuntimeException("Cannot proceed: unable to find git command."); |
||||
| 63 | } |
||||
| 64 | |||||
| 65 | if (! $this->git->isGitRepository()) { |
||||
| 66 | throw new \RuntimeException(sprintf("Cannot proceed: %s is not a git repository.", getcwd())); |
||||
| 67 | } |
||||
| 68 | |||||
| 69 | if (! $this->git->updateIndex()) { |
||||
| 70 | throw new \RuntimeException("Cannot proceed: unable to update index."); |
||||
| 71 | } |
||||
| 72 | |||||
| 73 | if ($this->git->hasUnstagedChanges()) { |
||||
| 74 | throw new \RuntimeException("Cannot proceed: you have unstaged changes. " . |
||||
| 75 | "Please commit or stash them."); |
||||
| 76 | } |
||||
| 77 | |||||
| 78 | if ($this->git->hasUncommittedChanges()) { |
||||
| 79 | throw new \RuntimeException("Cannot proceed: your index contains uncommitted changes. " . |
||||
| 80 | "Please commit or stash them."); |
||||
| 81 | } |
||||
| 82 | } |
||||
| 83 | |||||
| 84 | private function getBranch(InputInterface $input): string |
||||
| 85 | { |
||||
| 86 | $branch = $input->getArgument('branch'); |
||||
| 87 | |||||
| 88 | if (! $this->git->isValidBranch($branch)) { |
||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 89 | throw new \RuntimeException("Branch {$branch} is not valid."); |
||||
| 90 | } |
||||
| 91 | |||||
| 92 | return $branch; |
||||
| 93 | } |
||||
| 94 | |||||
| 95 | private function getTasks(InputInterface $input): array |
||||
| 96 | { |
||||
| 97 | $file = $input->getArgument('tasks'); |
||||
| 98 | |||||
| 99 | if (! is_readable($file)) { |
||||
|
0 ignored issues
–
show
It seems like
$file can also be of type null and string[]; however, parameter $filename of is_readable() does only seem to accept string, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 100 | throw new \RuntimeException("File {$file} does not exists."); |
||||
| 101 | } |
||||
| 102 | |||||
| 103 | $tasks = []; |
||||
| 104 | |||||
| 105 | foreach ((require $file)['tasks'] ?? [] as $name => $task) { |
||||
| 106 | if (! is_array($task) || ! isset($task['command'])) { |
||||
| 107 | $task = ['command' => $task]; |
||||
| 108 | } |
||||
| 109 | |||||
| 110 | $tasks[] = new Task($this->process, $name, $task['command'], $task['patterns'] ?? []); |
||||
| 111 | } |
||||
| 112 | |||||
| 113 | return $tasks; |
||||
| 114 | } |
||||
| 115 | |||||
| 116 | private function runTasks(iterable $tasks, iterable $commits): \Generator |
||||
| 117 | { |
||||
| 118 | // headers |
||||
| 119 | yield array_merge(['commit', 'date'], ...array_map(fn($task) => $task->getHeaders(), $tasks)); |
||||
|
0 ignored issues
–
show
$tasks of type iterable is incompatible with the type array expected by parameter $array of array_map().
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 120 | |||||
| 121 | foreach ($commits as $commit) { |
||||
| 122 | $this->git->checkout($commit); |
||||
| 123 | |||||
| 124 | $timestamp = $this->git->getCommitTimestamp($commit); |
||||
| 125 | $data = [ |
||||
| 126 | 'commit' => $commit, |
||||
| 127 | 'date' => date('Y-m-d H:i:s', $timestamp), |
||||
| 128 | ]; |
||||
| 129 | |||||
| 130 | foreach ($tasks as $task) { |
||||
| 131 | $data += $task->run($commit)->toArray(); |
||||
| 132 | } |
||||
| 133 | |||||
| 134 | yield $data; |
||||
| 135 | } |
||||
| 136 | } |
||||
| 137 | |||||
| 138 | private function format(iterable $data): \Generator |
||||
| 139 | { |
||||
| 140 | $buffer = fopen('php://temp', 'r+'); |
||||
| 141 | |||||
| 142 | foreach ($data as $row) { |
||||
| 143 | $length = fputcsv($buffer, $row, ',', '"', '\\'); |
||||
| 144 | fseek($buffer, ftell($buffer) - $length); |
||||
| 145 | yield fgets($buffer); |
||||
| 146 | } |
||||
| 147 | |||||
| 148 | fclose($buffer); |
||||
| 149 | } |
||||
| 150 | } |
||||
| 151 |