X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=Debbugs%2FStatus.pm;h=0030a7ccda131d9f348816e51e3b4f34ff4049b2;hb=5e18dc96bf01d38542c7efddfec990dbe83fdca4;hp=dac6d75944aee1c29f80f2d9fbf615caab30343b;hpb=0d773d8f9e51ed8923d0f8aaa511ddd9e9fdf2f1;p=debbugs.git diff --git a/Debbugs/Status.pm b/Debbugs/Status.pm index dac6d75..0030a7c 100644 --- a/Debbugs/Status.pm +++ b/Debbugs/Status.pm @@ -32,6 +32,7 @@ status of a particular bug use warnings; use strict; + use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT); use base qw(Exporter); @@ -55,7 +56,9 @@ BEGIN{ %EXPORT_TAGS = (status => [qw(splitpackages get_bug_status buggy bug_archiveable), qw(isstrongseverity bug_presence), ], - read => [qw(readbug read_bug lockreadbug lockreadbugmerge)], + read => [qw(readbug read_bug lockreadbug lockreadbugmerge), + qw(lock_read_all_merged_bugs), + ], write => [qw(writebug makestatus unlockwritebug)], versions => [qw(addfoundversions addfixedversions), qw(removefoundversions removefixedversions) @@ -97,6 +100,8 @@ my %fields = (originator => 'submitter', blocks => 'blocks', blockedby => 'blocked-by', unarchived => 'unarchived', + summary => 'summary', + affects => 'affects', ); # Fields which need to be RFC1522-decoded in format versions earlier than 3. @@ -129,6 +134,10 @@ path to the summary file instead of the bug number and/or location. =item summary -- complete path to the .summary file which will be read +=item lock -- whether to obtain a lock for the bug to prevent +something modifying it while the bug has been read. You B call +C if something not undef is returned from read_bug. + =back One of C or C must be passed. This function will return @@ -154,6 +163,9 @@ sub read_bug{ summary => {type => SCALAR, optional => 1, }, + lock => {type => BOOLEAN, + optional => 1, + }, }, ); die "One of bug or summary must be passed to read_bug" @@ -178,8 +190,17 @@ sub read_bug{ $log =~ s/\.summary$/.log/; ($location) = $status =~ m/(db-h|db|archive)/; } - my $status_fh = new IO::File $status, 'r' or - warn "Unable to open $status for reading: $!" and return undef; + if ($param{lock}) { + filelock("$config{spool_dir}/lock/$param{bug}"); + } + my $status_fh = IO::File->new($status, 'r'); + if (not defined $status_fh) { + warn "Unable to open $status for reading: $!"; + if ($param{lock}) { + unfilelock(); + } + return undef; + } my %data; my @lines; @@ -193,7 +214,13 @@ sub read_bug{ } # Version 3 is the latest format version currently supported. - return undef if $version > 3; + if ($version > 3) { + warn "Unsupported status version '$version'"; + if ($param{lock}) { + unfilelock(); + } + return undef; + } my %namemap = reverse %fields; for my $line (@lines) { @@ -226,6 +253,8 @@ sub read_bug{ # Add log last modified time $data{log_modified} = (stat($log))[9]; $data{location} = $location; + $data{archived} = (defined($location) and ($location eq 'archive'))?1:0; + $data{bug_num} = $param{bug}; return \%data; } @@ -244,10 +273,7 @@ See readbug above for information on what this returns sub lockreadbug { my ($lref, $location) = @_; - &filelock("lock/$lref"); - my $data = read_bug(bug => $lref, location => $location); - &unfilelock unless defined $data; - return $data; + return read_bug(bug => $lref, location => $location, lock => 1); } =head2 lockreadbugmerge @@ -270,7 +296,7 @@ sub lockreadbugmerge { return (1,$data); } unfilelock(); - filelock('lock/merge'); + filelock("$config{spool_dir}/lock/merge"); $data = lockreadbug(@_); if (not defined $data) { unfilelock(); @@ -279,6 +305,67 @@ sub lockreadbugmerge { return (2,$data); } +=head2 lock_read_all_merged_bugs + + my ($locks,@bug_data) = lock_read_all_merged_bugs($bug_num,$location); + +Performs a filelock, then reads the bug passed. If the bug is merged, +locks the merge lock, then reads and locks all of the other merged +bugs. Returns a list of the number of locks and the bug data for all +of the merged bugs. + +Will also return undef if any of the merged bugs failed to be read, +even if all of the others were read properly. + +=cut + +sub lock_read_all_merged_bugs { + my ($bug_num,$location) = @_; + my @data = (lockreadbug(@_)); + if (not @data and not defined $data[0]) { + return (0,undef); + } + if (not length $data[0]->{mergedwith}) { + return (1,@data); + } + unfilelock(); + filelock("$config{spool_dir}/lock/merge"); + my $locks = 0; + @data = (lockreadbug(@_)); + if (not @data and not defined $data[0]) { + unfilelock(); #for merge lock above + return (0,undef); + } + $locks++; + my @bugs = split / /, $data[0]->{mergedwith}; + for my $bug (@bugs) { + my $newdata = undef; + if ($bug ne $bug_num) { + $newdata = lockreadbug($bug,$location); + if (not defined $newdata) { + for (1..$locks) { + unfilelock(); + } + $locks = 0; + warn "Unable to read bug: $bug while handling merged bug: $bug_num"; + return ($locks,undef); + } + $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)); + if ($newdata->{mergedwith} ne $expectmerge) { + for (1..$locks) { + unfilelock(); + } + die "Bug $bug_num differs from bug $bug: ($newdata->{mergedwith}) vs. ($expectmerge) (".join(' ',@bugs).")"; + } + } + return (2,@data); +} + my @v1fieldorder = qw(originator date subject msgid package keywords done forwarded mergedwith severity); @@ -369,17 +456,17 @@ sub writebug { for my $version (keys %outputs) { next if defined $minversion and $version < $minversion; my $status = getbugcomponent($ref, $outputs{$version}, $location); - &quit("can't find location for $ref") unless defined $status; - open(S,"> $status.new") || &quit("opening $status.new: $!"); + die "can't find location for $ref" unless defined $status; + open(S,"> $status.new") || die "opening $status.new: $!"; print(S makestatus($data, $version)) || - &quit("writing $status.new: $!"); - close(S) || &quit("closing $status.new: $!"); + die "writing $status.new: $!"; + close(S) || die "closing $status.new: $!"; if (-e $status) { $change = 'change'; } else { $change = 'new'; } - rename("$status.new",$status) || &quit("installing new $status: $!"); + rename("$status.new",$status) || die "installing new $status: $!"; } # $disablebughook is a bit of a hack to let format migration scripts use @@ -529,24 +616,20 @@ sub removefixedversions { my $version = shift; my $isbinary = shift; return unless defined $version; - undef $package if $package =~ m[(?:\s|/)]; - my $source = $package; - - if (defined $package and $isbinary) { - my @srcinfo = binarytosource($package, $version, undef); - if (@srcinfo) { - # We know the source package(s). Use a fully-qualified version. - removefixedversions($data, $_->[0], $_->[1], '') foreach @srcinfo; - return; - } - # Otherwise, an unqualified version will have to do. - undef $source; - } foreach my $ver (split /[,\s]+/, $version) { - my $sver = defined($source) ? "$source/$ver" : ''; - @{$data->{fixed_versions}} = - grep { $_ ne $ver and $_ ne $sver } @{$data->{fixed_versions}}; + if ($ver =~ m{/}) { + # fully qualified version + @{$data->{fixed_versions}} = + grep {$_ ne $ver} + @{$data->{fixed_versions}}; + } + else { + # non qualified version; delete all matchers + @{$data->{fixed_versions}} = + grep {$_ !~ m[(?:^|/)\Q$ver\E$]} + @{$data->{fixed_versions}}; + } } } @@ -620,17 +703,44 @@ sub bug_archiveable{ my $status = $param{status}; if (not exists $param{status} or not defined $status) { $status = read_bug(bug=>$param{bug}); - return undef if not defined $status; + if (not defined $status) { + print STDERR "Cannot archive $param{bug} because it does not exist\n" if $DEBUG; + return undef; + } } # Bugs can be archived if they are # 1. Closed - return $cannot_archive if not defined $status->{done} or not length $status->{done}; + if (not defined $status->{done} or not length $status->{done}) { + print STDERR "Cannot archive $param{bug} because it is not done\n" if $DEBUG; + return $cannot_archive + } + # 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}||'')) { + 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; + } + } + } + # If we just are checking if the bug can be archived, we'll not even bother # checking the versioning information if the bug has been -done for less than 28 days. + my $log_file = getbugcomponent($param{bug},'log'); + if (not defined $log_file) { + print STDERR "Cannot archive $param{bug} because the log doesn't exist\n" if $DEBUG; + return $cannot_archive; + } + my $max_log_age = max(map {$config{remove_age} - -M $_} + $log_file, map {my $log = getbugcomponent($_,'log'); + defined $log ? ($log) : (); + } + split / /, $status->{mergedwith} + ); if (not $param{days_until} and not $param{ignore_time} - and $config{remove_age} > - -M getbugcomponent($param{bug},'log') + and $max_log_age > 0 ) { + print STDERR "Cannot archive $param{bug} because of time\n" if $DEBUG; return $cannot_archive; } # At this point, we have to get the versioning information for this bug. @@ -638,6 +748,10 @@ sub bug_archiveable{ # tags set, we assume a default set, otherwise we use the tags the bug # has set. + # In cases where we are assuming a default set, if the severity + # is strong, we use the strong severity default; otherwise, we + # use the normal default. + # There must be fixed_versions for us to look at the versioning # information my $min_fixed_time = time; @@ -647,11 +761,20 @@ sub bug_archiveable{ @dist_tags{@{$config{removal_distribution_tags}}} = (1) x @{$config{removal_distribution_tags}}; my %dists; - @dists{@{$config{removal_default_distribution_tags}}} = - (1) x @{$config{removal_default_distribution_tags}}; for my $tag (split ' ', ($status->{tags}||'')) { - next unless $dist_tags{$tag}; - $dists{$tag} = 1; + next unless exists $config{distribution_aliases}{$tag}; + next unless $dist_tags{$config{distribution_aliases}{$tag}}; + $dists{$config{distribution_aliases}{$tag}} = 1; + } + if (not keys %dists) { + if (isstrongseverity($status->{severity})) { + @dists{@{$config{removal_strong_severity_default_distribution_tags}}} = + (1) x @{$config{removal_strong_severity_default_distribution_tags}}; + } + else { + @dists{@{$config{removal_default_distribution_tags}}} = + (1) x @{$config{removal_default_distribution_tags}}; + } } my %source_versions; my @sourceversions = get_versions(package => $status->{package}, @@ -668,6 +791,7 @@ sub bug_archiveable{ version_cache => $version_cache, package => $status->{package}, )) { + print STDERR "Cannot archive $param{bug} because it's found\n" if $DEBUG; return $cannot_archive; } # Since the bug has at least been fixed in the architectures @@ -698,15 +822,19 @@ sub bug_archiveable{ last if $buggy eq 'found'; $min_fixed_time = min($time_versions{$version},$min_fixed_time); } - $min_archive_days = max($min_archive_days,ceil($config{remove_age} - (time - $min_fixed_time)/(60*60*24))); + $min_archive_days = max($min_archive_days,ceil($config{remove_age} - (time - $min_fixed_time)/(60*60*24))) + # if there are no versions in the archive at all, then + # we can archive if enough days have passed + if @sourceversions; } # If $param{ignore_time}, then we should ignore time. if ($param{ignore_time}) { return $param{days_until}?0:1; } # 6. at least 28 days have passed since the last action has occured or the bug was closed - my $age = ceil($config{remove_age} - -M getbugcomponent($param{bug},'log')); + my $age = ceil($max_log_age); if ($age > 0 or $min_archive_days > 0) { + print STDERR "Cannot archive $param{bug} because not enough days have passed\n" if $DEBUG; return $param{days_until}?max($age,$min_archive_days):0; } else { @@ -745,6 +873,11 @@ currently not correctly implemented. dist, arch, and version. [The entries in this array must be in the "source/version" format.] Eventually this can be used to for caching. +=item indicatesource -- if true, indicate which source packages this +bug could belong to. Defaults to false. [Note that eventually we will +properly allow bugs that only affect a source package, and this will +become always on.] + =back Note: Currently the version information is cached; this needs to be @@ -781,6 +914,9 @@ sub get_bug_status { sourceversions => {type => ARRAYREF, optional => 1, }, + indicatesource => {type => BOOLEAN, + default => 0, + }, }, ); my %status; @@ -811,6 +947,12 @@ sub get_bug_status { my %tags = map { $_ => 1 } split ' ', $status{tags}; $status{"package"} =~ s/\s*$//; + if ($param{indicatesource} and $status{package} ne '') { + $status{source} = join(', ',binarytosource($status{package})); + } + else { + $status{source} = 'unknown'; + } $status{"package"} = 'unknown' if ($status{"package"} eq ''); $status{"severity"} = 'normal' if ($status{"severity"} eq ''); @@ -820,7 +962,8 @@ sub get_bug_status { $status{"pending"} = 'fixed' if ($tags{fixed}); - my $presence = bug_presence(map{(exists $param{$_})?($_,$param{$_}):()} + my $presence = bug_presence(status => \%status, + map{(exists $param{$_})?($_,$param{$_}):()} qw(bug sourceversions arch dist version found fixed package) ); if (defined $presence) { @@ -904,9 +1047,14 @@ sub bug_presence { } my @sourceversions; + my $pseudo_desc = getpseudodesc(); if (not exists $param{sourceversions}) { my %sourceversions; - if (defined $param{version}) { + # pseudopackages do not have source versions by definition. + if (exists $pseudo_desc->{$status{package}}) { + # do nothing. + } + elsif (defined $param{version}) { foreach my $arch (make_list($param{arch})) { for my $package (split /\s*,\s*/, $status{package}) { my @temp = makesourceversions($package, @@ -934,6 +1082,7 @@ sub bug_presence { # TODO: This should probably be handled further out for efficiency and # for more ease of distinguishing between pkg= and src= queries. + # DLA: src= queries should just pass arch=source, and they'll be happy. @sourceversions = keys %sourceversions; } else { @@ -949,7 +1098,8 @@ sub bug_presence { version_cache => $version_cache, ); } - elsif (defined $param{dist}) { + elsif (defined $param{dist} and + not exists $pseudo_desc->{$status{package}}) { return 'absent'; } if (length($status{done}) and @@ -1076,23 +1226,31 @@ sub buggy { ); } if ($param{version} !~ m{/}) { - $param{version} = makesourceversions($param{package},undef, - $param{version} - ); + my ($version) = makesourceversions($param{package},undef, + $param{version} + ); + $param{version} = $version if defined $version; } # Figure out which source packages we need my %sources; @sources{map {m{(.+)/}; $1} @found} = (1) x @found; @sources{map {m{(.+)/}; $1} @fixed} = (1) x @fixed; - @sources{map {m{(.+)/}; $1} $param{version}} = 1; + @sources{map {m{(.+)/}; $1} $param{version}} = 1 if + $param{version} =~ m{/}; my $version; if (not defined $param{version_cache} or not exists $param{version_cache}{join(',',sort keys %sources)}) { $version = Debbugs::Versions->new(\&Debbugs::Versions::Dpkg::vercmp); foreach my $source (keys %sources) { my $srchash = substr $source, 0, 1; - my $version_fh = IO::File->new("$config{version_packages_dir}/$srchash/$source", 'r') or - warn "Unable to open $config{version_packages_dir}/$srchash/$source: $!" and next; + my $version_fh = IO::File->new("$config{version_packages_dir}/$srchash/$source", 'r'); + if (not defined $version_fh) { + # We only want to warn if it's a package which actually has a maintainer + my $maints = getmaintainers(); + next if not exists $maints->{$source}; + warn "Bug $param{bug}: unable to open $config{version_packages_dir}/$srchash/$source: $!"; + next; + } $version->load($version_fh); } if (defined $param{version_cache}) { @@ -1174,7 +1332,7 @@ sub update_realtime { sub bughook_archive { my @refs = @_; - &filelock("debbugs.trace.lock"); + &filelock("$config{spool_dir}/debbugs.trace.lock"); &appendfile("debbugs.trace","archive ".join(',',@refs)."\n"); my %bugs = update_realtime("$config{spool_dir}/index.db.realtime", map{($_,'REMOVE')} @refs); @@ -1185,7 +1343,7 @@ sub bughook_archive { sub bughook { my ( $type, %bugs_temp ) = @_; - &filelock("debbugs.trace.lock"); + &filelock("$config{spool_dir}/debbugs.trace.lock"); my %bugs; for my $bug (keys %bugs_temp) {