SplitCommitsWorker::work()   D
last analyzed

Complexity

Conditions 35
Paths 20

Size

Total Lines 145
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 35
eloc 87
c 4
b 0
f 0
nc 20
nop 3
dl 0
loc 145
rs 4.1666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php declare(strict_types=1);
2
3
/*
4
 * This file is part of Biurad opensource projects.
5
 *
6
 * @copyright 2022 Biurad Group (https://biurad.com/)
7
 * @license   https://opensource.org/licenses/BSD-3-Clause License
8
 *
9
 * For the full copyright and license information, please view the LICENSE
10
 * file that was distributed with this source code.
11
 */
12
13
namespace Biurad\Monorepo\Worker;
14
15
use Biurad\Git\Repository;
16
use Biurad\Monorepo\{Monorepo, WorkerInterface, WorkflowCommand};
17
use Symfony\Component\Console\Input\{InputInterface, InputOption};
18
use Symfony\Component\Console\Style\SymfonyStyle;
19
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
20
use Symfony\Component\Process\Process;
21
22
/**
23
 * A workflow worker for splitting commits to repositories.
24
 *
25
 * @author Divine Niiquaye Ibok <[email protected]>
26
 */
27
class SplitCommitsWorker implements WorkerInterface
28
{
29
    private function __construct()
30
    {
31
    }
32
33
    /**
34
     * {@inheritdoc}
35
     */
36
    public function getDescription(): string
37
    {
38
        return 'Running repositories commits splitting';
39
    }
40
41
    /**
42
     * {@inheritdoc}
43
     */
44
    public static function configure(WorkflowCommand $command): self
45
    {
46
        $multi = InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL;
47
        $command->addOption('branch', 'b', $multi, 'Defaults to all branches that match the configured branch filter. (also accepts -b "*")', []);
48
        $command->addOption('no-branch', null, InputOption::VALUE_NONE, 'If set, no branches will be pushed.');
49
        $command->addOption('release', 't', InputOption::VALUE_OPTIONAL, 'Release of new tag (accepted pattern: <tag>[=<branch>])');
50
51
        return new self();
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     */
57
    public function work(Monorepo $repo, InputInterface $input, SymfonyStyle $output): int
58
    {
59
        [$mainRepo, $branches] = [$repo->getRepository(), $input->getOption('branch')];
60
        $currentBranch = $mainRepo->getBranch()->getName();
61
62
        if (!\is_executable($split = __DIR__.'/../../bin/splitsh-lite')) {
63
            $mainRepo->run('update-index', ['--chmod=+x']);
64
        }
65
66
        if ('\\' === \DIRECTORY_SEPARATOR) {
67
            $output->error([
68
                'The splitsh-lite command used to split commits to repositories',
69
                'is currently not supported on Windows.',
70
                'Kindly use Windows Subsystem for Linux (WSL) to run this command.',
71
                'Support for Windows is being worked on and will be available soon.',
72
            ]);
73
74
            return WorkflowCommand::FAILURE;
75
        }
76
77
        if ($branches && '*' === $branches[0]) {
78
            $allBranches = true;
79
80
            if (\count($branches) > 1) {
81
                $output->writeln(\sprintf('<error>Expected "*" as the only value for option "--branch", got "%s"</error>', \implode(', ', $branches)));
82
83
                return WorkflowCommand::FAILURE;
84
            }
85
        }
86
87
        foreach ($mainRepo->getBranches() as $branch) {
88
            if (isset($allBranches) || (1 === \preg_match($repo->config['branch_filter'], $branch->getName()) && !$input->getOption('no-branch'))) {
89
                $branches[] = $branch->isRemote() ? \substr($branch->getName(), 7) : $branch->getName();
90
            }
91
        }
92
93
        return $repo->resolveRepository(
94
            $output,
95
            static function (array $required) use ($input, $output, $currentBranch, $branches, $split, $repo, $mainRepo): int {
96
                [$url, $remote, $path, $clonePath] = $required;
97
98
                if (!\file_exists($mainRepo->getPath()."/$path")) {
99
                    throw new InvalidOptionsException(\sprintf('The repo for "%s" path "%s" does not exist.', $remote, $path));
100
                }
101
102
                foreach (\array_unique($branches) as $branch) {
103
                    $output->writeln(\sprintf('<info>Splitting commits from branch %s into %s</info>', $branch, $url));
104
                    $verify = ['-1', '--format=%ad | %s [%an]', '--date=short'];
105
                    $pushChanges = [];
106
107
                    ($s = Process::fromShellCommandline(
108
                        "{$split} --prefix={$path} --origin=origin/{$branch} --target=".$target = "refs/splits/$remote",
109
                        $mainRepo->getPath(),
110
                        timeout: 1200
111
                    ))->run();
112
113
                    if ($output->isVerbose()) {
114
                        $output->writeln(\sprintf('<%s>[debug] Command "%s": %s</%1$s>', $s->isSuccessful() ? 'info' : 'error', $s->getCommandLine(), $s->getErrorOutput()));
115
116
                        if (!$s->isSuccessful()) {
117
                            continue;
118
                        }
119
                    }
120
121
                    if ($mainRepo->run('log', [$branch, ...$verify], cwd: $clonePath) !== $mainRepo->run('log', [$target, ...$verify])) {
122
                        $count = (int) \rtrim($mainRepo->run('rev-list', ['--count', $target]) ?? '0');
123
                        $updates = (int) \rtrim($mainRepo->run('rev-list', ['--count', $branch], cwd: $clonePath) ?? '0');
124
125
                        $output->writeln(\sprintf("<info>Target commit count: %d</info>", $count));
126
                        $output->writeln(\sprintf("<info>Source commit count: %d</info>", $updates));
127
128
                        if (($count = $updates > $count ? $updates - $count : $count - $updates) < 0) {
129
                            continue;
130
                        }
131
132
                        $output->writeln(\sprintf('<info>Pushing (%d) commits from branch %s to %s</info>', $count, $branch, $url));
133
                        $mainRepo->runConcurrent(0 === $updates ? [
134
                            ['push', $input->getOption('force') ? '-f' : '-q', $remote, "+$target:refs/heads/$branch"],
135
                            ['update-ref', '-d', $target],
136
                        ] : [
137
                            ['checkout', '--orphan', "split-$remote"],
138
                            ['reset', '--hard'],
139
                            ['pull', $remote, $branch],
140
                            ['cherry-pick', ...\explode(' ', "$target~".\implode(" $target~", \array_reverse(\range(0, $count - 1))))],
141
                            ['push', $input->getOption('force') ? '-f' : '-q', $remote, "+refs/heads/split-$remote:$branch"],
142
                            ['checkout', $currentBranch],
143
                            ['branch', '-D', "split-$remote"],
144
                            ['update-ref', '-d', $target],
145
                        ]);
146
147
                        if (!$input->getOption('no-push')) {
148
                            $pushChanges[] = ['push', ...($input->getOption('force') ? ['-f'] : []), 'origin', $branch];
149
                        }
150
                    } else {
151
                        $output->writeln(\sprintf('<info>Nothing to commit; On branch %s, "%s/%1$s" is up to date</info>', $branch, $remote));
152
                    }
153
                }
154
155
                if ($tagged = $input->getOption('release')) {
156
                    [$tagged, $repo] = [\explode('=', $tagged, 2), new Repository($clonePath, [], $repo->isDebug(), $repo->getLogger())];
157
158
                    if (!$repo->getBranch($rBranch = $tagged[1] ?? $currentBranch)) {
159
                        $output->writeln(\sprintf('<error>Release Branch %s does not exist</error>', $rBranch));
160
                    } else {
161
                        if ($repo->getBranch()->getName() !== $rBranch) {
162
                            $repo->run('checkout', [$rBranch]);
163
                        }
164
165
                        $tags = '*' === $tagged[0] ? \explode("\n", $mainRepo->run('tag', ['--list', '--points-at', $rBranch]) ?? '') : [$tagged[0]];
166
                        $tagPushes = [];
167
168
                        if ($repo->getBranch()->getName() !== $rBranch) {
169
                            $tagPushes[] = ['checkout', $rBranch];
170
                        }
171
172
                        foreach (\array_filter($tags) as $tag) {
173
                            if (\str_starts_with($tag, $remote.'/')) {
174
                                $tag = \substr($tag, \strlen($remote.'/'));
175
                            }
176
177
                            if (!$repo->getTag($tag)) {
178
                                $output->writeln(\sprintf('<info>Creating tag %s for repo %s</info>', $tagged[0], $remote));
179
                                $tagPushes[] = ['tag', $tagged[0], '-m', 'Release '.$tagged[0]];
180
                                $tagPushes[] = ['push', ...($input->getOption('force') ? ['origin', '--tags', '-f'] : ['origin', '--tags']), $rBranch];
181
182
                                if (!$input->getOption('no-push')) {
183
                                    $pushChanges[] = \end($tagPushes);
184
                                }
185
                            }
186
                        }
187
188
                        $repo->runConcurrent($tagPushes);
189
                    }
190
                }
191
192
                if (!empty($pushChanges)) {
193
                    $output->writeln(\sprintf('<info>Preparing to push changes to %s</info>', $url));
194
                    $mainRepo->runConcurrent($pushChanges, cwd: $clonePath);
195
                }
196
197
                return $mainRepo->getExitCode();
198
            }
199
        );
200
201
        return $mainRepo->getExitCode();
0 ignored issues
show
Unused Code introduced by
return $mainRepo->getExitCode() is not reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
202
    }
203
}
204