X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=Debbugs%2FVersions.pm;h=5545b487e3fbcc09790ab5d0facbdda2f020a3aa;hb=refs%2Fheads%2Fdebian;hp=3da63ce2be86824e63b915b1a4268011ee88f742;hpb=dfd1450ad5b74e934db2cecde27983113351f3cb;p=debbugs.git diff --git a/Debbugs/Versions.pm b/Debbugs/Versions.pm index 3da63ce..5545b48 100644 --- a/Debbugs/Versions.pm +++ b/Debbugs/Versions.pm @@ -1,5 +1,15 @@ +# This module is part of debbugs, and is released +# under the terms of the GPL version 2, or any later +# version at your option. +# See the file README and COPYING for more information. +# +# [Other people have contributed to this file; their copyrights should +# go here too.] + package Debbugs::Versions; +use warnings; + use strict; =head1 NAME @@ -53,7 +63,7 @@ function. =cut -sub new ($$) +sub new { my $this = shift; my $class = ref($this) || $this; @@ -73,7 +83,7 @@ This method is expected mainly to be used internally by the C method. =cut -sub isancestor ($$$) +sub isancestor { my $self = shift; my $ancestor = shift; @@ -87,6 +97,29 @@ sub isancestor ($$$) return 0; } +=item leaves + +Find the leaves of the version tree, i.e. those versions with no +descendants. + +This method is mainly for internal use. + +=cut + +sub leaves +{ + my $self = shift; + + my $parent = $self->{parent}; + my @vers = keys %$parent; + my %leaf; + @leaf{@vers} = (1) x @vers; + for my $v (@vers) { + delete $leaf{$parent->{$v}} if defined $parent->{$v}; + } + return keys %leaf; +} + =item merge Merges one branch of version data into this object. This branch takes the @@ -95,7 +128,7 @@ the next in the list. =cut -sub merge ($@) +sub merge { my $self = shift; return unless @_; @@ -126,7 +159,7 @@ whitespace. =cut -sub load ($*) +sub load { my $self = shift; my $fh = shift; @@ -144,20 +177,20 @@ method. =cut -sub save ($*) +sub save { my $self = shift; my $fh = shift; local $_; my $parent = $self->{parent}; - my @vers = keys %$parent; - my %leaf; - @leaf{@vers} = (1) x @vers; - for my $v (@vers) { - delete $leaf{$parent->{$v}} if defined $parent->{$v}; - } - my @leaves = reverse sort { $self->{vercmp}->($a, $b) } keys %leaf; + # TODO: breaks with tcp-wrappers/1.0-1 tcpd/2.0-1 case + my @leaves = reverse sort { + my ($x, $y) = ($a, $b); + $x =~ s{.*/}{}; + $y =~ s{.*/}{}; + $self->{vercmp}->($x, $y); + } $self->leaves(); my %seen; for my $lf (@leaves) { @@ -186,7 +219,7 @@ that nothing is known about any of the found versions. =cut -sub buggy ($$$$) +sub buggy { my $self = shift; my $version = shift; @@ -207,6 +240,16 @@ sub buggy ($$$$) return 'fixed' if $fixed{$node}; } + unless (@$found) { + # We don't know when it was found. Was it fixed in a descendant of + # this version? If so, this one should be considered buggy. + for my $f (@$fixed) { + for (my $node = $f; defined $node; $node = $parent->{$node}) { + return 'found' if $node eq $version; + } + } + } + # Nothing in the requested version's ancestor chain can be confirmed as # a version in which the bug was found or fixed. If it was only found or # fixed on some other branch, then this one isn't buggy. @@ -220,6 +263,130 @@ sub buggy ($$$$) return 'found'; } +=item allstates + +Takes two arguments, C and C, which are interpreted as in +L. Efficiently returns the state of the bug at every known version, +in the form of a hash from versions to states (as returned by L). If +you pass a third argument, C, this method will stop after +determining the state of the bug at all the versions listed therein. + +Whether this is faster than calling L for each version you're +interested in is not altogether clear, and depends rather strongly on the +number of known and interested versions. + +=cut + +sub allstates +{ + my $self = shift; + my $found = shift; + my $fixed = shift; + my $interested = shift; + + my %found = map { $_ => 1 } @$found; + my %fixed = map { $_ => 1 } @$fixed; + my %interested; + if (defined $interested) { + %interested = map { $_ => 1 } @$interested; + } + my $parent = $self->{parent}; + my @leaves = $self->leaves(); + + # Are any of the found or fixed versions known? We'll need this later. + my $known = 0; + for my $f (@$found, @$fixed) { + if (exists $parent->{$f}) { + $known = 1; + last; + } + } + + # Start at each leaf in turn, working our way up and remembering the + # list of versions in the branch. + my %state; + LEAF: for my $lf (@leaves) { + my @branch; + my $fixeddesc = 0; + + for (my $node = $lf; defined $node; $node = $parent->{$node}) { + # If we're about to start a new branch, check whether we know + # the state of every version in which we're interested. If so, + # we can stop now. + if (defined $interested and not @branch) { + my @remove; + for my $interest (keys %interested) { + if (exists $state{$interest}) { + push @remove, $interest; + } + } + delete @interested{@remove}; + last LEAF unless keys %interested; + } + + # We encounter a version whose state we already know. Record the + # branch with the same state as that version, and go on to the + # next leaf. + if (exists $state{$node}) { + $state{$_} = $state{$node} foreach @branch; + last; + } + + push @branch, $node; + + # We encounter a version in the found list. Record the branch as + # 'found', and start a new branch. + if ($found{$node}) { + $state{$_} = 'found' foreach @branch; + @branch = (); + } + + # We encounter a version in the fixed list. Record the branch as + # 'fixed', and start a new branch, remembering that we have a + # fixed descendant. + elsif ($fixed{$node}) { + $state{$_} = 'fixed' foreach @branch; + @branch = (); + $fixeddesc = 1; + } + + # We encounter a root. + elsif (not defined $parent->{$node}) { + # If the found list is empty and we have a fixed descendant, + # record the branch as 'found' (since they probably just + # forgot to report a version when opening the bug). + if (not @$found and $fixeddesc) { + $state{$_} = 'found' foreach @branch; + } + + # If any of the found or fixed versions are known, record + # the branch as 'absent' (since all the activity must have + # happened on some other branch). + elsif ($known) { + $state{$_} = 'absent' foreach @branch; + } + + # If there are any fixed versions at all (but they're + # unknown), then who knows, but we guess at recording the + # branch as 'fixed'. + elsif (@$fixed) { + $state{$_} = 'fixed' foreach @branch; + } + + # Otherwise, fall back to recording the branch as 'found'. + else { + $state{$_} = 'found' foreach @branch; + } + + # In any case, we're done. + last; + } + } + } + + return %state; +} + =back =cut