#!/usr/bin/perl -w # -*- Mode: perl; indent-tabs-mode: nil -*- # # The contents of this file are subject to the Mozilla Public # License Version 1.1 (the "License"); you may not use this file # except in compliance with the License. You may obtain a copy of # the License at http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or # implied. See the License for the specific language governing # rights and limitations under the License. # # The Original Code is the Gnats To Bugzilla Conversion Utility. # # The Initial Developer of the Original Code is Tom # Schutter. Portions created by Tom Schutter are # Copyright (C) 1999 Tom Schutter. All # Rights Reserved. # # Contributor(s): Tom Schutter # # Perl script to convert a GNATS database to a Bugzilla database. # This script generates a file that contains SQL commands for MySQL. # This script DOES NOT MODIFY the GNATS database. # This script DOES NOT MODIFY the Bugzilla database. # # Usage procedure: # 1) Regenerate the GNATS index file. It sometimes has inconsistencies, # and this script relies on it being correct. Use the GNATS command: # gen-index --numeric --outfile=$GNATS_DIR/gnats-db/gnats-adm/index # 2) Modify variables at the beginning of this script to match # what your site requires. # 3) Modify translate_pr() and write_bugs() below to fixup mapping from # your GNATS policies to Bugzilla. For example, how do the # Severity/Priority fields map to bug_severity/priority? # 4) Run this script. # 5) Fix the problems in the GNATS database identified in the output # script file gnats2bz_cleanup.sh. Fixing problems may be a job # for a custom perl script. If you make changes to GNATS, goto step 2. # 6) Examine the statistics in the output file gnats2bz_stats.txt. # These may indicate some more cleanup that is needed. For example, # you may find that there are invalid "State"s, or not a consistent # scheme for "Release"s. If you make changes to GNATS, goto step 2. # 7) Examine the output data file gnats2bz_data.sql. If problems # exist, goto step 2. # 8) Create a new, empty Bugzilla database. # 9) Import the data using the command: # mysql -uroot -p'ROOT_PASSWORD' bugs < gnats2bz_data.sql # 10) Update the shadow directory with the command: # cd $BUGZILLA_DIR; ./processmail regenerate # 11) Run a sanity check by visiting the sanitycheck.cgi page. # 12) Manually verify that the database is ok. If it is not, goto step 2. # # Important notes: # Confidential is not mapped or exported. # Submitter-Id is not mapped or exported. # # Design decisions: # This script generates a SQL script file rather than dumping the data # directly into the database. This is to allow the user to check # and/or modify the results before they are put into the database. # The PR number is very important and must be maintained as the Bugzilla # bug number, because there are many references to the PR number, such # as in code comments, CVS comments, customer communications, etc. # Reading ENUMERATED and TEXT fields: # 1) All leading and trailing whitespace is stripped. # Reading MULTITEXT fields: # 1) All leading blank lines are stripped. # 2) All trailing whitespace is stripped. # 3) Indentation is preserved. # Audit-Trail is not mapped to bugs_activity table, because there # is no place to put the "Why" text, which can have a fair amount # of information content. # # 15 January 2002 - changes from Andrea Dell'Amico # # * Adapted to the new database structure: now long_descs is a # separate table. # * Set a default for the target milestone, otherwise bugzilla # doesn't work with the imported database if milestones are used. # * In gnats version 3.113 records are separated by "|" and not ":". # * userid "1" is for the bugzilla administrator, so it's better to # start from 2. # use strict; # Suffix to be appended to username to make it an email address. my($username_suffix) = "\@platte.com"; # Default organization that should be ignored and not passed on to Bugzilla. # Only bugs that are reported outside of the default organization will have # their Originator,Organization fields passed on. # The assumption here is that if the Organization is identical to the # $default_organization, then the Originator will most likely be only an # alias for the From field in the mail header. my($default_organization) = "Platte River Associates|platte"; # Username for reporter field if unable to determine from mail header my($gnats_username) = "gnats\@platte.com"; # Flag indicating if cleanup file should use edit-pr or ${EDITOR}. # Using edit-pr is safer, but may be too slow if there are too many # PRs that need cleanup. If you use ${EDITOR}, then you must make # sure that you have exclusive access to the database, and that you # do not screw up any fields. my($cleanup_with_edit_pr) = 0; # Component name and description for bugs imported from GNATS. my($default_component) = "GNATS Import"; my($default_component_description) = "Bugs imported from GNATS."; # First generated userid. Start from 2: 1 is used for the bugzilla # administrator. my($userid_base) = 2; # Output filenames. my($cleanup_pathname) = "gnats2bz_cleanup.sh"; my($stats_pathname) = "gnats2bz_stats.txt"; my($data_pathname) = "gnats2bz_data.sql"; # List of ENUMERATED and TEXT fields. my(@text_fields) = qw(Number Category Synopsis Confidential Severity Priority Responsible State Class Submitter-Id Arrival-Date Originator Release); # List of MULTITEXT fields. my(@multitext_fields) = qw(Mail-Header Organization Environment Description How-To-Repeat Fix Audit-Trail Unformatted); # List of fields to report statistics for. my(@statistics_fields) = qw(Category Confidential Severity Priority Responsible State Class Submitter-Id Originator Organization Release Environment); # Array to hold list of GNATS PRs. my(@pr_list); # Array to hold list of GNATS categories. my(@categories_list); # Array to hold list of GNATS responsible users. my(@responsible_list); # Array to hold list of usernames. my(@username_list); # Put the gnats_username in first. get_userid($gnats_username); # Hash to hold list of versions. my(%versions_table); # Hash to hold contents of PR. my(%pr_data); # String to hold duplicate fields found during read of PR. my($pr_data_dup_fields) = ""; # String to hold badly labeled fields found during read of PR. # This usually happens when the user does not separate the field name # from the field data with whitespace. my($pr_data_bad_fields) = " "; # Hash to hold statistics (note that this a hash of hashes). my(%pr_stats); # Process commmand line. my($gnats_db_dir) = @ARGV; defined($gnats_db_dir) || die "gnats-db dir not specified"; (-d $gnats_db_dir) || die "$gnats_db_dir is not a directory"; # Load @pr_list from GNATS index file. my($index_pathname) = $gnats_db_dir . "/gnats-adm/index"; (-f $index_pathname) || die "$index_pathname not found"; print "Reading $index_pathname...\n"; if (!load_index($index_pathname)) { return(0); } # Load @category_list from GNATS categories file. my($categories_pathname) = $gnats_db_dir . "/gnats-adm/categories"; (-f $categories_pathname) || die "$categories_pathname not found"; print "Reading $categories_pathname...\n"; if (!load_categories($categories_pathname)) { return(0); } # Load @responsible_list from GNATS responsible file. my($responsible_pathname) = $gnats_db_dir . "/gnats-adm/responsible"; (-f $responsible_pathname) || die "$responsible_pathname not found"; print "Reading $responsible_pathname...\n"; if (!load_responsible($responsible_pathname)) { return(0); } # Open cleanup file. open(CLEANUP, ">$cleanup_pathname") || die "Unable to open $cleanup_pathname: $!"; chmod(0744, $cleanup_pathname) || warn "Unable to chmod $cleanup_pathname: $!"; print CLEANUP "#!/bin/sh\n"; print CLEANUP "# List of PRs that have problems found by gnats2bz.pl.\n"; # Open data file. open(DATA, ">$data_pathname") || die "Unable to open $data_pathname: $!"; print DATA "-- Exported data from $gnats_db_dir by gnats2bz.pl.\n"; print DATA "-- Load it into a Bugzilla database using the command:\n"; print DATA "-- mysql -uroot -p'ROOT_PASSWORD' bugs < gnats2bz_data.sql\n"; print DATA "--\n"; # Loop over @pr_list. my($pr); foreach $pr (@pr_list) { print "Processing $pr...\n"; if (!read_pr("$gnats_db_dir/$pr")) { next; } translate_pr(); check_pr($pr); collect_stats(); update_versions(); write_bugs(); write_longdescs(); } write_non_bugs_tables(); close(CLEANUP) || die "Unable to close $cleanup_pathname: $!"; close(DATA) || die "Unable to close $data_pathname: $!"; print "Generating $stats_pathname...\n"; report_stats(); sub load_index { my($pathname) = @_; my($record); my(@fields); open(INDEX, $pathname) || die "Unable to open $pathname: $!"; while ($record = ) { @fields = split(/\|/, $record); push(@pr_list, $fields[0]); } close(INDEX) || die "Unable to close $pathname: $!"; return(1); } sub load_categories { my($pathname) = @_; my($record); open(CATEGORIES, $pathname) || die "Unable to open $pathname: $!"; while ($record = ) { if ($record =~ /^#/) { next; } push(@categories_list, [split(/:/, $record)]); } close(CATEGORIES) || die "Unable to close $pathname: $!"; return(1); } sub load_responsible { my($pathname) = @_; my($record); open(RESPONSIBLE, $pathname) || die "Unable to open $pathname: $!"; while ($record = ) { if ($record =~ /^#/) { next; } push(@responsible_list, [split(/\|/, $record)]); } close(RESPONSIBLE) || die "Unable to close $pathname: $!"; return(1); } sub read_pr { my($pr_filename) = @_; my($multitext) = "Mail-Header"; my($field, $mail_header); # Empty the hash. %pr_data = (); # Empty the list of duplicate fields. $pr_data_dup_fields = ""; # Empty the list of badly labeled fields. $pr_data_bad_fields = ""; unless (open(PR, $pr_filename)) { warn "error opening $pr_filename: $!"; return(0); } LINELOOP: while () { chomp; if ($multitext eq "Unformatted") { # once we reach "Unformatted", rest of file goes there $pr_data{$multitext} = append_multitext($pr_data{$multitext}, $_); next LINELOOP; } # Handle ENUMERATED and TEXT fields. foreach $field (@text_fields) { if (/^>$field:($|\s+)/) { $pr_data{$field} = $'; # part of string after match $pr_data{$field} =~ s/\s+$//; # strip trailing whitespace $multitext = ""; next LINELOOP; } } # Handle MULTITEXT fields. foreach $field (@multitext_fields) { if (/^>$field:\s*$/) { $_ = $'; # set to part of string after match part if (defined($pr_data{$field})) { if ($pr_data_dup_fields eq "") { $pr_data_dup_fields = $field; } else { $pr_data_dup_fields = "$pr_data_dup_fields $field"; } } $pr_data{$field} = $_; $multitext = $field; next LINELOOP; } } # Check for badly labeled fields. foreach $field ((@text_fields, @multitext_fields)) { if (/^>$field:/) { if ($pr_data_bad_fields eq "") { $pr_data_bad_fields = $field; } else { $pr_data_bad_fields = "$pr_data_bad_fields $field"; } } } # Handle continued MULTITEXT field. $pr_data{$multitext} = append_multitext($pr_data{$multitext}, $_); } close(PR) || warn "error closing $pr_filename: $!"; # Strip trailing newlines from MULTITEXT fields. foreach $field (@multitext_fields) { if (defined($pr_data{$field})) { $pr_data{$field} =~ s/\s+$//; } } return(1); } sub append_multitext { my($original, $addition) = @_; if (defined($original) && $original ne "") { return "$original\n$addition"; } else { return $addition; } } sub check_pr { my($pr) = @_; my($error_list) = ""; if ($pr_data_dup_fields ne "") { $error_list = append_error($error_list, "Multiple '$pr_data_dup_fields'"); } if ($pr_data_bad_fields ne "") { $error_list = append_error($error_list, "Bad field labels '$pr_data_bad_fields'"); } if (!defined($pr_data{"Description"}) || $pr_data{"Description"} eq "") { $error_list = append_error($error_list, "Description empty"); } if (defined($pr_data{"Unformatted"}) && $pr_data{"Unformatted"} ne "") { $error_list = append_error($error_list, "Unformatted text"); } if (defined($pr_data{"Release"}) && length($pr_data{"Release"}) > 16) { $error_list = append_error($error_list, "Release > 16 chars"); } if (defined($pr_data{"Fix"}) && $pr_data{"Fix"} =~ /State-Changed-/) { $error_list = append_error($error_list, "Audit in Fix field"); } if (defined($pr_data{"Arrival-Date"})) { if ($pr_data{"Arrival-Date"} eq "") { $error_list = append_error($error_list, "Arrival-Date empty"); } elsif (unixdate2datetime($pr, $pr_data{"Arrival-Date"}) eq "") { $error_list = append_error($error_list, "Arrival-Date format"); } } # More checks should go here. if ($error_list ne "") { if ($cleanup_with_edit_pr) { my(@parts) = split("/", $pr); my($pr_num) = $parts[1]; print CLEANUP "echo \"$error_list\"; edit-pr $pr_num\n"; } else { print CLEANUP "echo \"$error_list\"; \${EDITOR} $pr\n"; } } } sub append_error { my($original, $addition) = @_; if ($original ne "") { return "$original, $addition"; } else { return $addition; } } sub translate_pr { # This function performs GNATS -> Bugzilla translations that should # happen before collect_stats(). if (!defined($pr_data{"Organization"})) { $pr_data{"Originator"} = ""; } if ($pr_data{"Organization"} =~ /$default_organization/) { $pr_data{"Originator"} = ""; $pr_data{"Organization"} = ""; } $pr_data{"Organization"} =~ s/^\s+//g; # strip leading whitespace if (!defined($pr_data{"Release"}) || $pr_data{"Release"} eq "" || $pr_data{"Release"} =~ /^unknown-1.0$/ ) { $pr_data{"Release"} = "unknown"; } if (defined($pr_data{"Responsible"})) { $pr_data{"Responsible"} =~ /\w+/; $pr_data{"Responsible"} = "$&$username_suffix"; } my($rep_platform, $op_sys) = ("All", "All"); if (defined($pr_data{"Environment"})) { if ($pr_data{"Environment"} =~ /[wW]in.*NT/) { $rep_platform = "PC"; $op_sys = "Windows NT"; } elsif ($pr_data{"Environment"} =~ /[wW]in.*95/) { $rep_platform = "PC"; $op_sys = "Windows 95"; } elsif ($pr_data{"Environment"} =~ /[wW]in.*98/) { $rep_platform = "PC"; $op_sys = "Windows 98"; } elsif ($pr_data{"Environment"} =~ /OSF/) { $rep_platform = "DEC"; $op_sys = "OSF/1"; } elsif ($pr_data{"Environment"} =~ /AIX/) { $rep_platform = "RS/6000"; $op_sys = "AIX"; } elsif ($pr_data{"Environment"} =~ /IRIX/) { $rep_platform = "SGI"; $op_sys = "IRIX"; } elsif ($pr_data{"Environment"} =~ /SunOS.*5\.\d/) { $rep_platform = "Sun"; $op_sys = "Solaris"; } elsif ($pr_data{"Environment"} =~ /SunOS.*4\.\d/) { $rep_platform = "Sun"; $op_sys = "SunOS"; } } $pr_data{"Environment"} = "$rep_platform:$op_sys"; } sub collect_stats { my($field, $value); foreach $field (@statistics_fields) { $value = $pr_data{$field}; if (!defined($value)) { $value = ""; } if (defined($pr_stats{$field}{$value})) { $pr_stats{$field}{$value}++; } else { $pr_stats{$field}{$value} = 1; } } } sub report_stats { my($field, $value, $count); open(STATS, ">$stats_pathname") || die "Unable to open $stats_pathname: $!"; print STATS "Statistics of $gnats_db_dir collated by gnats2bz.pl.\n"; my($field_stats); while (($field, $field_stats) = each(%pr_stats)) { print STATS "\n$field:\n"; while (($value, $count) = each(%$field_stats)) { print STATS " $value: $count\n"; } } close(STATS) || die "Unable to close $stats_pathname: $!"; } sub get_userid { my($responsible) = @_; my($username, $userid); if (!defined($responsible)) { return(-1); } # Search for current username in the list. $userid = $userid_base; foreach $username (@username_list) { if ($username eq $responsible) { return($userid); } $userid++; } push(@username_list, $responsible); return($userid); } sub update_versions { if (!defined($pr_data{"Release"}) || !defined($pr_data{"Category"})) { return; } my($curr_product) = $pr_data{"Category"}; my($curr_version) = $pr_data{"Release"}; if ($curr_version eq "") { return; } if (!defined($versions_table{$curr_product})) { $versions_table{$curr_product} = [ ]; } my($version_list) = $versions_table{$curr_product}; my($version); foreach $version (@$version_list) { if ($version eq $curr_version) { return; } } push(@$version_list, $curr_version); } sub write_bugs { my($bug_id) = $pr_data{"Number"}; my($userid) = get_userid($pr_data{"Responsible"}); # Mapping from Class,Severity to bug_severity # At our site, the Severity,Priority fields have degenerated # into a 9-level priority field. my($bug_severity) = "normal"; if ($pr_data{"Class"} eq "change-request") { $bug_severity = "enhancement"; } elsif (defined($pr_data{"Synopsis"})) { if ($pr_data{"Synopsis"} =~ /crash|assert/i) { $bug_severity = "critical"; } elsif ($pr_data{"Synopsis"} =~ /wrong|error/i) { $bug_severity = "major"; } } $bug_severity = SqlQuote($bug_severity); # Mapping from Severity,Priority to priority # At our site, the Severity,Priority fields have degenerated # into a 9-level priority field. my($priority) = "P1"; if (defined($pr_data{"Severity"}) && defined($pr_data{"Severity"})) { if ($pr_data{"Severity"} eq "critical") { if ($pr_data{"Priority"} eq "high") { $priority = "P1"; } else { $priority = "P2"; } } elsif ($pr_data{"Severity"} eq "serious") { if ($pr_data{"Priority"} eq "low") { $priority = "P4"; } else { $priority = "P3"; } } else { if ($pr_data{"Priority"} eq "high") { $priority = "P4"; } else { $priority = "P5"; } } } $priority = SqlQuote($priority); # Map State,Class to bug_status,resolution my($bug_status, $resolution); if ($pr_data{"State"} eq "open" || $pr_data{"State"} eq "analyzed") { $bug_status = "ASSIGNED"; $resolution = ""; } elsif ($pr_data{"State"} eq "feedback") { $bug_status = "RESOLVED"; $resolution = "FIXED"; } elsif ($pr_data{"State"} eq "closed") { $bug_status = "CLOSED"; if (defined($pr_data{"Class"}) && $pr_data{"Class"} =~ /^duplicate/) { $resolution = "DUPLICATE"; } elsif (defined($pr_data{"Class"}) && $pr_data{"Class"} =~ /^mistaken/) { $resolution = "INVALID"; } else { $resolution = "FIXED"; } } elsif ($pr_data{"State"} eq "suspended") { $bug_status = "RESOLVED"; $resolution = "LATER"; } else { $bug_status = "NEW"; $resolution = ""; } $bug_status = SqlQuote($bug_status); $resolution = SqlQuote($resolution); my($creation_ts) = ""; if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") { $creation_ts = unixdate2datetime($bug_id, $pr_data{"Arrival-Date"}); } $creation_ts = SqlQuote($creation_ts); my($delta_ts) = ""; if (defined($pr_data{"Audit-Trail"})) { # note that (?:.|\n)+ is greedy, so this should match the # last Changed-When if ($pr_data{"Audit-Trail"} =~ /(?:.|\n)+-Changed-When: (.+)/) { $delta_ts = unixdate2timestamp($bug_id, $1); } } if ($delta_ts eq "") { if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") { $delta_ts = unixdate2timestamp($bug_id, $pr_data{"Arrival-Date"}); } } $delta_ts = SqlQuote($delta_ts); my($short_desc) = SqlQuote($pr_data{"Synopsis"}); my($rep_platform, $op_sys) = split(/\|/, $pr_data{"Environment"}); $rep_platform = SqlQuote($rep_platform); $op_sys = SqlQuote($op_sys); my($reporter) = get_userid($gnats_username); if ( defined($pr_data{"Mail-Header"}) && $pr_data{"Mail-Header"} =~ /From ([\w.]+\@[\w.]+)/ ) { $reporter = get_userid($1); } my($version) = ""; if (defined($pr_data{"Release"})) { $version = substr($pr_data{"Release"}, 0, 16); } $version = SqlQuote($version); my($product) = ""; if (defined($pr_data{"Category"})) { $product = $pr_data{"Category"}; } $product = SqlQuote($product); my($component) = SqlQuote($default_component); my($target_milestone) = "0"; # $target_milestone = SqlQuote($target_milestone); my($qa_contact) = "0"; # my($bug_file_loc) = ""; # $bug_file_loc = SqlQuote($bug_file_loc); # my($status_whiteboard) = ""; # $status_whiteboard = SqlQuote($status_whiteboard); print DATA "\ninsert into bugs (\n"; print DATA " bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts,\n"; print DATA " short_desc,\n"; print DATA " rep_platform, op_sys, reporter, version,\n"; print DATA " product, component, resolution, target_milestone, qa_contact\n"; print DATA ") values (\n"; print DATA " $bug_id, $userid, $bug_severity, $priority, $bug_status, $creation_ts, $delta_ts,\n"; print DATA " $short_desc,\n"; print DATA " $rep_platform, $op_sys, $reporter, $version,\n"; print DATA " $product, $component, $resolution, $target_milestone, $qa_contact\n"; print DATA ");\n"; } sub write_longdescs { my($bug_id) = $pr_data{"Number"}; my($who) = get_userid($pr_data{"Responsible"});; my($bug_when) = ""; if (defined($pr_data{"Arrival-Date"}) && $pr_data{"Arrival-Date"} ne "") { $bug_when = unixdate2datetime($bug_id, $pr_data{"Arrival-Date"}); } $bug_when = SqlQuote($bug_when); my($thetext) = $pr_data{"Description"}; if (defined($pr_data{"How-To-Repeat"}) && $pr_data{"How-To-Repeat"} ne "") { $thetext = $thetext . "\n\nHow-To-Repeat:\n" . $pr_data{"How-To-Repeat"}; } if (defined($pr_data{"Fix"}) && $pr_data{"Fix"} ne "") { $thetext = $thetext . "\n\nFix:\n" . $pr_data{"Fix"}; } if (defined($pr_data{"Originator"}) && $pr_data{"Originator"} ne "") { $thetext = $thetext . "\n\nOriginator:\n" . $pr_data{"Originator"}; } if (defined($pr_data{"Organization"}) && $pr_data{"Organization"} ne "") { $thetext = $thetext . "\n\nOrganization:\n" . $pr_data{"Organization"}; } if (defined($pr_data{"Audit-Trail"}) && $pr_data{"Audit-Trail"} ne "") { $thetext = $thetext . "\n\nAudit-Trail:\n" . $pr_data{"Audit-Trail"}; } if (defined($pr_data{"Unformatted"}) && $pr_data{"Unformatted"} ne "") { $thetext = $thetext . "\n\nUnformatted:\n" . $pr_data{"Unformatted"}; } $thetext = SqlQuote($thetext); print DATA "\ninsert into longdescs (\n"; print DATA " bug_id, who, bug_when, thetext\n"; print DATA ") values (\n"; print DATA " $bug_id, $who, $bug_when, $thetext\n"; print DATA ");\n"; } sub write_non_bugs_tables { my($categories_record); foreach $categories_record (@categories_list) { my($component) = SqlQuote($default_component); my($product) = SqlQuote(@$categories_record[0]); my($description) = SqlQuote(@$categories_record[1]); my($initialowner) = SqlQuote(@$categories_record[2] . $username_suffix); print DATA "\ninsert into products (\n"; print DATA " product, description, milestoneurl, disallownew\n"; print DATA ") values (\n"; print DATA " $product, $description, '', 0\n"; print DATA ");\n"; print DATA "\ninsert into components (\n"; print DATA " value, program, initialowner, initialqacontact, description\n"; print DATA ") values (\n"; print DATA " $component, $product, $initialowner, '', $description\n"; print DATA ");\n"; print DATA "\ninsert into milestones (\n"; print DATA " value, product, sortkey\n"; print DATA ") values (\n"; print DATA " 0, $product, 0\n"; print DATA ");\n"; } my($username); my($userid) = $userid_base; my($password) = "password"; my($realname); my($groupset) = 0; foreach $username (@username_list) { $realname = map_username_to_realname($username); $username = SqlQuote($username); $realname = SqlQuote($realname); print DATA "\ninsert into profiles (\n"; print DATA " userid, login_name, cryptpassword, realname, groupset\n"; print DATA ") values (\n"; print DATA " $userid, $username, encrypt('$password'), $realname, $groupset\n"; print DATA ");\n"; $userid++; } my($product); my($version_list); while (($product, $version_list) = each(%versions_table)) { $product = SqlQuote($product); my($version); foreach $version (@$version_list) { $version = SqlQuote($version); print DATA "\ninsert into versions (value, program) "; print DATA "values ($version, $product);\n"; } } } sub map_username_to_realname() { my($username) = @_; my($name, $realname); # get the portion before the @ $name = $username; $name =~ s/\@.*//; my($responsible_record); foreach $responsible_record (@responsible_list) { if (@$responsible_record[0] eq $name) { return(@$responsible_record[1]); } if (defined(@$responsible_record[2])) { if (@$responsible_record[2] eq $username) { return(@$responsible_record[1]); } } } return(""); } # This routine was copied from globals.pl which was largely copied # from Mysql.pm. sub detaint_string { my ($str) = @_; $str =~ m/^(.*)$/s; $str = $1; } sub SqlQuote { my ($str) = (@_); $str =~ s/([\\\'])/\\$1/g; $str =~ s/\0/\\0/g; # If it's been SqlQuote()ed, then it's safe, so we tell -T that. $str = detaint_string($str); return "'$str'"; } sub unixdate2datetime { my($bugid, $unixdate) = @_; my($year, $month, $day, $hour, $min, $sec); if (!split_unixdate($bugid, $unixdate, \$year, \$month, \$day, \$hour, \$min, \$sec)) { return(""); } return("$year-$month-$day $hour:$min:$sec"); } sub unixdate2timestamp { my($bugid, $unixdate) = @_; my($year, $month, $day, $hour, $min, $sec); if (!split_unixdate($bugid, $unixdate, \$year, \$month, \$day, \$hour, \$min, \$sec)) { return(""); } return("$year$month$day$hour$min$sec"); } sub split_unixdate { # "Tue Jun 6 14:50:00 1995" # "Mon Nov 20 17:03:11 [MST] 1995" # "12/13/94" # "jan 1, 1995" my($bugid, $unixdate, $year, $month, $day, $hour, $min, $sec) = @_; my(@parts); $$hour = "00"; $$min = "00"; $$sec = "00"; @parts = split(/ +/, $unixdate); if (@parts >= 5) { # year $$year = $parts[4]; if ($$year =~ /[A-Z]{3}/) { # Must be timezone, try next field. $$year = $parts[5]; } if ($$year =~ /\D/) { warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n"; return(0); } if ($$year < 30) { $$year = "20" . $$year; } elsif ($$year < 100) { $$year = "19" . $$year; } elsif ($$year < 1970 || $$year > 2029) { warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n"; return(0); } # month $$month = $parts[1]; if ($$month =~ /\D/) { if (!month2number($month)) { warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n"; return(0); } } elsif ($$month < 1 || $$month > 12) { warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n"; return(0); } elsif (length($$month) == 1) { $$month = "0" . $$month; } # day $$day = $parts[2]; if ($$day < 1 || $$day > 31) { warn "$bugid: Error processing day part '$day' of date '$unixdate'\n"; return(0); } elsif (length($$day) == 1) { $$day = "0" . $$day; } @parts = split(/:/, $parts[3]); $$hour = $parts[0]; $$min = $parts[1]; $$sec = $parts[2]; return(1); } elsif (@parts == 3) { # year $$year = $parts[2]; if ($$year =~ /\D/) { warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n"; return(0); } if ($$year < 30) { $$year = "20" . $$year; } elsif ($$year < 100) { $$year = "19" . $$year; } elsif ($$year < 1970 || $$year > 2029) { warn "$bugid: Error processing year part '$$year' of date '$unixdate'\n"; return(0); } # month $$month = $parts[0]; if ($$month =~ /\D/) { if (!month2number($month)) { warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n"; return(0); } } elsif ($$month < 1 || $$month > 12) { warn "$bugid: Error processing month part '$$month' of date '$unixdate'\n"; return(0); } elsif (length($$month) == 1) { $$month = "0" . $$month; } # day $$day = $parts[1]; $$day =~ s/,//; if ($$day < 1 || $$day > 31) { warn "$bugid: Error processing day part '$day' of date '$unixdate'\n"; return(0); } elsif (length($$day) == 1) { $$day = "0" . $$day; } return(1); } @parts = split(/:/, $unixdate); if (@parts == 3 && length($unixdate) <= 8) { $$year = "19" . $parts[2]; $$month = $parts[0]; if (length($$month) == 1) { $$month = "0" . $$month; } $$day = $parts[1]; if (length($$day) == 1) { $$day = "0" . $$day; } return(1); } warn "$bugid: Error processing date '$unixdate'\n"; return(0); } sub month2number { my($month) = @_; if ($$month =~ /jan/i) { $$month = "01"; } elsif ($$month =~ /feb/i) { $$month = "02"; } elsif ($$month =~ /mar/i) { $$month = "03"; } elsif ($$month =~ /apr/i) { $$month = "04"; } elsif ($$month =~ /may/i) { $$month = "05"; } elsif ($$month =~ /jun/i) { $$month = "06"; } elsif ($$month =~ /jul/i) { $$month = "07"; } elsif ($$month =~ /aug/i) { $$month = "08"; } elsif ($$month =~ /sep/i) { $$month = "09"; } elsif ($$month =~ /oct/i) { $$month = "10"; } elsif ($$month =~ /nov/i) { $$month = "11"; } elsif ($$month =~ /dec/i) { $$month = "12"; } else { return(0); } return(1); }