1 package Debbugs::Versions;
7 Debbugs::Versions - debbugs version information processing
11 The Debbugs::Versions module provides generic support functions for the
12 implementation of version tracking in debbugs.
14 Complex organizations, such as Debian, require the tracking of bugs in
15 multiple versions of packages. The versioning scheme is frequently branched:
16 for example, a security update announced by an upstream developer will be
17 packaged as-is for the unstable distribution while a minimal backport is
18 made to the stable distribution. In order to report properly on the bugs
19 open in each distribution, debbugs must be aware of the structure of the
20 version tree for each package.
22 Gathering the version data is beyond the scope of this module: in the case
23 of Debian it is carried out by mechanical analysis of package changelogs.
24 Debbugs::Versions takes version data for a package generated by this or any
25 other means, merges it into a tree structure, and allows the user to perform
26 queries based on supplied data about the versions in which bugs have been
27 found and the versions in which they have been fixed.
31 The data format looks like this (backslashes are not actually there, and
32 indicate continuation lines):
34 1.5.4 1.5.0 1.5-iwj.0.4 1.5-iwj.0.3 1.5-iwj.0.2 1.5-iwj.0.1 1.4.0 1.3.14 \
35 1.3.13 1.3.12 1.3.11 1.3.10 ...
36 1.4.1.6 1.4.1.5 1.4.1.4 1.4.1.3 1.4.1.2 1.4.1.1 1.4.1 1.4.0.31 1.4.0.30 \
37 1.4.0.29 1.4.0.28 1.4.0.27 1.4.0.26.0.1 1.4.0.26 1.4.0.25 1.4.0.24 \
38 1.4.0.23.2 1.4.0.23.1 1.4.0.23 1.4.0.22 1.4.0.21 1.4.0.20 1.4.0.19 \
39 1.4.0.18 1.4.0.17 1.4.0.16 1.4.0.15 1.4.0.14 1.4.0.13 1.4.0.12 \
40 1.4.0.11 1.4.0.10 1.4.0.9 1.4.0.8 1.4.0.7 1.4.0.6 1.4.0.5 1.4.0.4 \
41 1.4.0.3 1.4.0.2 1.4.0.1 1.4.0 \
42 1.4.0.35 1.4.0.34 1.4.0.33 1.4.0.32 1.4.0.31
50 Constructs a Debbugs::Versions object. The argument is a reference to a
51 version comparison function, which must be usable by Perl's built-in C<sort>
59 my $class = ref($this) || $this;
61 my $self = { parent => {}, vercmp => $vercmp };
62 return bless $self, $class;
67 Takes two arguments, C<ancestor> and C<descendant>. Returns true if and only
68 if C<ancestor> is a version on which C<descendant> is based according to the
69 version data supplied to this object. (As a degenerate case, this relation
70 is reflexive: a version is considered to be an ancestor of itself.)
72 This method is expected mainly to be used internally by the C<merge> method.
80 my $descendant = shift;
82 my $parent = $self->{parent};
83 for (my $node = $descendant; defined $node; $node = $parent->{$node}) {
84 return 1 if $node eq $ancestor;
92 Merges one branch of version data into this object. This branch takes the
93 form of a list of versions, each of which is to be considered as based on
103 for my $i (1 .. $#_) {
105 next if $self->isancestor($last, $_[$i]);
107 # If it's already an ancestor version, don't add it again. This
108 # keeps the tree correct when we get several partial branches, such
109 # as '1.4.0 1.3.14 1.3.13 1.3.12' followed by '1.4.0 1.3.12 1.3.10'.
110 unless ($self->isancestor($_[$i], $last)) {
111 $self->{parent}{$last} = $_[$i];
116 # Insert undef for the last version so that we can tell a known version
117 # by seeing if it exists in $self->{parent}.
118 $self->{parent}{$_[$#_]} = undef unless exists $self->{parent}{$_[$#_]};
123 Loads version data from the filehandle passed as the argument. Each line of
124 input is expected to represent one branch, with versions separated by
141 Outputs the version tree represented by this object to the filehandle passed
142 as the argument. The format is the same as that expected by the C<load>
152 my $parent = $self->{parent};
154 my @vers = keys %$parent;
156 @leaf{@vers} = (1) x @vers;
158 delete $leaf{$parent->{$v}} if defined $parent->{$v};
160 # TODO: breaks with tcp-wrappers/1.0-1 tcpd/2.0-1 case
161 my @leaves = reverse sort {
162 my ($x, $y) = ($a, $b);
165 $self->{vercmp}->($x, $y);
169 for my $lf (@leaves) {
172 for (my $node = $parent->{$lf}; defined $node;
173 $node = $parent->{$node}) {
175 last if exists $seen{$node};
184 Takes three arguments, C<version>, C<found>, and C<fixed>. Returns true if
185 and only if C<version> is based on or equal to a version in the list
186 referenced by C<found>, and not based on or equal to one referenced by
189 C<buggy> attempts to cope with found and fixed versions not in the version
190 tree by simply checking whether any fixed versions are recorded in the event
191 that nothing is known about any of the found versions.
202 my %found = map { $_ => 1 } @$found;
203 my %fixed = map { $_ => 1 } @$fixed;
204 my $parent = $self->{parent};
205 for (my $node = $version; defined $node; $node = $parent->{$node}) {
206 # The found and fixed tests are this way round because the most
207 # likely scenario is that somebody thought they'd fixed a bug and
208 # then it was reopened because it turned out not to have been fixed
209 # after all. However, tools that build found and fixed lists should
210 # generally know the order of events and make sure that the two
211 # lists have no common entries.
212 return 'found' if $found{$node};
213 return 'fixed' if $fixed{$node};
217 # We don't know when it was found. Was it fixed in a descendant of
218 # this version? If so, this one should be considered buggy.
219 for my $f (@$fixed) {
220 for (my $node = $f; defined $node; $node = $parent->{$node}) {
221 return 'found' if $node eq $version;
226 # Nothing in the requested version's ancestor chain can be confirmed as
227 # a version in which the bug was found or fixed. If it was only found or
228 # fixed on some other branch, then this one isn't buggy.
229 for my $f (@$found, @$fixed) {
230 return 'absent' if exists $parent->{$f};
233 # Otherwise, we degenerate to checking whether any fixed versions at all
235 return 'fixed' if @$fixed;