]> git.donarmstrong.com Git - debbugs.git/blob - bin/debbugs-loadsql
Add Debbugs::BugWalker to abstract out bug-walking code in
[debbugs.git] / bin / debbugs-loadsql
1 #! /usr/bin/perl
2 # debbugs-loadsql is part of debbugs, and is released
3 # under the terms of the GPL version 2, or any later version, at your
4 # option. See the file README and COPYING for more information.
5 # Copyright 2012 by Don Armstrong <don@donarmstrong.com>.
6
7
8 use warnings;
9 use strict;
10
11 use Getopt::Long qw(:config no_ignore_case);
12 use Pod::Usage;
13
14 =head1 NAME
15
16 debbugs-loadsql -- load debbugs sql database
17
18 =head1 SYNOPSIS
19
20 debbugs-loadsql [options]
21
22  Options:
23   --quick, -q only load changed bugs
24   --progress Show progress bar
25   --service, -s service name
26   --sysconfdir, -c postgresql service config dir
27   --spool-dir debbugs spool directory
28   --debug, -d debugging level (Default 0)
29   --help, -h display this help
30   --man, -m display manual
31
32 =head1 SUBCOMMANDS
33
34 =head2 help
35
36 Display this manual
37
38 =head2 bugs
39
40 Add bugs
41
42 =head2 versions
43
44 Add versions
45
46 =head2 maintainers
47
48 Add source maintainers
49
50 =head1 OPTIONS
51
52 =over
53
54 =item B<--quick, -q>
55
56 Only load changed bugs
57
58 =item B<--progress>
59
60 Show progress bar (requires Term::ProgressBar)
61
62 =item B<--service,-s>
63
64 Postgreql service to use; defaults to debbugs
65
66 =item B<--sysconfdir,-c>
67
68 System configuration directory to use; if not set, defaults to the
69 postgresql default. [Operates by setting PGSYSCONFDIR]
70
71 =item B<--spool-dir>
72
73 Debbugs spool directory; defaults to the value configured in the
74 debbugs configuration file.
75
76 =item B<--verbose>
77
78 Output more information about what is happening. Probably not useful
79 if you also set --progress.
80
81 =item B<--debug, -d>
82
83 Debug verbosity.
84
85 =item B<--help, -h>
86
87 Display brief useage information.
88
89 =item B<--man, -m>
90
91 Display this manual.
92
93 =back
94
95
96 =cut
97
98
99 use vars qw($DEBUG);
100
101 use Debbugs::Common qw(checkpid lockpid get_hashname getparsedaddrs getbugcomponent make_list getsourcemaintainers);
102 use Debbugs::Config qw(:config);
103 use Debbugs::Status qw(read_bug split_status_fields);
104 use Debbugs::Log;
105 use Debbugs::DB;
106 use Debbugs::DB::Load qw(load_bug handle_load_bug_queue);
107 use Debbugs::BugWalker;
108 use DateTime;
109 use File::stat;
110
111
112 my %options =
113     (debug           => 0,
114      help            => 0,
115      man             => 0,
116      verbose         => 0,
117      quiet           => 0,
118      quick           => 0,
119      service         => $config{debbugs_db},
120      progress        => 0,
121     );
122
123 Getopt::Long::Configure('pass_through');
124 GetOptions(\%options,
125            'quick|q',
126            'service|s=s',
127            'sysconfdir|c=s',
128            'progress!',
129            'spool_dir|spool-dir=s',
130            'verbose|v+',
131            'quiet+',
132            'debug|d+','help|h|?','man|m');
133 Getopt::Long::Configure('default');
134
135 pod2usage() if $options{help};
136 pod2usage({verbose=>2}) if $options{man};
137
138 $DEBUG = $options{debug};
139
140 my %subcommands =
141     ('bugs' => {function => \&add_bugs,
142                },
143      'versions' => {function => \&add_versions,
144                    },
145      'debinfo' => {function => \&add_debinfo,
146                   },
147      'maintainers' => {function => \&add_maintainers,
148                       },
149      'configuration' => {function => \&add_configuration,
150                         },
151      'logs' => {function => \&add_logs,
152                },
153      'help' => {function => sub {pod2usage({verbose => 2});}}
154     );
155
156 my @USAGE_ERRORS;
157 $options{verbose} = $options{verbose} - $options{quiet};
158
159 if ($options{progress}) {
160     eval "use Term::ProgressBar";
161     push @USAGE_ERRORS, "You asked for a progress bar, but Term::ProgressBar isn't installed" if $@;
162 }
163
164
165 pod2usage(join("\n",@USAGE_ERRORS)) if @USAGE_ERRORS;
166
167 if (exists $options{sysconfdir}) {
168     if (not defined $options{sysconfdir} or not length $options{sysconfdir}) {
169         delete $ENV{PGSYSCONFDIR};
170     } else {
171         $ENV{PGSYSCONFDIR} = $options{sysconfdir};
172     }
173 }
174
175 if (exists $options{spool_dir} and defined $options{spool_dir}) {
176     $config{spool_dir} = $options{spool_dir};
177 }
178
179 my $prog_bar;
180 if ($options{progress}) {
181     $prog_bar = eval "Term::ProgressBar->new({count => 1,ETA=>q(linear)})";
182     warn "Unable to initialize progress bar: $@" if not $prog_bar;
183 }
184
185
186 my ($subcommand) = shift @ARGV;
187 if (not defined $subcommand) {
188     $subcommand = 'help';
189     print STDERR "You must provide a subcommand; displaying usage.\n";
190     pod2usage();
191 } elsif (not exists $subcommands{$subcommand}) {
192     print STDERR "$subcommand is not a valid subcommand; displaying usage.\n";
193     pod2usage();
194 }
195
196 my $opts =
197     handle_subcommand_arguments(\@ARGV,$subcommands{$subcommand}{arguments});
198 $subcommands{$subcommand}{function}->(\%options,$opts,$prog_bar,\%config,\@ARGV);
199
200 sub add_bugs {
201     my ($options,$opts,$p,$config,$argv) = @_;
202     chdir($config->{spool_dir}) or
203         die "chdir $config->{spool_dir} failed: $!";
204
205     my $verbose = $options->{debug};
206
207     my $initialdir = "db-h";
208
209     if (defined $argv->[0] and $argv->[0] eq "archive") {
210         $initialdir = "archive";
211     }
212     my $s = db_connect($options);
213
214
215     my $time = 0;
216     my $start_time = time;
217
218
219     my @dirs = (@{$argv}?@{$argv} : $initialdir);
220     my $cnt = 0;
221     my %tags;
222     my %severities;
223     my %queue;
224     my $w = Debbugs::BugWalker->new(dir => [@dirs],
225                                     defined $p ? (progress => $p):(),
226                                     what => 'bug',
227                                    );
228     my $bug;
229     while (defined($bug = $w->get_next())) {
230             print "Up to $cnt bugs...\n" if (++$cnt % 100 == 0 && $verbose);
231             my $stat = stat(getbugcomponent($bug,'summary',$initialdir));
232             if (not defined $stat) {
233                 print STDERR "Unable to stat $bug $!\n";
234                 next;
235             }
236             if ($options{quick}) {
237                 my $rs = $s->resultset('Bug')->search({bug=>$bug})->single();
238                 next if defined $rs and $stat->mtime < $rs->last_modified()->epoch();
239             }
240             my $data = read_bug(bug => $bug,
241                                 location => $initialdir);
242             eval {
243                 load_bug(db => $s,
244                          data => split_status_fields($data),
245                          tags => \%tags,
246                          severities => \%severities,
247                          queue => \%queue);
248             };
249             if ($@) {
250                 use Data::Dumper;
251                 print STDERR Dumper($data) if $DEBUG;
252                 die "failure while trying to load bug $bug\n$@";
253             }
254     }
255     $p->remove() if $p;
256     handle_load_bug_queue(db => $s,
257                           queue => \%queue);
258 }
259
260 sub add_versions {
261     my ($options,$opts,$p,$config,$argv) = @_;
262
263     my $s = db_connect($options);
264
265     my @files = @{$argv};
266     $p->target(scalar @files) if $p;
267     for my $file (@files) {
268         my $fh = IO::File->new($file,'r') or
269             die "Unable to open $file for reading: $!";
270         my @versions;
271         my %src_pkgs;
272         while (<$fh>) {
273             chomp;
274             next unless length $_;
275             if (/(\w[-+0-9a-z.]+) \(([^\(\) \t]+)\)/) {
276                 push @versions, [$1,$2];
277             }
278         }
279         close($fh);
280         my $ancestor_sv;
281         for my $i (reverse 0..($#versions)) {
282             my $sp;
283             if (not defined $src_pkgs{$versions[$i][0]}) {
284                 $src_pkgs{$versions[$i][0]} =
285                     $s->resultset('SrcPkg')->find_or_create({pkg => $versions[$i][0]});
286             }
287             $sp = $src_pkgs{$versions[$i][0]};
288             # There's probably something wrong if the source package
289             # doesn't exist, but we'll skip it for now
290             next unless defined $sp;
291             my $sv = $s->resultset('SrcVer')->find({src_pkg=>$sp->id(),
292                                                     ver => $versions[$i][1],
293                                                    });
294             if (defined $ancestor_sv and defined $sv and not defined $sv->based_on()) {
295                 $sv->update({based_on => $ancestor_sv->id()})
296             }
297             $ancestor_sv = $sv;
298         }
299         $p->update() if $p;
300     }
301     $p->remove() if $p;
302 }
303
304 sub add_debinfo {
305     my ($options,$opts,$p,$config,$argv) = @_;
306
307     my @files = @{$argv};
308     return unless @files;
309     my $s = db_connect($options);
310     my %arch;
311     $p->target(scalar @files) if $p;
312     for my $file (@files) {
313         my $fh = IO::File->new($file,'r') or
314             die "Unable to open $file for reading: $!";
315         my $f_stat = stat($file);
316         while (<$fh>) {
317             chomp;
318             next unless length $_;
319             my ($binname, $binver, $binarch, $srcname, $srcver) = split;
320             # if $srcver is not defined, this is probably a broken
321             # .debinfo file [they were causing #686106, see commit
322             # 49c85ab8 in dak.] Basically, $binarch didn't get put into
323             # the file, so we'll fudge it from the filename.
324             if (not defined $srcver) {
325                 ($srcname,$srcver) = ($binarch,$srcname);
326                 ($binarch) = $file =~ /_([^\.]+)\.debinfo/;
327             }
328             my $sp = $s->resultset('SrcPkg')->find_or_create({pkg => $srcname});
329             my $sv = $s->resultset('SrcVer')->find_or_create({src_pkg =>$sp->id(),
330                                                               ver => $srcver});
331             my $arch;
332             if (defined $arch{$binarch}) {
333                 $arch = $arch{$binarch};
334             } else {
335                 $arch = $s->resultset('Arch')->find_or_create({arch => $binarch});
336                 $arch{$binarch} = $arch;
337             }
338             my $bp = $s->resultset('BinPkg')->find_or_create({pkg => $binname});
339             $s->resultset('BinVer')->find_or_create({bin_pkg => $bp->id(),
340                                                      src_ver => $sv->id(),
341                                                      arch    => $arch->id(),
342                                                      ver        => $binver,
343                                                     });
344         }
345         $p->update() if $p;
346     }
347     $p->remove() if $p;
348 }
349
350 sub add_maintainers {
351     my ($options,$opts,$p,$config,$argv) = @_;
352
353     my $s = db_connect($options);
354     my $maintainers = getsourcemaintainers();
355     $p->target(scalar keys %{$maintainers}) if $p;
356     for my $pkg (keys %{$maintainers}) {
357         my $maint = $maintainers->{$pkg};
358         # see if a maintainer already exists; if so, we don't do
359         # anything here
360         my $maint_r = $s->resultset('Maintainer')->
361             find({name => $maint});
362         if (not defined $maint_r) {
363             # get e-mail address of maintainer
364             my $addr = getparsedaddrs($maint);
365             my $e_mail = $addr->address();
366             my $full_name = $addr->phrase();
367             $full_name =~ s/^\"|\"$//g;
368             $full_name =~ s/^\s+|\s+$//g;
369             # find correspondent
370             my $correspondent = $s->resultset('Correspondent')->
371                 find_or_create({addr => $e_mail});
372             if (length $full_name) {
373                 my $c_full_name = $correspondent->find_or_create_related('correspondent_full_names',
374                                                                         {full_name => $full_name}) if length $full_name;
375                 $c_full_name->update({last_seen => 'NOW()'});
376             }
377             $maint_r =
378                 $s->resultset('Maintainer')->
379                 find_or_create({name => $maint,
380                                 correspondent => $correspondent,
381                                });
382         }
383         # add the maintainer to the source package for packages with
384         # no maintainer
385         $s->txn_do(sub {
386                       $s->resultset('SrcPkg')->search({pkg => $pkg})->
387                           search_related_rs('src_vers',{ maintainer => undef})->
388                           update_all({maintainer => $maint_r->id()});
389                   });
390         $p->update() if $p;
391     }
392     $p->remove() if $p;
393 }
394
395 sub add_configuration {
396     my ($options,$opts,$p,$config,$argv) = @_;
397 }
398
399 sub add_logs {
400     my ($options,$opts,$p,$config,$argv) = @_;
401 }
402
403 sub handle_subcommand_arguments {
404     my ($argv,$args) = @_;
405     my $subopt = {};
406     Getopt::Long::GetOptionsFromArray($argv,
407                               $subopt,
408                               keys %{$args},
409                              );
410     my @usage_errors;
411     for my $arg  (keys %{$args}) {
412         next unless $args->{$arg};
413         my $r_arg = $arg; # real argument name
414         $r_arg =~ s/[=\|].+//g;
415         if (not defined $subopt->{$r_arg}) {
416             push @usage_errors, "You must give a $r_arg option";
417         }
418     }
419     pod2usage(join("\n",@usage_errors)) if @usage_errors;
420     return $subopt;
421 }
422
423 sub get_lock{
424     my ($subcommand,$config,$options) = @_;
425     if (not lockpid($config->{spool_dir}.'/lock/debbugs-loadsql-$subcommand')) {
426         if ($options->{quick}) {
427             # If this is a quick run, just exit
428             print STDERR "Another debbugs-loadsql is running; stopping\n" if $options->{verbose};
429             exit 0;
430         }
431         print STDERR "Another debbugs-loadsql is running; stopping\n";
432         exit 1;
433     }
434 }
435
436 sub db_connect {
437     my ($options) = @_;
438     # connect to the database; figure out how to handle errors
439     # properly here.
440     my $s = Debbugs::DB->connect('dbi:Pg:service='.$options->{service}) or
441         die "Unable to connect to database: ";
442 }
443
444
445
446 __END__