Skip to content

Commit

Permalink
MRG: Merge pull request #40 from octue/fix/support-commit-messages-wi…
Browse files Browse the repository at this point in the history
…th-multiline-bodies

Support commit messages with multi-line bodies
  • Loading branch information
cortadocodes authored Oct 13, 2021
2 parents be5084a + b527e01 commit f642d40
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 84 deletions.
59 changes: 33 additions & 26 deletions conventional_commits/compile_release_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,49 +142,56 @@ def _get_current_pull_request(self, pull_request_url, api_token):
return requests.get(pull_request_url, headers=headers).json()

def _get_git_log(self):
"""Get the one-line decorated git log formatted with "|" delimiting the commit hash, message, and decoration.
"""Get the one-line decorated git log formatted in the pattern of "hash|§header|§body|§decoration@@@".
Explanation:
* "|§" delimits the hash from the header, the header from the potentially multi-line body, and the body from the
decoration
* "@@@" indicates the end of the git log line. "\n" cannot be used as commit bodies can contain newlines, so
they can't be used by themselves to delimit git log entries.
* The specific characters used for the delimiters have been chosen so that they are very uncommon to reduce
delimiting errors
:return str:
"""
git_log = subprocess.run(["git", "log", "--pretty=format:%h|%s|%b|%d"], capture_output=True).stdout.strip()

# Ensure commit message bodies always appear on one line
return git_log.replace(b"\n|", b"|").decode()
return (
subprocess.run(["git", "log", "--pretty=format:%h|§%s|§%b|§%d@@@"], capture_output=True)
.stdout.strip()
.decode()
)

def _parse_commit_messages(self, formatted_oneline_git_log):
def _parse_commit_messages(self, formatted_one_line_git_log):
"""Parse commit messages from the git log (formatted using `--pretty=format:%s|%d`) until the stop point is
reached. The parsed commit messages are returned separately to any that fail to parse.
:param str formatted_oneline_git_log:
:param str formatted_one_line_git_log:
:return list(tuple), list(str):
"""
parsed_commits = []
unparsed_commits = []

for commit in formatted_oneline_git_log.splitlines():
# The pipe symbol "|" is used to delimit the commit header from its decoration.
split_commit = commit.split("|")
commits = formatted_one_line_git_log.split("@@@")

if len(split_commit) == 4:
_, header, body, decoration = split_commit
for commit in commits:
hash, header, body, decoration = commit.split("|§")

if self._is_stop_point(header, decoration):
break
if self._is_stop_point(header, decoration):
break

# A colon separating the commit code from the commit header is required - keep commit messages that
# don't conform to this but put them into an unparsed category. Ignore commits that are merges of one
# commit ref into another (GitHub Actions produces these - they don't appear in the actual history of
# the branch so can be safely ignored when making release notes).
if ":" not in header:
if not COMMIT_REF_MERGE_PATTERN.search(header):
unparsed_commits.append(header.strip())
continue
# A colon separating the commit code from the commit header is required - keep commit messages that
# don't conform to this but put them into an unparsed category. Ignore commits that are merges of one
# commit ref into another (GitHub Actions produces these - they don't appear in the actual history of
# the branch so can be safely ignored when making release notes).
if ":" not in header:
if not COMMIT_REF_MERGE_PATTERN.search(header):
unparsed_commits.append(header.strip())
continue

# Allow commit headers with extra colons.
code, *header = header.split(":")
header = ":".join(header)
# Allow commit headers with extra colons.
code, *header = header.split(":")
header = ":".join(header)

parsed_commits.append((code.strip(), header.strip(), body.strip(), decoration.strip()))
parsed_commits.append((code.strip(), header.strip(), body.strip(), decoration.strip()))

return parsed_commits, unparsed_commits

Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[metadata]
name = conventional_commits
version = 0.3.0
version = 0.3.1
description = A pre-commit hook, semantic version checker, and release note compiler for facilitating continuous deployment via Conventional Commits.
long_description = file: README.md
long_description_content_type = text/markdown
Expand Down
141 changes: 84 additions & 57 deletions tests/test_compile_release_notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from conventional_commits.compile_release_notes import ReleaseNotesCompiler, main


MOCK_GIT_LOG = "\n".join(
MOCK_GIT_LOG = "@@@\n".join(
[
"3e7dc54|REF: Merge commit message checker modules|| (HEAD -> refactor/test-release-notes-generator, origin/refactor/test-release-notes-generator)",
"fabd2ab|MRG: Merge pull request #3 from octue/feature/add-other-conventional-commit-ci-components||",
"ef77729|CHO: Remove hook installation from branch|| (tag: 0.0.3, origin/main, origin/HEAD, main)",
"b043bc8|ENH: Support getting versions from poetry and npm||",
"27dcef0|FIX: Fix semantic version script; add missing config||",
"3e7dc54|§REF: Merge commit message checker modules|§|§ (HEAD -> refactor/test-release-notes-generator, origin/refactor/test-release-notes-generator)",
"fabd2ab|§MRG: Merge pull request #3 from octue/feature/add-other-conventional-commit-ci-components|§|§",
"ef77729|§CHO: Remove hook installation from branch|§|§ (tag: 0.0.3, origin/main, origin/HEAD, main)",
"b043bc8|§ENH: Support getting versions from poetry and npm|§|§",
"27dcef0|§FIX: Fix semantic version script; add missing config|§|§",
]
)

Expand Down Expand Up @@ -105,15 +105,15 @@ def test_last_pull_request_stop_point(self):

def test_branch_point_stop_point(self):
"""Test generating release notes that stop at the last branch point."""
mock_git_log = "\n".join(
mock_git_log = "@@@\n".join(
[
"27dcef0|TST: Improve presentation of long strings|| (fix/fix-other-release-notes-stop-point-bug)",
"358ffd5|REF: Move stop point checking into separate method||",
"44927c6|FIX: Fix LAST_PULL_REQUEST stop point bug||",
"7cdc980|FIX: Ensure uncategorised commits are not lost|| (fix/allow-extra-colons-in-commit-message)",
"741bb8d|OPS: Increase version to 0.0.11|| (tag: 0.0.11, my-base-branch)",
"27092a4|FIX: Allow extra colons in commit headers in release notes compiler||",
"6dcdc41|MRG: Merge pull request #17 from octue/fix/fix-release-notes-stop-point-bug|| (tag: 0.0.10)",
"27dcef0|§TST: Improve presentation of long strings|§|§ (fix/fix-other-release-notes-stop-point-bug)",
"358ffd5|§REF: Move stop point checking into separate method|§|§",
"44927c6|§FIX: Fix LAST_PULL_REQUEST stop point bug|§|§",
"7cdc980|§FIX: Ensure uncategorised commits are not lost|§|§ (fix/allow-extra-colons-in-commit-message)",
"741bb8d|§OPS: Increase version to 0.0.11|§|§ (tag: 0.0.11, my-base-branch)",
"27092a4|§FIX: Allow extra colons in commit headers in release notes compiler|§|§",
"6dcdc41|§MRG: Merge pull request #17 from octue/fix/fix-release-notes-stop-point-bug|§|§ (tag: 0.0.10)",
]
)

Expand Down Expand Up @@ -249,7 +249,7 @@ def test_autogenerated_section_gets_overwritten_but_text_outside_does_not(self):

def test_commit_messages_in_non_standard_format_are_left_uncategorised(self):
"""Test that commit messages in a non-standard format are put under an uncategorised heading."""
mock_git_log = "\n".join(["fabd2ab|This is not in the right format||", "27dcef0|FIX: Fix a bug||"])
mock_git_log = "fabd2ab|§This is not in the right format|§|§@@@\n27dcef0|§FIX: Fix a bug|§|§"

with patch(self.GIT_LOG_METHOD_PATH, return_value=mock_git_log):
release_notes = ReleaseNotesCompiler(stop_point="LAST_PULL_REQUEST").compile_release_notes()
Expand All @@ -273,7 +273,7 @@ def test_commit_messages_in_non_standard_format_are_left_uncategorised(self):

def test_commit_messages_with_unrecognised_commit_codes_are_categorised_as_other(self):
"""Test that commit messages with an unrecognised commit code are categorised under "other"."""
mock_git_log = "\n".join(["27dcef0|BAM: An unrecognised commit code||", "fabd2ab|FIX: Fix a bug||"])
mock_git_log = "27dcef0|§BAM: An unrecognised commit code|§|§@@@\nfabd2ab|§FIX: Fix a bug|§|§"

with patch(self.GIT_LOG_METHOD_PATH, return_value=mock_git_log):
with patch(self.GET_CURRENT_PULL_REQUEST_PATH, return_value={"body": ""}):
Expand Down Expand Up @@ -314,7 +314,7 @@ def test_updating_release_notes_works_and_does_not_add_extra_newlines_after_auto
).compile_release_notes()

# Add a new commit to the git log.
updated_mock_git_log = "fabd2ab|FIX: Fix a bug||\n" + MOCK_GIT_LOG
updated_mock_git_log = "fabd2ab|§FIX: Fix a bug|§|§@@@\n" + MOCK_GIT_LOG

# Run the compiler on the new git log to update the previous set of release notes.
with patch(self.GIT_LOG_METHOD_PATH, return_value=updated_mock_git_log):
Expand Down Expand Up @@ -347,29 +347,29 @@ def test_last_release_stop_point_is_respected_even_if_tagged_commit_has_no_commi
made on GitHub that isn't subject to the Conventional Commits pre-commit check that we use locally) has no
commit code.
"""
mock_git_log = "\n".join(
mock_git_log = "@@@\n".join(
[
"3e7dc54|OPS: Update mkver.conf to correct pattern|| (HEAD -> develop/issue-wq-521-turbine-prevailing-direction, origin/develop/issue-wq-521-turbine-prevailing-direction)",
"fabd2ab|OPS: Update conventional commit version in python-ci||",
"ef77729|DOC: update readme for new pre-commit ops||",
"b043bc8|OPS: update pull request workflow updated||",
"27dcef0|DEP: Version bump to 0.1.0||",
"358ffd5|OPS: Delete repo issue and pr template||",
"44927c6|OPS: Precommit config updated to use conventional commit||",
"6589a8e|CHO: Add _mocks file with the mocked classes||",
"7cdc980|ENH: Windmap file inputs format now supports space separated headers||",
"741bb8d|ENH: Add prevailing_wind_direction to twine file output schema||",
"27092a4|ENH: Handle edge case when wind dir not in the data and WD_<whatever> in the time series file. Add tests for checking for None prevailing wind directions||",
"6dcdc41|ENH: Mast prevailing wind direction happens when each mast time series file is imported instead of in every turbine||",
"b69e7e1|FEA: Add turbine prevailing wind direction to the result as a property of turbine||",
"0b20f41|CHO: Clear up the comments and add logging||",
"05ec990|CHORE: Remove unused test||",
"e506bb4|ENH: The prevailing wind calculation now de-seasons the count of the bins and determines the prevailing wind direction. Slow as *||",
"9c125ab|FEAT: Added a utility module which calculates the prevailing wind direction given a wind direction time series||",
"3d981b6|Merge pull request #103 from windpioneers/release/0.0.11|| (tag: 0.0.11, origin/main, main)",
"9b88bc6|ENH: Added an assert to bring the coverage up||",
"17d8de1|ENH: Remove a json turbine data importer, we are not going to be importing a turbine from json 'cos we use pcu for that||",
"d242271|DEP: Update pcu version to 0.0.6||",
"3e7dc54|§OPS: Update mkver.conf to correct pattern|§|§ (HEAD -> develop/issue-wq-521-turbine-prevailing-direction, origin/develop/issue-wq-521-turbine-prevailing-direction)",
"fabd2ab|§OPS: Update conventional commit version in python-ci|§|§",
"ef77729|§DOC: update readme for new pre-commit ops|§|§",
"b043bc8|§OPS: update pull request workflow updated|§|§",
"27dcef0|§DEP: Version bump to 0.1.0|§|§",
"358ffd5|§OPS: Delete repo issue and pr template|§|§",
"44927c6|§OPS: Precommit config updated to use conventional commit|§|§",
"6589a8e|§CHO: Add _mocks file with the mocked classes|§|§",
"7cdc980|§ENH: Windmap file inputs format now supports space separated headers|§|§",
"741bb8d|§ENH: Add prevailing_wind_direction to twine file output schema|§|§",
"27092a4|§ENH: Handle edge case when wind dir not in the data and WD_<whatever> in the time series file. Add tests for checking for None prevailing wind directions|§|§",
"6dcdc41|§ENH: Mast prevailing wind direction happens when each mast time series file is imported instead of in every turbine|§|§",
"b69e7e1|§FEA: Add turbine prevailing wind direction to the result as a property of turbine|§|§",
"0b20f41|§CHO: Clear up the comments and add logging|§|§",
"05ec990|§CHORE: Remove unused test|§|§",
"e506bb4|§ENH: The prevailing wind calculation now de-seasons the count of the bins and determines the prevailing wind direction. Slow as *|§|§",
"9c125ab|§FEAT: Added a utility module which calculates the prevailing wind direction given a wind direction time series|§|§",
"3d981b6|§Merge pull request #103 from windpioneers/release/0.0.11|§|§ (tag: 0.0.11, origin/main, main)",
"9b88bc6|§ENH: Added an assert to bring the coverage up|§|§",
"17d8de1|§ENH: Remove a json turbine data importer, we are not going to be importing a turbine from json 'cos we use pcu for that|§|§",
"d242271|§DEP: Update pcu version to 0.0.6|§|§",
]
)

Expand Down Expand Up @@ -421,14 +421,14 @@ def test_last_pull_request_stop_point_is_respected_even_if_tagged_commit_has_no_
commit made on GitHub that isn't subject to the Conventional Commits pre-commit check that we use locally) has
no commit code.
"""
mock_git_log = "\n".join(
mock_git_log = "@@@\n".join(
[
"3e7dc54|OPS: Update mkver.conf to correct pattern|| (HEAD -> develop/issue-wq-521-turbine-prevailing)",
"fabd2ab|OPS: update pull request workflow updated||",
"ef77729|DEP: Version bump to 0.1.0||",
"b043bc8|Merge pull request #3 from octue/feature/add-other-conventional-commit-ci-components||",
"27dcef0|OPS: Precommit config updated to use conventional commit||",
"358ffd5|CHO: Add _mocks file with the mocked classes||",
"3e7dc54|§OPS: Update mkver.conf to correct pattern|§|§ (HEAD -> develop/issue-wq-521-turbine-prevailing)",
"fabd2ab|§OPS: update pull request workflow updated|§|§",
"ef77729|§DEP: Version bump to 0.1.0|§|§",
"b043bc8|§Merge pull request #3 from octue/feature/add-other-conventional-commit-ci-components|§|§",
"27dcef0|§OPS: Precommit config updated to use conventional commit|§|§",
"358ffd5|§CHO: Add _mocks file with the mocked classes|§|§",
]
)

Expand Down Expand Up @@ -457,10 +457,10 @@ def test_commit_message_with_extra_colons_are_still_categorised(self):
"""Test that commit headers containing extra colons in addition to the colon splitting the commit code from the
rest of the commit header are still categorised correctly.
"""
mock_git_log = "\n".join(
mock_git_log = "@@@\n".join(
[
"3e7dc54|OPS: My message: something||",
"fabd2ab|OPS: Update conventional commit version in python-ci||",
"3e7dc54|§OPS: My message: something|§|§",
"fabd2ab|§OPS: Update conventional commit version in python-ci|§|§",
]
)

Expand All @@ -483,11 +483,11 @@ def test_commit_message_with_extra_colons_are_still_categorised(self):

def test_commit_hash_merges_are_ignored(self):
"""Ensure commit messages that are just a merge of a commit ref into another commit ref are ignored."""
mock_git_log = "\n".join(
mock_git_log = "@@@\n".join(
[
"3e7dc54|OPS: My message: something||",
"fabd2ab|Merge ef777290453f11b7519dbd3410b01d34d2e13566 into b043bc85cf558f1706188fafe9676ecd0642ab5a||",
"ef77729|OPS: Update conventional commit version in python-ci||",
"3e7dc54|§OPS: My message: something|§|§",
"fabd2ab|§Merge ef777290453f11b7519dbd3410b01d34d2e13566 into b043bc85cf558f1706188fafe9676ecd0642ab5a|§|§",
"ef77729|§OPS: Update conventional commit version in python-ci|§|§",
]
)

Expand Down Expand Up @@ -533,7 +533,7 @@ def test_single_breaking_change_is_indicated(self):
"""Test that a single breaking change is indicated in the release notes at the top and next to the categorised
commit message.
"""
mock_git_log = "fabd2ab|ENH: Make big change|BREAKING CHANGE: blah blah blah|\n" + MOCK_GIT_LOG
mock_git_log = "fabd2ab|§ENH: Make big change|§BREAKING CHANGE: blah blah blah|§@@@\n" + MOCK_GIT_LOG

with patch(self.GIT_LOG_METHOD_PATH, return_value=mock_git_log):
release_notes = ReleaseNotesCompiler(stop_point="LAST_RELEASE").compile_release_notes()
Expand Down Expand Up @@ -565,9 +565,9 @@ def test_multiple_breaking_changes_are_indicated(self):
commit message.
"""
mock_git_log = (
"fabd2ab|ENH: Make big change|BREAKING-CHANGE: blah blah blah|\n"
"fabd2ab|FIX: Make breaking fix|BREAKING CHANGE: blob|\n"
"fabd2ab|REF: Change interface|BREAKING-CHANGE: glob|\n"
"fabd2ab|§ENH: Make big change|§BREAKING-CHANGE: blah blah blah|§@@@\n"
"fabd2ab|§FIX: Make breaking fix|§BREAKING CHANGE: blob|§@@@\n"
"fabd2ab|§REF: Change interface|§BREAKING-CHANGE: glob|§@@@\n"
) + MOCK_GIT_LOG

with patch(self.GIT_LOG_METHOD_PATH, return_value=mock_git_log):
Expand Down Expand Up @@ -598,3 +598,30 @@ def test_multiple_breaking_changes_are_indicated(self):
]
),
)

def test_commit_messages_with_multi_line_bodies(self):
"""Test that commits with multi-line bodies work with the release notes compiler."""
mock_git_log = "fabd2ab|§ENH: Blah blah|§This is the body.\n Here is another body line|§@@@\n" + MOCK_GIT_LOG

with patch(self.GIT_LOG_METHOD_PATH, return_value=mock_git_log):
release_notes = ReleaseNotesCompiler(stop_point="LAST_RELEASE").compile_release_notes()

expected = "\n".join(
[
"<!--- START AUTOGENERATED NOTES --->",
"## Contents",
"",
"### Enhancements",
"- Blah blah",
"",
"### Refactoring",
"- Merge commit message checker modules",
"",
"### Other",
"- Merge pull request #3 from octue/feature/add-other-conventional-commit-ci-components",
"",
"<!--- END AUTOGENERATED NOTES --->",
]
)

self.assertEqual(release_notes, expected)

0 comments on commit f642d40

Please sign in to comment.