Skip to content

Commit 740564c

Browse files
Copilotmglaman
andauthored
Make mr-iid optional: auto-select single MR or prompt interactively (#332)
* Initial plan * Make mr-iid optional: auto-select single MR or prompt interactively Co-authored-by: mglaman <3698644+mglaman@users.noreply.github.com> Agent-Logs-Url: https://github.com/mglaman/drupalorg-cli/sessions/dbee614d-7c46-457f-be68-7bc9979c9b16 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mglaman <3698644+mglaman@users.noreply.github.com>
1 parent 39f3e8c commit 740564c

2 files changed

Lines changed: 99 additions & 3 deletions

File tree

src/Cli/Command/MergeRequest/MrCommandBase.php

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,17 @@
22

33
namespace mglaman\DrupalOrgCli\Command\MergeRequest;
44

5+
use mglaman\DrupalOrg\Action\MergeRequest\ListMergeRequestsAction;
6+
use mglaman\DrupalOrg\Enum\MergeRequestState;
7+
use mglaman\DrupalOrg\GitLab\Client as GitLabClient;
58
use mglaman\DrupalOrg\GitLab\MergeRequestRef;
9+
use mglaman\DrupalOrg\Result\MergeRequest\MergeRequestItem;
610
use mglaman\DrupalOrgCli\Command\Command;
711
use mglaman\DrupalOrgCli\Command\Issue\IssueCommandBase;
812
use Symfony\Component\Console\Input\InputArgument;
913
use Symfony\Component\Console\Input\InputInterface;
1014
use Symfony\Component\Console\Output\OutputInterface;
15+
use Symfony\Component\Console\Question\ChoiceQuestion;
1116

1217
abstract class MrCommandBase extends IssueCommandBase
1318
{
@@ -50,9 +55,50 @@ protected function initialize(InputInterface $input, OutputInterface $output): v
5055
parent::initialize($input, $output);
5156

5257
$mrIid = $this->stdIn->getArgument('mr-iid');
53-
if ($mrIid === null || $mrIid === '') {
54-
throw new \RuntimeException('Argument mr-iid is required.');
58+
if ($mrIid !== null && $mrIid !== '') {
59+
$this->mrIid = (int) $mrIid;
60+
return;
61+
}
62+
63+
// mr-iid not provided — auto-select from open merge requests.
64+
$listAction = new ListMergeRequestsAction($this->client, new GitLabClient());
65+
$listResult = $listAction($this->nid, MergeRequestState::Opened);
66+
$mergeRequests = $listResult->mergeRequests;
67+
68+
if ($mergeRequests === []) {
69+
throw new \RuntimeException('No open merge requests found for this issue.');
70+
}
71+
72+
if (count($mergeRequests) === 1) {
73+
$this->mrIid = $mergeRequests[0]->iid;
74+
$this->stdOut->writeln(sprintf(
75+
'<info>Auto-selected MR !%d: %s</info>',
76+
$mergeRequests[0]->iid,
77+
$mergeRequests[0]->title
78+
));
79+
return;
80+
}
81+
82+
// Multiple open MRs.
83+
if (!self::$interactive) {
84+
$this->stdErr->writeln('<error>Multiple open merge requests found. Specify one of:</error>');
85+
foreach ($mergeRequests as $mr) {
86+
$this->stdErr->writeln(sprintf(' !%d — %s', $mr->iid, $mr->title));
87+
}
88+
throw new \RuntimeException('Argument mr-iid is required when multiple merge requests are open.');
89+
}
90+
91+
$choices = array_map(
92+
static fn(MergeRequestItem $mr) => sprintf('!%d — %s', $mr->iid, $mr->title),
93+
$mergeRequests
94+
);
95+
/** @var \Symfony\Component\Console\Helper\QuestionHelper $helper */
96+
$helper = $this->getHelper('question');
97+
$question = new ChoiceQuestion('Select a merge request:', $choices);
98+
$selected = (string) $helper->ask($input, $output, $question);
99+
if (!preg_match('/^!(\d+)/', $selected, $matches)) {
100+
throw new \RuntimeException('Failed to extract merge request IID from selection.');
55101
}
56-
$this->mrIid = (int) $mrIid;
102+
$this->mrIid = (int) $matches[1];
57103
}
58104
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
namespace mglaman\DrupalOrg\Tests\Command\MergeRequest;
4+
5+
use mglaman\DrupalOrgCli\Command\MergeRequest\MrCommandBase;
6+
use PHPUnit\Framework\Attributes\CoversClass;
7+
use PHPUnit\Framework\TestCase;
8+
use Symfony\Component\Console\Input\InputInterface;
9+
use Symfony\Component\Console\Output\OutputInterface;
10+
11+
#[CoversClass(MrCommandBase::class)]
12+
class MrCommandBaseTest extends TestCase
13+
{
14+
public function testClassExists(): void
15+
{
16+
$command = new class ('test:mr-command') extends MrCommandBase {
17+
protected function configure(): void
18+
{
19+
$this->configureNidAndMrIid();
20+
}
21+
22+
protected function execute(InputInterface $input, OutputInterface $output): int
23+
{
24+
return 0;
25+
}
26+
};
27+
self::assertInstanceOf(MrCommandBase::class, $command);
28+
}
29+
30+
public function testConfigureNidAndMrIidAddsArguments(): void
31+
{
32+
$command = new class ('test:mr-command') extends MrCommandBase {
33+
protected function configure(): void
34+
{
35+
$this->configureNidAndMrIid();
36+
}
37+
38+
protected function execute(InputInterface $input, OutputInterface $output): int
39+
{
40+
return 0;
41+
}
42+
};
43+
44+
$definition = $command->getDefinition();
45+
self::assertTrue($definition->hasArgument('nid'));
46+
self::assertTrue($definition->hasArgument('mr-iid'));
47+
self::assertFalse($definition->getArgument('nid')->isRequired());
48+
self::assertFalse($definition->getArgument('mr-iid')->isRequired());
49+
}
50+
}

0 commit comments

Comments
 (0)