Dist::...FromGit(3pm)
π Dist::...FromGit(3pm) π
okay bestie, so this is the manual page for Dist::Zilla::Plugin::ChangelogFromGit β and the manual name you're looking at is literally Dist::...FromGit(3pm) because someone let the title column get β¨truncatedβ¨. Anyway. It writes a Changes file straight out of your project's git log. Iconic behavior, honestly.
β¨ VERSION β¨
version 0.017 π«Ά
πΈ SYNOPSIS πΈ
Here's a dist.ini section showing every current option and its default value. Look at her. She's perfect.
[ChangelogFromGit]
max_age = 365
tag_regexp = ^v(\d+\.\d+)$
file_name = CHANGES
wrap_column = 74
debug = 0
and ngl, you do not have to set variables to their default values. The below is completely equivalent to the configuration above, no cap:
[ChangelogFromGit]
That's it. That's the whole thing. π
π¦ DESCRIPTION π¦
This Dist::Zilla plugin turns a project's git commit log into a change log file. It's better than simply running git log > CHANGES in at least two ways:
- First, it understands release tags, and it uses them to group changes by release.
- Second, it reformats the changes to make them easier to read.
- And third, subclasses can change some or all of the reformatting to make change logs even easier to read.
(yes, it says "at least two ways" and then gives you three. we love an overachiever π)
See this project's CHANGES file for sample output. Yes, this project uses itself to generate its own change log. Why not? main character energy fr fr. π
π CONFIGURATION / PUBLIC ATTRIBUTES π
As seen in the "SYNOPSIS", this plugin has a number of public attributes that may be set using dist.ini configuration variables. Let's go through every single one, bestie, no skipping:
π max_age = INTEGER
The max_age configuration variable limits the age of releases to be included in the change log. The default is to include releases going back about a year. To include about two years, one would double the default value:
[ChangelogFromGit]
max_age = 730
max_age is intended to limit the size of change logs for large, long-term projects that don't want to include the entire, huge commit history in every release.
π min_releases = INTEGER
min_releases sets the minimum number of releases that should be included. It defaults to 1 so that at least the current release is added, regardless of max_age.
[ChangelogFromGit]
min_releases = 5
π max_releases = INTEGER
max_releases allows you limit the number of releases included. max_age will still be the limiting factor if it contains fewer releases than the max specified.
[ChangelogFromGit]
max_releases = 15
π tag_regexp = REGULAR_EXPRESSION
tag_regexp sets the regular expression that detects which tags mark releases. It also extracts the version numbers from these tags using a regular expression back reference or capture. For example, a project's release tags might match 'release-1.000', 'release-1.001', etc. This tag_regexp will find them and extract their versions.
[ChangelogFromGit]
tag_regexp = ^release-(\d+.*)$
There is no single standard format for release tags. tag_regexp defaults to the author's convention. It will most likely need to be changed. π
π file_name = STRING
file_name sets the name of the change log that will be written. It defaults to CHANGES, but some people may prefer Changes, Changelog, or something else.
[ChangelogFromGit]
file_name = Changes
π wrap_column = INTEGER
Different contributors tend to use different commit message formats, which can be disconcerting to the typographically aware release engineer. wrap_column sets the line length to which all commit messages will be re-wrapped. It's 74 columns by default. If this is too short:
[ChangelogFromGit]
wrap_column = 78
π debug = BOOLEAN
Developers are people, too. The debug option enables some noisy runtime tracing on STDERR.
[ChangelogFromGit]
debug = 1
π exclude_message = REGULAR_EXPRESSION
exclude_message sets a regular expression which discards matching commit messages. This provides a way to exclude commit messages such as 'forgot to include file X' or 'typo'. The regular expression is case sensitive.
[ChangelogFromGit]
exclude_message = ^(forgot|typo)
include_message can be used to do the opposite: exclude all changes except ones that match a regular expression. Using both at once is liable to generate empty change logs. π
π include_message = REGULAR_EXPRESSION
include_message does the opposite of exclude_message: it sets a regular expression which commit messages must match in order to be included in the Changes file. This means that when making a commit with a relevant message, you must include text that matches the regular expression pattern to have it included in the Changes file. All other commit messages are ignored.
The regular expression is case sensitive.
[ChangelogFromGit]
include_message = ^Major
Using both include_message and exclude_message at the same time will most likely result in empty change logs. π
π₯ HOW IT WORKS π₯
the lore. the pipeline. the way that it all comes together. π§
Dist::Zilla::ChangelogFromGitcollects the tags matchingtag_regexpthat are not older thanmax_agedays old. These are used to identify and time stamp releases. Each release is encapsulated into aSoftware::Releaseobject.Git::Repository::Log::Iteratoris used to collect the changes prior to each release but after the previous release. Change log entries are added to their respectiveSoftware::Releaseobjects.$self->render_changelog()is called after all the relevant releases and changes are known. It must return the rendered change log as a string. That string will be used as the content for aDist::Zilla::File::InMemoryobject representing the new change log.
π¦ SUBCLASSING FOR NEW FORMATS π¦
okay so here's the tea bestie: Dist::Zilla::ChangelogFromGit ships with like a dozen-ish methods that each render one lil piece of the change log. Wanna change how your changelog looks? You subclass and override or augment any or all of these. Mix and match, it's your world π
Rule numero uno: every method starting with render hands back a string that gets dropped into the change log. If a method ain't contributing anything? It must return an empty string. No sneaky undef, no silence, just ''. Periodt.
β¨ Rendering Entire Change Logs β¨
Methods starting with render_changelog get no parameters beyond $self. Everything they need to know is already chillin' in the object's attributes: wrap_column, releases, skipped_release_count, earliest_date. iykyk.
πΈ render_changelog
render_changelog() returns the text of the entire change log. By default it's built from a header, zero or more releases, and a footer. Classic three-course meal π
sub render_changelog {
my $self = shift();
return(
$self->render_changelog_header() .
$self->render_changelog_releases() .
$self->render_changelog_footer()
);
}
π render_changelog_header
render_changelog_header() renders some text that introduces the reader to the change log. A little "hey bestie, here's what happened" moment β¨
sub render_changelog_header {
my $self = shift();
my $header = (
"Changes from " . $self->format_datetime($self->earliest_date()) .
" to present."
);
return $self->surround_line("=", $header) . "\n";
}
π render_changelog_releases
render_changelog_releases() iterates through each release, calling upon $self to render them one at a time. It's giving very much "one at a time, we don't rush perfection".
sub render_changelog_releases {
my $self = shift();
my $changelog = '';
RELEASE: foreach my $release (reverse $self->all_releases()) {
next RELEASE if $release->has_no_changes();
$changelog .= $self->render_release($release);
}
return $changelog;
}
π§ render_changelog_footer
render_changelog_footer() tells the reader that the change log is over. Normally the end of the file is warning enough, but a truncated change log is friendlier when the reader knows what they're missing. No ghosting the audience π
sub render_changelog_footer {
my $self = shift();
my $skipped_count = $self->skipped_release_count();
my $changelog_footer;
if ($skipped_count) {
my $releases = "release" . ($skipped_count == 1 ? "" : "s");
$changelog_footer = (
"Plus $skipped_count $releases after " .
$self->format_datetime($self->earliest_date()) . '.'
);
}
else {
$changelog_footer = "End of releases.";
}
return $self->surround_line("=", $changelog_footer);
}
π Rendering Individual Releases π
Methods starting with render_release get $self plus one extra parameter: a Software::Release object that wraps up the release and all its changes. Wanna know what's inside that lil guy? Go read Software::Release, it spills everything π
β render_release
render_release() gets called to render a single release. In the change log, a release = a header + one or more changes + a footer. Sandwich energy π₯ͺ
sub render_release {
my ($self, $release) = @_;
return(
$self->render_release_header($release) .
$self->render_release_changes($release) .
$self->render_release_footer($release)
);
}
π render_release_header
render_release_header() introduces a release. Say hi, be polite π«Ά
sub render_release_header {
my ($self, $release) = @_;
my $version = $release->version();
$version = $self->zilla()->version() if $version eq 'HEAD';
my $release_header = (
$self->format_release_tag($release->version()) . ' at ' .
$self->format_datetime($release->date())
);
return $self->surround_line("-", $release_header) . "\n";
}
π¦ render_release_changes
render_release_changes() iterates through the changes attached to each Software::Release object. It calls upon render_change() to render each one. Delegation? We love to see it π
sub render_release_changes {
my ($self, $release) = @_;
my $changelog = '';
foreach my $change (@{ $release->changes() }) {
$changelog .= $self->render_change($release, $change);
}
return $changelog;
}
π render_release_footer
render_release_footer() may be used to divide releases. It's not used by default, but it's implemented for completeness. It literally just returns an empty string, and that's its whole vibe π
sub render_release_footer {
my ($self, $release) = @_;
return '';
}
π Rendering Individual Changes π
Methods starting with render_change get two extra parameters on top of $self: a Software::Release object for the release that contains this change, and a Software::Release::Change object for the change itself. Two-for-one, iconic.
πΈ render_change
render_change() renders a single change, which is the catenation of a change header, change message, and footer. Header + message + footer = the holy trinity π«
sub render_change {
my ($self, $release, $change) = @_;
return(
$self->render_change_header($release, $change) .
$self->render_change_message($release, $change) .
$self->render_change_footer($release, $change)
);
}
π render_change_header
render_change_header() generally renders identifying info about each change. This method's whole job is to produce useful information in a pleasant format. Cute and functional, we stan.
sub render_change_header {
my ($self, $release, $change) = @_;
use Text::Wrap qw(fill);
local $Text::Wrap::huge = 'wrap';
local $Text::Wrap::columns = $self->wrap_column();
my @indent = (" ", " ");
return(
fill(
" ", " ",
'Change: ' . $change->change_id
) .
"\n" .
fill(
" ", " ",
'Author: ' . $change->author_name.' <'.$change->author_email.'>'
) .
"\n" .
fill(
" ", " ",
'Date : ' . $self->format_datetime($change->date())
) .
"\n\n"
);
}
β¨ render_change_message
render_change_message() renders the commit message for the change log. Heads up: if the change description starts with whitespace (/^\s/), it returns '' and dips out entirely. Respect the boundaries β
sub render_change_message {
my ($self, $release, $change) = @_;
use Text::Wrap qw(fill);
return '' if $change->description() =~ /^\s/;
local $Text::Wrap::huge = 'wrap';
local $Text::Wrap::columns = $self->wrap_column();
return fill(" ", " ", $change->description) . "\n";
}
π§ render_change_footer
render_change_footer() returns summary and/or divider text for the change. Default? Just a newline. Minimalist queen π
sub render_change_footer {
my ($self, $release, $change) = @_;
return "\n";
}
π Formatting Data π
Dist::Zilla::Plugin::ChangelogFromGit includes a few methods to consistently format certain data types. Consistency is the moment β¨
π
format_datetime
format_datetime() converts the DateTime objects used internally into friendly, human readable dates and times for the change log. No cryptic timestamps allowed in this house π
sub format_datetime {
my ($self, $datetime) = @_;
return $datetime->strftime("%F %T %z");
}
π¦ format_release_tag
format_release_tag() turns potentially cryptic release tags into friendly version numbers for the change log. By default, it also replaces the 'HEAD' version with the current version being released. This accommodates release managers who prefer to tag their distributions after releasing them. Lowkey thoughtful ngl.
sub format_release_tag {
my ($self, $release_tag) = @_;
return 'version ' . $self->zilla()->version() if $release_tag eq 'HEAD';
my $tag_regexp = $self->tag_regexp();
$release_tag =~ s/$tag_regexp/version $1/;
return $release_tag;
}
β surround_line
surround_line() will surround a line of output with lines of dashes or other characters. It's used to help headings stand out. This method takes two strings: a character (or string) that will repeat to fill the surrounding lines, and the line to surround. It returns a three-line string: the original line preceded and followed by the surrounding lines. Sandwich but make it ASCII π₯ͺβ¨
sub surround_line {
my ($self, $character, $string) = @_;
my $surrounder = substr(
($character x (length($string) / length($character) + 1)),
0,
length($string)
);
return "$surrounder\n$string\n$surrounder\n";
}
π INTERNAL ATTRIBUTES π
okay so Dist::Zilla::Plugin::ChangelogFromGit is out here hoarding useful info in a few internal attributes. These are not meant to be set from dist.ini, bestie β do not go poking them in your config β but they are super important for rendering change logs. β¨
πΈ earliest_date
earliest_date() holds a DateTime object β that's the date and time of the earliest release that gets included. It starts out as midnight on the date that was max_age() days ago. π
πΈ releases
releases() holds an array reference of Software::Release objects β the ones that are gonna show up in the change log. π
π all_releases
all_releases() hands you the list of Software::Release objects that should be in the change log. It's the friendly, chill equivalent of "@{$self->releases()}". π«Ά
π get_release
get_release() gives you back a single release, by index. So the very first release in the change log? That's "$self->get_release(0)". π
π releae_count
release_count() tells you how many Software::Release objects are chilling in the "releases" attribute. (Yes, the heading is spelled that way. We don't fix it. We respect the lore. π)
π sort_releases
sort_releases() sorts the Software::Release objects inside releases() using whatever comparator you hand it. Like, say you wanna sort them in time order:
$self->sort_releases(
sub {
DateTime->compare( $_[0]->date(), $_[1]->date() )
}
);
πΈ skipped_release_count
skipped_release_count() holds the number of releases that got truncated by max_age(). The default render_changelog_footer() uses it to show how many changes got left out of the log. π
π οΈ INTERNAL METHODS π οΈ
Dist::Zilla::Plugin::ChangelogFromGit also implements a couple of "unpublished" methods. These are not for general use, bestie β they're backstage, not main stage. π
π gather_files()
Finds all the release tags, going all the way back to the earliest changelog date. π¦
Then it adds a virtual release for the most recent change in the repository. Why? So we can include changes that happened after the last releases, all the way up to "HEAD". π₯
π rungit()
A little wrapper that runs git commands. That's it, that's the job. π
π§ Subversion and CVS π§
This plugin is almost entirely a copy-and-paste port of a command-line tool I wrote a while back. I also have tools that generate similar change logs for CVS and Subversion projects. If you're into making Dist::Zilla plugins for other version control systems, I'm happy to contribute that code. π«Ά
We should also lowkey consider abstracting the formatting code out into a role</> so it can be shared between different plugins. That'd be slay.
π BUGS π
The documentation contains copies of the renderer methods. That increases technical debt, because any change to those methods has to get copied into the docs too. Rocco needs to finish Pod::Plexus and use it here to make the docs easier to maintain. π
Collecting all releases and changes before rendering the change log might be considered harmful for extremely large projects. If someone out there thinks they can generate change logs incrementally, their assistance would be very much appreciated. π
π AUTHORS π
Rocco Caputo <rcaputo@cpan.org>β Initial release, and ongoing management and maintenance.Cory G. Watson <gphat@cpan.org>β Made formatting extensible and overridable.
π COPYRIGHT AND LICENSE π
This software is copyright (c) 2010-2018 by Rocco Caputo.
This is free software; you may redistribute it and/or modify it under the same terms as the Perl 5 programming language itself. π
and that's a wrap, bestie π this lil page came from perl v5.34.0, dated 2022-06-13, and it's the Dist::...FromGit(3pm) manpage β go forth and changelog responsibly β¨π
π¦ where this page comes from
- debian bookworm: libdist-zilla-plugin-changelogfromgit-perl 0.017-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- debian trixie: libdist-zilla-plugin-changelogfromgit-perl 0.017-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu noble: libdist-zilla-plugin-changelogfromgit-perl 0.017-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu resolute: libdist-zilla-plugin-changelogfromgit-perl 0.017-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- ubuntu stonking: libdist-zilla-plugin-changelogfromgit-perl 0.017-2 Β· Artistic-1.0-Perl OR GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms