po: Update German man pages translation
[dpkg.git] / gen-changelog
blob58637e0de0cc1d89985b1aca0c97045ffc9e9aee
1 #!/usr/bin/perl
3 # gen-changelog
5 # Copyright © 2020 Guillem Jover <guillem@debian.org>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 use strict;
22 use warnings;
24 use lib 'scripts';
26 use List::Util qw(uniq);
27 use Text::Wrap;
28 use Dpkg::IPC;
29 use Dpkg::Index;
31 sub gen_l10n_group
33 my $commit = shift;
34 my $title = $commit->{Title} =~ s/^po: //r;
36 if ($title =~ m/^Regenerate/) {
37 # Skip.
38 return;
39 } elsif ($title =~ /^(Update|Add) ([A-Za-z ]+) (program|script|dselect|man page)s? translations?/) {
40 my ($action, $lang, $part) = ($1, $2, $3);
42 $part .= 's' if $part ne 'dselect';
43 $commit->{Title} = "$lang ($commit->{Author})";
45 return ('l10n', "$action $part translations");
46 } else {
47 return ('main', $commit->{Committer});
51 my @sections = qw(
52 main
53 arch
54 port
55 perl-mod
56 doc
57 code-int
58 build-sys
59 pkg
60 test
61 l10n
64 my %sections = (
65 arch => {
66 title => 'Architecture support',
67 match => qr/^arch: /,
69 port => {
70 title => 'Portability',
71 type => 'porting',
73 'perl-mod' => {
74 title => 'Perl modules',
75 match => qr/^Dpkg.*[,:] /,
76 keep => 1,
78 doc => {
79 title => 'Documentation',
80 match => qr/^(?:doc|man)[,:] /,
81 keep => 1,
83 'code-int' => {
84 title => 'Code internals',
85 type => 'internal',
87 'build-sys' => {
88 title => 'Build system',
89 match => qr/^build: /,
91 pkg => {
92 title => 'Packaging',
93 match => qr/^debian: /,
95 test => {
96 title => 'Test suite',
97 match => qr/^(?:test|t): /,
99 l10n => {
100 title => 'Localization',
101 group => \&gen_l10n_group,
102 match => qr/^po: /,
106 my @metafields = qw(
107 Thanks-to
108 Co-Author
109 Based-on-patch-by
110 Improved-by
111 Prompted-by
112 Reported-by
113 Required-by
114 Analysis-by
115 Requested-by
116 Suggested-by
117 Spotted-by
118 Naming-by
122 my %metafield = (
123 'Co-Author' => 'Co-authored by',
124 'Based-on-patch-by' => 'Based on a patch by',
125 'Improved-by' => 'Improved by',
126 'Prompted-by' => 'Prompted by',
127 'Reported-by' => 'Reported by',
128 'Required-by' => 'Required by',
129 'Analysis-by' => 'Anaylisis by',
130 'Requested-by' => 'Requested by',
131 'Suggested-by' => 'Suggested by',
132 'Spotted-by' => 'Spotted by',
133 'Naming-by' => 'Naming by',
134 'Thanks-to' => 'Thanks to',
135 'Ref' => 'See',
138 my %mappings = (
139 'u-a' => 'update-alternatives',
140 's-s-d' => 'start-stop-daemon',
141 'dpkg-m-h' => 'dpkg-maintscript-helper',
144 my $log_format =
145 'Commit: %H%n' .
146 'Author: %aN%n' .
147 'AuthorEmail: %aE%n' .
148 'Committer: %cN%n' .
149 'CommitterEmail: %cE%n' .
150 'Title: %s%n' .
151 '%(trailers:only,unfold)%N';
153 my $tag_prev = qx(git describe --abbrev=0);
154 chomp $tag_prev;
156 my $fh_gitlog;
158 spawn(
159 exec => [
160 qw(git log --first-parent), "--format=tformat:$log_format", "$tag_prev.."
162 to_pipe => \$fh_gitlog,
165 my $log = Dpkg::Index->new(
166 get_key_func => sub { return $_[0]->{Commit} },
167 item_opts => {
168 allow_duplicate => 1,
171 $log->parse($fh_gitlog, 'git log');
173 my %entries;
174 my %groups;
175 my (@groups, @groups_l10n);
177 # Analyze the commits and select which group and section to place them in.
178 foreach my $id (reverse $log->get_keys()) {
179 my $commit = $log->get_by_key($id);
180 my $title = $commit->{Title};
181 my $group = $commit->{Committer};
182 my $changelog = $commit->{'Changelog'};
183 my $sectmatch = 'main';
184 my $grouptype = 'main';
186 # Skip irrelevant commits.
187 if ($title =~ m/^(Bump version to|Release) /) {
188 next;
191 if (defined $changelog) {
192 # Skip silent commits.
193 next if $changelog =~ m/(?:silent|skip|ignore)/;
195 # Include the entire commit body for verbose commits.
196 if ($changelog =~ m/(?:verbose|full)/) {
197 my $body = qx(git show -s --pretty=tformat:%b $id);
198 $commit->{title} .= "\n$body";
202 # Decide into what section the commit should go.
203 foreach my $sectname (keys %sections) {
204 my $section = $sections{$sectname};
206 if ((exists $section->{match} and $title =~ m/$section->{match}/) or
207 (exists $section->{type} and defined $changelog and
208 $changelog =~ m/$section->{type}/)) {
209 $sectmatch = $sectname;
210 last;
214 # Programmatically fix the title, and select the group.
215 if (exists $sections{$sectmatch}->{group}) {
216 ($grouptype, $group) = $sections{$sectmatch}->{group}->($commit);
217 next unless defined $group;
220 # Add the group entries in order, with l10n ones at the end.
221 $groups{$group} = $grouptype;
222 if (not exists $entries{$group}) {
223 if ($grouptype eq 'l10n') {
224 push @groups_l10n, $group;
225 } else {
226 push @groups, $group;
230 push @{$entries{$group}{$sectmatch}}, $commit;
233 # Go over the groups and their sections and format them.
234 foreach my $groupname (@groups, sort @groups_l10n) {
235 my $grouptype = $groups{$groupname};
237 print "\n";
238 print " [ $groupname ]\n";
240 foreach my $sectname (@sections) {
241 my $section = $sections{$sectname};
243 next unless exists $entries{$groupname}{$sectname};
244 next if @{$entries{$groupname}{$sectname}} == 0;
246 if ($sectname ne 'main' and $grouptype ne 'l10n') {
247 print " * $sections{$sectname}->{title}:\n";
249 my @entries;
250 foreach my $commit (@{$entries{$groupname}{$sectname}}) {
251 my $title = $commit->{Title} =~ s/\.$//r . '.';
253 # Remove the title prefix if needed.
254 if (exists $section->{match} and not exists $section->{keep}) {
255 $title =~ s/$section->{match}//;
258 # Metafields.
259 if ($grouptype ne 'l10n' and $commit->{Author} ne $commit->{Committer}) {
260 $commit->{'Thanks-to'} = "$commit->{Author} <$commit->{AuthorEmail}>";
262 foreach my $metafield (@metafields) {
263 next unless exists $commit->{$metafield};
265 $title .= "\n$metafield{$metafield} $commit->{$metafield}.";
267 # Handle the Closes metafield last.
268 if (exists $commit->{Closes}) {
269 $title .= " Closes: $commit->{Closes}";
272 # Handle mappings.
273 foreach my $mapping (keys %mappings) {
274 $title =~ s/$mapping/$mappings{$mapping}/g;
277 # Select prefix formatting.
278 my ($entry_tab, $body_tab);
279 if ($sectname eq 'main' or $grouptype eq 'l10n') {
280 $entry_tab = ' * ';
281 $body_tab = ' ';
282 } else {
283 $entry_tab = ' - ';
284 $body_tab = ' ';
287 local $Text::Wrap::columns = 80;
288 local $Text::Wrap::unexpand = 0;
289 local $Text::Wrap::huge = 'overflow';
290 local $Text::Wrap::break = qr/(?<!Closes:)\s/;
291 push @entries, wrap($entry_tab, $body_tab, $title) . "\n";
294 if ($grouptype eq 'l10n') {
295 print uniq(sort @entries);
296 } else {
297 print @entries;