Skip to content

Commit ba1d67b

Browse files
Add project push rule API
Co-authored-by: Nicolas Giraud <9560327+niconoe-@users.noreply.github.com>
1 parent 6f86e75 commit ba1d67b

3 files changed

Lines changed: 282 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
* Add support for personal access tokens
1414
* Add support for project integrations endpoints
1515
* Add support for `Projects::updateDeployKey`
16+
* Add support for project push rules
1617
* Add support for group hook endpoints
1718
* Add support for `job_inputs` and `job_variables_attributes` in `Jobs::play`
1819
* Add support for `inputs` in `Projects::createPipeline`

src/Api/Projects.php

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,68 @@ public function deleteDeployToken(int|string $project_id, int $token_id): mixed
699699
return $this->delete($this->getProjectPath($project_id, 'deploy_tokens/'.self::encodePath($token_id)));
700700
}
701701

702+
public function pushRule(int|string $project_id): mixed
703+
{
704+
return $this->get($this->getProjectPath($project_id, 'push_rule'));
705+
}
706+
707+
/**
708+
* @param array $parameters {
709+
*
710+
* @var string $author_email_regex All commit author emails must match this regular expression.
711+
* @var string $branch_name_regex All branch names must match this regular expression.
712+
* @var bool $commit_committer_check Only allow commits when the committer email is one of the user's verified emails.
713+
* @var bool $commit_committer_name_check Only allow commits when the author name matches the user's GitLab account name.
714+
* @var string $commit_message_negative_regex Reject commit messages matching this regular expression.
715+
* @var string $commit_message_regex Require commit messages to match this regular expression.
716+
* @var bool $deny_delete_tag Deny deleting tags.
717+
* @var string $file_name_regex Reject committed filenames matching this regular expression.
718+
* @var int $max_file_size Maximum file size in MB.
719+
* @var bool $member_check Restrict commit authors by email to existing GitLab users.
720+
* @var bool $prevent_secrets Reject files likely to contain secrets.
721+
* @var bool $reject_non_dco_commits Reject commits that are not DCO certified.
722+
* @var bool $reject_unsigned_commits Reject unsigned commits.
723+
* }
724+
*
725+
* @throws UndefinedOptionsException If an option name is undefined
726+
* @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules
727+
*/
728+
public function createPushRule(int|string $project_id, array $parameters = []): mixed
729+
{
730+
return $this->post($this->getProjectPath($project_id, 'push_rule'), self::createPushRuleOptionsResolver()->resolve($parameters));
731+
}
732+
733+
/**
734+
* @param array $parameters {
735+
*
736+
* @var string $author_email_regex All commit author emails must match this regular expression.
737+
* @var string $branch_name_regex All branch names must match this regular expression.
738+
* @var bool $commit_committer_check Only allow commits when the committer email is one of the user's verified emails.
739+
* @var bool $commit_committer_name_check Only allow commits when the author name matches the user's GitLab account name.
740+
* @var string $commit_message_negative_regex Reject commit messages matching this regular expression.
741+
* @var string $commit_message_regex Require commit messages to match this regular expression.
742+
* @var bool $deny_delete_tag Deny deleting tags.
743+
* @var string $file_name_regex Reject committed filenames matching this regular expression.
744+
* @var int $max_file_size Maximum file size in MB.
745+
* @var bool $member_check Restrict commit authors by email to existing GitLab users.
746+
* @var bool $prevent_secrets Reject files likely to contain secrets.
747+
* @var bool $reject_non_dco_commits Reject commits that are not DCO certified.
748+
* @var bool $reject_unsigned_commits Reject unsigned commits.
749+
* }
750+
*
751+
* @throws UndefinedOptionsException If an option name is undefined
752+
* @throws InvalidOptionsException If an option doesn't fulfill the specified validation rules
753+
*/
754+
public function updatePushRule(int|string $project_id, array $parameters = []): mixed
755+
{
756+
return $this->put($this->getProjectPath($project_id, 'push_rule'), self::createPushRuleOptionsResolver()->resolve($parameters));
757+
}
758+
759+
public function deletePushRule(int|string $project_id): mixed
760+
{
761+
return $this->delete($this->getProjectPath($project_id, 'push_rule'));
762+
}
763+
702764
/**
703765
* @param array $parameters {
704766
*
@@ -1503,4 +1565,41 @@ public function search(int|string $id, array $parameters = []): mixed
15031565

15041566
return $this->get('projects/'.self::encodePath($id).'/search', $resolver->resolve($parameters));
15051567
}
1568+
1569+
private static function createPushRuleOptionsResolver(): OptionsResolver
1570+
{
1571+
$resolver = new OptionsResolver();
1572+
1573+
foreach ([
1574+
'author_email_regex',
1575+
'branch_name_regex',
1576+
'commit_message_negative_regex',
1577+
'commit_message_regex',
1578+
'file_name_regex',
1579+
] as $option) {
1580+
$resolver->setDefined($option)
1581+
->setAllowedTypes($option, 'string')
1582+
;
1583+
}
1584+
1585+
foreach ([
1586+
'commit_committer_check',
1587+
'commit_committer_name_check',
1588+
'deny_delete_tag',
1589+
'member_check',
1590+
'prevent_secrets',
1591+
'reject_non_dco_commits',
1592+
'reject_unsigned_commits',
1593+
] as $option) {
1594+
$resolver->setDefined($option)
1595+
->setAllowedTypes($option, 'bool')
1596+
;
1597+
}
1598+
1599+
$resolver->setDefined('max_file_size')
1600+
->setAllowedTypes('max_file_size', 'int')
1601+
;
1602+
1603+
return $resolver;
1604+
}
15061605
}

tests/Api/ProjectsTest.php

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
use PHPUnit\Framework\Attributes\DataProvider;
2020
use PHPUnit\Framework\Attributes\Test;
2121
use PHPUnit\Framework\MockObject\MockObject;
22+
use Symfony\Component\OptionsResolver\Exception\InvalidOptionsException;
23+
use Symfony\Component\OptionsResolver\Exception\UndefinedOptionsException;
2224

2325
class ProjectsTest extends TestCase
2426
{
@@ -1600,6 +1602,186 @@ public function shouldDeleteDeployToken(): void
16001602
$this->assertEquals($expectedBool, $api->deleteDeployToken(1, 2));
16011603
}
16021604

1605+
#[Test]
1606+
public function shouldGetPushRule(): void
1607+
{
1608+
$expectedArray = [
1609+
'id' => 1,
1610+
'project_id' => 3,
1611+
'commit_message_regex' => 'Fixes \\d+\\..*',
1612+
'branch_name_regex' => 'feature\\/.*',
1613+
'author_email_regex' => '@example.com$',
1614+
];
1615+
1616+
$api = $this->getApiMock();
1617+
$api->expects($this->once())
1618+
->method('get')
1619+
->with('projects/3/push_rule')
1620+
->willReturn($expectedArray);
1621+
1622+
$this->assertEquals($expectedArray, $api->pushRule(3));
1623+
}
1624+
1625+
#[Test]
1626+
public function shouldGetUnsetPushRule(): void
1627+
{
1628+
$api = $this->getApiMock();
1629+
$api->expects($this->once())
1630+
->method('get')
1631+
->with('projects/3/push_rule')
1632+
->willReturn('null');
1633+
1634+
$this->assertEquals('null', $api->pushRule(3));
1635+
}
1636+
1637+
#[Test]
1638+
public function shouldCreatePushRule(): void
1639+
{
1640+
$expectedBool = true;
1641+
$parameters = [
1642+
'commit_message_regex' => 'Fixes \\d+\\..*',
1643+
'branch_name_regex' => 'feature\\/.*',
1644+
'author_email_regex' => '@example.com$',
1645+
];
1646+
1647+
$api = $this->getApiMock();
1648+
$api->expects($this->once())
1649+
->method('post')
1650+
->with('projects/3/push_rule', $parameters)
1651+
->willReturn($expectedBool);
1652+
1653+
$this->assertEquals($expectedBool, $api->createPushRule(3, $parameters));
1654+
}
1655+
1656+
#[Test]
1657+
public function shouldCreatePushRuleWithBooleanAndIntegerParameters(): void
1658+
{
1659+
$expectedBool = true;
1660+
$parameters = [
1661+
'deny_delete_tag' => false,
1662+
'member_check' => true,
1663+
'prevent_secrets' => true,
1664+
'commit_committer_check' => false,
1665+
'commit_committer_name_check' => true,
1666+
'reject_unsigned_commits' => false,
1667+
'reject_non_dco_commits' => true,
1668+
'max_file_size' => 100,
1669+
];
1670+
1671+
$api = $this->getApiMock();
1672+
$api->expects($this->once())
1673+
->method('post')
1674+
->with('projects/3/push_rule', $parameters)
1675+
->willReturn($expectedBool);
1676+
1677+
$this->assertEquals($expectedBool, $api->createPushRule(3, $parameters));
1678+
}
1679+
1680+
#[Test]
1681+
public function shouldUpdatePushRule(): void
1682+
{
1683+
$expectedBool = true;
1684+
$parameters = [
1685+
'commit_message_regex' => 'Fixes \\d+\\..*',
1686+
'branch_name_regex' => 'feature\\/.*',
1687+
'author_email_regex' => '@example.com$',
1688+
];
1689+
1690+
$api = $this->getApiMock();
1691+
$api->expects($this->once())
1692+
->method('put')
1693+
->with('projects/3/push_rule', $parameters)
1694+
->willReturn($expectedBool);
1695+
1696+
$this->assertEquals($expectedBool, $api->updatePushRule(3, $parameters));
1697+
}
1698+
1699+
#[Test]
1700+
public function shouldUpdatePushRuleWithBooleanAndIntegerParameters(): void
1701+
{
1702+
$expectedBool = true;
1703+
$parameters = [
1704+
'deny_delete_tag' => true,
1705+
'member_check' => false,
1706+
'prevent_secrets' => false,
1707+
'commit_committer_check' => true,
1708+
'commit_committer_name_check' => false,
1709+
'reject_unsigned_commits' => true,
1710+
'reject_non_dco_commits' => false,
1711+
'max_file_size' => 25,
1712+
];
1713+
1714+
$api = $this->getApiMock();
1715+
$api->expects($this->once())
1716+
->method('put')
1717+
->with('projects/3/push_rule', $parameters)
1718+
->willReturn($expectedBool);
1719+
1720+
$this->assertEquals($expectedBool, $api->updatePushRule(3, $parameters));
1721+
}
1722+
1723+
#[Test]
1724+
public function shouldDeletePushRule(): void
1725+
{
1726+
$expectedBool = true;
1727+
1728+
$api = $this->getApiMock();
1729+
$api->expects($this->once())
1730+
->method('delete')
1731+
->with('projects/3/push_rule')
1732+
->willReturn($expectedBool);
1733+
1734+
$this->assertEquals($expectedBool, $api->deletePushRule(3));
1735+
}
1736+
1737+
#[Test]
1738+
public function shouldRejectUndefinedParameterWhenCreatingPushRule(): void
1739+
{
1740+
$api = $this->getApiMock();
1741+
$api->expects($this->never())
1742+
->method('post');
1743+
1744+
$this->expectException(UndefinedOptionsException::class);
1745+
1746+
$api->createPushRule(3, ['unsupported_parameter' => true]);
1747+
}
1748+
1749+
#[Test]
1750+
public function shouldRejectUndefinedParameterWhenUpdatingPushRule(): void
1751+
{
1752+
$api = $this->getApiMock();
1753+
$api->expects($this->never())
1754+
->method('put');
1755+
1756+
$this->expectException(UndefinedOptionsException::class);
1757+
1758+
$api->updatePushRule(3, ['unsupported_parameter' => true]);
1759+
}
1760+
1761+
#[Test]
1762+
public function shouldRequireIntegerMaxFileSizeWhenCreatingPushRule(): void
1763+
{
1764+
$api = $this->getApiMock();
1765+
$api->expects($this->never())
1766+
->method('post');
1767+
1768+
$this->expectException(InvalidOptionsException::class);
1769+
1770+
$api->createPushRule(3, ['max_file_size' => '100']);
1771+
}
1772+
1773+
#[Test]
1774+
public function shouldRequireIntegerMaxFileSizeWhenUpdatingPushRule(): void
1775+
{
1776+
$api = $this->getApiMock();
1777+
$api->expects($this->never())
1778+
->method('put');
1779+
1780+
$this->expectException(InvalidOptionsException::class);
1781+
1782+
$api->updatePushRule(3, ['max_file_size' => '100']);
1783+
}
1784+
16031785
#[Test]
16041786
public function shouldGetEvents(): void
16051787
{

0 commit comments

Comments
 (0)