0
# Plugin Development
1
2
Extensible plugin system for creating custom commit message rules, version providers, changelog formats, and version schemes. Commitizen's plugin architecture enables teams to define their own commit conventions and integrate with various project types and workflows.
3
4
## Capabilities
5
6
### Plugin Discovery
7
8
Automatic discovery and registration of commitizen plugins installed via entry points.
9
10
```python { .api }
11
def discover_plugins() -> None:
12
"""
13
Discover and register all available commitizen plugins.
14
15
Searches for plugins in the 'commitizen.plugin' entry point group
16
and registers them in the global plugin registry.
17
"""
18
19
registry: dict[str, type[BaseCommitizen]]
20
"""Global plugin registry mapping plugin names to plugin classes."""
21
```
22
23
### Plugin Factory
24
25
Factory function for creating commitizen plugin instances based on configuration.
26
27
```python { .api }
28
def commiter_factory(config: BaseConfig) -> BaseCommitizen:
29
"""
30
Create commitizen plugin instance based on configuration.
31
32
Parameters:
33
- config: Configuration object containing plugin name
34
35
Returns:
36
BaseCommitizen instance for the configured plugin
37
38
Raises:
39
NoCommitizenFoundException: If plugin is not found
40
"""
41
```
42
43
### Base Plugin Class
44
45
Abstract base class that all commitizen plugins must inherit from.
46
47
```python { .api }
48
class BaseCommitizen:
49
"""
50
Abstract base class for all commitizen plugins.
51
52
Defines the interface that plugins must implement for commit message
53
generation, validation, and version bump logic.
54
"""
55
def __init__(self, config: BaseConfig):
56
"""
57
Initialize plugin with configuration.
58
59
Parameters:
60
- config: Configuration object
61
"""
62
63
def questions(self) -> Questions:
64
"""
65
Return interactive questions for commit creation.
66
67
Returns:
68
List of question dictionaries for questionary prompts
69
"""
70
71
def message(self, answers: dict[str, Any]) -> str:
72
"""
73
Generate commit message from user answers.
74
75
Parameters:
76
- answers: Dictionary of answers from interactive questions
77
78
Returns:
79
Formatted commit message string
80
"""
81
82
def example(self) -> str:
83
"""
84
Return example commit message.
85
86
Returns:
87
Example commit message following plugin rules
88
"""
89
90
def schema(self) -> str:
91
"""
92
Return commit message schema description.
93
94
Returns:
95
Human-readable schema description
96
"""
97
98
def schema_pattern(self) -> str:
99
"""
100
Return regex pattern for commit message validation.
101
102
Returns:
103
Regex pattern string for validation
104
"""
105
106
def info(self) -> str:
107
"""
108
Return plugin information.
109
110
Returns:
111
Plugin description and usage information
112
"""
113
114
def process_commit(self, commit: str) -> str:
115
"""
116
Process commit message for changelog generation.
117
118
Parameters:
119
- commit: Raw commit message
120
121
Returns:
122
Processed commit message for changelog
123
"""
124
125
def changelog_pattern(self) -> str:
126
"""
127
Return regex pattern for changelog parsing.
128
129
Returns:
130
Regex pattern for extracting changelog information
131
"""
132
133
def change_type_map(self) -> dict[str, str]:
134
"""
135
Return mapping of change types to display names.
136
137
Returns:
138
Dictionary mapping change types to changelog section names
139
"""
140
141
def change_type_order(self) -> list[str]:
142
"""
143
Return ordered list of change types for changelog.
144
145
Returns:
146
List of change types in display order
147
"""
148
149
def bump_pattern(self) -> str:
150
"""
151
Return regex pattern for version bump detection.
152
153
Returns:
154
Regex pattern for determining version increments
155
"""
156
157
def bump_map(self) -> dict[str, str]:
158
"""
159
Return mapping of patterns to version increments.
160
161
Returns:
162
Dictionary mapping regex groups to increment types (MAJOR, MINOR, PATCH)
163
"""
164
```
165
166
### Built-in Plugins
167
168
#### Conventional Commits Plugin
169
170
Standard plugin implementing the Conventional Commits specification.
171
172
```python { .api }
173
class ConventionalCommitsCz(BaseCommitizen):
174
"""
175
Plugin implementing the Conventional Commits specification.
176
177
Supports standard commit types: feat, fix, docs, style, refactor, test, chore.
178
Includes BREAKING CHANGE detection for major version bumps.
179
"""
180
def questions(self) -> Questions:
181
"""
182
Interactive questions for conventional commits.
183
184
Prompts for commit type, scope, description, body, and breaking changes.
185
"""
186
187
def message(self, answers: dict[str, Any]) -> str:
188
"""
189
Generate conventional commit message.
190
191
Format: <type>[optional scope]: <description>
192
193
[optional body]
194
195
[optional footer(s)]
196
"""
197
```
198
199
#### Jira Smart Plugin
200
201
Plugin for integrating with Jira issue tracking.
202
203
```python { .api }
204
class JiraSmartCz(BaseCommitizen):
205
"""
206
Plugin for Jira integration with smart branch detection.
207
208
Automatically detects Jira issue keys from branch names and includes
209
them in commit messages.
210
"""
211
def questions(self) -> Questions:
212
"""
213
Interactive questions with Jira issue integration.
214
215
Includes Jira issue key detection and linking.
216
"""
217
```
218
219
#### Customizable Plugin
220
221
Highly configurable plugin for teams with specific commit conventions.
222
223
```python { .api }
224
class CustomizeCommitsCz(BaseCommitizen):
225
"""
226
Customizable plugin supporting user-defined commit conventions.
227
228
Allows complete customization of questions, message format, and validation
229
through configuration file settings.
230
"""
231
def questions(self) -> Questions:
232
"""
233
Questions defined in configuration [tool.commitizen.customize.questions].
234
235
Supports all questionary question types with full customization.
236
"""
237
```
238
239
## Plugin Development Guide
240
241
### Creating a Custom Plugin
242
243
```python
244
from commitizen.cz.base import BaseCommitizen
245
from commitizen.defaults import Questions
246
247
class MyCustomCz(BaseCommitizen):
248
"""Custom commitizen plugin for my team's conventions."""
249
250
def questions(self) -> Questions:
251
return [
252
{
253
"type": "list",
254
"name": "type",
255
"message": "Select the type of change:",
256
"choices": [
257
{"value": "feature", "name": "feature: New feature"},
258
{"value": "bugfix", "name": "bugfix: Bug fix"},
259
{"value": "docs", "name": "docs: Documentation"},
260
{"value": "refactor", "name": "refactor: Code refactoring"}
261
]
262
},
263
{
264
"type": "input",
265
"name": "scope",
266
"message": "Scope (optional):"
267
},
268
{
269
"type": "input",
270
"name": "subject",
271
"message": "Short description:",
272
"validate": lambda x: len(x) > 0 and len(x) <= 50
273
},
274
{
275
"type": "input",
276
"name": "body",
277
"message": "Longer description (optional):"
278
}
279
]
280
281
def message(self, answers: dict[str, Any]) -> str:
282
scope = f"({answers['scope']})" if answers.get('scope') else ""
283
message = f"{answers['type']}{scope}: {answers['subject']}"
284
285
if answers.get('body'):
286
message += f"\n\n{answers['body']}"
287
288
return message
289
290
def example(self) -> str:
291
return "feature(auth): add OAuth2 integration"
292
293
def schema(self) -> str:
294
return "<type>[(scope)]: <subject>\n\n[body]"
295
296
def schema_pattern(self) -> str:
297
return r"^(feature|bugfix|docs|refactor)(\(.+\))?: .{1,50}$"
298
299
def info(self) -> str:
300
return "Custom plugin for MyTeam commit conventions"
301
302
def bump_pattern(self) -> str:
303
return r"^(feature|BREAKING)"
304
305
def bump_map(self) -> dict[str, str]:
306
return {
307
"BREAKING": "MAJOR",
308
"feature": "MINOR",
309
"bugfix": "PATCH"
310
}
311
312
def change_type_map(self) -> dict[str, str]:
313
return {
314
"feature": "Features",
315
"bugfix": "Bug Fixes",
316
"docs": "Documentation",
317
"refactor": "Refactoring"
318
}
319
```
320
321
### Plugin Entry Points
322
323
#### setup.py Entry Points
324
325
```python
326
from setuptools import setup, find_packages
327
328
setup(
329
name="my-commitizen-plugin",
330
version="1.0.0",
331
packages=find_packages(),
332
install_requires=["commitizen>=3.0.0"],
333
entry_points={
334
"commitizen.plugin": [
335
"my_custom_cz = my_plugin:MyCustomCz"
336
]
337
}
338
)
339
```
340
341
#### pyproject.toml Entry Points
342
343
```toml
344
[build-system]
345
requires = ["poetry-core"]
346
build-backend = "poetry.core.masonry.api"
347
348
[tool.poetry]
349
name = "my-commitizen-plugin"
350
version = "1.0.0"
351
description = "Custom commitizen plugin"
352
353
[tool.poetry.dependencies]
354
python = "^3.8"
355
commitizen = "^3.0.0"
356
357
[tool.poetry.plugins."commitizen.plugin"]
358
my_custom_cz = "my_plugin:MyCustomCz"
359
```
360
361
### Version Provider Plugins
362
363
Custom version providers for different project types.
364
365
```python { .api }
366
class VersionProvider:
367
"""Abstract base class for version providers."""
368
369
def get_version(self) -> str:
370
"""
371
Get current version from provider source.
372
373
Returns:
374
Current version string
375
"""
376
377
def set_version(self, version: str) -> None:
378
"""
379
Set version in provider source.
380
381
Parameters:
382
- version: New version string
383
"""
384
385
@property
386
def keyword(self) -> str:
387
"""
388
Provider keyword for entry point registration.
389
390
Returns:
391
String identifier for the provider
392
"""
393
394
class CustomProvider(VersionProvider):
395
"""Example custom version provider."""
396
397
@property
398
def keyword(self) -> str:
399
return "custom"
400
401
def get_version(self) -> str:
402
# Read version from custom source
403
with open("VERSION.txt") as f:
404
return f.read().strip()
405
406
def set_version(self, version: str) -> None:
407
# Write version to custom source
408
with open("VERSION.txt", "w") as f:
409
f.write(version)
410
```
411
412
### Changelog Format Plugins
413
414
Custom changelog formats for different documentation styles.
415
416
```python { .api }
417
class ChangelogFormat(Protocol):
418
"""Protocol for changelog format implementations."""
419
420
def get_changelog(
421
self,
422
tree: dict[str, Any],
423
header_format: str = "",
424
**kwargs: Any
425
) -> str:
426
"""
427
Generate changelog content from commit tree.
428
429
Parameters:
430
- tree: Parsed commit tree structure
431
- header_format: Header format template
432
- kwargs: Additional format-specific options
433
434
Returns:
435
Formatted changelog content
436
"""
437
438
class CustomChangelogFormat:
439
"""Example custom changelog format."""
440
441
def get_changelog(
442
self,
443
tree: dict[str, Any],
444
header_format: str = "",
445
**kwargs: Any
446
) -> str:
447
# Generate custom format changelog
448
content = []
449
450
for version, changes in tree.items():
451
content.append(f"## Version {version}")
452
453
for change_type, items in changes.items():
454
content.append(f"### {change_type.title()}")
455
for item in items:
456
content.append(f"- {item}")
457
content.append("")
458
459
return "\n".join(content)
460
```
461
462
## Plugin Configuration
463
464
### Using Custom Plugins
465
466
```toml
467
[tool.commitizen]
468
name = "my_custom_cz" # Plugin entry point name
469
version = "1.0.0"
470
471
# Plugin-specific configuration
472
[tool.commitizen.customize]
473
# Custom plugin settings go here
474
```
475
476
### Plugin Testing
477
478
```python
479
import pytest
480
from commitizen.config import BaseConfig
481
from my_plugin import MyCustomCz
482
483
def test_plugin_questions():
484
config = BaseConfig({"name": "my_custom_cz"})
485
plugin = MyCustomCz(config)
486
487
questions = plugin.questions()
488
assert len(questions) > 0
489
assert questions[0]["name"] == "type"
490
491
def test_plugin_message():
492
config = BaseConfig({"name": "my_custom_cz"})
493
plugin = MyCustomCz(config)
494
495
answers = {
496
"type": "feature",
497
"subject": "add new feature"
498
}
499
500
message = plugin.message(answers)
501
assert message == "feature: add new feature"
502
503
def test_plugin_validation():
504
config = BaseConfig({"name": "my_custom_cz"})
505
plugin = MyCustomCz(config)
506
507
pattern = plugin.schema_pattern()
508
import re
509
510
assert re.match(pattern, "feature: valid message")
511
assert not re.match(pattern, "invalid message format")
512
```