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