]> git.donarmstrong.com Git - debbugs.git/blob - Debbugs/Bugs.pm
merge changes from dla source
[debbugs.git] / Debbugs / Bugs.pm
1 # This module is part of debbugs, and is released
2 # under the terms of the GPL version 2, or any later
3 # version at your option.
4 # See the file README and COPYING for more information.
5 #
6 # Copyright 2007 by Don Armstrong <don@donarmstrong.com>.
7
8 package Debbugs::Bugs;
9
10 =head1 NAME
11
12 Debbugs::Bugs -- Bug selection routines for debbugs
13
14 =head1 SYNOPSIS
15
16 use Debbugs::Bugs qw(get_bugs);
17
18
19 =head1 DESCRIPTION
20
21 This module is a replacement for all of the various methods of
22 selecting different types of bugs.
23
24 It implements a single function, get_bugs, which defines the master
25 interface for selecting bugs.
26
27 It attempts to use subsidiary functions to actually do the selection,
28 in the order specified in the configuration files. [Unless you're
29 insane, they should be in order from fastest (and often most
30 incomplete) to slowest (and most complete).]
31
32 =head1 BUGS
33
34 =head1 FUNCTIONS
35
36 =cut
37
38 use warnings;
39 use strict;
40 use vars qw($VERSION $DEBUG %EXPORT_TAGS @EXPORT_OK @EXPORT);
41 use base qw(Exporter);
42
43 BEGIN{
44      $VERSION = 1.00;
45      $DEBUG = 0 unless defined $DEBUG;
46
47      @EXPORT = ();
48      %EXPORT_TAGS = ();
49      @EXPORT_OK = (qw(get_bugs count_bugs newest_bug bug_filter));
50      $EXPORT_TAGS{all} = [@EXPORT_OK];
51 }
52
53 use Debbugs::Config qw(:config);
54 use Params::Validate qw(validate_with :types);
55 use IO::File;
56 use Debbugs::Status qw(splitpackages get_bug_status);
57 use Debbugs::Packages qw(getsrcpkgs getpkgsrc);
58 use Debbugs::Common qw(getparsedaddrs getmaintainers getmaintainers_reverse make_list);
59 use Fcntl qw(O_RDONLY);
60 use MLDBM qw(DB_File Storable);
61 use List::Util qw(first);
62 use Carp;
63
64 =head2 get_bugs
65
66      get_bugs()
67
68 =head3 Parameters
69
70 The following parameters can either be a single scalar or a reference
71 to an array. The parameters are ANDed together, and the elements of
72 arrayrefs are a parameter are ORed. Future versions of this may allow
73 for limited regular expressions, and/or more complex expressions.
74
75 =over
76
77 =item package -- name of the binary package
78
79 =item src -- name of the source package
80
81 =item maint -- address of the maintainer
82
83 =item submitter -- address of the submitter
84
85 =item severity -- severity of the bug
86
87 =item status -- status of the bug
88
89 =item tag -- bug tags
90
91 =item owner -- owner of the bug
92
93 =item correspondent -- address of someone who sent mail to the log
94
95 =item affects -- bugs which affect this package
96
97 =item dist -- distribution (I don't know about this one yet)
98
99 =item bugs -- list of bugs to search within
100
101 =item function -- see description below
102
103 =back
104
105 =head3 Special options
106
107 The following options are special options used to modulate how the
108 searches are performed.
109
110 =over
111
112 =item archive -- whether to search archived bugs or normal bugs;
113 defaults to false. As a special case, if archive is 'both', but
114 archived and unarchived bugs are returned.
115
116 =item usertags -- set of usertags and the bugs they are applied to
117
118 =back
119
120
121 =head3 Subsidiary routines
122
123 All subsidiary routines get passed exactly the same set of options as
124 get_bugs. If for some reason they are unable to handle the options
125 passed (for example, they don't have the right type of index for the
126 type of selection) they should die as early as possible. [Using
127 Params::Validate and/or die when files don't exist makes this fairly
128 trivial.]
129
130 This function will then immediately move on to the next subroutine,
131 giving it the same arguments.
132
133 =head3 function
134
135 This option allows you to provide an arbitrary function which will be
136 given the information in the index.db file. This will be super, super
137 slow, so only do this if there's no other way to write the search.
138
139 You'll be given a list (which you can turn into a hash) like the
140 following:
141
142  (pkg => ['a','b'], # may be a scalar (most common)
143   bug => 1234,
144   status => 'pending',
145   submitter => 'boo@baz.com',
146   severity => 'serious',
147   tags => ['a','b','c'], # may be an empty arrayref
148  )
149
150 The function should return 1 if the bug should be included; 0 if the
151 bug should not.
152
153 =cut
154
155 sub get_bugs{
156      my %param = validate_with(params => \@_,
157                                spec   => {package   => {type => SCALAR|ARRAYREF,
158                                                         optional => 1,
159                                                        },
160                                           src       => {type => SCALAR|ARRAYREF,
161                                                         optional => 1,
162                                                        },
163                                           maint     => {type => SCALAR|ARRAYREF,
164                                                         optional => 1,
165                                                        },
166                                           submitter => {type => SCALAR|ARRAYREF,
167                                                         optional => 1,
168                                                        },
169                                           severity  => {type => SCALAR|ARRAYREF,
170                                                         optional => 1,
171                                                        },
172                                           status    => {type => SCALAR|ARRAYREF,
173                                                         optional => 1,
174                                                        },
175                                           tag       => {type => SCALAR|ARRAYREF,
176                                                         optional => 1,
177                                                        },
178                                           owner     => {type => SCALAR|ARRAYREF,
179                                                         optional => 1,
180                                                        },
181                                           dist      => {type => SCALAR|ARRAYREF,
182                                                         optional => 1,
183                                                        },
184                                           correspondent => {type => SCALAR|ARRAYREF,
185                                                             optional => 1,
186                                                            },
187                                           affects   => {type => SCALAR|ARRAYREF,
188                                                         optional => 1,
189                                                        },
190                                           function  => {type => CODEREF,
191                                                         optional => 1,
192                                                        },
193                                           bugs      => {type => SCALAR|ARRAYREF,
194                                                         optional => 1,
195                                                        },
196                                           archive   => {type => BOOLEAN|SCALAR,
197                                                         default => 0,
198                                                        },
199                                           usertags  => {type => HASHREF,
200                                                         optional => 1,
201                                                        },
202                                          },
203                               );
204
205      # Normalize options
206      my %options = %param;
207      my @bugs;
208      if ($options{archive} eq 'both') {
209           push @bugs, get_bugs(%options,archive=>0);
210           push @bugs, get_bugs(%options,archive=>1);
211           my %bugs;
212           @bugs{@bugs} = @bugs;
213           return keys %bugs;
214      }
215      # A configuration option will set an array that we'll use here instead.
216      for my $routine (qw(Debbugs::Bugs::get_bugs_by_idx Debbugs::Bugs::get_bugs_flatfile)) {
217           my ($package) = $routine =~ m/^(.+)\:\:/;
218           eval "use $package;";
219           if ($@) {
220                # We output errors here because using an invalid function
221                # in the configuration file isn't something that should
222                # be done.
223                warn "use $package failed with $@";
224                next;
225           }
226           @bugs = eval "${routine}(\%options)";
227           if ($@) {
228
229                # We don't output errors here, because failure here
230                # via die may be a perfectly normal thing.
231                print STDERR "$@" if $DEBUG;
232                next;
233           }
234           last;
235      }
236      # If no one succeeded, die
237      if ($@) {
238           die "$@";
239      }
240      return @bugs;
241 }
242
243 =head2 count_bugs
244
245      count_bugs(function => sub {...})
246
247 Uses a subroutine to classify bugs into categories and return the
248 number of bugs which fall into those categories
249
250 =cut
251
252 sub count_bugs {
253      my %param = validate_with(params => \@_,
254                                spec   => {function => {type => CODEREF,
255                                                       },
256                                           archive  => {type => BOOLEAN,
257                                                        default => 0,
258                                                       },
259                                          },
260                               );
261      my $flatfile;
262      if ($param{archive}) {
263           $flatfile = IO::File->new("$config{spool_dir}/index.archive", 'r')
264                or die "Unable to open $config{spool_dir}/index.archive for reading: $!";
265      }
266      else {
267           $flatfile = IO::File->new("$config{spool_dir}/index.db", 'r')
268                or die "Unable to open $config{spool_dir}/index.db for reading: $!";
269      }
270      my %count = ();
271      while(<$flatfile>) {
272           if (m/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+\[\s*([^]]*)\s*\]\s+(\w+)\s+(.*)$/) {
273                my @x = $param{function}->(pkg       => $1,
274                                           bug       => $2,
275                                           status    => $4,
276                                           submitter => $5,
277                                           severity  => $6,
278                                           tags      => $7,
279                                          );
280                local $_;
281                $count{$_}++ foreach @x;
282           }
283      }
284      close $flatfile;
285      return %count;
286 }
287
288 =head2 newest_bug
289
290      my $bug = newest_bug();
291
292 Returns the bug number of the newest bug, which is nextnumber-1.
293
294 =cut
295
296 sub newest_bug {
297      my $nn_fh = IO::File->new("$config{spool_dir}/nextnumber",'r')
298           or die "Unable to open $config{spool_dir}nextnumber for reading: $!";
299      local $/;
300      my $next_number = <$nn_fh>;
301      close $nn_fh;
302      chomp $next_number;
303      return $next_number-1;
304 }
305
306 =head2 bug_filter
307
308      bug_filter
309
310 Allows filtering bugs on commonly used criteria
311
312
313
314 =cut
315
316 sub bug_filter {
317      my %param = validate_with(params => \@_,
318                                spec   => {bug    => {type => ARRAYREF|SCALAR,
319                                                      optional => 1,
320                                                     },
321                                           status => {type => HASHREF|ARRAYREF,
322                                                      optional => 1,
323                                                     },
324                                           seen_merged => {type => HASHREF,
325                                                           optional => 1,
326                                                          },
327                                           repeat_merged => {type => BOOLEAN,
328                                                             optional => 1,
329                                                            },
330                                           include => {type => HASHREF,
331                                                       optional => 1,
332                                                      },
333                                           exclude => {type => HASHREF,
334                                                       optional => 1,
335                                                      },
336                                           min_days => {type => SCALAR,
337                                                        optional => 1,
338                                                       },
339                                           max_days => {type => SCALAR,
340                                                        optional => 1,
341                                                       },
342                                          },
343                               );
344      if (exists $param{repeat_merged} and
345          not $param{repeat_merged} and
346          not defined $param{seen_merged}) {
347           croak "repeat_merged false requires seen_merged to be passed";
348      }
349      if (not exists $param{bug} and not exists $param{status}) {
350          croak "one of bug or status must be passed";
351      }
352
353      if (not exists $param{status}) {
354           my $location = getbuglocation($param{bug}, 'summary');
355           return 0 if not defined $location or not length $location;
356           $param{status} = readbug( $param{bug}, $location );
357           return 0 if not defined $param{status};
358      }
359
360      if (exists $param{include}) {
361           return 1 if (!__bug_matches($param{include}, $param{status}));
362      }
363      if (exists $param{exclude}) {
364           return 1 if (__bug_matches($param{exclude}, $param{status}));
365      }
366      if (exists $param{repeat_merged} and not $param{repeat_merged}) {
367           my @merged = sort {$a<=>$b} $param{bug}, split(/ /, $param{status}{mergedwith});
368           return 1 if first {defined $_} @{$param{seen_merged}}{@merged};
369           @{$param{seen_merged}}{@merged} = (1) x @merged;
370      }
371      my $daysold = int((time - $param{status}{date}) / 86400);   # seconds to days
372      if (exists $param{min_days}) {
373           return 1 unless $param{min_days} <= $daysold;
374      }
375      if (exists $param{max_days}) {
376           return 1 unless $param{max_days} == -1 or
377                $param{max_days} >= $daysold;
378      }
379      return 0;
380 }
381
382
383 =head2 get_bugs_by_idx
384
385 This routine uses the by-$index.idx indicies to try to speed up
386 searches.
387
388
389 =cut
390
391 sub get_bugs_by_idx{
392      my %param = validate_with(params => \@_,
393                                spec   => {package   => {type => SCALAR|ARRAYREF,
394                                                         optional => 1,
395                                                        },
396                                           submitter => {type => SCALAR|ARRAYREF,
397                                                         optional => 1,
398                                                        },
399                                           severity  => {type => SCALAR|ARRAYREF,
400                                                         optional => 1,
401                                                        },
402                                           tag       => {type => SCALAR|ARRAYREF,
403                                                         optional => 1,
404                                                        },
405                                           archive   => {type => BOOLEAN,
406                                                         default => 0,
407                                                        },
408                                           owner     => {type => SCALAR|ARRAYREF,
409                                                         optional => 1,
410                                                        },
411                                           src       => {type => SCALAR|ARRAYREF,
412                                                         optional => 1,
413                                                        },
414                                           maint     => {type => SCALAR|ARRAYREF,
415                                                         optional => 1,
416                                                        },
417                                           bugs      => {type => SCALAR|ARRAYREF,
418                                                         optional => 1,
419                                                        },
420                                           correspondent => {type => SCALAR|ARRAYREF,
421                                                             optional => 1,
422                                                            },
423                                           affects => {type => SCALAR|ARRAYREF,
424                                                       optional => 1,
425                                                      },
426                                           usertags  => {type => HASHREF,
427                                                         optional => 1,
428                                                        },
429                                          },
430                               );
431      my %bugs = ();
432
433      # If we're given an empty maint (unmaintained packages), we can't
434      # handle it, so bail out here
435      for my $maint (make_list(exists $param{maint}?$param{maint}:[])) {
436           if (defined $maint and $maint eq '') {
437                die "Can't handle empty maint (unmaintained packages) in get_bugs_by_idx";
438           }
439      }
440
441      # We handle src packages, maint and maintenc by mapping to the
442      # appropriate binary packages, then removing all packages which
443      # don't match all queries
444      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
445                                                qw(package src maint)
446                                               );
447      if (exists $param{package} or
448          exists $param{src} or
449          exists $param{maint}) {
450           delete @param{qw(maint src)};
451           $param{package} = [@packages];
452      }
453      my $keys = grep {$_ !~ /^(archive|usertags|bugs)$/} keys(%param);
454      die "Need at least 1 key to search by" unless $keys;
455      my $arc = $param{archive} ? '-arc':'';
456      my %idx;
457      for my $key (grep {$_ !~ /^(archive|usertags|bugs)$/} keys %param) {
458           my $index = $key;
459           $index = 'submitter-email' if $key eq 'submitter';
460           $index = "$config{spool_dir}/by-${index}${arc}.idx";
461           tie(%idx, MLDBM => $index, O_RDONLY)
462                or die "Unable to open $index: $!";
463           my %bug_matching = ();
464           for my $search (make_list($param{$key})) {
465                for my $bug (keys %{$idx{$search}||{}}) {
466                     next if $bug_matching{$bug};
467                     # increment the number of searches that this bug matched
468                     $bugs{$bug}++;
469                     $bug_matching{$bug}=1;
470                }
471                if ($search ne lc($search)) {
472                     for my $bug (keys %{$idx{lc($search)}||{}}) {
473                          next if $bug_matching{$bug};
474                          # increment the number of searches that this bug matched
475                          $bugs{$bug}++;
476                          $bug_matching{$bug}=1;
477                     }
478                }
479           }
480           if ($key eq 'tag' and exists $param{usertags}) {
481                for my $bug (make_list(grep {defined $_ } @{$param{usertags}}{make_list($param{tag})})) {
482                     next if $bug_matching{$bug};
483                     $bugs{$bug}++;
484                     $bug_matching{$bug}=1;
485                }
486           }
487           untie %idx or die 'Unable to untie %idx';
488      }
489      if ($param{bugs}) {
490           $keys++;
491           for my $bug (make_list($param{bugs})) {
492                $bugs{$bug}++;
493           }
494      }
495      # Throw out results that do not match all of the search specifications
496      return map {$keys <= $bugs{$_}?($_):()} keys %bugs;
497 }
498
499
500 =head2 get_bugs_flatfile
501
502 This is the fallback search routine. It should be able to complete all
503 searches. [Or at least, that's the idea.]
504
505 =cut
506
507 sub get_bugs_flatfile{
508      my %param = validate_with(params => \@_,
509                                spec   => {package   => {type => SCALAR|ARRAYREF,
510                                                         optional => 1,
511                                                        },
512                                           src       => {type => SCALAR|ARRAYREF,
513                                                         optional => 1,
514                                                        },
515                                           maint     => {type => SCALAR|ARRAYREF,
516                                                         optional => 1,
517                                                        },
518                                           submitter => {type => SCALAR|ARRAYREF,
519                                                         optional => 1,
520                                                        },
521                                           severity  => {type => SCALAR|ARRAYREF,
522                                                         optional => 1,
523                                                        },
524                                           status    => {type => SCALAR|ARRAYREF,
525                                                         optional => 1,
526                                                        },
527                                           tag       => {type => SCALAR|ARRAYREF,
528                                                         optional => 1,
529                                                        },
530                                           owner     => {type => SCALAR|ARRAYREF,
531                                                         optional => 1,
532                                                        },
533                                           correspondent => {type => SCALAR|ARRAYREF,
534                                                             optional => 1,
535                                                            },
536                                           affects   => {type => SCALAR|ARRAYREF,
537                                                         optional => 1,
538                                                        },
539 # not yet supported
540 #                                         dist      => {type => SCALAR|ARRAYREF,
541 #                                                       optional => 1,
542 #                                                      },
543                                           bugs      => {type => SCALAR|ARRAYREF,
544                                                         optional => 1,
545                                                        },
546                                           archive   => {type => BOOLEAN,
547                                                         default => 1,
548                                                        },
549                                           usertags  => {type => HASHREF,
550                                                         optional => 1,
551                                                        },
552                                           function  => {type => CODEREF,
553                                                         optional => 1,
554                                                        },
555                                          },
556                               );
557      my $flatfile;
558      if ($param{archive}) {
559           $flatfile = IO::File->new("$config{spool_dir}/index.archive", 'r')
560                or die "Unable to open $config{spool_dir}/index.archive for reading: $!";
561      }
562      else {
563           $flatfile = IO::File->new("$config{spool_dir}/index.db", 'r')
564                or die "Unable to open $config{spool_dir}/index.db for reading: $!";
565      }
566      my %usertag_bugs;
567      if (exists $param{tag} and exists $param{usertags}) {
568           # This complex slice makes a hash with the bugs which have the
569           # usertags passed in $param{tag} set.
570           @usertag_bugs{make_list(@{$param{usertags}}{make_list($param{tag})})
571                         } = (1) x make_list(@{$param{usertags}}{make_list($param{tag})});
572      }
573      my $unmaintained_packages = 0;
574      # unmaintained packages is a special case
575      for my $maint (make_list(exists $param{maint}?$param{maint}:[])) {
576           if (defined $maint and $maint eq '' and not $unmaintained_packages) {
577                $unmaintained_packages = 1;
578                our %maintainers = %{getmaintainers()};
579                $param{function} = [exists $param{function}?
580                                    (ref $param{function}?@{$param{function}}:$param{function}):(),
581                                    sub {my %d=@_;
582                                         foreach my $try (splitpackages($d{"pkg"})) {
583                                              return 1 if not exists $maintainers{$try};
584                                         }
585                                         return 0;
586                                    }
587                                   ];
588           }
589      }
590      # We handle src packages, maint and maintenc by mapping to the
591      # appropriate binary packages, then removing all packages which
592      # don't match all queries
593      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
594                                                qw(package src maint)
595                                               );
596      if (exists $param{package} or
597          exists $param{src} or
598          exists $param{maint}) {
599           delete @param{qw(maint src)};
600           $param{package} = [@packages] if @packages;
601      }
602      my $grep_bugs = 0;
603      my %bugs;
604      if (exists $param{bugs}) {
605           $bugs{$_} = 1 for make_list($param{bugs});
606           $grep_bugs = 1;
607      }
608      # These queries have to be handled by get_bugs_by_idx
609      if (exists $param{owner}
610          or exists $param{correspondent}
611          or exists $param{affects}) {
612           $bugs{$_} = 1 for get_bugs_by_idx(map {exists $param{$_}?($_,$param{$_}):()}
613                                             qw(owner correspondent affects),
614                                            );
615           $grep_bugs = 1;
616      }
617      my @bugs;
618      BUG: while (<$flatfile>) {
619           next unless m/^(\S+)\s+(\d+)\s+(\d+)\s+(\S+)\s+\[\s*([^]]*)\s*\]\s+(\w+)\s+(.*)$/;
620           my ($pkg,$bug,$time,$status,$submitter,$severity,$tags) = ($1,$2,$3,$4,$5,$6,$7);
621           next if $grep_bugs and not exists $bugs{$bug};
622           if (exists $param{package}) {
623                my @packages = splitpackages($pkg);
624                next unless grep { my $pkg_list = $_;
625                                   grep {$pkg_list eq $_} make_list($param{package})
626                              } @packages;
627           }
628           if (exists $param{src}) {
629                my @src_packages = map { getsrcpkgs($_)} make_list($param{src});
630                my @packages = splitpackages($pkg);
631                next unless grep { my $pkg_list = $_;
632                                   grep {$pkg_list eq $_} @packages
633                              } @src_packages;
634           }
635           if (exists $param{submitter}) {
636                my @p_addrs = map {lc($_->address)}
637                     map {getparsedaddrs($_)}
638                          make_list($param{submitter});
639                my @f_addrs = map {$_->address}
640                     getparsedaddrs($submitter||'');
641                next unless grep { my $f_addr = $_; 
642                                   grep {$f_addr eq $_} @p_addrs
643                              } @f_addrs;
644           }
645           next if exists $param{severity} and not grep {$severity eq $_} make_list($param{severity});
646           next if exists $param{status} and not grep {$status eq $_} make_list($param{status});
647           if (exists $param{tag}) {
648                my $bug_ok = 0;
649                # either a normal tag, or a usertag must be set
650                $bug_ok = 1 if exists $param{usertags} and $usertag_bugs{$bug};
651                my @bug_tags = split ' ', $tags;
652                $bug_ok = 1 if grep {my $bug_tag = $_;
653                                     grep {$bug_tag eq $_} make_list($param{tag});
654                                } @bug_tags;
655                next unless $bug_ok;
656           }
657           # We do this last, because a function may be slow...
658           if (exists $param{function}) {
659                my @bug_tags = split ' ', $tags;
660                my @packages = splitpackages($pkg);
661                my $package = (@packages > 1)?\@packages:$packages[0];
662                for my $function (make_list($param{function})) {
663                     next BUG unless
664                          $function->(pkg       => $package,
665                                      bug       => $bug,
666                                      status    => $status,
667                                      submitter => $submitter,
668                                      severity  => $severity,
669                                      tags      => \@bug_tags,
670                                     );
671                }
672           }
673           push @bugs, $bug;
674      }
675      return @bugs;
676 }
677
678 =head1 PRIVATE FUNCTIONS
679
680 =head2 __handle_pkg_src_and_maint
681
682      my @packages = __handle_pkg_src_and_maint(map {exists $param{$_}?($_,$param{$_}):()}
683                                                qw(package src maint)
684                                               );
685
686 Turn package/src/maint into a list of packages
687
688 =cut
689
690 sub __handle_pkg_src_and_maint{
691      my %param = validate_with(params => \@_,
692                                spec   => {package   => {type => SCALAR|ARRAYREF,
693                                                         optional => 1,
694                                                        },
695                                           src       => {type => SCALAR|ARRAYREF,
696                                                         optional => 1,
697                                                        },
698                                           maint     => {type => SCALAR|ARRAYREF,
699                                                         optional => 1,
700                                                        },
701                                          },
702                                allow_extra => 1,
703                               );
704
705      my @packages;
706      @packages = make_list($param{package}) if exists $param{package};
707      my $package_keys = @packages?1:0;
708      my %packages;
709      @packages{@packages} = (1) x @packages;
710      if (exists $param{src}) {
711           # We only want to increment the number of keys if there is
712           # something to match
713           my $key_inc = 0;
714           # in case there are binaries with the same name as the
715           # source
716           my %_temp_p = ();
717           for my $package ((map {getsrcpkgs($_)} make_list($param{src}))) {
718                $packages{$package}++ unless exists $_temp_p{$package};
719                $_temp_p{$package} = 1;
720                $key_inc=1;
721           }
722           for my $package (make_list($param{src})) {
723                $packages{"src:$package"}++ unless exists $_temp_p{"src:$package"};
724                $_temp_p{"src:$package"} = 1;
725                $key_inc=1;
726                # As a temporary hack, we will also include $param{src}
727                # in this list for packages passed which do not have a
728                # corresponding binary package
729                if (not exists getpkgsrc()->{$package}) {
730                    $packages{$package}++ unless exists $_temp_p{$package};
731                    $_temp_p{$package} = 1;
732                }
733           }
734           $package_keys += $key_inc;
735      }
736      if (exists $param{maint}) {
737           my $key_inc = 0;
738           my $maint_rev = getmaintainers_reverse();
739           my %_temp_p = ();
740           for my $package (map { exists $maint_rev->{$_}?@{$maint_rev->{$_}}:()}
741                            make_list($param{maint})) {
742                $packages{$package}++ unless exists $_temp_p{$package};
743                $_temp_p{$package} = 1;
744                $key_inc = 1;
745           }
746           $package_keys += $key_inc;
747      }
748      return grep {$packages{$_} >= $package_keys} keys %packages;
749 }
750
751 my %field_match = (
752     'subject' => \&__contains_field_match,
753     'tags' => sub {
754         my ($field, $values, $status) = @_; 
755         my %values = map {$_=>1} @$values;
756         foreach my $t (split /\s+/, $status->{$field}) {
757             return 1 if (defined $values{$t});
758         }
759         return 0;
760     },
761     'severity' => \&__exact_field_match,
762     'pending' => \&__exact_field_match,
763     'package' => \&__exact_field_match,
764     'originator' => \&__contains_field_match,
765     'forwarded' => \&__contains_field_match,
766     'owner' => \&__contains_field_match,
767 );
768
769 sub __bug_matches {
770     my ($hash, $status) = @_;
771     foreach my $key( keys( %$hash ) ) {
772         my $value = $hash->{$key};
773         my $sub = $field_match{$key};
774         return 1 if ($sub->($key, $value, $status));
775     }
776     return 0;
777 }
778
779 sub __exact_field_match {
780     my ($field, $values, $status) = @_; 
781     my @values = @$values;
782     my @ret = grep {$_ eq $status->{$field} } @values;
783     $#ret != -1;
784 }
785
786 sub __contains_field_match {
787     my ($field, $values, $status) = @_; 
788     foreach my $data (@$values) {
789         return 1 if (index($status->{$field}, $data) > -1);
790     }
791     return 0;
792 }
793
794
795
796
797
798 1;
799
800 __END__