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

fix(changelog): handle custom tag_format in changelog generation #995

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

grahamhar
Copy link

@grahamhar grahamhar commented Feb 28, 2024

When the tag_format does not follow the allowed schemas patterns then changlog generation fails.

Description

I am working in a mono repo with multiple .cz.toml configs (one per component) using tag_format to create tags for each component so they can be released independent of each other. When trying to generate the changelog for each component errors are generated see #845.

I was unsure how to add a test case for this if you have ideas please let me know and I will be happy to add.

Checklist

  • Add test cases to all the changes you introduce
  • Run ./scripts/format and ./scripts/test locally to ensure this change passes linter check and test
  • Test the changes on the local machine manually
  • Update the documentation for the changes

Expected behavior

changelogs will now be generated correctly when running cz bump --changelog

Steps to Test This Pull Request

Copy link

codecov bot commented Feb 28, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.60%. Comparing base (120d514) to head (7400023).
Report is 350 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #995      +/-   ##
==========================================
+ Coverage   97.33%   97.60%   +0.26%     
==========================================
  Files          42       55      +13     
  Lines        2104     2545     +441     
==========================================
+ Hits         2048     2484     +436     
- Misses         56       61       +5     
Flag Coverage Δ
unittests 97.60% <100.00%> (+0.26%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@woile
Copy link
Member

woile commented Mar 1, 2024

You can add tests similar to this:
https://github.com/commitizen-tools/commitizen/blob/master/tests/commands/test_changelog_command.py#L1457-L1491
where you can also configure the toml file and simulate a user

@grahamhar
Copy link
Author

I have taken a stab at the test case.

Comment on lines 173 to 185
latest_tag_version: str = bump.normalize_tag(
changelog_meta.latest_version,
tag_format=self.tag_format,
scheme=self.scheme,
)
start_rev = self._find_incremental_rev(
strip_local_version(latest_tag_version), tags
strip_local_version(changelog_meta.latest_version), tags
)

Copy link

@Lowaiz Lowaiz Mar 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello,
Removing this logic is breaking the search for custom tags (with a tag-format like example-${version}).
The first bump (with --changelog option) will have no problem and it will create the CHANGELOG.md file but any bump afterward will result in an error: commitizen.exceptions.NoRevisionError: No tag found to do an incremental changelog.

This is due to the _find_incremental_rev function that look at similarity between a given tag and the tags list. But without the normalization, it calculates similarity between a stripped tag (without any format so like 0.1.1) and the formatted tag (like example-0.1.0) resulting in a similarity below threshold.

So, except if you have another edge case justifying this removal, I think it should be kept.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thanks for the feedback, I'll put this to a draft status and try to find a solution that doesn't break the current way of working. My gut feeling is it might need an additional flag maybe something like --strict-tag-matching what are your thoughts on taking that approach?

I guess the fact you found that error might mean there is a missing test case, I will try adding that first to replicate the error with my changes so I have something to validate against.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used your version with a revert on the selected part (here), and it seems to works pretty damn well for me, that's why I was asking for the "why" of the change.
I was able to bump and generate for multiple cz.toml file and multiple tag-format (in a monorepo with 3 sub-packages).

The normalizing part was introduced by that PR, exactly fixing the error I got.

I really think that you just need to keep the normalizing part, and we should be OK.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

Thanks for clarifying I misunderstood your point.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test that shows the failure you describe with my approach.

However, when I update my test to then do a second commit/bump then I start to get a failure as the changelog_meta.latest_version for my scenario where the custom tag values comes after the version number is returned as 0.2.0custom (str) which then causes an Invalid version error from the call to scheme in the bump.normalize_tag function.

I found that the change log is written with tag_format in the title for each section but then parse_version_from_title uses the parser from the version schema which means that when the tag format has a suffix rather than a prefix the issue occurs.

I will try to investigate further but would value your thoughts.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there anything that needs clarification for this one? (I guess not?) or should I start reviewing?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want to check the way I have handled the different possible tag_formats in markdown.py seems OK. If it is I will make the appropriate changes to the other formats taking the same approach. Once I have completed that it will be ready for review

Copy link
Member

@Lee-W Lee-W Apr 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any major flaws at first glance, but I will try to take a deeper look this weekend.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry it took a while for me to get this finished off. Ready for review when you have time.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I was not able to review it last week. 😞 Let's see whether I can at least check a portion of it this week. 💪

Copy link
Member

@noirbizarre noirbizarre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems good but:

  • TAG_FORMAT_REGEXS should be factorized and declared once
  • the new ${} syntax should be documented

Comment on lines 34 to 47
TAG_FORMAT_REGEXS = {
"$version": version_regex,
"$major": r"(?P<major>\d+)",
"$minor": r"(?P<minor>\d+)",
"$patch": r"(?P<patch>\d+)",
"$prerelease": r"(?P<prerelease>\w+\d+)?",
"$devrelease": r"(?P<devrelease>\.dev\d+)?",
"${version}": version_regex,
"${major}": r"(?P<major>\d+)",
"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are exactly the same as in changelog:get_version_tag so I suggest to factorize this into a public factory, something like:

TAG_FORMAT_REGEXS = {
    "$major": r"(?P<major>\d+)",
    "$minor": r"(?P<minor>\d+)",
    "$patch": r"(?P<patch>\d+)",
    "$prerelease": r"(?P<prerelease>\w+\d+)?",
    "$devrelease": r"(?P<devrelease>\.dev\d+)?",
    "${version}": version_regex,
    "${major}": r"(?P<major>\d+)",
    "${minor}": r"(?P<minor>\d+)",
    "${patch}": r"(?P<patch>\d+)",
    "${prerelease}": r"(?P<prerelease>\w+\d+)?",
    "${devrelease}": r"(?P<devrelease>\.dev\d+)?",
}

def tag_format_regexps_for(version: Pattern) -> dict[str, Pattern]:
    return {
        "$version": version,
		"${version}": version,
		**TAG_FORMAT_REGEXS
    }

This way:

  • single source of thrust
  • custom format implementations can reuse TAG_FORMAT_REGEXS and tag_format_regexps_for

Comment on lines 34 to 39
"${version}": r"(?P<version>.+)",
"${major}": r"(?P<major>\d+)",
"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a new accepted syntax.
Documentation and conventional commit message should reflect that

"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very similar to the 2 previous factorizable TAG_FORMAT_REGEXS so I think it should be factorized too,

@grahamhar
Copy link
Author

Seems good but:

  • TAG_FORMAT_REGEXS should be factorized and declared once

  • the new ${} syntax should be documented

I think I have implemented the suggestions

@Lee-W Lee-W requested a review from noirbizarre April 21, 2024 06:53
commitizen/changelog_formats/asciidoc.py Outdated Show resolved Hide resolved
commitizen/changelog_formats/asciidoc.py Outdated Show resolved Hide resolved
commitizen/changelog_formats/markdown.py Outdated Show resolved Hide resolved
commitizen/changelog_formats/markdown.py Outdated Show resolved Hide resolved
commitizen/providers/scm_provider.py Show resolved Hide resolved
commitizen/changelog_formats/textile.py Outdated Show resolved Hide resolved
commitizen/changelog_formats/textile.py Outdated Show resolved Hide resolved
commitizen/changelog_formats/textile.py Outdated Show resolved Hide resolved
@@ -133,3 +133,20 @@ class Settings(TypedDict, total=False):
)
change_type_order = ["BREAKING CHANGE", "Feat", "Fix", "Refactor", "Perf"]
bump_message = "bump: version $current_version → $new_version"


def get_tag_regexes(version_regex: str) -> dict[str | Any, str | Any]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will the key return Any?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

Copy link
Member

@Lee-W Lee-W left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for taking so long to review. Most of my comments are just nitpicks. It would be great if we could fix them, but I'm good with it if we cannot. As this is somewhat a larger change, I think it would be better if we can have @woile and @noirbizarre take a look as well 🙂

"${minor}": r"(?P<minor>\d+)",
"${patch}": r"(?P<patch>\d+)",
"${prerelease}": r"(?P<prerelease>\w+\d+)?",
"${devrelease}": r"(?P<devrelease>\.dev\d+)?",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: not sure we can deduplicate $.* and ${.*} 🤔 Non-blocker for this PR

commitizen/providers/scm_provider.py Show resolved Hide resolved
docs/bump.md Outdated Show resolved Hide resolved
docs/tutorials/monorepo_guidance.md Outdated Show resolved Hide resolved
docs/tutorials/monorepo_guidance.md Outdated Show resolved Hide resolved
tests/commands/test_changelog_command.py Show resolved Hide resolved
tests/commands/test_changelog_command.py Show resolved Hide resolved
tests/test_changelog_format_asciidoc.py Show resolved Hide resolved
Comment on lines +174 to +193
pytest.param("${version}-example", "1.0.0-example", "1.0.0"),
pytest.param("${version}example", "1.0.0example", "1.0.0"),
pytest.param("example${version}", "example1.0.0", "1.0.0"),
pytest.param("example-${version}", "example-1.0.0", "1.0.0"),
pytest.param("example-${major}-${minor}-${patch}", "example-1-0-0", "1.0.0"),
pytest.param("example-${major}-${minor}", "example-1-0-0", None),
pytest.param(
"${major}-${minor}-${patch}-${prerelease}-example",
"1-0-0-rc1-example",
"1.0.0-rc1",
),
pytest.param(
"${major}-${minor}-${patch}-${prerelease}-example",
"1-0-0-a1-example",
"1.0.0-a1",
),
pytest.param(
"${major}-${minor}-${patch}-${prerelease}${devrelease}-example",
"1-0-0-a1.dev1-example",
"1.0.0-a1.dev1",
),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel like we can make it reuseable somewhere 🤔

@Lee-W Lee-W assigned Lee-W, noirbizarre and woile and unassigned Lee-W May 20, 2024
@Lee-W
Copy link
Member

Lee-W commented May 20, 2024

We probably need to resolve the conflict as well 👀 . We're now using screenshots as part of our doc. you can generate the latest screenshot through poetry run python scripts/gen_cli_help_screenshots.py

.cz.toml
```

Each component will have its own changelog, commits will need to use scopes so only relevant commits are included in the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if you are not using conventional commits and you don't have scopes?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I guess I made an assumption here that everyone uses them. Should I just reword this to reflect that assumption?

@Lee-W Lee-W unassigned woile May 22, 2024
@grahamhar
Copy link
Author

Sorry for taking so long to review. Most of my comments are just nitpicks. It would be great if we could fix them, but I'm good with it if we cannot. As this is somewhat a larger change, I think it would be better if we can have @woile and @noirbizarre take a look as well 🙂

No worries on the time taken, I will address all the points but might take me a week due to other commitments

@grahamhar
Copy link
Author

We probably need to resolve the conflict as well 👀 . We're now using screenshots as part of our doc. you can generate the latest screenshot through poetry run python scripts/gen_cli_help_screenshots.py

Hi @Lee-W,

I have attempted to address the comments, please validate when you have time.

When attempting to run the gen_cli_help_screenshots.py script requested above I just get this output which appears to just be given usage errors of cz

image

I'm not sure if you are already aware, but with the push hook in place I got an error for a commit not in my PR

commit validation: failed!
please enter a commit message in the commitizen format.
commit "37522866e4788deb12b2ef1c426662400b0ebac8": "docs(cli/screenshots) update CLI screenshots"
pattern: (?s)(build|ci|docs|feat|fix|perf|refactor|style|test|chore|revert|bump)(\(\S+\))?!?:( [^\n\r]+)((\n\n.*)|(\s*))?$

@Lee-W
Copy link
Member

Lee-W commented May 30, 2024

When attempting to run the gen_cli_help_screenshots.py script requested above I just get this output which appears to just be given usage errors of cz

The screenshot doesn't look like an error to me 🤔 so basically, this is a command we use to generate the latest cz help message. so if the screenshots are generated, I think we are good 🙂

I'm not sure if you are already aware, but with the push hook in place I got an error for a commit not in my PR

Even if you try with rebase? There was one commit with an ill-formatted message. But our hook should only check the commits in this branch, I think 🤔 It Looks like the CI is passing now. Is this still an issue?

@grahamhar
Copy link
Author

When attempting to run the gen_cli_help_screenshots.py script requested above I just get this output which appears to just be given usage errors of cz

The screenshot doesn't look like an error to me 🤔 so basically, this is a command we use to generate the latest cz help message. so if the screenshots are generated, I think we are good 🙂

I'm not sure if you are already aware, but with the push hook in place I got an error for a commit not in my PR

Even if you try with rebase? There was one commit with an ill-formatted message. But our hook should only check the commits in this branch, I think 🤔 It Looks like the CI is passing now. Is this still an issue?

Maybe my PR didn't need any changes to the generated images, possibly just confusion on my side.

I have just made sure my fork is updated and as I have no changes I just tried running the hook

 pre-commit run --hook-stage pre-push                      
Check hooks apply to the repository.......................(no files to check)Skipped
Check for useless excludes................................(no files to check)Skipped
check vcs permalinks......................................(no files to check)Skipped
fix end of files..........................................(no files to check)Skipped
trim trailing whitespace..................................(no files to check)Skipped
debug statements (python).................................(no files to check)Skipped
don't commit to branch........................................................Passed
check for merge conflicts.................................(no files to check)Skipped
check toml................................................(no files to check)Skipped
check yaml................................................(no files to check)Skipped
detect private key........................................(no files to check)Skipped
blacken-docs..............................................(no files to check)Skipped
Run codespell to check for common misspellings in files...(no files to check)Skipped
commitizen check branch.......................................................Failed
- hook id: commitizen-branch
- exit code: 23

fatal: ambiguous argument 'origin/HEAD..HEAD': unknown revision or path not in the working tree.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'



format....................................................(no files to check)Skipped
linter and test...........................................(no files to check)Skipped

Doing some further investigation my fork doesn't have origin/HEAD set. After running

git remote set-head origin master

The hook now works :)

@Lee-W
Copy link
Member

Lee-W commented May 31, 2024

The hook now works :)

The hook now works :)

Niiiiiiice! I will probably not be around for half a month, but I will try to take a look after I'm back from traveling. Thanks for actively helping out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants