X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=Debbugs%2FStatus.pm;h=0d97a323b5c1adcdba61013890e15ac6c0b153c4;hb=51ad28535bcc0bddf55b8d763bd2a07f146c961f;hp=f9f0af6689d3e12fa8c957c74b8a3fc875faee6d;hpb=e0f6d63c812d07fb972189e6740dd273db624e00;p=debbugs.git diff --git a/Debbugs/Status.pm b/Debbugs/Status.pm index f9f0af6..0d97a32 100644 --- a/Debbugs/Status.pm +++ b/Debbugs/Status.pm @@ -5,7 +5,7 @@ # # [Other people have contributed to this file; their copyrights should # go here too.] -# Copyright 2007 by Don Armstrong . +# Copyright 2007-9 by Don Armstrong . package Debbugs::Status; @@ -40,13 +40,16 @@ use Params::Validate qw(validate_with :types); use Debbugs::Common qw(:util :lock :quit :misc); use Debbugs::Config qw(:config); use Debbugs::MIME qw(decode_rfc1522 encode_rfc1522); -use Debbugs::Packages qw(makesourceversions make_source_versions getversions get_versions binarytosource); +use Debbugs::Packages qw(makesourceversions make_source_versions getversions get_versions binary_to_source); use Debbugs::Versions; use Debbugs::Versions::Dpkg; use POSIX qw(ceil); +use File::Copy qw(copy); +use Storable qw(dclone); use List::Util qw(min max); +use Carp qw(croak); BEGIN{ $VERSION = 1.00; @@ -54,12 +57,13 @@ BEGIN{ @EXPORT = (); %EXPORT_TAGS = (status => [qw(splitpackages get_bug_status buggy bug_archiveable), - qw(isstrongseverity bug_presence), + qw(isstrongseverity bug_presence split_status_fields), ], read => [qw(readbug read_bug lockreadbug lockreadbugmerge), qw(lock_read_all_merged_bugs), ], write => [qw(writebug makestatus unlockwritebug)], + new => [qw(new_bug)], versions => [qw(addfoundversions addfixedversions), qw(removefoundversions removefixedversions) ], @@ -67,7 +71,7 @@ BEGIN{ fields => [qw(%fields)], ); @EXPORT_OK = (); - Exporter::export_ok_tags(qw(status read write versions hook fields)); + Exporter::export_ok_tags(keys %EXPORT_TAGS); $EXPORT_TAGS{all} = [@EXPORT_OK]; } @@ -106,6 +110,7 @@ our %fields = (originator => 'submitter', affects => 'affects', ); + # Fields which need to be RFC1522-decoded in format versions earlier than 3. my @rfc1522_fields = qw(originator subject done forwarded owner); @@ -140,6 +145,10 @@ path to the summary file instead of the bug number and/or location. something modifying it while the bug has been read. You B call C if something not undef is returned from read_bug. +=item locks -- hashref of already obtained locks; incremented as new +locks are needed, and decremented as locks are released on particular +files. + =back One of C or C must be passed. This function will return @@ -168,6 +177,9 @@ sub read_bug{ lock => {type => BOOLEAN, optional => 1, }, + locks => {type => HASHREF, + optional => 1, + }, }, ); die "One of bug or summary must be passed to read_bug" @@ -194,13 +206,13 @@ sub read_bug{ ($location) = $status =~ m/(db-h|db|archive)/; } if ($param{lock}) { - filelock("$config{spool_dir}/lock/$param{bug}"); + filelock("$config{spool_dir}/lock/$param{bug}",exists $param{locks}?$param{locks}:()); } my $status_fh = IO::File->new($status, 'r'); if (not defined $status_fh) { warn "Unable to open $status for reading: $!"; if ($param{lock}) { - unfilelock(); + unfilelock(exists $param{locks}?$param{locks}:()); } return undef; } @@ -220,7 +232,7 @@ sub read_bug{ if ($version > 3) { warn "Unsupported status version '$version'"; if ($param{lock}) { - unfilelock(); + unfilelock(exists $param{locks}?$param{locks}:()); } return undef; } @@ -229,7 +241,11 @@ sub read_bug{ for my $line (@lines) { if ($line =~ /(\S+?): (.*)/) { my ($name, $value) = (lc $1, $2); - $data{$namemap{$name}} = $value if exists $namemap{$name}; + # this is a bit of a hack; we should never, ever have \r + # or \n in the fields of status. Kill them off here. + # [Eventually, this should be superfluous.] + $value =~ s/[\r\n]//g; + $data{$namemap{$name}} = $value if exists $namemap{$name}; } } for my $field (keys %fields) { @@ -264,6 +280,109 @@ sub read_bug{ return \%data; } +=head2 split_status_fields + + my @data = split_status_fields(@data); + +Splits splittable status fields (like package, tags, blocks, +blockedby, etc.) into arrayrefs (use make_list on these). Keeps the +passed @data intact using dclone. + +In scalar context, returns only the first element of @data. + +=cut + +our $ditch_empty = sub{ + my @t = @_; + my $splitter = shift @t; + return grep {length $_} map {split $splitter} @t; +}; + +my $ditch_empty_space = sub {return &{$ditch_empty}(' ',@_)}; +my %split_fields = + (package => \&splitpackages, + affects => \&splitpackages, + blocks => $ditch_empty_space, + blockedby => $ditch_empty_space, + # this isn't strictly correct, but we'll split both of them for + # the time being until we ditch all use of keywords everywhere + # from the code + keywords => $ditch_empty_space, + tags => $ditch_empty_space, + found_versions => $ditch_empty_space, + fixed_versions => $ditch_empty_space, + mergedwith => $ditch_empty_space, + ); + +sub split_status_fields { + my @data = @{dclone(\@_)}; + for my $data (@data) { + next if not defined $data; + croak "Passed an element which is not a hashref to split_status_field".ref($data) if + not (ref($data) and ref($data) eq 'HASH'); + for my $field (keys %{$data}) { + next unless defined $data->{$field}; + if (exists $split_fields{$field}) { + next if ref($data->{$field}); + my @elements; + if (ref($split_fields{$field}) eq 'CODE') { + @elements = &{$split_fields{$field}}($data->{$field}); + } + elsif (not ref($split_fields{$field}) or + UNIVERSAL::isa($split_fields{$field},'Regex') + ) { + @elements = split $split_fields{$field}, $data->{$field}; + } + $data->{$field} = \@elements; + } + } + } + return wantarray?@data:$data[0]; +} + +=head2 join_status_fields + + my @data = join_status_fields(@data); + +Handles joining the splitable status fields. (Basically, the inverse +of split_status_fields. + +Primarily called from makestatus, but may be useful for other +functions after calling split_status_fields (or for legacy functions +if we transition to split fields by default). + +=cut + +sub join_status_fields { + my %join_fields = + (package => ', ', + affects => ', ', + blocks => ' ', + blockedby => ' ', + tags => ' ', + found_versions => ' ', + fixed_versions => ' ', + found_date => ' ', + fixed_date => ' ', + mergedwith => ' ', + ); + my @data = @{dclone(\@_)}; + for my $data (@data) { + next if not defined $data; + croak "Passed an element which is not a hashref to split_status_field: ". + ref($data) + if ref($data) ne 'HASH'; + for my $field (keys %{$data}) { + next unless defined $data->{$field}; + next unless ref($data->{$field}) eq 'ARRAY'; + next unless exists $join_fields{$field}; + $data->{$field} = join($join_fields{$field},@{$data->{$field}}); + } + } + return wantarray?@data:$data[0]; +} + + =head2 lockreadbug lockreadbug($bug_num,$location) @@ -325,9 +444,24 @@ even if all of the others were read properly. =cut sub lock_read_all_merged_bugs { - my ($bug_num,$location) = @_; + my %param = validate_with(params => \@_, + spec => {bug => {type => SCALAR, + regex => qr/^\d+$/, + }, + location => {type => SCALAR, + optional => 1, + }, + locks => {type => HASHREF, + optional => 1, + }, + }, + ); my $locks = 0; - my @data = (lockreadbug(@_)); + my @data = read_bug(bug => $param{bug}, + lock => 1, + exists $param{location} ? (location => $param{location}):(), + exists $param{locks} ? (locks => $param{locks}):(), + ); if (not @data or not defined $data[0]) { return ($locks,()); } @@ -335,46 +469,112 @@ sub lock_read_all_merged_bugs { if (not length $data[0]->{mergedwith}) { return ($locks,@data); } - unfilelock(); + unfilelock(exists $param{locks}?$param{locks}:()); $locks--; - filelock("$config{spool_dir}/lock/merge"); + filelock("$config{spool_dir}/lock/merge",exists $param{locks}?$param{locks}:()); $locks++; - @data = (lockreadbug(@_)); + @data = read_bug(bug => $param{bug}, + lock => 1, + exists $param{location} ? (location => $param{location}):(), + exists $param{locks} ? (locks => $param{locks}):(), + ); if (not @data or not defined $data[0]) { - unfilelock(); #for merge lock above + unfilelock(exists $param{locks}?$param{locks}:()); #for merge lock above $locks--; return ($locks,()); } $locks++; my @bugs = split / /, $data[0]->{mergedwith}; + push @bugs, $param{bug}; for my $bug (@bugs) { my $newdata = undef; - if ($bug ne $bug_num) { - $newdata = lockreadbug($bug,$location); + if ($bug != $param{bug}) { + $newdata = + read_bug(bug => $bug, + lock => 1, + exists $param{location} ? (location => $param{location}):(), + exists $param{locks} ? (locks => $param{locks}):(), + ); if (not defined $newdata) { for (1..$locks) { - unfilelock(); + unfilelock(exists $param{locks}?$param{locks}:()); } $locks = 0; - warn "Unable to read bug: $bug while handling merged bug: $bug_num"; + warn "Unable to read bug: $bug while handling merged bug: $param{bug}"; return ($locks,()); } $locks++; push @data,$newdata; - } - # perform a sanity check to make sure that the merged bugs are - # all merged with eachother - my $expectmerge= join(' ',grep {$_ != $bug } sort { $a <=> $b } (@bugs,$bug_num)); - if ($newdata->{mergedwith} ne $expectmerge) { - for (1..$locks) { - unfilelock(); + # perform a sanity check to make sure that the merged bugs + # are all merged with eachother + my $expectmerge= join(' ',grep {$_ != $bug } sort { $a <=> $b } @bugs); + if ($newdata->{mergedwith} ne $expectmerge) { + for (1..$locks) { + unfilelock(exists $param{locks}?$param{locks}:()); + } + die "Bug $param{bug} differs from bug $bug: ($newdata->{bug_num}: '$newdata->{mergedwith}') vs. ('$expectmerge') (".join(' ',@bugs).")"; } - die "Bug $bug_num differs from bug $bug: ($newdata->{bug_num}: $newdata->{mergedwith}) vs. ($expectmerge) (".join(' ',@bugs).")"; } } return ($locks,@data); } +=head2 new_bug + + my $new_bug_num = new_bug(copy => $data->{bug_num}); + +Creates a new bug and returns the new bug number upon success. + +Dies upon failures. + +=cut + +sub new_bug { + my %param = + validate_with(params => \@_, + spec => {copy => {type => SCALAR, + regex => qr/^\d+/, + optional => 1, + }, + }, + ); + filelock("nextnumber.lock"); + my $nn_fh = IO::File->new("nextnumber",'r') or + die "Unable to open nextnuber for reading: $!"; + local $\; + my $nn = <$nn_fh>; + ($nn) = $nn =~ m/^(\d+)\n$/ or die "Bad format of nextnumber; is not exactly ".'^\d+\n$'; + close $nn_fh; + overwritefile("nextnumber", + ($nn+1)."\n"); + unfilelock(); + my $nn_hash = get_hashname($nn); + use IO::File; + my $t_fh = IO::File->new("/home/don/temp.txt",'a') or die "Unable to open ~don/temp.txt for writing: $!"; + use Data::Dumper; + print {$t_fh} Dumper({%param,nn => $nn, nn_hash => $nn_hash, nextnumber => qx(cat nextnumber)}); + close $t_fh; + if ($param{copy}) { + my $c_hash = get_hashname($param{copy}); + for my $file (qw(log status summary report)) { + copy("db-h/$c_hash/$param{copy}.$file", + "db-h/$nn_hash/${nn}.$file") + } + } + else { + for my $file (qw(log status summary report)) { + overwritefile("db-h/$nn_hash/${nn}.$file", + ""); + } + } + + # this probably needs to be munged to do something more elegant +# &bughook('new', $clone, $data); + + return($nn); +} + + my @v1fieldorder = qw(originator date subject msgid package keywords done forwarded mergedwith severity); @@ -408,10 +608,7 @@ sub makestatus { [map {$newdata{$field}{$_}||''} keys %{$newdata{$field}}]; } } - - for my $field (qw(found_versions fixed_versions found_date fixed_date)) { - $newdata{$field} = join ' ', @{$newdata{$field}||[]}; - } + %newdata = %{join_status_fields(\%newdata)}; if ($version < 3) { for my $field (@rfc1522_fields) { @@ -419,6 +616,13 @@ sub makestatus { } } + # this is a bit of a hack; we should never, ever have \r or \n in + # the fields of status. Kill them off here. [Eventually, this + # should be superfluous.] + for my $field (keys %newdata) { + $newdata{$field} =~ s/[\r\n]//g if defined $newdata{$field}; + } + if ($version == 1) { for my $field (@v1fieldorder) { if (exists $newdata{$field} and defined $newdata{$field}) { @@ -494,7 +698,7 @@ options mean. sub unlockwritebug { writebug(@_); - &unfilelock; + unfilelock(); } =head1 VERSIONS @@ -505,7 +709,7 @@ The following functions are exported with the :versions tag addfoundversions($status,$package,$version,$isbinary); - +All use of this should be phased out in favor of Debbugs::Control::fixed/found =cut @@ -518,9 +722,14 @@ sub addfoundversions { return unless defined $version; undef $package if $package =~ m[(?:\s|/)]; my $source = $package; + if ($package =~ s/^src://) { + $isbinary = 0; + $source = $package; + } if (defined $package and $isbinary) { - my @srcinfo = binarytosource($package, $version, undef); + my @srcinfo = binary_to_source(binary => $package, + version => $version); if (@srcinfo) { # We know the source package(s). Use a fully-qualified version. addfoundversions($data, $_->[0], $_->[1], '') foreach @srcinfo; @@ -594,7 +803,8 @@ sub addfixedversions { my $source = $package; if (defined $package and $isbinary) { - my @srcinfo = binarytosource($package, $version, undef); + my @srcinfo = binary_to_source(binary => $package, + version => $version); if (@srcinfo) { # We know the source package(s). Use a fully-qualified version. addfixedversions($data, $_->[0], $_->[1], '') foreach @srcinfo; @@ -725,7 +935,7 @@ sub bug_archiveable{ } # Check to make sure that the bug has none of the unremovable tags set if (@{$config{removal_unremovable_tags}}) { - for my $tag (split ' ', ($status->{tags}||'')) { + for my $tag (split ' ', ($status->{keywords}||'')) { if (grep {$tag eq $_} @{$config{removal_unremovable_tags}}) { print STDERR "Cannot archive $param{bug} because it has an unremovable tag '$tag'\n" if $DEBUG; return $cannot_archive; @@ -770,7 +980,7 @@ sub bug_archiveable{ @dist_tags{@{$config{removal_distribution_tags}}} = (1) x @{$config{removal_distribution_tags}}; my %dists; - for my $tag (split ' ', ($status->{tags}||'')) { + for my $tag (split ' ', ($status->{keywords}||'')) { next unless exists $config{distribution_aliases}{$tag}; next unless $dist_tags{$config{distribution_aliases}{$tag}}; $dists{$config{distribution_aliases}{$tag}} = 1; @@ -956,25 +1166,10 @@ sub get_bug_status { $status{package} = '' if not defined $status{package}; $status{"package"} =~ s/\s*$//; - # if we aren't supposed to indicate the source, we'll return - # unknown here. - $status{source} = 'unknown'; - if ($param{indicatesource}) { - my @packages = split /\s*,\s*/, $status{package}; - my @source; - for my $package (@packages) { - next if $package eq ''; - if ($package =~ /^src\:$/) { - push @source,$1; - } - else { - push @source, binarytosource($package); - } - } - if (@source) { - $status{source} = join(', ',@source); - } - } + + $status{source} = binary_to_source(binary=>[split /\s*,\s*/, $status{package}], + source_only => 1, + ); $status{"package"} = 'unknown' if ($status{"package"} eq ''); $status{"severity"} = 'normal' if (not defined $status{severity} or $status{"severity"} eq ''); @@ -1093,7 +1288,7 @@ sub bug_presence { (1) x @{$config{affects_distribution_tags}}; my $some_distributions_disallowed = 0; my %allowed_distributions; - for my $tag (split ' ', ($status{tags}||'')) { + for my $tag (split ' ', ($status{keywords}||'')) { if (exists $config{distribution_aliases}{$tag} and exists $affects_distribution_tags{$config{distribution_aliases}{$tag}}) { $some_distributions_disallowed = 1; @@ -1104,8 +1299,10 @@ sub bug_presence { $allowed_distributions{$tag} = 1; } } - foreach my $arch (make_list(exists $param{arch}?$param{arch}:undef)) { - for my $package (split /\s*,\s*/, $status{package}) { + my @archs = make_list(exists $param{arch}?$param{arch}:()); + GET_SOURCE_VERSIONS: + foreach my $arch (@archs) { + for my $package (split /\s*,\s*/, $status{package}) { my @versions = (); my $source = 0; if ($package =~ /^src:(.+)$/) { @@ -1136,6 +1333,15 @@ sub bug_presence { @sourceversions{@temp} = (1) x @temp; } } + # this should really be split out into a subroutine, + # but it'd touch so many things currently, that we fake + # it; it's needed to properly handle bugs which are + # erroneously assigned to the binary package, and we'll + # probably have it go away eventually. + if (not keys %sourceversions and (not @archs or defined $archs[0])) { + @archs = (undef); + goto GET_SOURCE_VERSIONS; + } } # TODO: This should probably be handled further out for efficiency and @@ -1390,23 +1596,23 @@ sub update_realtime { sub bughook_archive { my @refs = @_; - &filelock("$config{spool_dir}/debbugs.trace.lock"); - &appendfile("debbugs.trace","archive ".join(',',@refs)."\n"); + filelock("$config{spool_dir}/debbugs.trace.lock"); + appendfile("$config{spool_dir}/debbugs.trace","archive ".join(',',@refs)."\n"); my %bugs = update_realtime("$config{spool_dir}/index.db.realtime", map{($_,'REMOVE')} @refs); update_realtime("$config{spool_dir}/index.archive.realtime", %bugs); - &unfilelock; + unfilelock(); } sub bughook { my ( $type, %bugs_temp ) = @_; - &filelock("$config{spool_dir}/debbugs.trace.lock"); + filelock("$config{spool_dir}/debbugs.trace.lock"); my %bugs; for my $bug (keys %bugs_temp) { my $data = $bugs_temp{$bug}; - &appendfile("debbugs.trace","$type $bug\n",makestatus($data, 1)); + appendfile("$config{spool_dir}/debbugs.trace","$type $bug\n",makestatus($data, 1)); my $whendone = "open"; my $severity = $config{default_severity}; @@ -1424,7 +1630,7 @@ sub bughook { } update_realtime("$config{spool_dir}/index.db.realtime", %bugs); - &unfilelock; + unfilelock(); }