Cleaned up code a bit.
authorAndrew DeFaria <Andrew@DeFaria.com>
Wed, 30 Aug 2017 23:39:36 +0000 (16:39 -0700)
committerAndrew DeFaria <Andrew@DeFaria.com>
Wed, 30 Aug 2017 23:39:36 +0000 (16:39 -0700)
JIRA/importComments.pl
JIRA/lib/BugzillaUtils.pm
JIRA/lib/JIRAUtils.pm

index 6a1712c..ed6447c 100644 (file)
@@ -94,7 +94,7 @@ our ($log, %total);
 
 sub sanitize ($) {
   my ($str) = @_;
-  
+
   my $p4web    = 'http://p4web.audience.local:8080/@md=d&cd=//&c=vLW@/';
   my $bugzilla = 'http://bugs.audience.com/show_bug.cgi?id=';
 
@@ -159,7 +159,7 @@ sub addComments ($$) {
 
 sub main () {
   my $startTime = time;
-  
+
   GetOptions (
     \%opts,
     'verbose',
@@ -182,24 +182,24 @@ sub main () {
   if ($opts{file}) {
     open my $file, '<', $opts{file} 
       or $log->err ("Unable to open $opts{file} - $!", 1);
-      
+
     $opts{bugids} = [<$file>];
-    
+
     chomp @{$opts{bugids}};
   } else {
     my @bugids;
-    
+
     push @bugids, (split /,/, join (',', $_)) for (@{$opts{bugids}}); 
-  
+
     $opts{bugids} = [@bugids];
   } # if
-  
+
   pod2usage 'Must specify -bugids <bugid>[,<bugid>,...] or -file <filename>'
     unless $opts{bugids};
-  
+
   openBugzilla $opts{bugzillaserver}
     or $log->err ("Unable to connect to $opts{bugzillaserver}", 1);
-  
+
   Connect2JIRA ($opts{username}, $opts{password}, $opts{jiraserver})
     or $log->err ("Unable to connect to $opts{jiraserver}", 1);
 
@@ -207,10 +207,10 @@ sub main () {
 
   for (@{$opts{bugids}}) {
     my $jiraIssue = findIssue $_;
-    
+
     if ($jiraIssue =~ /^[A-Z]{1,5}-\d+$/) {
       my $result = addComments $jiraIssue, $_;
-      
+
       if ($result =~ /^Unable/) {
         $total{'Comment failures'}++;
 
@@ -222,13 +222,13 @@ sub main () {
       } # if
     } else {
       $total{'Missing JIRA Issues'}++;
-      
+
       $log->err ("Unable to find JIRA Issue for Bug $_");
     } # if
   } # for
 
   display_duration $startTime, $log;
-  
+
   Stats (\%total, $log) unless $opts{quiet};
 
   return 0;
index 899f506..7a825c2 100644 (file)
@@ -91,10 +91,10 @@ sub openBugzilla (;$$$$) {
   $dbhost //= 'jira-dev';
   $dbname //= 'bugzilla';
   $dbuser //= 'adefaria';
-  $dbpass //= 'reader';
-  
+  $dbpass //= '********';
+
   $main::log->msg ("Connecting to Bugzilla ($dbuser\@$dbhost)");
-  
+
   $bugzilla = DBI->connect (
     "DBI:mysql:$dbname:$dbhost",
     $dbuser,
@@ -103,7 +103,7 @@ sub openBugzilla (;$$$$) {
       RaiseError => 1,
     },
   );
-  
+
   _checkDBError 'Unable to execute statement', 'Connect';
 
   return $bugzilla;
@@ -111,16 +111,16 @@ sub openBugzilla (;$$$$) {
 
 sub getBug ($;@) {
   my ($bugid, @fields) = @_;
-  
+
   push @fields, 'short_desc' unless @fields;
-  
+
   my $statement = 'select ' . join (',', @fields) .
                   " from bugs where bug_id = $bugid";
-                  
+
   my $sth = $bugzilla->prepare ($statement);
 
   _checkDBError 'Unable to prepare statement', $statement;
-  
+
   _checkDBError 'Unable to execute statement', $statement;
 
   $sth->execute;
@@ -130,7 +130,7 @@ sub getBug ($;@) {
 
 sub getBugComments ($) {
   my ($bugid) = @_;
-  
+
   my $statement = <<"END";
 select 
   bug_id, 
@@ -144,17 +144,17 @@ where
   who    = userid and
   bug_id = $bugid 
 END
-  
+
   my $sth = $bugzilla->prepare ($statement);
-  
+
   _checkDBError 'Unable to prepare statement', $statement;
 
   $sth->execute;
-  
+
   _checkDBError 'Unable to execute statement', $statement;
-  
+
   my @comments;
-  
+
   while (my $comment = $sth->fetchrow_hashref) {
     my $commentText = <<"END";
 The following comment was entered by [~$comment->{username}] on $comment->{bug_when}:
@@ -164,75 +164,75 @@ END
 
     push @comments, $commentText;
   } # while
-  
+
   return \@comments;
 } # getBugComments
 
 sub getRelationships ($$$$@) {
   my ($table, $returnField, $testField, $relationshipType, @bugs) = @_;
-  
+
   $main::log->msg ("Getting $relationshipType");
-  
+
   my $statement = "select $returnField from $table where $table.$testField = ?";
 
   my $sth = $bugzilla->prepare ($statement);
 
   _checkDBError 'Unable to prepare statement', $statement;
-  
+
   my %relationships;
 
   my %bugmap;
-  
+
   map {$bugmap{$_} = 1} @bugs unless %bugmap;
-      
+
   for my $bugid (@bugs) {
     $sth->execute ($bugid);
-    
+
     _checkDBError 'Unable to exit statement', $statement;
-    
+
     my $result = JIRAUtils::findIssue ($bugid, %bugmap);
-    
+
     if ($result =~ /^Unable/) {
       $main::log->warn ($result);
-      
+
       $main::total{'Missing JIRA Issues'}++;
-      
+
       undef $result;
     } elsif ($result =~ /^Future/) {
       $main::total{'Future JIRA Issues'}++;
-      
+
       undef $result;
     } # if
-    
+
     my $jiraIssue = $result;
     my $key       = $jiraIssue || $bugid;
 
     my @relationships;
     my $relations = $sth->fetchall_arrayref;
     my @relations;
-    
+
     map {push @relations, $_->[0]} @$relations;
-    
+
     for my $relation (@relations) {
       $jiraIssue = JIRAUtils::findIssue ($relation);
-      
+
       if ($jiraIssue =~ /^Unable/ || $jiraIssue =~ /^Future/) {
         $main::log->warn ($jiraIssue);
 
         $main::total{'Missing JIRA Issues'}++ if $jiraIssue =~ /^Unable/;
         $main::total{'Future JIRA Issues'}++  if $jiraIssue =~ /^Future/;
-        
+
         push @relationships, $relation;
       } else {
         push @relationships, $jiraIssue;
       } # if
     } # for
-      
+
     push @{$relationships{$key}}, @relationships if @relationships;
   } # for
-  
+
   $main::total{$relationshipType} = keys %relationships;
-  
+
   return \%relationships;
 } # getRelationships
 
@@ -250,7 +250,7 @@ sub getDependencies (@) {
 
 sub getBlockers (@) {
   my (@bugs) = @_;
-  
+
   return getRelationships (
     'dependencies', 
     'blocked', 
@@ -274,7 +274,7 @@ sub getDuplicates (@) {
 
 sub getRelated (@) {
   my (@bugs) = @_;
-  
+
   return getRelationships (
     'bug_see_also', 
     'value', 
@@ -286,7 +286,7 @@ sub getRelated (@) {
 
 sub getWatchers ($) {
   my ($bugid) = @_;
-  
+
   my $statement = <<"END";
 select 
   profiles.login_name
index 70629aa..5caefa5 100644 (file)
@@ -203,14 +203,14 @@ sub _checkDBError ($;$) {
   my ($msg, $statement) = @_;
 
   $statement //= 'Unknown';
-  
+
   if ($main::log) {
    $main::log->err ('JIRA database not opened!', 1) unless $jiradb;
   } # if
-  
+
   my $dberr    = $jiradb->err;
   my $dberrmsg = $jiradb->errstr;
-  
+
   $dberr    ||= 0;
   $dberrmsg ||= 'Success';
 
@@ -285,10 +285,10 @@ Handle for database
   $dbhost //= $main::opts{jiradbhost};
   $dbname //= 'jiradb';
   $dbuser //= 'root';
-  $dbpass //= 'r00t';
-  
+  $dbpass //= '*********';
+
   $main::log->msg ("Connecting to JIRA ($dbuser\@$dbhost)...") if $main::log;
-  
+
   $jiradb = DBI->connect (
     "DBI:mysql:$dbname:$dbhost",
     $dbuser,
@@ -299,7 +299,7 @@ Handle for database
   );
 
   _checkDBError "Unable to open $dbname ($dbuser\@$dbhost)";
-  
+
   return $jiradb;
 } # openJIRADB
 
@@ -351,19 +351,19 @@ JIRA REST handle
 =cut
 
   my %opts;
-  
+
   $opts{username} = $username || 'jira-admin';
-  $opts{password} = $password || $ENV{PASSWORD}    || 'jira-admin';
+  $opts{password} = $password || $ENV{PASSWORD}    || '********';
   $opts{server}   = $server   || $ENV{JIRA_SERVER} || 'jira-dev';
   $opts{URL}      = "http://$opts{server}/rest/api/latest";
-  
+
   $main::log->msg ("Connecting to JIRA ($opts{username}\@$opts{server})") if $main::log;
-  
+
   $jira = JIRA::REST->new ($opts{URL}, $opts{username}, $opts{password});
 
   # Store username as we might need it (see updateIssueWatchers)
   $jira->{username} = $opts{username};
-  
+
   return $jira;  
 } # Connect2JIRA
 
@@ -411,7 +411,7 @@ Count of qualifying entries
 =cut
 
   my $statement;
-  
+
   $jiradb = openJIRADB unless $jiradb;
 
   if ($condition) {
@@ -421,11 +421,11 @@ Count of qualifying entries
   } # if
 
   my $sth = $jiradb->prepare ($statement);
-  
+
   _checkDBError 'count: Unable to prepare statement', $statement;
 
   $sth->execute;
-  
+
   _checkDBError 'count: Unable to execute statement', $statement;
 
   # Get return value, which should be how many message there are
@@ -538,10 +538,10 @@ Returns:
 =for html </blockquote>
 
 =cut
-  
+
   if ($main::opts{exec}) {
     eval {$jira->POST ("/issue/$issue/comment", undef, { body => $comment })};
-  
+
     if ($@) {
       return "Unable to add comment\n$@";
     } else {
@@ -554,10 +554,10 @@ Returns:
 
 sub blankBugzillaNbr ($) {
   my ($issue) = @_;
-  
+
   eval {$jira->PUT ("/issue/$issue", undef, {fields => {'Bugzilla Bug Origin' => ''}})};
   #eval {$jira->PUT ("/issue/$issue", undef, {fields => {'customfield_10132' => ''}})};
-  
+
   if ($@) {
     return "Unable to blank Bugzilla number$@\n"
   } else {
@@ -567,7 +567,7 @@ sub blankBugzillaNbr ($) {
 
 sub attachmentExists ($$) {
   my ($issue, $filename) = @_;
-  
+
 =pod
 
 =head2 attachmentExists ()
@@ -607,11 +607,11 @@ Returns:
 =cut
 
   my $attachments = getIssue ($issue, qw(attachment));
-  
+
   for (@{$attachments->{fields}{attachment}}) {
     return 1 if $filename eq $_->{filename};
   } # for
-  
+
   return 0;
 } # attachmentExists
 
@@ -657,31 +657,31 @@ Returns:
 =cut  
 
   my $status = $jira->attach_files ($issue, @files);
-  
+
   return $status;
 } # attachFiles2Issue
 
 sub getIssueFromBugID ($) {
   my ($bugid) = @_;
-  
+
   my $issue;
-  
+
   my %query = (
     jql    => "\"Bugzilla Bug Origin\" ~ $bugid",
     fields => [ 'key' ],
   );
-  
+
   eval {$issue = $jira->GET ("/search/", \%query)};
 
   my $issueID = $issue->{issues}[0]{key};
-  
+
   return $issue->{issues} if @{$issue->{issues}} > 1;
   return $issueID;
 } # getIssueFromBugID
 
 sub findIssue ($%) {
   my ($bugid, %bugmap) = @_;
-  
+
 =pod
 
   # Check the cache...
@@ -704,69 +704,69 @@ sub findIssue ($%) {
     jql    => "\"Bugzilla Bug Origin\" ~ $bugid",
     fields => [ 'key' ],
   );
-  
+
   eval {$issue = $jira->GET ("/search/", \%query)};
 
   my $issueID = $issue->{issues}[0]{key};
-  
+
   if (@{$issue->{issues}} > 2) {
     $main::log->err ("Found more than 2 issues for Bug ID $bugid") if $main::log;
-    
+
     return "Found more than 2 issues for Bug ID $bugid";
   } elsif (@{$issue->{issues}} == 2) {
     my ($issueNum0, $issueNum1, $projectName0, $projectName1);
-    
+
     if ($issue->{issues}[0]{key} =~ /(.*)-(\d+)/) {
       $projectName0 = $1;
       $issueNum0    = $2;
     } # if
-    
+
     if ($issue->{issues}[1]{key} =~ /(.*)-(\d+)/) {
       $projectName1 = $1;
       $issueNum1    = $2;
     } # if
-    
+
     if ($issueNum0 < $issueNum1) {
       $issueID = $issue->{issues}[1]{key};
     } # if
-    
+
     # Let's mark them as clones. See if this clone link already exists...
     my $alreadyCloned;
-    
+
     for (getIssueLinks ($issueID, 'Cloners')) {
       my $inwardIssue  = $_->{inwardIssue}{key}  || '';
       my $outwardIssue = $_->{outwardIssue}{key} || '';
-      
+
       if ("$projectName0-$issueNum0" eq $inwardIssue  ||
           "$projectName0-$issueNum0" eq $outwardIssue ||
           "$projectName1-$issueNum1" eq $inwardIssue  ||
           "$projectName1-$issueNum1" eq $outwardIssue) {
          $alreadyCloned = 1;
-         
+
          last;
       } # if
     } # for
 
     unless ($alreadyCloned) {
       my $result = linkIssues ("$projectName0-$issueNum0", 'Cloners', "$projectName1-$issueNum1");
-    
+
       return $result if $result =~ /Unable to/;
-    
+
       $main::log->msg ($result) if $main::log;
     } # unless
   } # if
 
   if ($issueID) {
     $main::log->msg ("Found JIRA issue $issueID for Bug $bugid") if $main::log;
-  
+
     #$cache{$bugid} = $issueID;
-      
+
     #return $cache{$bugid};
     return $issueID;
   } else {
     my $status = $bugmap{$bugid} ? 'Future JIRA Issue'
                                  : "Unable to find a JIRA issue for Bug $bugid";
-    
+
     # Here we put this bugid into the cache but instead of a the JIRA issue
     # id we put the bugid. This will stop us from adding up multiple hits on
     # this bugid.
@@ -818,18 +818,18 @@ Returns:
 =cut  
 
   push @fields, '*all' unless @fields;
-  
+
   $findQuery{jql}        = $condition || '';
   $findQuery{startAt}    = 0;
   $findQuery{maxResults} = 1;
   $findQuery{fields}     = join ',', @fields;
-  
+
   return;
 } # findIssues
 
 sub getNextIssue () {
   my $result;
-  
+
 =pod
 
 =head2 getNextIssue ()
@@ -863,23 +863,23 @@ Perl hash of the fields in the next JIRA issue
 =for html </blockquote>
 
 =cut
-  
+
   eval {$result = $jira->GET ('/search/', \%findQuery)};
-  
+
   $findQuery{startAt}++;
-  
+
   # Move id and key into fields
   return unless @{$result->{issues}};
-  
+
   $result->{issues}[0]{fields}{id} = $result->{issues}[0]{id};
   $result->{issues}[0]{fields}{key} = $result->{issues}[0]{key};
-    
+
   return %{$result->{issues}[0]{fields}};
 } # getNextIssue
 
 sub getIssues (;$$$@) {
   my ($condition, $start, $max, @fields) = @_;
-  
+
 =pod
 
 =head2 getIssues ()
@@ -929,16 +929,16 @@ Perl array of hashes of JIRA issue records
 =for html </blockquote>
 
 =cut
-  
+
   push @fields, '*all' unless @fields;
-  
+
   my ($result, %query);
-  
+
   $query{jql}        = $condition || '';
   $query{startAt}    = $start     || 0;
   $query{maxResults} = $max       || 50;
   $query{fields}     = join ',', @fields;
-  
+
   eval {$result = $jira->GET ('/search/', \%query)};
 
   # We sometimes get an error here when $result->{issues} is undef.
@@ -995,7 +995,7 @@ Perl hash of JIRA issue
 =for html </blockquote>
 
 =cut
-  
+
   my $fields = @fields ? "?fields=" . join ',', @fields : '';
 
   return $jira->GET ("/issue/$issue$fields");
@@ -1003,26 +1003,26 @@ Perl hash of JIRA issue
 
 sub getIssueLinkTypes () {
   my $issueLinkTypes = $jira->GET ('/issueLinkType/');
-  
+
   map {push @issueLinkTypes, $_->{name}} @{$issueLinkTypes->{issueLinkTypes}};
-  
+
   return @issueLinkTypes
 } # getIssueLinkTypes
 
 sub linkIssues ($$$) {
   my ($from, $type, $to) = @_;
-  
+
   unless (@issueLinkTypes) {
     getIssueLinkTypes;
   } # unless
-  
+
   unless (grep {$type eq $_} @issueLinkTypes) {
     $main::log->err ("Type $type is not a valid issue link type\nValid types include:\n\t" 
                . join "\n\t", @issueLinkTypes) if $main::log;
-               
+
     return "Unable to $type link $from -> $to";           
   } # unless  
-  
+
   my %link = (
     inwardIssue  => {
       key        => $from,
@@ -1037,12 +1037,12 @@ sub linkIssues ($$$) {
       body       => "Link ported as part of the migration from Bugzilla: $from <-> $to",
     },
   );
-  
+
   $main::total{'IssueLinks Added'}++;
-  
+
   if ($main::opts{exec}) {
     eval {$jira->POST ("/issueLink", undef, \%link)};
-    
+
     if ($@) {
       return "Unable to $type link $from -> $to\n$@";
     } else {
@@ -1055,7 +1055,7 @@ sub linkIssues ($$$) {
 
 sub getRemoteLink ($;$) {
   my ($jiraIssue, $id) = @_;
-  
+
 =pod
 
 =head2 getRemoteLink ()
@@ -1095,15 +1095,15 @@ Perl hash of remote links
 =for html </blockquote>
 
 =cut
-  
+
   $id //= '';
-  
+
   my $result;
-  
+
   eval {$result = $jira->GET ("/issue/$jiraIssue/remotelink/$id")};
-  
+
   return if $@;
-  
+
   my %remoteLinks;
 
   if (ref $result eq 'ARRAY') {
@@ -1111,49 +1111,49 @@ Perl hash of remote links
   } else {
     $remoteLinks{$result->{id}} = $result->{object}{title};
   } # if
-    
+
   return \%remoteLinks;
 } # getRemoteLink
 
 sub getRemoteLinks (;$) {
   my ($bugid) = @_;
-  
+
   $jiradb = openJIRADB unless $jiradb;
-  
+
   my $statement = 'select url from remotelink';
 
   $statement .= " where url like 'http://bugs%'";  
   $statement .= " and url like '%$bugid'" if $bugid; 
   $statement .= " group by issueid desc";
-  
+
   my $sth = $jiradb->prepare ($statement);
-  
+
   _checkDBError 'Unable to prepare statement', $statement;
-  
+
   $sth->execute;
-  
+
   _checkDBError 'Unable to execute statement', $statement;
 
   my %bugids;
-  
+
   while (my $record = $sth->fetchrow_array) {
     if ($record =~ /(\d+)$/) {
       $bugids{$1} = 1;
     } # if 
   } # while
-  
+
   return keys %bugids;
 } # getRemoteLinks
 
 sub findRemoteLinkByBugID (;$) {
   my ($bugid) = @_;
-  
+
   my $condition = 'where issueid = jiraissue.id and jiraissue.project = project.id';
-  
+
   if ($bugid) {
     $condition .= " and remotelink.url like '%id=$bugid'";
   } # unless
-  
+
   $jiradb = openJIRADB unless $jiradb;
 
   my $statement = <<"END";
@@ -1169,21 +1169,21 @@ $condition
 END
 
   my $sth = $jiradb->prepare ($statement);
-  
+
   _checkDBError 'Unable to prepare statement', $statement;
-  
+
   $sth->execute;
-  
+
   _checkDBError 'Unable to execute statement', $statement;
-  
+
   my @records;
-  
+
   while (my $row = $sth->fetchrow_hashref) {
     $row->{bugid} = $bugid;
-        
+
     push @records, $row;
   } # while
-  
+
   return \@records;
 } # findRemoteLinkByBugID
 
@@ -1191,43 +1191,43 @@ sub promoteBug2JIRAIssue ($$$$) {
   my ($bugid, $jirafrom, $jirato, $relationship) = @_;
 
   my $result = linkIssues $jirafrom, $relationship, $jirato;
-        
+
   return $result if $result =~ /Unable to link/;
-  
+
   $main::log->msg ($result . " (BugID $bugid)") if $main::log;
-  
+
   for (@{findRemoteLinkByBugID $bugid}) {
     my %record = %$_;
-    
+
     $result = removeRemoteLink ($record{issue}, $record{id});
-    
+
     # We may not care if we couldn't remove this link because it may have been
     # removed by a prior pass.
     return $result if $result =~ /Unable to remove link/;
-    
+
     if ($main::log) {
       $main::log->msg ($result) unless $result eq '';
     } # if
   } # for
-  
+
   return $result;
 } # promoteBug2JIRAIssue
 
 sub addRemoteLink ($$$) {
   my ($bugid, $relationship, $jiraIssue) = @_;
-  
+
   my $bug = getBug $bugid;
-  
+
   # Check to see if this Bug ID already exists on this JIRA Issue, otherwise
   # JIRA will duplicate it! 
   my $remoteLinks = getRemoteLink $jiraIssue;
-  
+
   for (keys %$remoteLinks) {
     if ($remoteLinks->{$_} =~ /Bug (\d+)/) {
       return "Bug $bugid is already linked to $jiraIssue" if $bugid == $1;
     } # if
   } # for
-  
+
   # Note this globalid thing is NOT working! ALl I see is null in the database
   my %remoteLink = (
 #    globalid     => "system=http://bugs.audience.com/show_bug.cgi?id=$bugid",
@@ -1246,12 +1246,12 @@ sub addRemoteLink ($$$) {
       },
     },
   );
-  
+
   $main::total{'RemoteLink Added'}++;
-  
+
   if ($main::opts{exec}) {
     eval {$jira->POST ("/issue/$jiraIssue/remotelink", undef, \%remoteLink)};
-  
+
     return $@;
   } else {
     return "Would have linked $bugid -> $jiraIssue";
@@ -1260,16 +1260,16 @@ sub addRemoteLink ($$$) {
 
 sub removeRemoteLink ($;$) {
   my ($jiraIssue, $id) = @_;
-  
+
   $id //= '';
-  
+
   my $remoteLinks = getRemoteLink ($jiraIssue, $id);
-  
+
   for (keys %$remoteLinks) {
     my $result;
-    
+
     $main::total{'RemoteLink Removed'}++;
-  
+
     if ($main::opts{exec}) {
       eval {$result = $jira->DELETE ("/issue/$jiraIssue/remotelink/$_")};
 
@@ -1277,12 +1277,12 @@ sub removeRemoteLink ($;$) {
         return "Unable to remove remotelink $jiraIssue ($id)\n$@" if $@;
       } else {
         my $bugid;
-        
+
         if ($remoteLinks->{$_} =~ /(\d+)/) {
           return "Removed remote link $jiraIssue (Bug ID $1)";
         } # if
       } # if
-      
+
       $main::total{'Remote Links Removed'}++;
     } else {
       if ($remoteLinks->{$_} =~ /(\d+)/) {
@@ -1294,40 +1294,40 @@ sub removeRemoteLink ($;$) {
 
 sub getIssueLinks ($;$) {
   my ($issue, $type) = @_;
-  
+
   my @links = getIssue ($issue, ('issuelinks'));
-  
+
   my @issueLinks;
 
   for (@{$links[0]->{fields}{issuelinks}}) {
      my %issueLink = %$_;
-     
+
      next if ($type && $type ne $issueLink{type}{name});
-     
+
      push @issueLinks, \%issueLink;  
-  }
-  
+  } # for
+
   return @issueLinks;
 } # getIssueLinks
 
 sub getIssueWatchers ($) {
   my ($issue) = @_;
-  
+
   my $watchers;
-  
+
   eval {$watchers = $jira->GET ("/issue/$issue/watchers")};
-  
+
   return if $@;
-  
+
   # The watcher information returned by the above is incomplete. Let's complete
   # it.
   my @watchers;
-  
+
   for (@{$watchers->{watchers}}) {
     my $user;
-    
+
     eval {$user = $jira->GET ("/user?username=$_->{key}")};
-    
+
     unless ($@) {
       push @watchers, $user;
     } else {
@@ -1337,13 +1337,13 @@ sub getIssueWatchers ($) {
       }# if
     } # unless
   } # for
-  
+
   return @watchers;
 } # getIssueWatchers
 
 sub updateIssueWatchers ($%) {
   my ($issue, %watchers) = @_;
-  
+
 =pod
 
 =head2 updateIssueWatchers ()
@@ -1385,56 +1385,56 @@ Error message or '' to indicate no error
 =cut
 
   my $existingWatchers;
-  
+
   eval {$existingWatchers = $jira->GET ("/issue/$issue/watchers")};
-  
+
   return "Unable to get issue $issue\n$@" if $@;
-  
+
   for (@{$existingWatchers->{watchers}}) {
     # Cleanup: Remove the current user from the watchers list.
     # If he's on the list then remove him.
     if ($_->{name} eq $jira->{username}) {
       $jira->DELETE ("/issue/$issue/watchers?username=$_->{name}");
-      
+
       $main::total{"Admins destroyed"}++;
     } # if
-    
+
     # Delete any matching watchers
     delete $watchers{lc ($_->{name})} if $watchers{lc ($_->{name})};
   } # for
 
   return '' if keys %watchers == 0;
-  
+
   my $issueUpdated;
-  
+
   for (keys %watchers) {
     if ($main::opts{exec}) {
       eval {$jira->POST ("/issue/$issue/watchers", undef, $_)};
-    
+
       if ($@) {
         $main::log->warn ("Unable to add user $_ as a watcher to JIRA Issue $issue") if $main::log;
-      
+
         $main::total{'Watchers skipped'}++;
       } else {
         $issueUpdated = 1;
-        
+
         $main::total{'Watchers added'}++;
       } # if
     } else {
       $main::log->msg ("Would have added user $_ as a watcher to JIRA Issue $issue") if $main::log;
-      
+
       $main::total{'Watchers that would have been added'}++;
     } # if
   } # for
-  
+
   $main::total{'Issues updated'}++ if $issueUpdated;
-  
+
   return '';
 } # updateIssueWatchers
 
 sub getUsersGroups ($) {
   my ($username) = @_;
-  
+
 =pod
 
 =head2 getUsersGroups ()
@@ -1472,20 +1472,20 @@ List of groups
 =cut
   
   my ($result, %query);
-  
+
   %query = (
     username => $username,
     expand   => 'groups',
   );
-  
+
   eval {$result = $jira->GET ('/user/', \%query)};
-  
+
   my @groups;
-  
+
   for (@{$result->{groups}{items}}) {
     push @groups, $_->{name};
   } # for
-  
+
   return @groups;
 } # getusersGroups
 
@@ -1531,35 +1531,35 @@ List of errors (if any)
 =for html </blockquote>
 
 =cut
-  
+
   my ($result, @errors);
-  
+
   my @oldgroups = getUsersGroups $username;
-  
+
   # We can't always add groups to the new user due to either the group not being
   # in the new LDAP directory or we are unable to see it. If we attempt to JIRA
   # will try to add the group and we don't have write permission to the 
   # directory. So we'll just return @errors and let the caller deal with it.  
   for my $group (@groups) {
     next if grep {$_ eq $group} @oldgroups;
-    
+
     eval {$result = $jira->POST ('/group/user', {groupname => $group}, {name => $username})};
-  
+
     push @errors, $@ if $@;  
   } # for
-  
+
   return @errors;
 } # updateUsersGroups
 
 sub copyGroupMembership ($$) {
   my ($from_username, $to_username) = @_;
-  
+
   return updateUsersGroups $to_username, getUsersGroups $from_username;
 } # copyGroupMembership
 
 sub updateColumn ($$$%) {
   my ($table, $oldvalue, $newvalue, %info) = @_;
-  
+
 =pod
 
 =head2 updateColumn ()
@@ -1607,23 +1607,23 @@ Number of rows updated
 =for html </blockquote>
 
 =cut
-  
+
   # UGH! Sometimes values need to be quoted
   $oldvalue = quotemeta $oldvalue;
   $newvalue = quotemeta $newvalue;
-  
+
   my $condition  =  "$info{column} = '$oldvalue'";
      $condition .= " and $info{condition}" if $info{condition};
   my $statement  = "update $table set $info{column} = '$newvalue' where $condition";
 
   my $nbrRows = count $table, $condition;
-  
+
   if ($nbrRows) {
     if ($main::opts{exec}) {
       $main::total{'Rows updated'}++;
-    
+
       $jiradb->do ($statement);
-      
+
       _checkDBError 'Unable to execute statement', $statement;
     } else {
       $main::total{'Rows would be updated'}++;
@@ -1631,7 +1631,7 @@ Number of rows updated
       $main::log->msg ("Would have executed $statement") if $main::log;
     } # if
   } # if 
-  
+
   return $nbrRows;
 } # updateColumn
 
@@ -1676,53 +1676,108 @@ Number of errors
 
   for my $olduser (sort keys %users) {
     my $newuser = $users{$olduser};
-    
+
     $main::log->msg ("Renaming $olduser -> $newuser") if $main::log;
     display ("Renaming $olduser -> $newuser");
-    
+
     if ($main::opts{exec}) {
       $main::total{'Users renamed'}++;
     } else {
       $main::total{'Users would be updated'}++;
     } # if
-    
+
     for my $table (sort keys %tables) {
       $main::log->msg ("\tTable: $table Column: ", 1) if $main::log;
-      
+
       my @columns = @{$tables{$table}};
-      
+
       for my $column (@columns) {
         my %info = %$column;
-        
+
         $main::log->msg ("$info{column} ", 1) if $main::log;
-        
+
         my $rowsUpdated = updateColumn ($table, $olduser, $newuser, %info);
-        
+
         if ($rowsUpdated) {
           my $msg  = " $rowsUpdated row";
              $msg .= 's' if $rowsUpdated > 1;
              $msg .= ' would have been' unless $main::opts{exec};
              $msg .= ' updated';
-             
+
           $main::log->msg ($msg, 1) if $main::log;
         } # if
       } # for
-      
+
       $main::log->msg ('') if $main::log;
     } # for
-    
+
     if (my @result = copyGroupMembership ($olduser, $newuser)) {
       # Skip errors of the form 'Could not add user... group is read-only
       @result = grep {!/Could not add user.*group is read-only/} @result;
-      
+
       if ($main::log) {
         $main::log->err ("Unable to copy group membership from $olduser -> $newuser\n@result", 1) if @result;
       } # if
     } # if
   } # for
-  
-  
+
   return $main::log ? $main::log->errors : 0;
 } # renameUsers
 
 1;
+
+=pod
+
+=head1 CONFIGURATION AND ENVIRONMENT
+
+DEBUG: If set then $debug is set to this level.
+
+VERBOSE: If set then $verbose is set to this level.
+
+TRACE: If set then $trace is set to this level.
+
+=head1 DEPENDENCIES
+
+=head2 Perl Modules
+
+L<FindBin|FindBin>
+
+L<Carp|Carp>
+
+L<DBI|DBI>
+
+=head3 ClearSCM Perl Modules
+
+=for html <p><a href="/php/scm_man.php?file=lib/Display.pm">Display</a></p>
+
+=for html <p><a href="/php/scm_man.php?file=JIRA/lib/BugzillaUtils.pm">BugzillaUtils</a></p>
+
+=head1 INCOMPATABILITIES
+
+None yet...
+
+=head1 BUGS AND LIMITATIONS
+
+There are no known bugs in this module.
+
+Please report problems to Andrew DeFaria <Andrew@ClearSCM.com>.
+
+=head1 LICENSE AND COPYRIGHT
+
+This Perl Module is freely available; you can redistribute it and/or
+modify it under the terms of the GNU General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+This Perl Module is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+General Public License (L<http://www.gnu.org/copyleft/gpl.html>) for more
+details.
+
+You should have received a copy of the GNU General Public License
+along with this Perl Module; if not, write to the Free Software Foundation,
+Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+reserved.
+
+=cut