]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
This is a perl script to help import bugs from a GNATS database into a
authorterry%mozilla.org <>
Wed, 22 Sep 1999 03:12:46 +0000 (03:12 +0000)
committerterry%mozilla.org <>
Wed, 22 Sep 1999 03:12:46 +0000 (03:12 +0000)
Bugzilla database.  Contributed by Tom Schutter <tom@platte.com>

contrib/gnats2bz.pl [new file with mode: 0644]

diff --git a/contrib/gnats2bz.pl b/contrib/gnats2bz.pl
new file mode 100644 (file)
index 0000000..449fb44
--- /dev/null
@@ -0,0 +1,1012 @@
+#!/usr/bin/perl -w
+# -*- Mode: perl; indent-tabs-mode: nil -*-
+#
+# The contents of this file are subject to the Mozilla Public License
+# Version 1.0 (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.
+# 
+# Contributor(s): Tom Schutter <tom@platte.com>
+
+
+# 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' < gnats2bz_data.sql
+#   10) 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.
+#
+
+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;
+
+# First generated userid.
+my($userid_base) = 10;
+
+# 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_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 = <INDEX>) {
+        @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 = <CATEGORIES>) {
+        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 = <RESPONSIBLE>) {
+        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 (<PR>) {
+        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($long_desc) = $pr_data{"Description"};
+    if (defined($pr_data{"How-To-Repeat"}) && $pr_data{"How-To-Repeat"} ne "") {
+        $long_desc =
+            $long_desc . "\n\nHow-To-Repeat:\n" . $pr_data{"How-To-Repeat"};
+    }
+    if (defined($pr_data{"Fix"}) && $pr_data{"Fix"} ne "") {
+        $long_desc = $long_desc . "\n\nFix:\n" . $pr_data{"Fix"};
+    }
+    if (defined($pr_data{"Originator"}) && $pr_data{"Originator"} ne "") {
+        $long_desc = $long_desc . "\n\nOriginator:\n" . $pr_data{"Originator"};
+    }
+    if (defined($pr_data{"Organization"}) && $pr_data{"Organization"} ne "") {
+        $long_desc = $long_desc . "\n\nOrganization:\n" . $pr_data{"Organization"};
+    }
+    if (defined($pr_data{"Audit-Trail"}) && $pr_data{"Audit-Trail"} ne "") {
+        $long_desc = $long_desc . "\n\nAudit-Trail:\n" . $pr_data{"Audit-Trail"};
+    }
+    if (defined($pr_data{"Unformatted"}) && $pr_data{"Unformatted"} ne "") {
+        $long_desc = $long_desc . "\n\nUnformatted:\n" . $pr_data{"Unformatted"};
+    }
+    $long_desc = SqlQuote($long_desc);
+
+    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) = "";
+    my($component) = "";
+    if (defined($pr_data{"Category"})) {
+        $product = $pr_data{"Category"};
+        $component = $product;
+    }
+    $product = SqlQuote($product);
+    $component = SqlQuote($component);
+
+    my($target_milestone) = "";
+    $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 "  long_desc\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 "  $long_desc\n";
+    print DATA ");\n";
+}
+
+sub write_non_bugs_tables {
+
+    my($categories_record);
+    foreach $categories_record (@categories_list) {
+        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, 'NULL', 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
+            "  $product, $product, $initialowner, 'NULL', $description\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, password, cryptpassword, realname, groupset\n";
+        print DATA ") values (\n";
+        print DATA
+            "  $userid, $username, '$password', 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 SqlQuote {
+    my($str) = @_;
+    $str =~ s/([\\\'])/\\$1/g;
+    $str =~ s/\0/\\0/g;
+    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);
+}
+