Removed /usr/local from CDPATH
[clearscm.git] / JIRA / jiradep.pl
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 =pod
6
7 =head1 NAME jiradep.pl
8
9 Update Bugzilla dependencies (Dependencies/Blockers/Duplicates and Related),
10 transfering those relationships over to any matching JIRA issues. 
11
12 =head1 VERSION
13
14 =over
15
16 =item Author
17
18 Andrew DeFaria <Andrew@ClearSCM.com>
19
20 =item Revision
21
22 $Revision: #1 $
23
24 =item Created
25
26 Thu Mar 20 10:11:53 PDT 2014
27
28 =item Modified
29
30 $Date: 2014/05/23 $
31
32 =back
33
34 =head1 SYNOPSIS
35
36   $ jiradep.pl [-bugzillaserver <bugshost>] [-login <login email>]
37                [-jiraserver <server>]
38                [-username <username>] [-password <password>] 
39                [-bugids bugid,bugid,... | -file <filename>] 
40                [-[no]exec] [-linkbugzilla] [-relinkbugzilla]
41                [-verbose] [-help] [-usage]
42
43   Where:
44
45     -v|erbose:       Display progress output
46     -he|lp:          Display full help
47     -usa|ge:         Display usage
48     -[no]e|xec:      Whether or not to update Bugilla. -noexec says only 
49                      tell me what you would have updated.
50     -use|rname:      Username to log into JIRA with (Default: jira-admin)
51     -p|assword:      Password to log into JIRA with (Default: jira-admin's 
52                      password)
53     -bugzillaserver: Machine where Bugzilla lives (Default: bugs-dev)
54     -jiraserver:     Machine where Jira lives (Default: jira-dev)
55     -bugi|ds:        Comma separated list of BugIDs to process
56     -f|ile:          File of BugIDs, one per line
57     -linkbugzilla:   If specified and we find that we cannot translate
58                      a Bugzilla Bud ID to a JIRA Issue then create a 
59                      remote link for the Bugzilla Bug. (Default:
60                      do not create Bugzilla remote links).
61     -relinkbugzilla: Scan current Remote Bugzilla links and if there
62                      exists a corresponding JIRA issue, remove the 
63                      Remote Bugzilla link and make it a JIRA Issue
64                      link.
65     -jiradbhost:     Host name of the machine where the MySQL jiradb
66                      database is located (Default: cm-db-ldev01)
67
68 =head1 DESCRIPTION
69
70 This script will process all BugIDs translating them into JIRA Issues, if
71 applicable. It will then determine the relationships of this BugID in Bugzilla -
72 what it blocks, what it depends on, if it's a duplicate of another bug or if
73 it has any related links. Those too will be translated to JIRA issues, again,
74 if applicable. Then the JIRA issue will be updates to reflect these 
75 relationships. 
76
77 Note that it's not known at this time what to do for situations where BugIDs
78 cannot be translated into JIRA issues if such Bugzilla bugs have not yet been
79 migrated to JIRA. There's a though to simply make a Bugzilla Link but we will
80 need to keep that in mind and when we import the next project to JIRA these
81 old, no longer used Bugzilla Links should be converted to their corresponding 
82 JIRA issue. Perhaps this script can do that too.
83
84 =cut
85
86 use FindBin;
87 use lib "$FindBin::Bin/lib";
88
89 $| = 1;
90
91 use DBI;
92 use Display;
93 use Logger;
94 use TimeUtils;
95 use Utils;
96 use JIRAUtils;
97 use BugzillaUtils;
98
99 use Getopt::Long; 
100 use Pod::Usage;
101
102 our %opts = (
103   exec           => 0,
104   bugzillaserver => $ENV{BUGZILLASERVER} || 'bugs-dev',
105   jiraserver     => $ENV{JIRASERVER}     || 'jira-dev',
106   jiradbhost     => $ENV{JIRA_DB_HOST}   || 'cm-db-ldev01',
107   username       => 'jira-admin',
108   password       => 'jira-admin',
109   usage          => sub { pod2usage },
110   help           => sub { pod2usage (-verbose => 2)},
111   verbose        => sub { set_verbose },
112   quiet          => 0,
113   usage          => sub { pod2usage },
114   help           => sub { pod2usage (-verbose => 2)},
115 );
116
117 our ($log, %total);
118
119 my %relationshipMap = (
120   Blocks    => 'Dependencies Linked',
121   Duplicate => 'Duplicates Linked',
122   Related   => 'Related Linked',
123 );
124
125 sub callLink ($$$$) {
126   my ($from, $type, $to, $counter) = @_;
127
128   my $bugzillaType;
129   
130   if ($from =~ /^\d+/) {
131     if ($type eq 'Blocks') {
132       $bugzillaType = 'is blocked by (Bugzilla)';
133     } elsif ($type eq 'Duplicate') {
134       $bugzillaType = 'duplicate (Bugzilla)';
135     } elsif ($type eq 'Related') {
136       $bugzillaType = 'related (Bugzilla)';
137     } # if
138   } elsif ($to =~ /^\d+/) {
139     if ($type eq 'Blocks') {
140       $bugzillaType = 'blocks (Bugzilla)';
141     } elsif ($type eq 'Duplicate') {
142       $bugzillaType = 'duplicate (Bugzilla)';
143     } elsif ($type eq 'Related') {
144       $bugzillaType = 'related (Bugzilla)';
145     } # if
146   } # if
147     
148   $total{$counter}++;
149
150   if ($from =~ /^\d+/ && $to =~ /^\d+/) {
151     $total{'Skipped Bugzilla Links'}++;
152     
153     return "Refusing to link because both from ($from) and to ($to) links are still a Bugzilla link";
154   } elsif ($from =~ /^\d+/) {
155     if ($opts{linkbugzilla}) {
156       my $result = addRemoteLink $from, $bugzillaType, $to;
157
158       $total{'Bugzilla Links'}++ unless $result;
159     
160       if ($result eq '') {
161         return "Created remote $type link between Issue $to and Bug $from";
162       } else {
163         return $result;
164       } # if 
165     } else {
166       $total{'Skipped Bugzilla Links'}++;
167     
168       return "Refusing to link because from link ($from) is still a Bugzilla link";
169     } # if
170   } elsif ($to =~ /^\d+/) {
171     if ($opts{linkbugzilla}) {
172       my $result = addRemoteLink $to, $bugzillaType, $from;
173     
174       $total{'Bugzilla Links'}++ unless $result;
175
176       if (!defined $result) {
177         print "huh?";
178       }
179       if ($result eq '') {
180         return "Created remote $type link between Issue $from and Bug $to";
181       } else {
182         return $result;
183       } # if 
184     } else {
185       $total{'Skipped Bugzilla Links'}++;
186     
187       return "Refusing to link because to link ($to) is still a Bugzilla link";
188     } # if
189   } # if
190    
191   my $result = linkIssues $from, $type, $to;
192   
193   $log->msg ($result);
194     
195   if ($result =~ /^Unable/) {
196     $total{'Link Failures'}++;
197   } elsif ($result =~ /^Link made/) {
198     $total{'Links made'}++;
199   } elsif ($result =~ /^Would have linked/) {
200     $total{'Links would be made'}++;
201   } # if
202       
203   return;
204 } # callLink
205
206 sub relinkBugzilla (@) {
207   my (@bugids) = @_;
208   
209   my %mapRelationships = (
210     'blocks (Bugzilla)'           => 'Blocks',
211     'is blocked by (Bugzilla)'    => 'Blocks',
212     'duplicates (Bugzilla)'       => 'Duplicates',
213     'is duplicated by (Bugzilla)' => 'Duplicates',
214     # old versions...
215     'Bugzilla blocks'             => 'Blocks',
216     'Bugzilla is blocked by'      => 'Blocks',
217     'Bugzilla duplicates'         => 'Duplicates',
218     'Bugzilla is duplicated by'   => 'Duplicates',
219   );
220   
221   @bugids = getRemoteLinks unless @bugids;
222   
223   for my $bugid (@bugids) {
224     $total{'Remote Links Scanned'}++;
225     
226     my $links = findRemoteLinkByBugID $bugid;
227
228     my $jirafrom = findIssue ($bugid);
229     
230     next if $jirafrom !~ /^[A-Z]{1,5}-\d+$/;
231         
232     for (@$links) {
233       my %link = %$_;
234       
235       # Found a link to JIRA. Remove remotelink and make an issuelink
236       if ($mapRelationships{$link{relationship}}) {
237         my ($fromIssue, $toIssue);
238         
239         if ($link{relationship} =~ / by/) {
240           $fromIssue = $jirafrom;
241           $toIssue   = $link{issue};
242         } else {
243           $fromIssue = $link{issue};
244           $toIssue   = $jirafrom;
245         } # if
246         
247         my $status = promoteBug2JIRAIssue $bugid, $fromIssue, $toIssue,
248                                           $mapRelationships{$link{relationship}};
249
250         $log->err ($status) if $status =~ /Unable to link/;
251       } else {
252         $log->err ("Unable to handle relationships of type $link{relationship}");
253       } # if
254     } # for
255   } # for
256     
257   return;
258 } # relinkBugzilla
259
260 sub main () {
261   my $startTime = time;
262   
263   GetOptions (
264     \%opts,
265     'verbose',
266     'usage',
267     'help',
268     'exec!',
269     'quiet',
270     'username=s',
271     'password=s',
272     'bugids=s@',
273     'file=s',
274     'jiraserver=s',
275     'bugzillaserver=s',
276     'linkbugzilla',
277     'relinkbugzilla',
278     'jiradbhost=s',
279   ) or pod2usage;
280   
281   $log = Logger->new;
282
283   if ($opts{file}) {
284     open my $file, '<', $opts{file} 
285       or $log->err ("Unable to open $opts{file} - $!", 1);
286       
287     $opts{bugids} = [<$file>];
288     
289     chomp @{$opts{bugids}};
290   } else {
291     my @bugids;
292     
293     push @bugids, (split /,/, join (',', $_)) for (@{$opts{bugids}}); 
294   
295     $opts{bugids} = [@bugids];
296   } # if
297   
298   pod2usage 'Must specify -bugids <bugid>[,<bugid>,...] or -file <filename>'
299     unless ($opts{bugids} > 0 or $opts{relinkbugzilla});
300   
301   openBugzilla $opts{bugzillaserver}
302     or $log->err ("Unable to connect to $opts{bugzillaserver}", 1);
303   
304   Connect2JIRA ($opts{username}, $opts{password}, $opts{jiraserver})
305     or $log->err ("Unable to connect to $opts{jiraserver}", 1);
306
307   if ($opts{relinkbugzilla}) {
308     unless (@{$opts{bugids}}) {
309       relinkBugzilla;
310     } else {
311       relinkBugzilla $_ for @{$opts{bugids}}
312     } # unless
313         
314     Stats (\%total, $log);
315     
316     exit $log->errors;
317   } # if
318   
319   my %relationships;
320   
321   # The 'Blocks' IssueLinkType has two types of relationships in it - both
322   # blocks and dependson. Since JIRA has only one type - Blocks - we take
323   # the $dependson and flip the from and to.
324   my $blocks    = getBlockers @{$opts{bugids}};
325   my $dependson = getDependencies @{$opts{bugids}};
326   
327   # Now merge them - we did it backwards!
328   for my $fromLink (keys %$dependson) {
329     for my $toLink (@{$dependson->{$fromLink}}) {
330       push @{$relationships{Blocks}{$toLink}}, $fromLink;
331     } # for
332   } # for
333
334   #%{$relationships{Blocks}} = %$dependson;
335   
336   for my $fromLink (keys %$blocks) {
337     # Check to see if we already have the reverse of this link
338     for my $toLink (@{$blocks->{$fromLink}}) {
339       unless (grep {$toLink eq $_} keys %{$relationships{Blocks}}) {
340         push @{$relationships{Blocks}{$fromLink}}, $toLink;
341       } # unless
342     } # for
343   } # for
344   
345   $relationships{Duplicate} = getDuplicates   @{$opts{bugids}};
346   $relationships{Relates}   = getRelated      @{$opts{bugids}};
347   
348   # Process relationships (social programming... ;-)
349   $log->msg ("Processing relationships");
350   
351   for my $type (keys %relationshipMap) {
352     for my $from (keys %{$relationships{$type}}) {
353       for my $to (@{$relationships{$type}{$from}}) {
354         $total{'Relationships processed'}++;
355         
356         my $result = callLink $from, $type, $to, $relationshipMap{$type};
357         
358         $log->msg ($result) if $result;
359       } # for
360     } # for
361   } # if
362
363   display_duration $startTime, $log;
364   
365   Stats (\%total, $log) unless $opts{quiet};
366
367   return;
368 } # main
369
370 main;
371
372 exit;