Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow version detection from conventional commits #21

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Hooks are now passed the VERSION as an environment variable.
- Hooks can now be placed in files inside `.maintain/hooks` such as
`.maintain/hooks/pre_release`.
- Added a Conventional Commits releaser.


## 0.2.0 (2016-05-10)
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ Maintain supports the following releasers:
release/npm
release/cocoapods
release/c
release/conventional-commits

Custom Hooks
~~~~~~~~~~~~
Expand Down
17 changes: 17 additions & 0 deletions docs/release/conventional-commits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Conventional Commits Releaser

Conventional Commit releaser allows the detection of the next semantic version
based upon the [conventional commit](http://conventionalcommits.org) messages.

## Detect

Detection is disabled. The Conventional Commits release needs to be explicitly
enabled.

## Bump

There is no bump steps for the Conventional Commits releaser.

## Release

There is no release steps for the Conventional Commits releaser.
59 changes: 59 additions & 0 deletions maintain/release/conventional_commits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import os
import re

from semantic_version import Version
from git import Repo, TagReference

from maintain.release.base import Releaser


class ConvetionalCommitsReleaser(Releaser):
name = 'Conventional Commits'

@classmethod
def detect(cls):
return os.path.exists('.git')

def __init__(self):
self.repo = Repo()

def determine_current_version(self):
tags = TagReference.list_items(self.repo)
versions = [Version(tag.tag.tag) for tag in tags]
return next(reversed(sorted(versions)))

def determine_next_version(self):
current_version = self.determine_current_version()

if current_version.prerelease or current_version.build:
return None

breaking = 0
feats = 0
fixes = 0

pattern = re.compile(r'^((\w+)(?:\(([^\)\s]+)\))?: (.+))+', re.MULTILINE)

commits = self.repo.iter_commits('master...{}'.format(current_version))
for commit in commits:
if 'BREAKING CHANGE:' in commit.message:
breaking += 1

result = pattern.search(commit.message.strip())
if result:
typ = result.group(2)
if typ == 'feat':
feats += 1
elif typ == 'fix':
fixes += 1
else:
raise Exception('Commit "{}" uses unsupported type {}'.format(commit, typ))
else:
raise Exception('Commit "{}" does not follow conventional commit'.format(commit))

if breaking > 0:
return current_version.next_major()
elif feats > 0:
return current_version.next_minor()
elif fixes > 0:
return current_version.next_patch()
122 changes: 122 additions & 0 deletions tests/release/test_conventional_commits.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import unittest

from maintain.release.conventional_commits import ConvetionalCommitsReleaser
from ..utils import git_repo, touch


class ConventionalCommitsReleaserTestCase(unittest.TestCase):
# Detection

def test_detects_git_repo(self):
with git_repo():
self.assertTrue(ConvetionalCommitsReleaser.detect())

# Detect Current Version

def test_detect_current_version(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

version = '1.1.0'
touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('Second Commit')
repo.create_tag(version, message=version)

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_current_version()
self.assertEqual(str(current_version), '1.1.0')

# Detect Next Version

def test_detect_next_version_patch(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('fix: some bug')

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_next_version()
self.assertEqual(str(current_version), '1.0.1')

def test_detect_next_version_minor(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('feat: some bug')

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_next_version()
self.assertEqual(str(current_version), '1.1.0')

def test_detect_next_version_major(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('feat: some bug\n\nBREAKING CHANGE: This removes some flag')

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_next_version()
self.assertEqual(str(current_version), '2.0.0')

def test_detect_next_version_various_commits(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('fix: that awful bug')

touch('TEST1.md')
repo.index.add(['TEST1.md'])
repo.index.commit('feat: awesome feature')

touch('TEST2.md')
repo.index.add(['TEST2.md'])
repo.index.commit('fix: some other bug')

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_next_version()
self.assertEqual(str(current_version), '1.1.0')

def test_detect_next_version_with_scope(self):
with git_repo() as repo:
version = '1.0.0'
touch('README.md')
repo.index.add(['README.md'])
repo.index.commit('Initial commit')
repo.create_tag(version, message=version)

touch('TEST.md')
repo.index.add(['TEST.md'])
repo.index.commit('feat(scope): that awful bug')

releaser = ConvetionalCommitsReleaser()
current_version = releaser.determine_next_version()
self.assertEqual(str(current_version), '1.1.0')