Completed
Push — master ( d9ccf7...2ab905 )
by Benjamin
09:21
created

Run::getBranch()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 9
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Bdelespierre\GitStats\Commands;
4
5
use Bdelespierre\GitStats\Interfaces\GitServiceInterface;
6
use Bdelespierre\GitStats\Interfaces\ProcessServiceInterface;
7
use Symfony\Component\Console\Command\Command;
8
use Symfony\Component\Console\Input\InputArgument;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Input\InputOption;
11
use Symfony\Component\Console\Output\OutputInterface;
12
13
class Run extends Command
14
{
15
    protected GitServiceInterface $git;
16
    protected ProcessServiceInterface $process;
17
18
    public function __construct(GitServiceInterface $git, ProcessServiceInterface $process)
19
    {
20
        $this->git = $git;
21
        $this->process = $process;
22
23
        parent::__construct();
24
    }
25
26
    protected function configure()
27
    {
28
        $this->setName('run')
29
            ->setDescription('Iterate through git commits to gather statistics')
30
            ->addArgument('branch', InputArgument::OPTIONAL, "The branch to use.", "master")
31
            ->addArgument('tasks', InputArgument::OPTIONAL, "The task file to use.", ".gitstats.php");
32
    }
33
34
    protected function execute(InputInterface $input, OutputInterface $output)
35
    {
36
        try {
37
            $this->checkEnvironment();
38
39
            $branch = $this->getBranch($input);
40
            $data = $this->runTasks(
41
                $this->getTasks($input),
42
                $this->git->getCommits($branch)
43
            );
44
45
            $output->write($this->format($data));
46
        } catch (\Exception $e) {
47
            $output->writeln("<error>{$e->getMessage()}</error>");
48
            return self::FAILURE;
49
        } finally {
50
            if (isset($branch)) {
51
                $this->git->checkout($branch);
52
            }
53
        }
54
55
        return self::SUCCESS;
56
    }
57
58
    private function checkEnvironment(): void
59
    {
60
        if (! $this->git->isGitAvailable()) {
61
            throw new \RuntimeException("Cannot proceed: unable to find git command.");
62
        }
63
64
        if (! $this->git->isGitRepository()) {
65
            throw new \RuntimeException(sprintf("Cannot proceed: %s is not a git repository.", getcwd()));
66
        }
67
68
        if (! $this->git->updateIndex()) {
69
            throw new \RuntimeException("Cannot proceed: unable to update index.");
70
        }
71
72
        if ($this->git->hasUnstagedChanges()) {
73
            throw new \RuntimeException("Cannot proceed: you have unstaged changes. " .
74
                "Please commit or stash them.");
75
        }
76
77
        if ($this->git->hasUncommittedChanges()) {
78
            throw new \RuntimeException("Cannot proceed: your index contains uncommitted changes. " .
79
                "Please commit or stash them.");
80
        }
81
    }
82
83
    private function getBranch(InputInterface $input): string
84
    {
85
        $branch = $input->getArgument('branch');
86
87
        if (! $this->git->isValidBranch($branch)) {
0 ignored issues
show
Bug introduced by
It seems like $branch can also be of type null and string[]; however, parameter $branch of Bdelespierre\GitStats\In...erface::isValidBranch() 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 ignore-type  annotation

87
        if (! $this->git->isValidBranch(/** @scrutinizer ignore-type */ $branch)) {
Loading history...
88
            throw new \RuntimeException("Branch {$branch} is not valid.");
89
        }
90
91
        return $branch;
92
    }
93
94
    private function getTasks(InputInterface $input): array
95
    {
96
        $file = $input->getArgument('tasks');
97
98
        if (! is_readable($file)) {
0 ignored issues
show
Bug introduced by
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 ignore-type  annotation

98
        if (! is_readable(/** @scrutinizer ignore-type */ $file)) {
Loading history...
99
            throw new \RuntimeException("File {$file} does not exists.");
100
        }
101
102
        return (require $file)['tasks'] ?? [];
103
    }
104
105
    private function runTasks(array $tasks, iterable $commits): \Generator
106
    {
107
        // headers
108
        yield ['commit', 'date', ...array_keys($tasks)];
109
110
        foreach ($commits as $commit) {
111
            $this->git->checkout($commit);
112
113
            $timestamp = $this->git->getCommitTimestamp($commit);
114
            $data = [
115
                'commit' => $commit,
116
                'date' => date('Y-m-d H:i:s', $timestamp),
117
            ];
118
119
            foreach ($tasks as $name => $command) {
120
                if (is_array($command)) {
121
                    $output = $this->process->run($command)->getOutput();
122
                } elseif (is_string($command)) {
123
                    $output = $this->process->exec($command);
0 ignored issues
show
Bug introduced by
The method exec() does not exist on Bdelespierre\GitStats\In...ProcessServiceInterface. Since it exists in all sub-types, consider adding an abstract or default implementation to Bdelespierre\GitStats\In...ProcessServiceInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

123
                    /** @scrutinizer ignore-call */ 
124
                    $output = $this->process->exec($command);
Loading history...
124
                }
125
126
                $data[$name] = trim($output);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output does not seem to be defined for all execution paths leading up to this point.
Loading history...
127
            }
128
129
            yield $data;
130
        }
131
    }
132
133
    private function format(iterable $data): \Generator
134
    {
135
        $buffer = fopen('php://temp', 'r+');
136
137
        foreach ($data as $row) {
138
            $length = fputcsv($buffer, $row, ',', '"', '\\');
139
            fseek($buffer, ftell($buffer) - $length);
140
            yield fgets($buffer);
141
        }
142
143
        fclose($buffer);
144
    }
145
}
146