| OLD | NEW |
| (Empty) |
| 1 # Copyright © 2005, 2007 Frank Lichtenheld <frank@lichtenheld.de> | |
| 2 # Copyright © 2009 Raphaël Hertzog <hertzog@debian.org> | |
| 3 # | |
| 4 # This program is free software; you can redistribute it and/or modify | |
| 5 # it under the terms of the GNU General Public License as published by | |
| 6 # the Free Software Foundation; either version 2 of the License, or | |
| 7 # (at your option) any later version. | |
| 8 # | |
| 9 # This program is distributed in the hope that it will be useful, | |
| 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| 12 # GNU General Public License for more details. | |
| 13 # | |
| 14 # You should have received a copy of the GNU General Public License | |
| 15 # along with this program. If not, see <https://www.gnu.org/licenses/>. | |
| 16 | |
| 17 =encoding utf8 | |
| 18 | |
| 19 =head1 NAME | |
| 20 | |
| 21 Dpkg::Changelog - base class to implement a changelog parser | |
| 22 | |
| 23 =head1 DESCRIPTION | |
| 24 | |
| 25 Dpkg::Changelog is a class representing a changelog file | |
| 26 as an array of changelog entries (Dpkg::Changelog::Entry). | |
| 27 By deriving this object and implementing its parse method, you | |
| 28 add the ability to fill this object with changelog entries. | |
| 29 | |
| 30 =head2 FUNCTIONS | |
| 31 | |
| 32 =cut | |
| 33 | |
| 34 package Dpkg::Changelog; | |
| 35 | |
| 36 use strict; | |
| 37 use warnings; | |
| 38 | |
| 39 our $VERSION = '1.00'; | |
| 40 | |
| 41 use Dpkg; | |
| 42 use Dpkg::Gettext; | |
| 43 use Dpkg::ErrorHandling qw(:DEFAULT report); | |
| 44 use Dpkg::Control; | |
| 45 use Dpkg::Control::Changelog; | |
| 46 use Dpkg::Control::Fields; | |
| 47 use Dpkg::Index; | |
| 48 use Dpkg::Version; | |
| 49 use Dpkg::Vendor qw(run_vendor_hook); | |
| 50 | |
| 51 use parent qw(Dpkg::Interface::Storable); | |
| 52 | |
| 53 use overload | |
| 54 '@{}' => sub { return $_[0]->{data} }; | |
| 55 | |
| 56 =over 4 | |
| 57 | |
| 58 =item my $c = Dpkg::Changelog->new(%options) | |
| 59 | |
| 60 Creates a new changelog object. | |
| 61 | |
| 62 =cut | |
| 63 | |
| 64 sub new { | |
| 65 my ($this, %opts) = @_; | |
| 66 my $class = ref($this) || $this; | |
| 67 my $self = { | |
| 68 verbose => 1, | |
| 69 parse_errors => [] | |
| 70 }; | |
| 71 bless $self, $class; | |
| 72 $self->set_options(%opts); | |
| 73 return $self; | |
| 74 } | |
| 75 | |
| 76 =item $c->load($filename) | |
| 77 | |
| 78 Parse $filename as a changelog. | |
| 79 | |
| 80 =cut | |
| 81 | |
| 82 =item $c->set_options(%opts) | |
| 83 | |
| 84 Change the value of some options. "verbose" (defaults to 1) defines | |
| 85 whether parse errors are displayed as warnings by default. "reportfile" | |
| 86 is a string to use instead of the name of the file parsed, in particular | |
| 87 in error messages. "range" defines the range of entries that we want to | |
| 88 parse, the parser will stop as soon as it has parsed enough data to | |
| 89 satisfy $c->get_range($opts{range}). | |
| 90 | |
| 91 =cut | |
| 92 | |
| 93 sub set_options { | |
| 94 my ($self, %opts) = @_; | |
| 95 $self->{$_} = $opts{$_} foreach keys %opts; | |
| 96 } | |
| 97 | |
| 98 =item $c->reset_parse_errors() | |
| 99 | |
| 100 Can be used to delete all information about errors occurred during | |
| 101 previous L<parse> runs. | |
| 102 | |
| 103 =cut | |
| 104 | |
| 105 sub reset_parse_errors { | |
| 106 my ($self) = @_; | |
| 107 $self->{parse_errors} = []; | |
| 108 } | |
| 109 | |
| 110 =item $c->parse_error($file, $line_nr, $error, [$line]) | |
| 111 | |
| 112 Record a new parse error in $file at line $line_nr. The error message is | |
| 113 specified with $error and a copy of the line can be recorded in $line. | |
| 114 | |
| 115 =cut | |
| 116 | |
| 117 sub parse_error { | |
| 118 my ($self, $file, $line_nr, $error, $line) = @_; | |
| 119 shift; | |
| 120 | |
| 121 push @{$self->{parse_errors}}, [ @_ ]; | |
| 122 | |
| 123 if ($self->{verbose}) { | |
| 124 if ($line) { | |
| 125 warning("%20s(l$line_nr): $error\nLINE: $line", $file); | |
| 126 } else { | |
| 127 warning("%20s(l$line_nr): $error", $file); | |
| 128 } | |
| 129 } | |
| 130 } | |
| 131 | |
| 132 =item $c->get_parse_errors() | |
| 133 | |
| 134 Returns all error messages from the last L<parse> run. | |
| 135 If called in scalar context returns a human readable | |
| 136 string representation. If called in list context returns | |
| 137 an array of arrays. Each of these arrays contains | |
| 138 | |
| 139 =over 4 | |
| 140 | |
| 141 =item 1. | |
| 142 | |
| 143 a string describing the origin of the data (a filename usually). If the | |
| 144 reportfile configuration option was given, its value will be used instead. | |
| 145 | |
| 146 =item 2. | |
| 147 | |
| 148 the line number where the error occurred | |
| 149 | |
| 150 =item 3. | |
| 151 | |
| 152 an error description | |
| 153 | |
| 154 =item 4. | |
| 155 | |
| 156 the original line | |
| 157 | |
| 158 =back | |
| 159 | |
| 160 =cut | |
| 161 | |
| 162 sub get_parse_errors { | |
| 163 my ($self) = @_; | |
| 164 | |
| 165 if (wantarray) { | |
| 166 return @{$self->{parse_errors}}; | |
| 167 } else { | |
| 168 my $res = ''; | |
| 169 foreach my $e (@{$self->{parse_errors}}) { | |
| 170 if ($e->[3]) { | |
| 171 $res .= report(_g('warning'),_g("%s(l%s): %s\nLINE: %s"), @$e ); | |
| 172 } else { | |
| 173 $res .= report(_g('warning'), _g('%s(l%s): %s'), @$e); | |
| 174 } | |
| 175 } | |
| 176 return $res; | |
| 177 } | |
| 178 } | |
| 179 | |
| 180 =item $c->set_unparsed_tail($tail) | |
| 181 | |
| 182 Add a string representing unparsed lines after the changelog entries. | |
| 183 Use undef as $tail to remove the unparsed lines currently set. | |
| 184 | |
| 185 =item $c->get_unparsed_tail() | |
| 186 | |
| 187 Return a string representing the unparsed lines after the changelog | |
| 188 entries. Returns undef if there's no such thing. | |
| 189 | |
| 190 =cut | |
| 191 | |
| 192 sub set_unparsed_tail { | |
| 193 my ($self, $tail) = @_; | |
| 194 $self->{unparsed_tail} = $tail; | |
| 195 } | |
| 196 | |
| 197 sub get_unparsed_tail { | |
| 198 my ($self) = @_; | |
| 199 return $self->{unparsed_tail}; | |
| 200 } | |
| 201 | |
| 202 =item @{$c} | |
| 203 | |
| 204 Returns all the Dpkg::Changelog::Entry objects contained in this changelog | |
| 205 in the order in which they have been parsed. | |
| 206 | |
| 207 =item $c->get_range($range) | |
| 208 | |
| 209 Returns an array (if called in list context) or a reference to an array of | |
| 210 Dpkg::Changelog::Entry objects which each represent one entry of the | |
| 211 changelog. $range is a hash reference describing the range of entries | |
| 212 to return. See section L<"RANGE SELECTION">. | |
| 213 | |
| 214 =cut | |
| 215 | |
| 216 sub __sanity_check_range { | |
| 217 my ($self, $r) = @_; | |
| 218 my $data = $self->{data}; | |
| 219 | |
| 220 if (defined($r->{offset}) and not defined($r->{count})) { | |
| 221 warning(_g("'offset' without 'count' has no effect")) if $self->{verbose
}; | |
| 222 delete $r->{offset}; | |
| 223 } | |
| 224 | |
| 225 ## no critic (ControlStructures::ProhibitUntilBlocks) | |
| 226 if ((defined($r->{count}) || defined($r->{offset})) && | |
| 227 (defined($r->{from}) || defined($r->{since}) || | |
| 228 defined($r->{to}) || defined($r->{until}))) | |
| 229 { | |
| 230 warning(_g("you can't combine 'count' or 'offset' with any other " . | |
| 231 'range option')) if $self->{verbose}; | |
| 232 delete $r->{from}; | |
| 233 delete $r->{since}; | |
| 234 delete $r->{to}; | |
| 235 delete $r->{until}; | |
| 236 } | |
| 237 if (defined($r->{from}) && defined($r->{since})) { | |
| 238 warning(_g("you can only specify one of 'from' and 'since', using " . | |
| 239 "'since'")) if $self->{verbose}; | |
| 240 delete $r->{from}; | |
| 241 } | |
| 242 if (defined($r->{to}) && defined($r->{until})) { | |
| 243 warning(_g("you can only specify one of 'to' and 'until', using " . | |
| 244 "'until'")) if $self->{verbose}; | |
| 245 delete $r->{to}; | |
| 246 } | |
| 247 | |
| 248 # Handle non-existing versions | |
| 249 my (%versions, @versions); | |
| 250 foreach my $entry (@{$data}) { | |
| 251 $versions{$entry->get_version()->as_string()} = 1; | |
| 252 push @versions, $entry->get_version()->as_string(); | |
| 253 } | |
| 254 if ((defined($r->{since}) and not exists $versions{$r->{since}})) { | |
| 255 warning(_g("'%s' option specifies non-existing version"), 'since'); | |
| 256 warning(_g('use newest entry that is earlier than the one specified')); | |
| 257 foreach my $v (@versions) { | |
| 258 if (version_compare_relation($v, REL_LT, $r->{since})) { | |
| 259 $r->{since} = $v; | |
| 260 last; | |
| 261 } | |
| 262 } | |
| 263 if (not exists $versions{$r->{since}}) { | |
| 264 # No version was earlier, include all | |
| 265 warning(_g('none found, starting from the oldest entry')); | |
| 266 delete $r->{since}; | |
| 267 $r->{from} = $versions[-1]; | |
| 268 } | |
| 269 } | |
| 270 if ((defined($r->{from}) and not exists $versions{$r->{from}})) { | |
| 271 warning(_g("'%s' option specifies non-existing version"), 'from'); | |
| 272 warning(_g('use oldest entry that is later than the one specified')); | |
| 273 my $oldest; | |
| 274 foreach my $v (@versions) { | |
| 275 if (version_compare_relation($v, REL_GT, $r->{from})) { | |
| 276 $oldest = $v; | |
| 277 } | |
| 278 } | |
| 279 if (defined($oldest)) { | |
| 280 $r->{from} = $oldest; | |
| 281 } else { | |
| 282 warning(_g("no such entry found, ignoring '%s' parameter"), 'from'); | |
| 283 delete $r->{from}; # No version was oldest | |
| 284 } | |
| 285 } | |
| 286 if (defined($r->{until}) and not exists $versions{$r->{until}}) { | |
| 287 warning(_g("'%s' option specifies non-existing version"), 'until'); | |
| 288 warning(_g('use oldest entry that is later than the one specified')); | |
| 289 my $oldest; | |
| 290 foreach my $v (@versions) { | |
| 291 if (version_compare_relation($v, REL_GT, $r->{until})) { | |
| 292 $oldest = $v; | |
| 293 } | |
| 294 } | |
| 295 if (defined($oldest)) { | |
| 296 $r->{until} = $oldest; | |
| 297 } else { | |
| 298 warning(_g("no such entry found, ignoring '%s' parameter"), 'until')
; | |
| 299 delete $r->{until}; # No version was oldest | |
| 300 } | |
| 301 } | |
| 302 if (defined($r->{to}) and not exists $versions{$r->{to}}) { | |
| 303 warning(_g("'%s' option specifies non-existing version"), 'to'); | |
| 304 warning(_g('use newest entry that is earlier than the one specified')); | |
| 305 foreach my $v (@versions) { | |
| 306 if (version_compare_relation($v, REL_LT, $r->{to})) { | |
| 307 $r->{to} = $v; | |
| 308 last; | |
| 309 } | |
| 310 } | |
| 311 if (not exists $versions{$r->{to}}) { | |
| 312 # No version was earlier | |
| 313 warning(_g("no such entry found, ignoring '%s' parameter"), 'to'); | |
| 314 delete $r->{to}; | |
| 315 } | |
| 316 } | |
| 317 | |
| 318 if (defined($r->{since}) and $data->[0]->get_version() eq $r->{since}) { | |
| 319 warning(_g("'since' option specifies most recent version, ignoring")); | |
| 320 delete $r->{since}; | |
| 321 } | |
| 322 if (defined($r->{until}) and $data->[-1]->get_version() eq $r->{until}) { | |
| 323 warning(_g("'until' option specifies oldest version, ignoring")); | |
| 324 delete $r->{until}; | |
| 325 } | |
| 326 ## use critic | |
| 327 } | |
| 328 | |
| 329 sub get_range { | |
| 330 my ($self, $range) = @_; | |
| 331 $range //= {}; | |
| 332 my $res = $self->_data_range($range); | |
| 333 if (defined $res) { | |
| 334 return @$res if wantarray; | |
| 335 return $res; | |
| 336 } else { | |
| 337 return; | |
| 338 } | |
| 339 } | |
| 340 | |
| 341 sub _is_full_range { | |
| 342 my ($self, $range) = @_; | |
| 343 | |
| 344 return 1 if $range->{all}; | |
| 345 | |
| 346 # If no range delimiter is specified, we want everything. | |
| 347 foreach (qw(since until from to count offset)) { | |
| 348 return 0 if exists $range->{$_}; | |
| 349 } | |
| 350 | |
| 351 return 1; | |
| 352 } | |
| 353 | |
| 354 sub _data_range { | |
| 355 my ($self, $range) = @_; | |
| 356 | |
| 357 my $data = $self->{data} or return; | |
| 358 | |
| 359 return [ @$data ] if $self->_is_full_range($range); | |
| 360 | |
| 361 $self->__sanity_check_range($range); | |
| 362 | |
| 363 my ($start, $end); | |
| 364 if (defined($range->{count})) { | |
| 365 my $offset = $range->{offset} || 0; | |
| 366 my $count = $range->{count}; | |
| 367 # Convert count/offset in start/end | |
| 368 if ($offset > 0) { | |
| 369 $offset -= ($count < 0); | |
| 370 } elsif ($offset < 0) { | |
| 371 $offset = $#$data + ($count > 0) + $offset; | |
| 372 } else { | |
| 373 $offset = $#$data if $count < 0; | |
| 374 } | |
| 375 $start = $end = $offset; | |
| 376 $start += $count+1 if $count < 0; | |
| 377 $end += $count-1 if $count > 0; | |
| 378 # Check limits | |
| 379 $start = 0 if $start < 0; | |
| 380 return if $start > $#$data; | |
| 381 $end = $#$data if $end > $#$data; | |
| 382 return if $end < 0; | |
| 383 $end = $start if $end < $start; | |
| 384 return [ @{$data}[$start .. $end] ]; | |
| 385 } | |
| 386 | |
| 387 ## no critic (ControlStructures::ProhibitUntilBlocks) | |
| 388 my @result; | |
| 389 my $include = 1; | |
| 390 $include = 0 if defined($range->{to}) or defined($range->{until}); | |
| 391 foreach (@$data) { | |
| 392 my $v = $_->get_version(); | |
| 393 $include = 1 if defined($range->{to}) and $v eq $range->{to}; | |
| 394 last if defined($range->{since}) and $v eq $range->{since}; | |
| 395 | |
| 396 push @result, $_ if $include; | |
| 397 | |
| 398 $include = 1 if defined($range->{until}) and $v eq $range->{until}; | |
| 399 last if defined($range->{from}) and $v eq $range->{from}; | |
| 400 } | |
| 401 ## use critic | |
| 402 | |
| 403 return \@result if scalar(@result); | |
| 404 return; | |
| 405 } | |
| 406 | |
| 407 =item $c->abort_early() | |
| 408 | |
| 409 Returns true if enough data have been parsed to be able to return all | |
| 410 entries selected by the range set at creation (or with set_options). | |
| 411 | |
| 412 =cut | |
| 413 | |
| 414 sub abort_early { | |
| 415 my ($self) = @_; | |
| 416 | |
| 417 my $data = $self->{data} or return; | |
| 418 my $r = $self->{range} or return; | |
| 419 my $count = $r->{count} || 0; | |
| 420 my $offset = $r->{offset} || 0; | |
| 421 | |
| 422 return if $self->_is_full_range($r); | |
| 423 return if $offset < 0 or $count < 0; | |
| 424 if (defined($r->{count})) { | |
| 425 if ($offset > 0) { | |
| 426 $offset -= ($count < 0); | |
| 427 } | |
| 428 my $start = my $end = $offset; | |
| 429 $end += $count-1 if $count > 0; | |
| 430 return ($start < @$data and $end < @$data); | |
| 431 } | |
| 432 | |
| 433 return unless defined($r->{since}) or defined($r->{from}); | |
| 434 foreach (@$data) { | |
| 435 my $v = $_->get_version(); | |
| 436 return 1 if defined($r->{since}) and $v eq $r->{since}; | |
| 437 return 1 if defined($r->{from}) and $v eq $r->{from}; | |
| 438 } | |
| 439 | |
| 440 return; | |
| 441 } | |
| 442 | |
| 443 =item $c->save($filename) | |
| 444 | |
| 445 Save the changelog in the given file. | |
| 446 | |
| 447 =item $c->output() | |
| 448 | |
| 449 =item "$c" | |
| 450 | |
| 451 Returns a string representation of the changelog (it's a concatenation of | |
| 452 the string representation of the individual changelog entries). | |
| 453 | |
| 454 =item $c->output($fh) | |
| 455 | |
| 456 Output the changelog to the given filehandle. | |
| 457 | |
| 458 =cut | |
| 459 | |
| 460 sub output { | |
| 461 my ($self, $fh) = @_; | |
| 462 my $str = ''; | |
| 463 foreach my $entry (@{$self}) { | |
| 464 my $text = $entry->output(); | |
| 465 print { $fh } $text if defined $fh; | |
| 466 $str .= $text if defined wantarray; | |
| 467 } | |
| 468 my $text = $self->get_unparsed_tail(); | |
| 469 if (defined $text) { | |
| 470 print { $fh } $text if defined $fh; | |
| 471 $str .= $text if defined wantarray; | |
| 472 } | |
| 473 return $str; | |
| 474 } | |
| 475 | |
| 476 =item my $control = $c->dpkg($range) | |
| 477 | |
| 478 Returns a Dpkg::Control::Changelog object representing the entries selected | |
| 479 by the optional range specifier (see L<"RANGE SELECTION"> for details). | |
| 480 Returns undef in no entries are matched. | |
| 481 | |
| 482 The following fields are contained in the object: | |
| 483 | |
| 484 =over 4 | |
| 485 | |
| 486 =item Source | |
| 487 | |
| 488 package name (in the first entry) | |
| 489 | |
| 490 =item Version | |
| 491 | |
| 492 packages' version (from first entry) | |
| 493 | |
| 494 =item Distribution | |
| 495 | |
| 496 target distribution (from first entry) | |
| 497 | |
| 498 =item Urgency | |
| 499 | |
| 500 urgency (highest of all printed entries) | |
| 501 | |
| 502 =item Maintainer | |
| 503 | |
| 504 person that created the (first) entry | |
| 505 | |
| 506 =item Date | |
| 507 | |
| 508 date of the (first) entry | |
| 509 | |
| 510 =item Closes | |
| 511 | |
| 512 bugs closed by the entry/entries, sorted by bug number | |
| 513 | |
| 514 =item Changes | |
| 515 | |
| 516 content of the the entry/entries | |
| 517 | |
| 518 =back | |
| 519 | |
| 520 =cut | |
| 521 | |
| 522 our ( @URGENCIES, %URGENCIES ); | |
| 523 BEGIN { | |
| 524 @URGENCIES = qw(low medium high critical emergency); | |
| 525 my $i = 1; | |
| 526 %URGENCIES = map { $_ => $i++ } @URGENCIES; | |
| 527 } | |
| 528 | |
| 529 sub dpkg { | |
| 530 my ($self, $range) = @_; | |
| 531 | |
| 532 my @data = $self->get_range($range) or return; | |
| 533 my $src = shift @data; | |
| 534 | |
| 535 my $f = Dpkg::Control::Changelog->new(); | |
| 536 $f->{Urgency} = $src->get_urgency() || 'unknown'; | |
| 537 $f->{Source} = $src->get_source() || 'unknown'; | |
| 538 $f->{Version} = $src->get_version() // 'unknown'; | |
| 539 $f->{Distribution} = join(' ', $src->get_distributions()); | |
| 540 $f->{Maintainer} = $src->get_maintainer() || ''; | |
| 541 $f->{Date} = $src->get_timestamp() || ''; | |
| 542 $f->{Changes} = $src->get_dpkg_changes(); | |
| 543 | |
| 544 # handle optional fields | |
| 545 my $opts = $src->get_optional_fields(); | |
| 546 my %closes; | |
| 547 foreach (keys %$opts) { | |
| 548 if (/^Urgency$/i) { # Already dealt | |
| 549 } elsif (/^Closes$/i) { | |
| 550 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes})); | |
| 551 } else { | |
| 552 field_transfer_single($opts, $f); | |
| 553 } | |
| 554 } | |
| 555 | |
| 556 foreach my $bin (@data) { | |
| 557 my $oldurg = $f->{Urgency} || ''; | |
| 558 my $oldurgn = $URGENCIES{$f->{Urgency}} || -1; | |
| 559 my $newurg = $bin->get_urgency() || ''; | |
| 560 my $newurgn = $URGENCIES{$newurg} || -1; | |
| 561 $f->{Urgency} = ($newurgn > $oldurgn) ? $newurg : $oldurg; | |
| 562 $f->{Changes} .= "\n" . $bin->get_dpkg_changes(); | |
| 563 | |
| 564 # handle optional fields | |
| 565 $opts = $bin->get_optional_fields(); | |
| 566 foreach (keys %$opts) { | |
| 567 if (/^Closes$/i) { | |
| 568 $closes{$_} = 1 foreach (split(/\s+/, $opts->{Closes})); | |
| 569 } elsif (not exists $f->{$_}) { # Don't overwrite an existing field | |
| 570 field_transfer_single($opts, $f); | |
| 571 } | |
| 572 } | |
| 573 } | |
| 574 | |
| 575 if (scalar keys %closes) { | |
| 576 $f->{Closes} = join ' ', sort { $a <=> $b } keys %closes; | |
| 577 } | |
| 578 run_vendor_hook('post-process-changelog-entry', $f); | |
| 579 | |
| 580 return $f; | |
| 581 } | |
| 582 | |
| 583 =item my @controls = $c->rfc822($range) | |
| 584 | |
| 585 Returns a Dpkg::Index containing Dpkg::Control::Changelog objects where | |
| 586 each object represents one entry in the changelog that is part of the | |
| 587 range requested (see L<"RANGE SELECTION"> for details). For the format of | |
| 588 such an object see the description of the L<"dpkg"> method (while ignoring | |
| 589 the remarks about which values are taken from the first entry). | |
| 590 | |
| 591 =cut | |
| 592 | |
| 593 sub rfc822 { | |
| 594 my ($self, $range) = @_; | |
| 595 | |
| 596 my @data = $self->get_range($range) or return; | |
| 597 my $index = Dpkg::Index->new(type => CTRL_CHANGELOG); | |
| 598 | |
| 599 foreach my $entry (@data) { | |
| 600 my $f = Dpkg::Control::Changelog->new(); | |
| 601 $f->{Urgency} = $entry->get_urgency() || 'unknown'; | |
| 602 $f->{Source} = $entry->get_source() || 'unknown'; | |
| 603 $f->{Version} = $entry->get_version() // 'unknown'; | |
| 604 $f->{Distribution} = join(' ', $entry->get_distributions()); | |
| 605 $f->{Maintainer} = $entry->get_maintainer() || ''; | |
| 606 $f->{Date} = $entry->get_timestamp() || ''; | |
| 607 $f->{Changes} = $entry->get_dpkg_changes(); | |
| 608 | |
| 609 # handle optional fields | |
| 610 my $opts = $entry->get_optional_fields(); | |
| 611 foreach (keys %$opts) { | |
| 612 field_transfer_single($opts, $f) unless exists $f->{$_}; | |
| 613 } | |
| 614 | |
| 615 run_vendor_hook('post-process-changelog-entry', $f); | |
| 616 | |
| 617 $index->add($f); | |
| 618 } | |
| 619 return $index; | |
| 620 } | |
| 621 | |
| 622 =back | |
| 623 | |
| 624 =head1 RANGE SELECTION | |
| 625 | |
| 626 A range selection is described by a hash reference where | |
| 627 the allowed keys and values are described below. | |
| 628 | |
| 629 The following options take a version number as value. | |
| 630 | |
| 631 =over 4 | |
| 632 | |
| 633 =item since | |
| 634 | |
| 635 Causes changelog information from all versions strictly | |
| 636 later than B<version> to be used. | |
| 637 | |
| 638 =item until | |
| 639 | |
| 640 Causes changelog information from all versions strictly | |
| 641 earlier than B<version> to be used. | |
| 642 | |
| 643 =item from | |
| 644 | |
| 645 Similar to C<since> but also includes the information for the | |
| 646 specified B<version> itself. | |
| 647 | |
| 648 =item to | |
| 649 | |
| 650 Similar to C<until> but also includes the information for the | |
| 651 specified B<version> itself. | |
| 652 | |
| 653 =back | |
| 654 | |
| 655 The following options don't take version numbers as values: | |
| 656 | |
| 657 =over 4 | |
| 658 | |
| 659 =item all | |
| 660 | |
| 661 If set to a true value, all entries of the changelog are returned, | |
| 662 this overrides all other options. | |
| 663 | |
| 664 =item count | |
| 665 | |
| 666 Expects a signed integer as value. Returns C<value> entries from the | |
| 667 top of the changelog if set to a positive integer, and C<abs(value)> | |
| 668 entries from the tail if set to a negative integer. | |
| 669 | |
| 670 =item offset | |
| 671 | |
| 672 Expects a signed integer as value. Changes the starting point for | |
| 673 C<count>, either counted from the top (positive integer) or from | |
| 674 the tail (negative integer). C<offset> has no effect if C<count> | |
| 675 wasn't given as well. | |
| 676 | |
| 677 =back | |
| 678 | |
| 679 Some examples for the above options. Imagine an example changelog with | |
| 680 entries for the versions 1.2, 1.3, 2.0, 2.1, 2.2, 3.0 and 3.1. | |
| 681 | |
| 682 Range Included entries | |
| 683 C<{ since =E<gt> '2.0' }> 3.1, 3.0, 2.2 | |
| 684 C<{ until =E<gt> '2.0' }> 1.3, 1.2 | |
| 685 C<{ from =E<gt> '2.0' }> 3.1, 3.0, 2.2, 2.1, 2.0 | |
| 686 C<{ to =E<gt> '2.0' }> 2.0, 1.3, 1.2 | |
| 687 C<{ count =E<gt> 2 }> 3.1, 3.0 | |
| 688 C<{ count =E<gt> -2 }> 1.3, 1.2 | |
| 689 C<{ count =E<gt> 3, offset=E<gt> 2 }> 2.2, 2.1, 2.0 | |
| 690 C<{ count =E<gt> 2, offset=E<gt> -3 }> 2.0, 1.3 | |
| 691 C<{ count =E<gt> -2, offset=E<gt> 3 }> 3.0, 2.2 | |
| 692 C<{ count =E<gt> -2, offset=E<gt> -3 }> 2.2, 2.1 | |
| 693 | |
| 694 Any combination of one option of C<since> and C<from> and one of | |
| 695 C<until> and C<to> returns the intersection of the two results | |
| 696 with only one of the options specified. | |
| 697 | |
| 698 =head1 AUTHOR | |
| 699 | |
| 700 Frank Lichtenheld, E<lt>frank@lichtenheld.deE<gt> | |
| 701 Raphaël Hertzog, E<lt>hertzog@debian.orgE<gt> | |
| 702 | |
| 703 =cut | |
| 704 1; | |
| OLD | NEW |