2 package Debbugs::Status;
6 Debbugs::Status -- Routines for dealing with summary and status files
15 This module is a replacement for the parts of errorlib.pl which write
16 and read status and summary files.
18 It also contains generic routines for returning information about the
19 status of a particular bug
27 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
28 use base qw(Exporter);
30 use Params::Validate qw(validate_with :types);
31 use Debbugs::Common qw(:util :lock);
32 use Debbugs::Config qw(:config);
36 $DEBUG = 0 unless defined $DEBUG;
39 %EXPORT_TAGS = (status => [qw(splitpackages getbugstatus)],
40 read => [qw(readbug lockreadbug)],
41 write => [qw(writebug makestatus unlockwritebug)],
42 versions => [qw(addfoundversion addfixedversion),
47 Exporter::export_ok_tags(qw(splitpackages));
48 $EXPORT_TAGS{all} = [@EXPORT_OK];
54 readbug($bug_number,$location)
56 Reads a summary file from the archive given a bug number and a bug
57 location. Valid locations are those understood by L</getbugcomponent>
62 my %fields = (originator => 'submitter',
65 msgid => 'message-id',
66 'package' => 'package',
69 forwarded => 'forwarded-to',
70 mergedwith => 'merged-with',
71 severity => 'severity',
73 found_versions => 'found-in',
74 found_date => 'found-date',
75 fixed_versions => 'fixed-in',
76 fixed_date => 'fixed-date',
78 blockedby => 'blocked-by',
81 # Fields which need to be RFC1522-decoded in format versions earlier than 3.
82 my @rfc1522_fields = qw(originator subject done forwarded owner);
86 readbug($bug_num,$location);
90 Retreives the information from the summary files for a particular bug
91 number. If location is not specified, getbuglocation is called to fill
97 my ($lref, $location) = @_;
98 if (not defined $location) {
99 $location = getbuglocation($lref,'summary');
100 return undef if not defined $location;
102 my $status = getbugcomponent($lref, 'summary', $location);
103 return undef unless defined $status;
104 my $status_fh = new IO::File $status, 'r' or
105 warn "Unable to open $status for reading: $!" and return undef;
112 while (<$status_fh>) {
115 $version = $1 if /^Format-Version: ([0-9]+)/i;
118 # Version 3 is the latest format version currently supported.
119 return undef if $version > 3;
121 my %namemap = reverse %fields;
122 for my $line (@lines) {
123 if ($line =~ /(\S+?): (.*)/) {
124 my ($name, $value) = (lc $1, $2);
125 $data{$namemap{$name}} = $value if exists $namemap{$name};
128 for my $field (keys %fields) {
129 $data{$field} = '' unless exists $data{$field};
132 $data{severity} = $config{default_severity} if $data{severity} eq '';
133 for my $field (qw(found_versions fixed_versions found_date fixed_date)) {
134 $data{$field} = [split ' ', $data{$field}];
136 for my $field (qw(found fixed)) {
137 $data{$field}{@{$data{${field}_versions}}} =
138 ('') x (@{$data{${field}_date}} - @{$data{${field}_versions}}),
139 @{$data{${field}_date}};
143 for my $field (@rfc1522_fields) {
144 $data{$field} = decode_rfc1522($data{$field});
153 lockreadbug($bug_num,$location)
155 Performs a filelock, then reads the bug; the bug is unlocked if the
156 return is undefined, otherwise, you need to call unfilelock or
159 See readbug above for information on what this returns
164 local ($lref, $location) = @_;
165 &filelock("lock/$lref");
166 my $data = readbug($lref, $location);
167 &unfilelock unless defined $data;
171 my @v1fieldorder = qw(originator date subject msgid package
172 keywords done forwarded mergedwith severity);
176 my $content = makestatus($status,$version)
177 my $content = makestatus($status);
179 Creates the content for a status file based on the $status hashref
182 Really only useful for writebug
184 Currently defaults to version 2 (non-encoded rfc1522 names) but will
185 eventually default to version 3. If you care, you should specify a
191 my ($data,$version) = @_;
192 $version = 2 unless defined $version;
196 my %newdata = %$data;
197 for my $field (qw(found fixed)) {
198 if (exists $data{$field}) {
199 $data{"${field}_date"} =
200 [map {$data{$field}{$_}||''} keys %{$data{$field}}];
204 for my $field (qw(found_versions fixed_versions found_date fixed_date)) {
205 $newdata{$field} = [split ' ', $newdata{$field}];
209 for my $field (@rfc1522_fields) {
210 $newdata{$field} = encode_rfc1522($newdata{$field});
215 for my $field (@v1fieldorder) {
216 if (exists $newdata{$field}) {
217 $contents .= "$newdata{$field}\n";
222 } elsif ($version == 2 or $version == 3) {
223 # Version 2 or 3. Add a file format version number for the sake of
224 # further extensibility in the future.
225 $contents .= "Format-Version: $version\n";
226 for my $field (keys %fields) {
227 if (exists $newdata{$field} and $newdata{$field} ne '') {
228 # Output field names in proper case, e.g. 'Merged-With'.
229 my $properfield = $fields{$field};
230 $properfield =~ s/(?:^|(?<=-))([a-z])/\u$1/g;
231 $contents .= "$properfield: $newdata{$field}\n";
241 writebug($bug_num,$status,$location,$minversion,$disablebughook)
243 Writes the bug status and summary files out.
245 Skips writting out a status file if minversion is 2
247 Does not call bughook if disablebughook is true.
252 my ($ref, $data, $location, $minversion, $disablebughook) = @_;
255 my %outputs = (1 => 'status', 2 => 'summary');
256 for my $version (keys %outputs) {
257 next if defined $minversion and $version < $minversion;
258 my $status = getbugcomponent($ref, $outputs{$version}, $location);
259 &quit("can't find location for $ref") unless defined $status;
260 open(S,"> $status.new") || &quit("opening $status.new: $!");
261 print(S makestatus($data, $version)) ||
262 &quit("writing $status.new: $!");
263 close(S) || &quit("closing $status.new: $!");
269 rename("$status.new",$status) || &quit("installing new $status: $!");
272 # $disablebughook is a bit of a hack to let format migration scripts use
273 # this function rather than having to duplicate it themselves.
274 &bughook($change,$ref,$data) unless $disablebughook;
277 =head2 unlockwritebug
279 unlockwritebug($bug_num,$status,$location,$minversion,$disablebughook);
281 Writes a bug, then calls unfilelock; see writebug for what these
293 The following functions are exported with the :versions tag
295 =head2 addfoundversions
297 addfoundversions($status,$package,$version,$isbinary);
304 sub addfoundversions {
308 my $isbinary = shift;
309 return unless defined $version;
310 undef $package if $package =~ m[(?:\s|/)];
311 my $source = $package;
313 if (defined $package and $isbinary) {
314 my @srcinfo = binarytosource($package, $version, undef);
316 # We know the source package(s). Use a fully-qualified version.
317 addfoundversions($data, $_->[0], $_->[1], '') foreach @srcinfo;
320 # Otherwise, an unqualified version will have to do.
324 # Strip off various kinds of brain-damage.
326 $version =~ s/ *\(.*\)//;
327 $version =~ s/ +[A-Za-z].*//;
329 foreach my $ver (split /[,\s]+/, $version) {
330 my $sver = defined($source) ? "$source/$ver" : '';
331 unless (grep { $_ eq $ver or $_ eq $sver } @{$data->{found_versions}}) {
332 push @{$data->{found_versions}}, defined($source) ? $sver : $ver;
334 @{$data->{fixed_versions}} =
335 grep { $_ ne $ver and $_ ne $sver } @{$data->{fixed_versions}};
339 sub removefoundversions {
343 my $isbinary = shift;
344 return unless defined $version;
345 undef $package if $package =~ m[(?:\s|/)];
346 my $source = $package;
348 if (defined $package and $isbinary) {
349 my @srcinfo = binarytosource($package, $version, undef);
351 # We know the source package(s). Use a fully-qualified version.
352 removefoundversions($data, $_->[0], $_->[1], '') foreach @srcinfo;
355 # Otherwise, an unqualified version will have to do.
359 foreach my $ver (split /[,\s]+/, $version) {
360 my $sver = defined($source) ? "$source/$ver" : '';
361 @{$data->{found_versions}} =
362 grep { $_ ne $ver and $_ ne $sver } @{$data->{found_versions}};
366 sub addfixedversions {
370 my $isbinary = shift;
371 return unless defined $version;
372 undef $package if $package =~ m[(?:\s|/)];
373 my $source = $package;
375 if (defined $package and $isbinary) {
376 my @srcinfo = binarytosource($package, $version, undef);
378 # We know the source package(s). Use a fully-qualified version.
379 addfixedversions($data, $_->[0], $_->[1], '') foreach @srcinfo;
382 # Otherwise, an unqualified version will have to do.
386 # Strip off various kinds of brain-damage.
388 $version =~ s/ *\(.*\)//;
389 $version =~ s/ +[A-Za-z].*//;
391 foreach my $ver (split /[,\s]+/, $version) {
392 my $sver = defined($source) ? "$source/$ver" : '';
393 unless (grep { $_ eq $ver or $_ eq $sver } @{$data->{fixed_versions}}) {
394 push @{$data->{fixed_versions}}, defined($source) ? $sver : $ver;
396 @{$data->{found_versions}} =
397 grep { $_ ne $ver and $_ ne $sver } @{$data->{found_versions}};
401 sub removefixedversions {
405 my $isbinary = shift;
406 return unless defined $version;
407 undef $package if $package =~ m[(?:\s|/)];
408 my $source = $package;
410 if (defined $package and $isbinary) {
411 my @srcinfo = binarytosource($package, $version, undef);
413 # We know the source package(s). Use a fully-qualified version.
414 removefixedversions($data, $_->[0], $_->[1], '') foreach @srcinfo;
417 # Otherwise, an unqualified version will have to do.
421 foreach my $ver (split /[,\s]+/, $version) {
422 my $sver = defined($source) ? "$source/$ver" : '';
423 @{$data->{fixed_versions}} =
424 grep { $_ ne $ver and $_ ne $sver } @{$data->{fixed_versions}};
434 Split a package string from the status file into a list of package names.
440 return unless defined $pkgs;
441 return map lc, split /[ \t?,()]+/, $pkgs;
445 =head2 bug_archiveable
447 bug_archiveable(ref => $bug_num);
453 =item ref -- bug number (required)
455 =item status -- Status hashref (optional)
457 =item version -- Debbugs::Version information (optional)
459 =item days_until -- return days until the bug can be archived
463 Returns 1 if the bug can be archived
464 Returns 0 if the bug cannot be archived
466 If days_until is true, returns the number of days until the bug can be
467 archived, -1 if it cannot be archived.
472 my %param = validate_with(params => \@_,
473 spec => {ref => {type => SCALAR,
476 status => {type => HASHREF,
479 version => {type => HASHREF,
482 days_until => {type => BOOLEAN,
487 # read the status information
488 # read the version information
489 # Bugs can be archived if they are
491 # 2. Fixed in unstable if tagged unstable
492 # 3. Fixed in stable if tagged stable
493 # 4. Fixed in testing if tagged testing
494 # 5. Fixed in experimental if tagged experimental
495 # 6. at least 28 days have passed since the last action has occured or the bug was closed
498 =head1 PRIVATE FUNCTIONS
502 sub update_realtime {
503 my ($file, $bug, $new) = @_;
505 # update realtime index.db
507 open(IDXDB, "<$file") or die "Couldn't open $file";
508 open(IDXNEW, ">$file.new");
512 while($line = <IDXDB>) {
513 @line = split /\s/, $line;
514 last if ($line[1] >= $bug);
519 if ($new eq "NOCHANGE") {
520 print IDXNEW $line if ($line ne "" && $line[1] == $ref);
521 } elsif ($new eq "REMOVE") {
526 if ($line ne "" && $line[1] > $bug) {
531 print IDXNEW while(<IDXDB>);
536 rename("$file.new", $file);
541 sub bughook_archive {
543 &filelock("debbugs.trace.lock");
544 &appendfile("debbugs.trace","archive $ref\n");
545 my $line = update_realtime(
546 "$gSpoolDir/index.db.realtime",
549 update_realtime("$gSpoolDir/index.archive.realtime",
555 my ( $type, $ref, $data ) = @_;
556 &filelock("debbugs.trace.lock");
558 &appendfile("debbugs.trace","$type $ref\n",makestatus($data, 1));
560 my $whendone = "open";
561 my $severity = $gDefaultSeverity;
562 (my $pkglist = $data->{package}) =~ s/[,\s]+/,/g;
565 $whendone = "forwarded" if length $data->{forwarded};
566 $whendone = "done" if length $data->{done};
567 $severity = $data->{severity} if length $data->{severity};
569 my $k = sprintf "%s %d %d %s [%s] %s %s\n",
570 $pkglist, $ref, $data->{date}, $whendone,
571 $data->{originator}, $severity, $data->{keywords};
573 update_realtime("$gSpoolDir/index.db.realtime", $ref, $k);