ReleaseCommand::configure()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 35
nc 1
nop 0
dl 0
loc 10
ccs 8
cts 8
cp 1
crap 1
rs 9.36
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the egabor/composer-release-plugin package.
5
 *
6
 * (c) Gábor Egyed <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace egabor\Composer\ReleasePlugin\Command;
13
14
use Composer\Command\BaseCommand;
15
use Composer\Util\Git as GitUtil;
16
use egabor\Composer\ReleasePlugin\Config;
17
use egabor\Composer\ReleasePlugin\ReleaseManager;
18
use egabor\Composer\ReleasePlugin\Util\Git;
19
use egabor\Composer\ReleasePlugin\Version;
20
use egabor\Composer\ReleasePlugin\VersionManipulator;
21
use Symfony\Component\Console\Helper\FormatterHelper;
22
use Symfony\Component\Console\Input\InputArgument;
23
use Symfony\Component\Console\Input\InputInterface;
24
use Symfony\Component\Console\Input\InputOption;
25
use Symfony\Component\Console\Output\OutputInterface;
26
27
class ReleaseCommand extends BaseCommand
28
{
29
    /**
30
     * @var Git
31
     */
32
    private $git;
33
34
    /**
35
     * @var ReleaseManager
36
     */
37
    private $releaseManager;
38
39 1
    protected function configure()
40
    {
41
        $this
42 1
            ->setName('release')
43 1
            ->setDescription('Creates a new tagged release.')
44 1
            ->addArgument('version', InputArgument::REQUIRED, 'Version to release.')
45 1
            ->addOption('message', 'm', InputOption::VALUE_REQUIRED, 'Message for the annotated tag.', 'Release %version%')
46 1
            ->addOption('no-push', 'np', InputOption::VALUE_NONE, 'Not push the new release tag.')
47 1
            ->addOption('latest-release', 'l', InputOption::VALUE_REQUIRED, 'Latest release version.')
48 1
            ->setHelp(<<<EOT
49
            
50
This command assists you to tag a new release for a package. It tries
51
to protect you from common mistakes and provide some shortcuts.
52
53
To create a new <comment>major|minor|patch|stable|alpha|beta|rc</comment> release:
54
55
    <comment>%command.full_name% major</comment>
56
    
57
This will create a new major release, make an annotated tag and push it 
58
to the remote branch. If you don't want to push right away, you can use
59
the <comment>--no-push</comment> option.
60
61
You can provide a custom message for the annotated tag like:
62
63
    <comment>%command.full_name% major --message "%version% released"</comment>
64
 
65
If provided the %version% placeholder will be replaced with the version. 
66
67
The command will inspect the repository and infer some information e.g. the 
68
previous release version. Based on these information it tries to protect 
69
you to make a wrong release. Sometimes when it can not detect the previous 
70
release version correctly or if you just want to override this behaviour 
71
than you can use the <comment>--latest-release</comment>:
72
73
    <comment>%command.full_name% major --latest-release "1.2.3"</comment>
74
75
EOT
76
            )
77
        ;
78 1
    }
79
80
    protected function initialize(InputInterface $input, OutputInterface $output)
81
    {
82
        GitUtil::cleanEnv();
83
84
        $io = $this->getIO();
85
        $this->git = Git::create($io);
86
        $config = new Config($this->getComposer()->getPackage());
87
88
        $this->assertGitRepository();
89
        $this->assertGitCurrentBranch($config);
90
        $this->assertGitIsUpToDate();
91
92
        $io->writeError(sprintf('Using the <info>%s</info> branch as the release branch.', $config->getReleaseBranch()));
93
94
        $this->releaseManager = new ReleaseManager($config, $this->git);
95
96
        if (null !== $input->getOption('latest-release')) {
97
            return;
98
        }
99
100
        if (null !== $latestReleaseVersion = $this->releaseManager->getLatestReleaseVersion()) {
101
            $io->writeError('Latest release version: <info>'.$latestReleaseVersion.'</info>');
102
            $input->setOption('latest-release', $latestReleaseVersion);
103
            $this->assertGitHasChangesSince($latestReleaseVersion);
104
        } else {
105
            $io->writeError('<error>Latest release version can not be guessed or seems invalid.</error>');
106
        }
107
    }
108
109
    protected function interact(InputInterface $input, OutputInterface $output)
110
    {
111
        $io = $this->getIO();
112
113
        if (null === $input->getOption('latest-release')) {
114
            $answer = $io->askAndValidate('Please provide the latest released version [<comment>0.0.0</comment>]: ', function ($version) {
115
                if (null === $version) {
116
                    throw new \InvalidArgumentException('Version can not be empty.');
117
                }
118
119
                Version::fromString($version);
120
121
                return $version;
122
            }, 3, '0.0.0');
123
            $input->setOption('latest-release', $answer);
124
        }
125
126
        if (null === $input->getArgument('version')) {
127
            $answer = $io->askAndValidate('Please provide a version to release (eg. 1.0.0 or major): ', function ($version) {
128
                if (null === $version) {
129
                    throw new \InvalidArgumentException('Version can not be empty.');
130
                }
131
132
                if (!VersionManipulator::supportsLevel($version)) {
133
                    Version::fromString($version);
134
                }
135
136
                return $version;
137
            });
138
            $input->setArgument('version', $answer);
139
        }
140
    }
141
142
    protected function execute(InputInterface $input, OutputInterface $output)
143
    {
144
        $io = $this->getIO();
145
        /** @var FormatterHelper $formatter */
146
        $formatter = $this->getHelperSet()->get('formatter');
147
148
        $futureVersion = $input->getArgument('version');
149
        $latestReleaseVersion = Version::fromString($input->getOption('latest-release'));
150
151
        $futureVersion = $this->releaseManager->gatFutureVersion($futureVersion, $latestReleaseVersion);
152
153
        $doRelease = true;
154
        if ($input->isInteractive()) {
155
            $doRelease = $io->askConfirmation('<info>You are about to release version "'.$futureVersion.'". Do you want to continue?</info> [<comment>Y,n</comment>]? ', true);
156
        }
157
158
        if ($doRelease) {
159
            if ($input->getOption('no-push')) {
160
                $this->releaseManager->release($futureVersion, $input->getOption('message'));
161
            } else {
162
                $this->releaseManager->releaseAndPush($futureVersion, $input->getOption('message'));
163
            }
164
165
            $io->write([
166
                '',
167
                $formatter->formatBlock('Version "'.$futureVersion.'" has been released.', 'bg=green;fg=black', true),
168
                '',
169
            ]);
170
        } else {
171
            $io->write([
172
                '',
173
                $formatter->formatBlock('Aborted.', 'bg=red;fg=white', true),
174
                '',
175
            ]);
176
        }
177
    }
178
179
    private function assertGitRepository()
180
    {
181
        if (!is_dir('.git')) {
182
            throw new \RuntimeException('The .git directory is missing from "'.getcwd().'". Only git repositories are supported.');
183
        }
184
    }
185
186
    private function assertGitCurrentBranch(Config $config)
187
    {
188
        $branches = $this->git->getBranches();
189
        if (!count($branches)) {
190
            throw new \LogicException('The repository does not have any branches with commits. Please commit your work before release.');
191
        }
192
193
        if (!array_key_exists($config->getReleaseBranch(), $branches)) {
194
            throw new \LogicException('The release branch "'.$config->getReleaseBranch().'" does not exists. Please create and switch to the release branch.');
195
        }
196
197
        if ($this->git->getCurrentBranch() !== $config->getReleaseBranch()) {
198
            throw new \LogicException('Please switch to the release branch "'.$config->getReleaseBranch().'".');
199
        }
200
    }
201
202
    private function assertGitIsUpToDate()
203
    {
204
        if (!$this->git->hasUpstream()) {
205
            throw new \LogicException('The current branch does not have a remote tracking branch. Please setup one before continue.');
206
        }
207
208
        $this->git->fetch();
209
210
        $status = $this->git->getRemoteStatus();
211
        if (Git::STATUS_NEED_TO_PULL === $status) {
212
            throw new \LogicException('The current branch does not not in sync with the remote tracking branch. Please pull before continue.');
213
        }
214
215
        if (Git::STATUS_DIVERGED === $status) {
216
            throw new \LogicException('The current branch is diverged from its remote tracking branch. Please rebase or merge before continue.');
217
        }
218
    }
219
220
    private function assertGitHasChangesSince($rev)
221
    {
222
        if (1 > $this->git->countCommitsSince($rev)) {
223
            throw new \LogicException('There are no committed changes since the last release. Please commit your changes.');
224
        }
225
    }
226
}
227