Changelog.pm 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. #
  2. # Dpkg::Changelog
  3. #
  4. # Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de>
  5. #
  6. # This program is free software; you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation; either version 2 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program; if not, write to the Free Software
  18. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  19. #
  20. =head1 NAME
  21. Dpkg::Changelog
  22. =head1 DESCRIPTION
  23. to be written
  24. =head2 Functions
  25. =cut
  26. package Dpkg::Changelog;
  27. use strict;
  28. use warnings;
  29. use English;
  30. use Dpkg;
  31. use Dpkg::Gettext;
  32. use Dpkg::ErrorHandling qw(warning report syserr subprocerr);
  33. use Dpkg::Cdata;
  34. use Dpkg::Fields;
  35. use base qw(Exporter);
  36. our %EXPORT_TAGS = ( 'util' => [ qw(
  37. find_closes
  38. data2rfc822
  39. data2rfc822_mult
  40. get_dpkg_changes
  41. parse_changelog
  42. ) ] );
  43. our @EXPORT_OK = @{$EXPORT_TAGS{util}};
  44. =pod
  45. =head3 init
  46. Creates a new object instance. Takes a reference to a hash as
  47. optional argument, which is interpreted as configuration options.
  48. There are currently no supported general configuration options, but
  49. see the other methods for more specific configuration options which
  50. can also specified to C<init>.
  51. If C<infile> or C<instring> are specified (see L<parse>), C<parse()>
  52. is called from C<init>. If a fatal error is encountered during parsing
  53. (e.g. the file can't be opened), C<init> will not return a
  54. valid object but C<undef>!
  55. =cut
  56. sub init {
  57. my $classname = shift;
  58. my $config = shift || {};
  59. my $self = {};
  60. bless( $self, $classname );
  61. $config->{verbose} = 1 if $config->{debug};
  62. $self->{config} = $config;
  63. $self->reset_parse_errors;
  64. if ($self->{config}{infile} || $self->{config}{instring}) {
  65. defined($self->parse) or return undef;
  66. }
  67. return $self;
  68. }
  69. =pod
  70. =head3 reset_parse_errors
  71. Can be used to delete all information about errors ocurred during
  72. previous L<parse> runs. Note that C<parse()> also calls this method.
  73. =cut
  74. sub reset_parse_errors {
  75. my ($self) = @_;
  76. $self->{errors}{parser} = [];
  77. }
  78. sub _do_parse_error {
  79. my ($self, $file, $line_nr, $error, $line) = @_;
  80. shift;
  81. push @{$self->{errors}{parser}}, [ @_ ];
  82. unless ($self->{config}{quiet}) {
  83. if ($line) {
  84. warning("%20s(l$NR): $error\nLINE: $line", $file);
  85. } else {
  86. warning("%20s(l$NR): $error", $file);
  87. }
  88. }
  89. }
  90. =pod
  91. =head3 get_parse_errors
  92. Returns all error messages from the last L<parse> run.
  93. If called in scalar context returns a human readable
  94. string representation. If called in list context returns
  95. an array of arrays. Each of these arrays contains
  96. =over 4
  97. =item 1.
  98. the filename of the parsed file or C<String> if a string was
  99. parsed directly
  100. =item 2.
  101. the line number where the error occurred
  102. =item 3.
  103. an error description
  104. =item 4.
  105. the original line
  106. =back
  107. NOTE: This format isn't stable yet and may change in later versions
  108. of this module.
  109. =cut
  110. sub get_parse_errors {
  111. my ($self) = @_;
  112. if (wantarray) {
  113. return @{$self->{errors}{parser}};
  114. } else {
  115. my $res = "";
  116. foreach my $e (@{$self->{errors}{parser}}) {
  117. if ($e->[3]) {
  118. $res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e );
  119. } else {
  120. $res .= report(_g('warning'),_g("%s(l%s): %s"), @$e );
  121. }
  122. }
  123. return $res;
  124. }
  125. }
  126. sub _do_fatal_error {
  127. my ($self, $msg, @msg) = @_;
  128. $self->{errors}{fatal} = report(_g('fatal error'), $msg, @msg);
  129. warning($msg, @msg) unless $self->{config}{quiet};
  130. }
  131. =pod
  132. =head3 get_error
  133. Get the last non-parser error (e.g. the file to parse couldn't be opened).
  134. =cut
  135. sub get_error {
  136. my ($self) = @_;
  137. return $self->{errors}{fatal};
  138. }
  139. =pod
  140. =head3 data
  141. C<data> returns an array (if called in list context) or a reference
  142. to an array of Dpkg::Changelog::Entry objects which each
  143. represent one entry of the changelog.
  144. This method supports the common output options described in
  145. section L<"COMMON OUTPUT OPTIONS">.
  146. =cut
  147. sub data {
  148. my ($self, $config) = @_;
  149. my $data = $self->{data};
  150. if ($config) {
  151. $self->{config}{DATA} = $config if $config;
  152. $data = $self->_data_range( $config ) or return undef;
  153. }
  154. return @$data if wantarray;
  155. return $data;
  156. }
  157. sub __sanity_check_range {
  158. my ( $data, $from, $to, $since, $until, $start, $end ) = @_;
  159. if (($$start || $$end) && ($$from || $$since || $$to || $$until)) {
  160. warning(_g( "you can't combine 'count' or 'offset' with any other range option" ));
  161. $$from = $$since = $$to = $$until = '';
  162. }
  163. if ($$from && $$since) {
  164. warning(_g( "you can only specify one of 'from' and 'since', using 'since'" ));
  165. $$from = '';
  166. }
  167. if ($$to && $$until) {
  168. warning(_g( "you can only specify one of 'to' and 'until', using 'until'" ));
  169. $$to = '';
  170. }
  171. if ($$since && ($data->[0]{Version} eq $$since)) {
  172. warning(_g( "'since' option specifies most recent version, ignoring" ));
  173. $$since = '';
  174. }
  175. if ($$until && ($data->[$#{$data}]{Version} eq $$until)) {
  176. warning(_g( "'until' option specifies oldest version, ignoring" ));
  177. $$until = '';
  178. }
  179. $$start = 0 if $$start < 0;
  180. return if $$start > $#$data;
  181. $$end = $#$data if $$end > $#$data;
  182. return if $$end < 0;
  183. $$end = $$start if $$end < $$start;
  184. #TODO: compare versions
  185. return 1;
  186. }
  187. sub _data_range {
  188. my ($self, $config) = @_;
  189. my $data = $self->data or return undef;
  190. return [ @$data ] if $config->{all};
  191. my $since = $config->{since} || '';
  192. my $until = $config->{until} || '';
  193. my $from = $config->{from} || '';
  194. my $to = $config->{to} || '';
  195. my $count = $config->{count} || 0;
  196. my $offset = $config->{offset} || 0;
  197. return if $offset and not $count;
  198. if ($offset > 0) {
  199. $offset -= ($count < 0);
  200. } elsif ($offset < 0) {
  201. $offset = $#$data + ($count > 0) + $offset;
  202. } else {
  203. $offset = $#$data if $count < 0;
  204. }
  205. my $start = my $end = $offset;
  206. $start += $count+1 if $count < 0;
  207. $end += $count-1 if $count > 0;
  208. return unless __sanity_check_range( $data, \$from, \$to,
  209. \$since, \$until,
  210. \$start, \$end );
  211. unless ($from or $to or $since or $until or $start or $end) {
  212. return [ @$data ] if $config->{default_all} and not $count;
  213. return [ $data->[0] ];
  214. }
  215. return [ @{$data}[$start .. $end] ] if $start or $end;
  216. my @result;
  217. my $include = 1;
  218. $include = 0 if $to or $until;
  219. foreach (@$data) {
  220. my $v = $_->{Version};
  221. $include = 1 if $v eq $to;
  222. last if $v eq $since;
  223. push @result, $_ if $include;
  224. $include = 1 if $v eq $until;
  225. last if $v eq $from;
  226. }
  227. return \@result;
  228. }
  229. sub _abort_early {
  230. my ($self) = @_;
  231. my $data = $self->data or return;
  232. my $config = $self->{config} or return;
  233. # use Data::Dumper;
  234. # warn "Abort early? (\$# = $#$data)\n".Dumper($config);
  235. return if $config->{all};
  236. my $since = $config->{since} || '';
  237. my $until = $config->{until} || '';
  238. my $from = $config->{from} || '';
  239. my $to = $config->{to} || '';
  240. my $count = $config->{count} || 0;
  241. my $offset = $config->{offset} || 0;
  242. return if $offset and not $count;
  243. return if $offset < 0 or $count < 0;
  244. if ($offset > 0) {
  245. $offset -= ($count < 0);
  246. }
  247. my $start = my $end = $offset;
  248. $end += $count-1 if $count > 0;
  249. unless ($from or $to or $since or $until or $start or $end) {
  250. return if not $count;
  251. return 1 if @$data;
  252. }
  253. return 1 if ($start or $end)
  254. and $start < @$data and $end < @$data;
  255. return unless $since or $from;
  256. foreach (@$data) {
  257. my $v = $_->{Version};
  258. return 1 if $v eq $since;
  259. return 1 if $v eq $from;
  260. }
  261. return;
  262. }
  263. =pod
  264. =head3 dpkg
  265. (and B<dpkg_str>)
  266. C<dpkg> returns a hash (in list context) or a hash reference
  267. (in scalar context) where the keys are field names and the values are
  268. field values. The following fields are given:
  269. =over 4
  270. =item Source
  271. package name (in the first entry)
  272. =item Version
  273. packages' version (from first entry)
  274. =item Distribution
  275. target distribution (from first entry)
  276. =item Urgency
  277. urgency (highest of all printed entries)
  278. =item Maintainer
  279. person that created the (first) entry
  280. =item Date
  281. date of the (first) entry
  282. =item Closes
  283. bugs closed by the entry/entries, sorted by bug number
  284. =item Changes
  285. content of the the entry/entries
  286. =back
  287. C<dpkg_str> returns a stringified version of this hash. The fields are
  288. ordered like in the list above.
  289. Both methods support the common output options described in
  290. section L<"COMMON OUTPUT OPTIONS">.
  291. =head3 dpkg_str
  292. See L<dpkg>.
  293. =cut
  294. our ( @CHANGELOG_FIELDS, %CHANGELOG_FIELDS );
  295. our ( @URGENCIES, %URGENCIES );
  296. BEGIN {
  297. @CHANGELOG_FIELDS = qw(Source Version Distribution
  298. Urgency Maintainer Date Closes Changes
  299. Timestamp Header Items Trailer
  300. Urgency_comment Urgency_lc);
  301. tie %CHANGELOG_FIELDS, 'Dpkg::Fields::Object';
  302. %CHANGELOG_FIELDS = map { $_ => 1 } @CHANGELOG_FIELDS;
  303. @URGENCIES = qw(low medium high critical emergency);
  304. my $i = 1;
  305. %URGENCIES = map { $_ => $i++ } @URGENCIES;
  306. }
  307. sub dpkg {
  308. my ($self, $config) = @_;
  309. $self->{config}{DPKG} = $config if $config;
  310. $config = $self->{config}{DPKG} || {};
  311. my $data = $self->_data_range( $config ) or return undef;
  312. my $f = new Dpkg::Changelog::Entry;
  313. foreach my $field (qw( Urgency Source Version
  314. Distribution Maintainer Date )) {
  315. $f->{$field} = $data->[0]{$field};
  316. }
  317. # handle unknown fields
  318. foreach my $field (keys %{$data->[0]}) {
  319. next if $CHANGELOG_FIELDS{$field};
  320. $f->{$field} = $data->[0]{$field};
  321. }
  322. $f->{Changes} = get_dpkg_changes( $data->[0] );
  323. $f->{Closes} = [ @{$data->[0]{Closes}} ];
  324. my $first = 1; my $urg_comment = '';
  325. foreach my $entry (@$data) {
  326. $first = 0, next if $first;
  327. my $oldurg = $f->{Urgency} || '';
  328. my $oldurgn = $URGENCIES{$f->{Urgency}} || -1;
  329. my $newurg = $entry->{Urgency_lc} || '';
  330. my $newurgn = $URGENCIES{$entry->{Urgency_lc}} || -1;
  331. $f->{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg;
  332. $urg_comment .= $entry->{Urgency_comment};
  333. $f->{Changes} .= "\n .".get_dpkg_changes( $entry );
  334. push @{$f->{Closes}}, @{$entry->{Closes}};
  335. # handle unknown fields
  336. foreach my $field (keys %$entry) {
  337. next if $CHANGELOG_FIELDS{$field};
  338. next if exists $f->{$field};
  339. $f->{$field} = $entry->{$field};
  340. }
  341. }
  342. $f->{Closes} = join " ", sort { $a <=> $b } @{$f->{Closes}};
  343. $f->{Urgency} .= $urg_comment;
  344. return %$f if wantarray;
  345. return $f;
  346. }
  347. sub dpkg_str {
  348. return data2rfc822(scalar dpkg(@_));
  349. }
  350. =pod
  351. =head3 rfc822
  352. (and B<rfc822_str>)
  353. C<rfc822> returns an array of hashes (in list context) or a reference
  354. to this array (in scalar context) where each hash represents one entry
  355. in the changelog. For the format of such a hash see the description
  356. of the L<"dpkg"> method (while ignoring the remarks about which
  357. values are taken from the first entry).
  358. C<rfc822_str> returns a stringified version of this array.
  359. Both methods support the common output options described in
  360. section L<"COMMON OUTPUT OPTIONS">.
  361. =head3 rfc822_str
  362. See L<rfc822>.
  363. =cut
  364. sub rfc822 {
  365. my ($self, $config) = @_;
  366. $self->{config}{RFC822} = $config if $config;
  367. $config = $self->{config}{RFC822} || {};
  368. my $data = $self->_data_range( $config ) or return undef;
  369. my @out_data;
  370. foreach my $entry (@$data) {
  371. my $f = new Dpkg::Changelog::Entry;
  372. foreach my $field (qw( Urgency Source Version
  373. Distribution Maintainer Date )) {
  374. $f->{$field} = $entry->{$field};
  375. }
  376. $f->{Urgency} .= $entry->{Urgency_Comment};
  377. $f->{Changes} = get_dpkg_changes( $entry );
  378. $f->{Closes} = join " ", sort { $a <=> $b } @{$entry->{Closes}};
  379. # handle unknown fields
  380. foreach my $field (keys %$entry) {
  381. next if $CHANGELOG_FIELDS{$field};
  382. $f->{$field} = $entry->{$field};
  383. }
  384. push @out_data, $f;
  385. }
  386. return @out_data if wantarray;
  387. return \@out_data;
  388. }
  389. sub rfc822_str {
  390. return data2rfc822(scalar rfc822(@_));
  391. }
  392. =pod
  393. =head1 COMMON OUTPUT OPTIONS
  394. The following options are supported by all output methods,
  395. all take a version number as value:
  396. =over 4
  397. =item since
  398. Causes changelog information from all versions strictly
  399. later than B<version> to be used.
  400. =item until
  401. Causes changelog information from all versions strictly
  402. earlier than B<version> to be used.
  403. =item from
  404. Similar to C<since> but also includes the information for the
  405. specified B<version> itself.
  406. =item to
  407. Similar to C<until> but also includes the information for the
  408. specified B<version> itself.
  409. =back
  410. The following options also supported by all output methods but
  411. don't take version numbers as values:
  412. =over 4
  413. =item all
  414. If set to a true value, all entries of the changelog are returned,
  415. this overrides all other options.
  416. =item count
  417. Expects a signed integer as value. Returns C<value> entries from the
  418. top of the changelog if set to a positive integer, and C<abs(value)>
  419. entries from the tail if set to a negative integer.
  420. =item offset
  421. Expects a signed integer as value. Changes the starting point for
  422. C<count>, either counted from the top (positive integer) or from
  423. the tail (negative integer). C<offset> has no effect if C<count>
  424. wasn't given as well.
  425. =back
  426. Some examples for the above options. Imagine an example changelog with
  427. entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1.
  428. Call Included entries
  429. C<E<lt>formatE<gt>({ since =E<gt> '2.0' })> 3.1, 3.0, 2.2
  430. C<E<lt>formatE<gt>({ until =E<gt> '2.0' })> 1.3, 1.2
  431. C<E<lt>formatE<gt>({ from =E<gt> '2.0' })> 3.1, 3.0, 2.2, 2.1, 2.0
  432. C<E<lt>formatE<gt>({ to =E<gt> '2.0' })> 2.0, 1.3, 1.2
  433. C<E<lt>formatE<gt>({ count =E<gt> 2 }>> 3.1, 3.0
  434. C<E<lt>formatE<gt>({ count =E<gt> -2 }>> 1.3, 1.2
  435. C<E<lt>formatE<gt>({ count =E<gt> 3,
  436. offset=E<gt> 2 }>> 2.2, 2.1, 2.0
  437. C<E<lt>formatE<gt>({ count =E<gt> 2,
  438. offset=E<gt> -3 }>> 2.0, 1.3
  439. C<E<lt>formatE<gt>({ count =E<gt> -2,
  440. offset=E<gt> 3 }>> 3.0, 2.2
  441. C<E<lt>formatE<gt>({ count =E<gt> -2,
  442. offset=E<gt> -3 }>> 2.2, 2.1
  443. Any combination of one option of C<since> and C<from> and one of
  444. C<until> and C<to> returns the intersection of the two results
  445. with only one of the options specified.
  446. =head1 UTILITY FUNCTIONS
  447. =head3 find_closes
  448. Takes one string as argument and finds "Closes: #123456, #654321" statements
  449. as supported by the Debian Archive software in it. Returns all closed bug
  450. numbers in an array reference.
  451. =cut
  452. sub find_closes {
  453. my $changes = shift;
  454. my @closes = ();
  455. while ($changes &&
  456. ($changes =~ /closes:\s*(?:bug)?\#?\s?\d+(?:,\s*(?:bug)?\#?\s?\d+)*/ig)) {
  457. push(@closes, $& =~ /\#?\s?(\d+)/g);
  458. }
  459. @closes = sort { $a <=> $b } @closes;
  460. return \@closes;
  461. }
  462. =pod
  463. =head3 data2rfc822
  464. Takes a single argument, either a Dpkg::Changelog::Entry object
  465. or a reference to an array of such objects.
  466. Returns the data in RFC822 format as string.
  467. =cut
  468. sub data2rfc822 {
  469. my ($data) = @_;
  470. if (ref($data) eq "ARRAY") {
  471. my @rfc822 = ();
  472. foreach my $entry (@$data) {
  473. push @rfc822, data2rfc822($entry);
  474. }
  475. return join "\n", @rfc822;
  476. } else {
  477. my $rfc822_str = $data->output;
  478. return $rfc822_str;
  479. }
  480. }
  481. =pod
  482. =head3 get_dpkg_changes
  483. Takes a Dpkg::Changelog::Entry object as first argument.
  484. Returns a string that is suitable for using it in a C<Changes> field
  485. in the output format of C<dpkg-parsechangelog>.
  486. =cut
  487. sub get_dpkg_changes {
  488. my $changes = "\n ".($_[0]->{Header}||'')."\n .\n".($_[0]->{Changes}||'');
  489. chomp $changes;
  490. $changes =~ s/^ $/ ./mgo;
  491. return $changes;
  492. }
  493. =pod
  494. =head3 parse_changelog($file, $format, $since)
  495. Calls "dpkg-parsechangelog -l$file -F$format -v$since" and returns a
  496. Dpkg::Fields::Object with the values output by the program.
  497. =cut
  498. sub parse_changelog {
  499. my ($changelogfile, $changelogformat, $since) = @_;
  500. my @exec = ('dpkg-parsechangelog');
  501. push(@exec, "-l$changelogfile");
  502. push(@exec, "-F$changelogformat") if defined($changelogformat);
  503. push(@exec, "-v$since") if defined($since);
  504. open(PARSECH, "-|", @exec) || syserr(_g("fork for parse changelog"));
  505. my $fields = parsecdata(\*PARSECH, _g("parsed version of changelog"));
  506. close(PARSECH) || subprocerr(_g("parse changelog"));
  507. return $fields;
  508. }
  509. =head1 NAME
  510. Dpkg::Changelog::Entry - represents one entry in a Debian changelog
  511. =head1 SYNOPSIS
  512. =head1 DESCRIPTION
  513. =cut
  514. package Dpkg::Changelog::Entry;
  515. sub new {
  516. my ($classname) = @_;
  517. tie my %entry, 'Dpkg::Fields::Object';
  518. tied(%entry)->set_field_importance(@CHANGELOG_FIELDS);
  519. my $entry = \%entry;
  520. bless $entry, $classname;
  521. }
  522. sub is_empty {
  523. my ($self) = @_;
  524. return !($self->{Changes}
  525. || $self->{Source}
  526. || $self->{Version}
  527. || $self->{Maintainer}
  528. || $self->{Date});
  529. }
  530. sub output {
  531. my $self = shift;
  532. return tied(%$self)->output(@_);
  533. }
  534. 1;
  535. __END__
  536. =head1 AUTHOR
  537. Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt>
  538. =head1 COPYRIGHT AND LICENSE
  539. Copyright E<copy> 2005, 2007 by Frank Lichtenheld
  540. This program is free software; you can redistribute it and/or modify
  541. it under the terms of the GNU General Public License as published by
  542. the Free Software Foundation; either version 2 of the License, or
  543. (at your option) any later version.
  544. This program is distributed in the hope that it will be useful,
  545. but WITHOUT ANY WARRANTY; without even the implied warranty of
  546. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  547. GNU General Public License for more details.
  548. You should have received a copy of the GNU General Public License
  549. along with this program; if not, write to the Free Software
  550. Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  551. =cut