SplitCommitsWorker::getDescription()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
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