Passed
Push — master ( 351308...878789 )
by Alexander
02:24 queued 10s
created

SemanticVersionUpdater::checkUncommittedChanges()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 5
c 1
b 0
f 0
nc 4
nop 0
dl 0
loc 9
ccs 6
cts 6
cp 1
crap 3
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Vasoft\VersionIncrement;
6
7
use Vasoft\VersionIncrement\Commits\CommitCollection;
8
use Vasoft\VersionIncrement\Contract\VcsExecutorInterface;
9
use Vasoft\VersionIncrement\Exceptions\BranchException;
10
use Vasoft\VersionIncrement\Exceptions\ChangelogException;
11
use Vasoft\VersionIncrement\Exceptions\ComposerException;
12
use Vasoft\VersionIncrement\Exceptions\GitCommandException;
13
use Vasoft\VersionIncrement\Exceptions\IncorrectChangeTypeException;
14
use Vasoft\VersionIncrement\Exceptions\UncommittedException;
15
16
class SemanticVersionUpdater
17
{
18
    private bool $debug = false;
19
    private VcsExecutorInterface $gitExecutor;
20
    private array $availableTypes = [
21
        'major',
22
        'minor',
23
        'patch',
24
    ];
25
26 29
    public function __construct(
27
        private readonly string $projectPath,
28
        private readonly Config $config,
29
        private string $changeType = '',
30
    ) {
31 29
        $this->gitExecutor = $config->getVcsExecutor();
32
    }
33
34
    /**
35
     * @throws IncorrectChangeTypeException
36
     */
37 29
    private function checkChangeType(): void
38
    {
39 29
        if ('' !== $this->changeType && !in_array($this->changeType, $this->availableTypes, true)) {
40 1
            throw  new IncorrectChangeTypeException($this->changeType);
41
        }
42
    }
43
44
    /**
45
     * @throws ComposerException
46
     */
47 22
    public function getComposerJson(): array
48
    {
49 22
        $composer = $this->projectPath . '/composer.json';
50 22
        if (!file_exists($composer)) {
51 1
            throw new ComposerException();
52
        }
53 21
        if (!is_writable($composer)) {
54 1
            throw new ComposerException('Composer file is not writable.');
55
        }
56
57
        try {
58 20
            $result = json_decode(file_get_contents($composer), true, 512, JSON_THROW_ON_ERROR);
59 1
        } catch (\JsonException $e) {
60 1
            throw new ComposerException('JSON: ' . $e->getMessage());
61
        }
62
63 19
        return $result;
64
    }
65
66
    /**
67
     * @throws BranchException
68
     * @throws ChangelogException
69
     * @throws ComposerException
70
     * @throws GitCommandException
71
     * @throws IncorrectChangeTypeException
72
     * @throws UncommittedException
73
     */
74 29
    public function updateVersion(): void
75
    {
76 29
        $this->checkChangeType();
77 28
        $this->checkGitBranch();
78 27
        $this->checkUncommittedChanges();
79
80 26
        $lastTag = $this->gitExecutor->getLastTag();
81 26
        if ($this->config->isEnabledComposerVersioning()) {
82 22
            $composerJson = $this->getComposerJson();
83 19
            $currentVersion = $composerJson['version'] ?? '1.0.0';
84
        } else {
85 4
            $currentVersion = $this->config->getTagFormatter()->extractVersion($lastTag);
0 ignored issues
show
Bug introduced by
It seems like $lastTag can also be of type null; however, parameter $tag of Vasoft\VersionIncrement\...rface::extractVersion() 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

85
            $currentVersion = $this->config->getTagFormatter()->extractVersion(/** @scrutinizer ignore-type */ $lastTag);
Loading history...
86
        }
87
88 23
        $commitCollection = $this->config->getCommitParser()->process($lastTag);
89 22
        $this->detectionTypeChange($commitCollection);
90
91 22
        $newVersion = $this->incrementVersion($currentVersion, $this->changeType);
92
93 22
        if ($this->config->isEnabledComposerVersioning()) {
94 18
            $composerJson['version'] = $newVersion;
95 18
            $this->updateComposerJson($composerJson);
96
        }
97 22
        $changelog = $this->config->getChangelogFormatter()($commitCollection, $newVersion);
98
99 22
        $this->updateChangeLog($changelog);
100 21
        $this->commitRelease($newVersion);
101
    }
102
103
    /**
104
     * @throws GitCommandException
105
     */
106 21
    private function commitRelease(string $newVersion): void
107
    {
108 21
        if (!$this->debug) {
109 19
            $releaseScope = trim($this->config->getReleaseScope());
110 19
            if ('' !== $releaseScope) {
111 18
                $releaseScope = sprintf('(%s)', $releaseScope);
112
            }
113 19
            $this->gitExecutor->commit(
114 19
                sprintf(
115 19
                    '%s%s: v%s',
116 19
                    $this->config->getReleaseSection(),
117 19
                    $releaseScope,
118 19
                    $newVersion,
119 19
                ),
120 19
            );
121 19
            $this->gitExecutor->setVersionTag($newVersion);
122 19
            echo "Release {$newVersion} successfully created!\n";
123
        }
124
    }
125
126
    /**
127
     * @throws ChangelogException
128
     * @throws GitCommandException
129
     */
130 22
    private function updateChangeLog(string $changelog): void
131
    {
132 22
        if ($this->debug) {
133 2
            echo $changelog;
134
        } else {
135 20
            $fileChangelog = $this->projectPath . '/CHANGELOG.md';
136 20
            if (file_exists($fileChangelog)) {
137 19
                if (!is_writable($fileChangelog)) {
138 1
                    throw new ChangelogException();
139
                }
140
141 18
                $changeLogContent = file_get_contents($fileChangelog);
142 18
                $changeLogAddToGit = false;
143
            } else {
144 1
                $changeLogContent = '';
145 1
                $changeLogAddToGit = true;
146
            }
147 19
            file_put_contents($fileChangelog, $changelog . $changeLogContent);
148 19
            if ($changeLogAddToGit) {
149 1
                $this->gitExecutor->addFile('CHANGELOG.md');
150
            }
151
        }
152
    }
153
154 18
    private function updateComposerJson(array $composerJson): void
155
    {
156 18
        if (!$this->debug) {
157 17
            file_put_contents(
158 17
                $this->projectPath . '/composer.json',
159 17
                json_encode($composerJson, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES),
160 17
            );
161
        }
162
    }
163
164 22
    private function detectionTypeChange(CommitCollection $commitCollection): void
165
    {
166 22
        if ('' === $this->changeType) {
167 21
            if ($commitCollection->hasMajorMarker()) {
168 4
                $this->changeType = 'major';
169 17
            } elseif ($commitCollection->hasMinorMarker()) {
170 13
                $this->changeType = 'minor';
171
            } else {
172 4
                $this->changeType = 'patch';
173
            }
174
        }
175
    }
176
177
    /**
178
     * @throws GitCommandException
179
     * @throws UncommittedException
180
     */
181 27
    private function checkUncommittedChanges(): void
182
    {
183 27
        $out = $this->gitExecutor->status();
184 27
        if ($this->config->mastIgnoreUntrackedFiles()) {
185 1
            $out = array_filter($out, static fn(string $item): bool => !str_starts_with($item, '??'));
186
        }
187
188 27
        if (!empty($out)) {
189 1
            throw new UncommittedException();
190
        }
191
    }
192
193
    /**
194
     * @throws BranchException
195
     * @throws GitCommandException
196
     */
197 28
    private function checkGitBranch(): void
198
    {
199 28
        $currentBranch = $this->gitExecutor->getCurrentBranch();
200 28
        $targetBranch = $this->config->getMasterBranch();
201 28
        if ($currentBranch !== $targetBranch) {
202 1
            throw new BranchException($currentBranch, $targetBranch);
203
        }
204
    }
205
206 22
    private function incrementVersion(string $currentVersion, string $changeType): string
207
    {
208 22
        [$major, $minor, $patch] = explode('.', $currentVersion);
209
        switch ($changeType) {
210 22
            case 'major':
211 5
                $major++;
212 5
                $minor = 0;
213 5
                $patch = 0;
214 5
                break;
215 17
            case 'minor':
216 13
                $minor++;
217 13
                $patch = 0;
218 13
                break;
219 4
            case 'patch':
220
            default:
221 4
                $patch++;
222 4
                break;
223
        }
224
225 22
        return "{$major}.{$minor}.{$patch}";
226
    }
227
228 4
    public function setDebug(bool $debug): self
229
    {
230 4
        $this->debug = $debug;
231
232 4
        return $this;
233
    }
234
}
235