From: Andrew DeFaria Date: Tue, 4 Aug 2015 20:07:50 +0000 (-0700) Subject: Merge branch 'master' of ssh://defaria.com/opt/git/clearscm X-Git-Url: https://defaria.com/gitweb/?a=commitdiff_plain;h=f4cba91d9cce314c632afe2e05987670c0d1813e;hp=28dfba1520d8867b3e5ba816d3c91bcc270804da;p=clearscm.git Merge branch 'master' of ssh://defaria.com/opt/git/clearscm --- diff --git a/audience/JIRA/importComments.pl b/audience/JIRA/importComments.pl new file mode 100644 index 0000000..6a1712c --- /dev/null +++ b/audience/JIRA/importComments.pl @@ -0,0 +1,237 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME importComments.pl + +This will import the comments from Bugzilla and update the corresponding JIRA +Issues. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: #1 $ + +=item Created + +Thu Mar 20 10:11:53 PDT 2014 + +=item Modified + +$Date: 2014/05/23 $ + +=back + +=head1 SYNOPSIS + + $ importComments.pl [-bugzillaserver ] [-login ] + [-jiraserver ] + [-username ] [-password ] + [-bugids bugid,bugid,... | -file ] + [-[no]exec] + [-verbose] [-help] [-usage] + + Where: + + -v|erbose: Display progress output + -he|lp: Display full help + -usa|ge: Display usage + -[no]e|xec: Whether or not to update JIRA. -noexec says only + tell me what you would have updated. + -use|rname: Username to log into JIRA with (Default: jira-admin) + -p|assword: Password to log into JIRA with (Default: jira-admin's + password) + -bugzillaserver: Machine where Bugzilla lives (Default: bugs-dev) + -jiraserver: Machine where Jira lives (Default: jira-dev) + -bugi|ds: Comma separated list of BugIDs to process + -f|ile: File of BugIDs, one per line + +=head1 DESCRIPTION + +This will import the comments from Bugzilla and update the corresponding JIRA +Issues. + +=cut + +use FindBin; +use lib "$FindBin::Bin/lib"; + +$| = 1; + +use DBI; +use Display; +use Logger; +use TimeUtils; +use Utils; +use JIRAUtils; +use BugzillaUtils; + +use Getopt::Long; +use Pod::Usage; + +our %opts = ( + exec => 0, + bugzillaserver => $ENV{BUGZILLASERVER} || 'bugs-dev', + jiraserver => $ENV{JIRASERVER} || 'jira-dev:8081', + username => $ENV{USERNAME}, + password => $ENV{PASSWORD}, + usage => sub { pod2usage }, + help => sub { pod2usage (-verbose => 2)}, + verbose => sub { set_verbose }, + quiet => 0, +); + +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='; + + # 0x93 (147) and 0x94 (148) are "smart" quotes + $str =~ s/[\x93\x94]/"/gm; + # 0x91 (145) and 0x92 (146) are "smart" singlequotes + $str =~ s/[\x91\x92]/'/gm; + # 0x96 (150) and 0x97 (151) are emdashes + $str =~ s/[\x96\x97]/--/gm; + # 0x85 (133) is an ellipsis + $str =~ s/\x85/.../gm; + # 0x95 • replacement for unordered list + $str =~ s/\x95/*/gm; + + # Make P4Web links for "CL (\d{3,6}+)" + $str =~ s/CL\s*(\d{3,6}+)/CL \[$1|${p4web}$1\?ac=10\]/igm; + + # Make Bugzilla links for "Bug ID (\d{1,5}+)" + $str =~ s/Bug\s*ID\s*(\d{1,5}+)/Bug \[$1|${bugzilla}$1\]/igm; + + # Make Bugzilla links for "Bug # (\d{1,5}+)" + $str =~ s/Bug\s*#\s*(\d{1,5}+)/Bug \[$1|${bugzilla}$1\]/igm; + + # Make Bugzilla links for "Bug (\d{1,5}+)" + $str =~ s/Bug\s*(\d{1,5}+)/Bug \[$1|${bugzilla}$1\]/igm; + + # Convert bug URLs to be more proper + $str =~ s/https\:\/\/bugs\.audience\.com\/show_bug\.cgi\?id=(\d{1,5}+)/Bug \[$1|${bugzilla}$1\]/igm; + + return $str; +} # sanitize + +sub addComments ($$) { + my ($jiraIssue, $bugid) = @_; + + my @comments = @{getBugComments ($bugid)}; + + # Note: In Bugzilla the first comment is considered the description. + my $description = shift @comments; + + my $result = addDescription $jiraIssue, sanitize $description; + + $total{'Descriptions added'}++; + + return $result if $result =~ /^Unable to add comment/; + + # Process the remaining comments + for (@comments) { + $result = addJIRAComment $jiraIssue, sanitize $_; + + if ($result =~ /Comment added/) { + $total{'Comments imported'}++; + } else { + return $result; + } # if + } # for + + $result = '' unless $result; + + return $result; +} # addComments + +sub main () { + my $startTime = time; + + GetOptions ( + \%opts, + 'verbose', + 'usage', + 'help', + 'exec!', + 'quiet', + 'username=s', + 'password=s', + 'bugids=s@', + 'file=s', + 'jiraserver=s', + 'bugzillaserver=s', + 'linkbugzilla', + 'relinkbugzilla' + ) or pod2usage; + + $log = Logger->new; + + 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 [,,...] or -file ' + 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); + + $log->msg ("Processing comments"); + + for (@{$opts{bugids}}) { + my $jiraIssue = findIssue $_; + + if ($jiraIssue =~ /^[A-Z]{1,5}-\d+$/) { + my $result = addComments $jiraIssue, $_; + + if ($result =~ /^Unable/) { + $total{'Comment failures'}++; + + $log->err ("Unable to add comments for $jiraIssue ($_)\n$result"); + } elsif ($result =~ /^Comment added/) { + $log->msg ("Added comments for $jiraIssue ($_)"); + } elsif ($result =~ /^Would have linked/) { + $total{'Comments would be added'}++; + } # 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; +} # main + +exit main; diff --git a/audience/JIRA/jiradep.pl b/audience/JIRA/jiradep.pl new file mode 100644 index 0000000..a8ce4c4 --- /dev/null +++ b/audience/JIRA/jiradep.pl @@ -0,0 +1,372 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME jiradep.pl + +Update Bugzilla dependencies (Dependencies/Blockers/Duplicates and Related), +transfering those relationships over to any matching JIRA issues. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: #1 $ + +=item Created + +Thu Mar 20 10:11:53 PDT 2014 + +=item Modified + +$Date: 2014/05/23 $ + +=back + +=head1 SYNOPSIS + + $ jiradep.pl [-bugzillaserver ] [-login ] + [-jiraserver ] + [-username ] [-password ] + [-bugids bugid,bugid,... | -file ] + [-[no]exec] [-linkbugzilla] [-relinkbugzilla] + [-verbose] [-help] [-usage] + + Where: + + -v|erbose: Display progress output + -he|lp: Display full help + -usa|ge: Display usage + -[no]e|xec: Whether or not to update Bugilla. -noexec says only + tell me what you would have updated. + -use|rname: Username to log into JIRA with (Default: jira-admin) + -p|assword: Password to log into JIRA with (Default: jira-admin's + password) + -bugzillaserver: Machine where Bugzilla lives (Default: bugs-dev) + -jiraserver: Machine where Jira lives (Default: jira-dev) + -bugi|ds: Comma separated list of BugIDs to process + -f|ile: File of BugIDs, one per line + -linkbugzilla: If specified and we find that we cannot translate + a Bugzilla Bud ID to a JIRA Issue then create a + remote link for the Bugzilla Bug. (Default: + do not create Bugzilla remote links). + -relinkbugzilla: Scan current Remote Bugzilla links and if there + exists a corresponding JIRA issue, remove the + Remote Bugzilla link and make it a JIRA Issue + link. + -jiradbhost: Host name of the machine where the MySQL jiradb + database is located (Default: cm-db-ldev01) + +=head1 DESCRIPTION + +This script will process all BugIDs translating them into JIRA Issues, if +applicable. It will then determine the relationships of this BugID in Bugzilla - +what it blocks, what it depends on, if it's a duplicate of another bug or if +it has any related links. Those too will be translated to JIRA issues, again, +if applicable. Then the JIRA issue will be updates to reflect these +relationships. + +Note that it's not known at this time what to do for situations where BugIDs +cannot be translated into JIRA issues if such Bugzilla bugs have not yet been +migrated to JIRA. There's a though to simply make a Bugzilla Link but we will +need to keep that in mind and when we import the next project to JIRA these +old, no longer used Bugzilla Links should be converted to their corresponding +JIRA issue. Perhaps this script can do that too. + +=cut + +use FindBin; +use lib "$FindBin::Bin/lib"; + +$| = 1; + +use DBI; +use Display; +use Logger; +use TimeUtils; +use Utils; +use JIRAUtils; +use BugzillaUtils; + +use Getopt::Long; +use Pod::Usage; + +our %opts = ( + exec => 0, + bugzillaserver => $ENV{BUGZILLASERVER} || 'bugs-dev', + jiraserver => $ENV{JIRASERVER} || 'jira-dev', + jiradbhost => $ENV{JIRA_DB_HOST} || 'cm-db-ldev01', + username => 'jira-admin', + password => 'jira-admin', + usage => sub { pod2usage }, + help => sub { pod2usage (-verbose => 2)}, + verbose => sub { set_verbose }, + quiet => 0, + usage => sub { pod2usage }, + help => sub { pod2usage (-verbose => 2)}, +); + +our ($log, %total); + +my %relationshipMap = ( + Blocks => 'Dependencies Linked', + Duplicate => 'Duplicates Linked', + Related => 'Related Linked', +); + +sub callLink ($$$$) { + my ($from, $type, $to, $counter) = @_; + + my $bugzillaType; + + if ($from =~ /^\d+/) { + if ($type eq 'Blocks') { + $bugzillaType = 'is blocked by (Bugzilla)'; + } elsif ($type eq 'Duplicate') { + $bugzillaType = 'duplicate (Bugzilla)'; + } elsif ($type eq 'Related') { + $bugzillaType = 'related (Bugzilla)'; + } # if + } elsif ($to =~ /^\d+/) { + if ($type eq 'Blocks') { + $bugzillaType = 'blocks (Bugzilla)'; + } elsif ($type eq 'Duplicate') { + $bugzillaType = 'duplicate (Bugzilla)'; + } elsif ($type eq 'Related') { + $bugzillaType = 'related (Bugzilla)'; + } # if + } # if + + $total{$counter}++; + + if ($from =~ /^\d+/ && $to =~ /^\d+/) { + $total{'Skipped Bugzilla Links'}++; + + return "Refusing to link because both from ($from) and to ($to) links are still a Bugzilla link"; + } elsif ($from =~ /^\d+/) { + if ($opts{linkbugzilla}) { + my $result = addRemoteLink $from, $bugzillaType, $to; + + $total{'Bugzilla Links'}++ unless $result; + + if ($result eq '') { + return "Created remote $type link between Issue $to and Bug $from"; + } else { + return $result; + } # if + } else { + $total{'Skipped Bugzilla Links'}++; + + return "Refusing to link because from link ($from) is still a Bugzilla link"; + } # if + } elsif ($to =~ /^\d+/) { + if ($opts{linkbugzilla}) { + my $result = addRemoteLink $to, $bugzillaType, $from; + + $total{'Bugzilla Links'}++ unless $result; + + if (!defined $result) { + print "huh?"; + } + if ($result eq '') { + return "Created remote $type link between Issue $from and Bug $to"; + } else { + return $result; + } # if + } else { + $total{'Skipped Bugzilla Links'}++; + + return "Refusing to link because to link ($to) is still a Bugzilla link"; + } # if + } # if + + my $result = linkIssues $from, $type, $to; + + $log->msg ($result); + + if ($result =~ /^Unable/) { + $total{'Link Failures'}++; + } elsif ($result =~ /^Link made/) { + $total{'Links made'}++; + } elsif ($result =~ /^Would have linked/) { + $total{'Links would be made'}++; + } # if + + return; +} # callLink + +sub relinkBugzilla (@) { + my (@bugids) = @_; + + my %mapRelationships = ( + 'blocks (Bugzilla)' => 'Blocks', + 'is blocked by (Bugzilla)' => 'Blocks', + 'duplicates (Bugzilla)' => 'Duplicates', + 'is duplicated by (Bugzilla)' => 'Duplicates', + # old versions... + 'Bugzilla blocks' => 'Blocks', + 'Bugzilla is blocked by' => 'Blocks', + 'Bugzilla duplicates' => 'Duplicates', + 'Bugzilla is duplicated by' => 'Duplicates', + ); + + @bugids = getRemoteLinks unless @bugids; + + for my $bugid (@bugids) { + $total{'Remote Links Scanned'}++; + + my $links = findRemoteLinkByBugID $bugid; + + my $jirafrom = findIssue ($bugid); + + next if $jirafrom !~ /^[A-Z]{1,5}-\d+$/; + + for (@$links) { + my %link = %$_; + + # Found a link to JIRA. Remove remotelink and make an issuelink + if ($mapRelationships{$link{relationship}}) { + my ($fromIssue, $toIssue); + + if ($link{relationship} =~ / by/) { + $fromIssue = $jirafrom; + $toIssue = $link{issue}; + } else { + $fromIssue = $link{issue}; + $toIssue = $jirafrom; + } # if + + my $status = promoteBug2JIRAIssue $bugid, $fromIssue, $toIssue, + $mapRelationships{$link{relationship}}; + + $log->err ($status) if $status =~ /Unable to link/; + } else { + $log->err ("Unable to handle relationships of type $link{relationship}"); + } # if + } # for + } # for + + return; +} # relinkBugzilla + +sub main () { + my $startTime = time; + + GetOptions ( + \%opts, + 'verbose', + 'usage', + 'help', + 'exec!', + 'quiet', + 'username=s', + 'password=s', + 'bugids=s@', + 'file=s', + 'jiraserver=s', + 'bugzillaserver=s', + 'linkbugzilla', + 'relinkbugzilla', + 'jiradbhost=s', + ) or pod2usage; + + $log = Logger->new; + + 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 [,,...] or -file ' + unless ($opts{bugids} > 0 or $opts{relinkbugzilla}); + + 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); + + if ($opts{relinkbugzilla}) { + unless (@{$opts{bugids}}) { + relinkBugzilla; + } else { + relinkBugzilla $_ for @{$opts{bugids}} + } # unless + + Stats (\%total, $log); + + exit $log->errors; + } # if + + my %relationships; + + # The 'Blocks' IssueLinkType has two types of relationships in it - both + # blocks and dependson. Since JIRA has only one type - Blocks - we take + # the $dependson and flip the from and to. + my $blocks = getBlockers @{$opts{bugids}}; + my $dependson = getDependencies @{$opts{bugids}}; + + # Now merge them - we did it backwards! + for my $fromLink (keys %$dependson) { + for my $toLink (@{$dependson->{$fromLink}}) { + push @{$relationships{Blocks}{$toLink}}, $fromLink; + } # for + } # for + + #%{$relationships{Blocks}} = %$dependson; + + for my $fromLink (keys %$blocks) { + # Check to see if we already have the reverse of this link + for my $toLink (@{$blocks->{$fromLink}}) { + unless (grep {$toLink eq $_} keys %{$relationships{Blocks}}) { + push @{$relationships{Blocks}{$fromLink}}, $toLink; + } # unless + } # for + } # for + + $relationships{Duplicate} = getDuplicates @{$opts{bugids}}; + $relationships{Relates} = getRelated @{$opts{bugids}}; + + # Process relationships (social programming... ;-) + $log->msg ("Processing relationships"); + + for my $type (keys %relationshipMap) { + for my $from (keys %{$relationships{$type}}) { + for my $to (@{$relationships{$type}{$from}}) { + $total{'Relationships processed'}++; + + my $result = callLink $from, $type, $to, $relationshipMap{$type}; + + $log->msg ($result) if $result; + } # for + } # for + } # if + + display_duration $startTime, $log; + + Stats (\%total, $log) unless $opts{quiet}; + + return; +} # main + +main; + +exit; \ No newline at end of file diff --git a/audience/JIRA/lib/BugzillaUtils.pm b/audience/JIRA/lib/BugzillaUtils.pm new file mode 100644 index 0000000..899f506 --- /dev/null +++ b/audience/JIRA/lib/BugzillaUtils.pm @@ -0,0 +1,322 @@ +=pod + +=head1 NAME $RCSfile: BugzillaUtils.pm,v $ + +Some shared functions dealing with Bugzilla. Note this uses DBI to directly +access Bugzilla's database. This requires that your userid was granted access. +For this I setup adefaria with pretty much read only access. + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.0 $ + +=item Created + +Fri Mar 12 10:17:44 PST 2004 + +=item Modified + +$Date: 2013/05/30 15:48:06 $ + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package BugzillaUtils; + +use strict; +use warnings; + +use base 'Exporter'; + +use FindBin; +use Display; +use Carp; +use DBI; + +use lib 'lib'; + +use JIRAUtils; + +our $bugzilla; + +our @EXPORT = qw ( + openBugzilla + getRelationships + getDependencies + getBlockers + getDuplicates + getRelated + getBug + getBugComments + getWatchers +); + +sub _checkDBError ($$) { + my ($msg, $statement) = @_; + + my $dberr = $bugzilla->err; + my $dberrmsg = $bugzilla->errstr; + + $dberr ||= 0; + $dberrmsg ||= 'Success'; + + my $message = ''; + + if ($dberr) { + my $function = (caller (1)) [3]; + + $message = "$function: $msg\nError #$dberr: $dberrmsg\n" + . "SQL Statement: $statement"; + } # if + + $main::log->err ($message, $dberr) if $dberr; + + return; +} # _checkDBError + +sub openBugzilla (;$$$$) { + my ($dbhost, $dbname, $dbuser, $dbpass) = @_; + + $dbhost //= 'jira-dev'; + $dbname //= 'bugzilla'; + $dbuser //= 'adefaria'; + $dbpass //= 'reader'; + + $main::log->msg ("Connecting to Bugzilla ($dbuser\@$dbhost)"); + + $bugzilla = DBI->connect ( + "DBI:mysql:$dbname:$dbhost", + $dbuser, + $dbpass, { + PrintError => 0, + RaiseError => 1, + }, + ); + + _checkDBError 'Unable to execute statement', 'Connect'; + + return $bugzilla; +} # 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; + + return $sth->fetchrow_hashref; +} # getBug + +sub getBugComments ($) { + my ($bugid) = @_; + + my $statement = <<"END"; +select + bug_id, + bug_when, + substring_index(login_name,'\@',1) as username, + thetext +from + longdescs, + profiles +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}: + +$comment->{thetext} +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 + +sub getDependencies (@) { + my (@bugs) = @_; + + return getRelationships ( + 'dependencies', # table + 'dependson', # returned field + 'blocked', # test field + 'Depends on', # relationship + @bugs + ); +} # getDependencies + +sub getBlockers (@) { + my (@bugs) = @_; + + return getRelationships ( + 'dependencies', + 'blocked', + 'dependson', + 'Blocks', + @bugs + ); +} # getBlockers + +sub getDuplicates (@) { + my (@bugs) = @_; + + return getRelationships ( + 'duplicates', + 'dupe', + 'dupe_of', + 'Duplicates', + @bugs + ); +} # getDuplicates + +sub getRelated (@) { + my (@bugs) = @_; + + return getRelationships ( + 'bug_see_also', + 'value', + 'bug_id', + 'Relates', + @bugs + ); +} # getRelated + +sub getWatchers ($) { + my ($bugid) = @_; + + my $statement = <<"END"; +select + profiles.login_name +from + cc, + profiles +where + cc.who = profiles.userid and + bug_id = ? +END + + my $sth = $bugzilla->prepare ($statement); + + _checkDBError 'Unable to prepare statement', $statement; + + $sth->execute ($bugid); + + _checkDBError 'Unable to execute statement', $statement; + + my @rows = @{$sth->fetchall_arrayref}; + + my %watchers; + + for (@rows) { + if ($$_[0] =~ /(.*)\@/) { + $watchers{$1} = 1; + } # if + + $main::total{'Watchers Processed'}++; + } # for + + return %watchers; +} # getWatchers \ No newline at end of file diff --git a/audience/JIRA/lib/JIRAUtils.pm b/audience/JIRA/lib/JIRAUtils.pm new file mode 100644 index 0000000..a60e01c --- /dev/null +++ b/audience/JIRA/lib/JIRAUtils.pm @@ -0,0 +1,655 @@ +=pod + +=head1 NAME $RCSfile: JIRAUtils.pm,v $ + +Some shared functions dealing with JIRA + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: 1.0 $ + +=item Created + +Fri Mar 12 10:17:44 PST 2004 + +=item Modified + +$Date: 2013/05/30 15:48:06 $ + +=back + +=head1 ROUTINES + +The following routines are exported: + +=cut + +package JIRAUtils; + +use strict; +use warnings; + +use base 'Exporter'; + +use FindBin; +use Display; +use Carp; +use DBI; + +use JIRA::REST; +use BugzillaUtils; + +our ($jira, %opts); + +our @EXPORT = qw ( + addJIRAComment + addDescription + Connect2JIRA + findIssue + getIssue + getIssueLinks + getIssueLinkTypes + getRemoteLinks + updateIssueWatchers + linkIssues + addRemoteLink + getRemoteLink + removeRemoteLink + getRemoteLinkByBugID + promoteBug2JIRAIssue + findRemoteLinkByBugID +); + +my (@issueLinkTypes, %total, %cache, $jiradb); + +sub _checkDBError ($;$) { + my ($msg, $statement) = @_; + + $statement //= 'Unknown'; + + $main::log->err ('JIRA database not opened!', 1) unless $jiradb; + + my $dberr = $jiradb->err; + my $dberrmsg = $jiradb->errstr; + + $dberr ||= 0; + $dberrmsg ||= 'Success'; + + my $message = ''; + + if ($dberr) { + my $function = (caller (1)) [3]; + + $message = "$function: $msg\nError #$dberr: $dberrmsg\n" + . "SQL Statement: $statement"; + } # if + + $main::log->err ($message, 1) if $dberr; + + return; +} # _checkDBError + +sub openJIRADB (;$$$$) { + my ($dbhost, $dbname, $dbuser, $dbpass) = @_; + + $dbhost //= $main::opts{jiradbhost}; + $dbname //= 'jiradb'; + $dbuser //= 'adefaria'; + $dbpass //= 'reader'; + + $main::log->msg ("Connecting to JIRA ($dbuser\@$dbhost)..."); + + $jiradb = DBI->connect ( + "DBI:mysql:$dbname:$dbhost", + $dbuser, + $dbpass, { + PrintError => 0, + RaiseError => 1, + }, + ); + + _checkDBError "Unable to open $dbname ($dbuser\@$dbhost)"; + + return $jiradb; +} # openJIRADB + +sub Connect2JIRA (;$$$) { + my ($username, $password, $server) = @_; + + my %opts; + + $opts{username} = $username || 'jira-admin'; + $opts{password} = $password || $ENV{PASSWORD} || 'jira-admin'; + $opts{server} = $server || $ENV{JIRA_SERVER} || 'jira-dev:8081'; + $opts{URL} = "http://$opts{server}/rest/api/latest"; + + $main::log->msg ("Connecting to JIRA ($opts{username}\@$opts{server})"); + + $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 + +sub addDescription ($$) { + my ($issue, $description) = @_; + + if ($main::opts{exec}) { + eval {$jira->PUT ("/issue/$issue", undef, {fields => {description => $description}})}; + + if ($@) { + return "Unable to add description\n$@"; + } else { + return 'Description added'; + } # if + } # if +} # addDescription + +sub addJIRAComment ($$) { + my ($issue, $comment) = @_; + + if ($main::opts{exec}) { + eval {$jira->POST ("/issue/$issue/comment", undef, { body => $comment })}; + + if ($@) { + return "Unable to add comment\n$@"; + } else { + return 'Comment added'; + } # if + } else { + return "Would have added comments to $issue"; + } # if +} # addJIRAComment + +sub findIssue ($%) { + my ($bugid, %bugmap) = @_; + +=pod + # Check the cache... + if ($cache{$bugid}) { + if ($cache{$bugid} =~ /^\d+/) { + # We have a cache hit but the contents here are a bugid. This means we had + # searched for the corresponding JIRA issue for this bug before and came + # up empty handed. In this situtaion we really have: + return "Unable to find a JIRA issue for Bug $bugid"; + } else { + return $cache{$bugid}; + } # if + } # if +=cut + my $issue; + + my %query = ( + jql => "\"Bugzilla Bug Number\" ~ $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"); + + return "Found more than 2 issues for Bug ID $bugid"; + } elsif (@{$issue->{issues}} == 2) { + my ($issueNum0, $issueNum1); + + if ($issue->{issues}[0]{key} =~ /(\d+)/) { + $issueNum0 = $1; + } # if + + if ($issue->{issues}[1]{key} =~ /(\d+)/) { + $issueNum1 = $1; + } # 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 ("RDBNK-$issueNum0" eq $inwardIssue || + "RDBNK-$issueNum0" eq $outwardIssue || + "RDBNK-$issueNum1" eq $inwardIssue || + "RDBNK-$issueNum1" eq $outwardIssue) { + $alreadyCloned = 1; + + last; + } # if + } # for + + unless ($alreadyCloned) { + my $result = linkIssues ("RDBNK-$issueNum0", 'Cloners', "RDBNK-$issueNum1"); + + return $result if $result =~ /Unable to/; + + $main::log->msg ($result); + } # unless + } # if + + if ($issueID) { + $main::log->msg ("Found JIRA issue $issueID for Bug $bugid"); + + #$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. + #$cache{$bugid} = $bugid; + + return $status; + } # if +} # findJIRA + +sub getIssue ($;@) { + my ($issue, @fields) = @_; + + my $fields = @fields ? "?fields=" . join ',', @fields : ''; + + return $jira->GET ("/issue/$issue$fields"); +} # getIssue + +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" + . join "\n\t", @issueLinkTypes); + + return "Unable to $type link $from -> $to"; + } # unless + + my %link = ( + inwardIssue => { + key => $from, + }, + type => { + name => $type, + }, + outwardIssue => { + key => $to, + }, + comment => { + 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 { + return "Made $type link $from -> $to"; + } # if + } else { + return "Would have $type linked $from -> $to"; + } # if +} # linkIssue + +sub getRemoteLink ($;$) { + my ($jiraIssue, $id) = @_; + + $id //= ''; + + my $result; + + eval {$result = $jira->GET ("/issue/$jiraIssue/remotelink/$id")}; + + return if $@; + + my %remoteLinks; + + if (ref $result eq 'ARRAY') { + map {$remoteLinks{$_->{id}} = $_->{object}{title}} @$result; + } 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"; +select + remotelink.id, + concat (project.pkey, '-', issuenum) as issue, + relationship +from + remotelink, + jiraissue, + project +$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 + +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)"); + + 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/; + + $main::log->msg ($result) unless $result eq ''; + } # 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", +# application => { +# type => 'Bugzilla', +# name => 'Bugzilla', +# }, + relationship => $relationship, + object => { + url => "http://bugs.audience.com/show_bug.cgi?id=$bugid", + title => "Bug $bugid", + summary => $bug->{short_desc}, + icon => { + url16x16 => 'http://bugs.audience.local/favicon.png', + title => 'Bugzilla Bug', + }, + }, + ); + + $main::total{'RemoteLink Added'}++; + + if ($main::opts{exec}) { + eval {$jira->POST ("/issue/$jiraIssue/remotelink", undef, \%remoteLink)}; + + return $@; + } else { + return "Would have linked $bugid -> $jiraIssue"; + } # if +} # 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/$_")}; + + if ($@) { + 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+)/) { + return "Would have removed remote link $jiraIssue (Bug ID $1)"; + } # if + } # if + } # for +} # 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; + } + + return @issueLinks; +} # getIssueLinks + +sub updateIssueWatchers ($%) { + my ($issue, %watchers) = @_; + + 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}"); + + $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"); + + $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"); + + $main::total{'Watchers that would have been added'}++; + } # if + } # for + + $main::total{'Issues updated'}++ if $issueUpdated; + + return ''; +} # updateIssueWatchers + +=pod + +I'm pretty sure I'm not using this routine anymore and I don't think it works. +If you wish to reserect this then please test. + +sub updateWatchers ($%) { + my ($issue, %watchers) = @_; + + my $existingWatchers; + + eval {$existingWatchers = $jira->GET ("/issue/$issue/watchers")}; + + if ($@) { + error "Unable to get issue $issue"; + + $main::total{'Missing JIRA Issues'}++; + + return; + } # if + + for (@{$existingWatchers->{watchers}}) { + # Cleanup: Mike Admin Cogan was added as a watcher for each issue imported. + # If he's on the list then remove him. + if ($_->{name} eq 'mcoganAdmin') { + $jira->DELETE ("/issue/$issue/watchers?username=$_->{name}"); + + $main::total{"mcoganAdmin's destroyed"}++; + } # if + + # Delete any matching watchers + delete $watchers{$_->{name}} if $watchers{$_->{name}}; + } # for + + return if keys %watchers == 0; + + my $issueUpdated; + + for (keys %watchers) { + if ($main::opts{exec}) { + eval {$jira->POST ("/issue/$issue/watchers", undef, $_)}; + + if ($@) { + error "Unable to add user $_ as a watcher to JIRA Issue $issue"; + + $main::total{'Watchers skipped'}++; + } else { + $main::total{'Watchers added'}++; + + $issueUpdated = 1; + } # if + } else { + $main::log->msg ("Would have added user $_ as a watcher to JIRA Issue $issue"); + + $main::total{'Watchers that would have been added'}++; + } # if + } # for + + $main::total{'Issues updated'}++ if $issueUpdated; + + return; +} # updateWatchers +=cut + +1; diff --git a/audience/JIRA/updateWatchLists.pl b/audience/JIRA/updateWatchLists.pl new file mode 100644 index 0000000..5a5c4a0 --- /dev/null +++ b/audience/JIRA/updateWatchLists.pl @@ -0,0 +1,170 @@ +#!/usr/bin/perl +use strict; +use warnings; + +=pod + +=head1 NAME updateWatchLists.pl + +Copy CC lists from Bugzilla -> JIRA + +=head1 VERSION + +=over + +=item Author + +Andrew DeFaria + +=item Revision + +$Revision: #1 $ + +=item Created + +Thu Mar 20 10:11:53 PDT 2014 + +=item Modified + +$Date: 2014/05/23 $ + +=back + +=head1 SYNOPSIS + + Updates JIRA watchlists by copying the CC list information from Bugzilla + + $ updateWatchLists.pl [-login ] [-products product1, + product2,...] [-[no]exec] + [-verbose] [-help] [-usage] + + Where: + + -v|erbose: Display progress output + -he|lp: Display full help + -usa|ge: Display usage + -[no]e|xec: Whether or not to update JIRA. -noexec says only + tell me what you would have updated. + -use|rname: Username to log into JIRA with (Default: jira-admin) + -p|assword: Password to log into JIRA with (Default: jira-admin's + password) + -bugzillaserver: Machine where Bugzilla lives (Default: bugs-dev) + -jiraserver: Machine where Jira lives (Default: jira-dev) + -bugi|ds: Comma separated list of BugIDs to process + -f|ile: File of BugIDs, one per line + +=head1 DESCRIPTION + +This script updates JIRA watchlists by copying the CC List information from +Bugzilla to JIRA. + +=cut + +use FindBin; +use lib "$FindBin::Bin/lib"; + +$| = 1; + +use DBI; +use Display; +use Logger; +use TimeUtils; +use Utils; +use JIRAUtils; +use BugzillaUtils; +use JIRA::REST; + +use Getopt::Long; +use Pod::Usage; + +# Login should be the email address of the bugzilla account which has +# priviledges to create products and components +our %opts = ( + exec => 0, + bugzillaserver => $ENV{BUGZILLASERVER} || 'bugs-dev', + jiraserver => $ENV{JIRASERVER} || 'jira-dev:8081', + username => 'jira-admin', + password => 'jira-admin', + usage => sub { pod2usage }, + help => sub { pod2usage (-verbose => 2)}, + verbose => sub { set_verbose }, +); + +our ($log, %total); + +my ($bugzilla, $jira); + +sub main () { + my $startTime = time; + + GetOptions ( + \%opts, + 'verbose', + 'usage', + 'help', + 'exec!', + 'quiet', + 'username=s', + 'password=s', + 'bugids=s@', + 'file=s', + 'jiraserver=s', + 'bugzillaserver=s', + ) or pod2usage; + + $log = Logger->new; + + if ($opts{file}) { + open my $file, '<', $opts{file} + or die "Unable to open $opts{file} - $!"; + + $opts{bugids} = [<$file>]; + + chomp @{$opts{bugids}}; + } else { + my @bugids; + + push @bugids, (split /,/, join (',', $_)) for (@{$opts{bugids}}); + + $opts{bugids} = [@bugids]; + } # if + + pod2usage 'Must specify -bugids [,,...] or -file ' + 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); + + for (@{$opts{bugids}}) { + my $issue = findIssue $_; + + if ($issue =~ /^Future JIRA Issue/ or $issue =~ /^Unable to find/) { + $log->msg ($issue); + } else { + my %watchers = getWatchers $_; + + $log->msg ('Found ' . scalar (keys %watchers) . " watchers for JIRA Issue $issue"); + + my $result = updateIssueWatchers ($issue, %watchers); + + if ($result =~ /^Unable to/) { + $total{'Missing JIRA Issues'}++; + + $log->err ($result); + } else { + $total{'Issues updated'}++; + } # if + } # if + } # for + + display_duration $startTime, $log; + + Stats (\%total, $log) unless $opts{quiet}; + + return 0; +} # main + +exit main; diff --git a/bin/bice.pl b/bin/bice.pl index 491a304..4f3073a 100755 --- a/bin/bice.pl +++ b/bin/bice.pl @@ -178,7 +178,7 @@ sub SendEmail ($$$$$) { mail ( from => "BICE\@$domain", to => $to, - cc => $contact, + #cc => $contact, subject => $subject, mode => 'html', data => $message, diff --git a/clearadm/etc/clearadm.conf b/clearadm/etc/clearadm.conf index 4bd38f3..e9d7d44 100644 --- a/clearadm/etc/clearadm.conf +++ b/clearadm/etc/clearadm.conf @@ -11,7 +11,7 @@ # (c) Copyright 2010, ClearSCM, Inc., all rights reserved # ############################################################################### -CLEARADM_SERVER: localhost +CLEARADM_SERVER: earth CLEARADM_PORT: 25327 CLEARADM_LOADAVG_THRESHOLD: 5.00 CLEARADM_USERNAME: clearwriter @@ -20,4 +20,4 @@ CLEARADM_BASE: /opt/clearscm/clearadm CLEARADM_LOGDIR: $CLEARADM_BASE/log CLEARADM_RUNDIR: $CLEARADM_BASE/var/run CLEARADM_WEBBASE: http://$CLEARADM_SERVER/clearadm -CLEARADM_NOTIFY: Andrew@DeFaria.com \ No newline at end of file +CLEARADM_NOTIFY: Andrew@DeFaria.com diff --git a/etc/EmacsEverywhere.txt b/etc/EmacsEverywhere.txt new file mode 100644 index 0000000..906f046 --- /dev/null +++ b/etc/EmacsEverywhere.txt @@ -0,0 +1,132 @@ +; +; AutoHotkey Version: 1.x +; Language: English +; Platform: Win9x/NT +; Author: David +; Modified: Andrew DeFaria +; +; Script Function: +; Provides an Emacs-like keybinding emulation mode that can be toggled on and off using +; the ScrollLoke key. + +;========================== +;Initialise +;========================== +#NoEnv ; Recommended for performance and compatibility with future AutoHotkey releases. +SendMode Input ; Recommended for new scripts due to its superior speed and reliability. +SetWorkingDir %A_ScriptDir% ; Ensures a consistent starting directory. +SetTitleMatchMode, RegEx + +enabledIcon := "emacs_everywhere_16.ico" +disabledIcon := "emacs_everywhere_disabled_16.ico" +IsInEmacsMode := false +SetEmacsMode(false) + +;========================== +;Functions +;========================== +SetEmacsMode(toActive) { + local iconFile := toActive ? enabledIcon : disabledIcon + local state := toActive ? "ON" : "OFF" + + IsInEmacsMode := toActive + TrayTip, Emacs Everywhere, Emacs mode is %state%, 10, 1 + Menu, Tray, Icon, %iconFile%, + Menu, Tray, Tip, Emacs Everywhere`nEmacs mode is %state% + + Send {Shift Up} +} + +SendCommand(emacsKey, translationToWindowsKeystrokes, secondWindowsKeystroke="") { + global IsInEmacsMode + if (IsInEmacsMode) { + Send, %translationToWindowsKeystrokes% + if (secondWindowsKeystroke<>"") { + Send, %secondWindowsKeystroke% + } + } else { + Send, %emacsKey% ;passthrough original keystroke + } + return +} + +;========================== +;Emacs mode toggle +;========================== +ScrollLock:: + SetEmacsMode(!IsInEmacsMode) +return + +;========================== +;Character navigation +;========================== + +#IfWinActive ahk_class Chrome_WidgetWin_1 +$^f::SendCommand("^f","{Right}") +$^s::SendCommand("^s","^g") +#IfWinActive + +$^p::SendCommand("^p","{Up}") +$^n::SendCommand("^n","{Down}") +$^b::SendCommand("^b","{Left}") +$^f::SendCommand("^f","{Right}") + +;========================== +;Word Navigation +;========================== + +$!p::SendCommand("!p","^{Up}") +$!n::SendCommand("!n","^{Down}") +$!f::SendCommand("!f","^{Right}") + +$!b::SendCommand("!b","^{Left}") + +;========================== +;Line Navigation +;========================== +$^a::SendCommand("^a","{Home}") +$^e::SendCommand("^e","{End}") + +;========================== +;Page Navigation +;========================== + +;Ctrl-V disabled. Too reliant on that for pasting :$ +$^v::SendCommand("^v","{PgDn}") +$!v::SendCommand("!v","{PgUp}") +$!<::SendCommand("!<","^{Home}") +$!>::SendCommand("!>","^{End}") + +;========================== +;Undo +;========================== + +;========================== +;=== + + + +#IfWinNotActive ahk_class SWT_Window0 +$^/::SendCommand("^_","^z") +#IfWinNotActive + +;========================== +;Killing and Deleting +;========================== +#IfWinNotActive ahk_class mintty +$^d::SendCommand("^d","{Delete}") + +$!d::SendCommand("!d","^+{Right}","{Delete}") +$^h::SendCommand("^h","{Backspace}") + +$!Delete::SendCommand("!{Del}","^+{Left}","{Del}") + +#IfWinNotActive ahk_class (mintty|SWT_Window0) +$^k::SendCommand("^k","+{End}","+{Delete}") ;cut region +#IfWinNotActive + +$^w::SendCommand("^w","+{Delete}","{Shift Up}") ;cut region + +$!w::SendCommand("!w","^{Insert}","{Shift Up}") ;copy region + +$^y::SendCommand("^y","+{Insert}") ;paste \ No newline at end of file diff --git a/etc/doskey.autorun.reg b/etc/doskey.autorun.reg new file mode 100644 index 0000000..cbd77bd Binary files /dev/null and b/etc/doskey.autorun.reg differ diff --git a/etc/doskey.mac b/etc/doskey.mac index 4f9dec6..9885fc6 100644 --- a/etc/doskey.mac +++ b/etc/doskey.mac @@ -1,59 +1,60 @@ -;= rem doskey macros to make cmd more Linux like -ls=dir /w $* -ll=dir /n /-c /q /ta $* -ct=cleartool $* -grep=findstr $* -rm=del $* -cp=copy $* -cat=type $* -history=doskey /history -asperl=C:\Perl\bin\perl $* -lsvob=cleartool lsvob $* -llvob=cleartool lsvob -long $* -lsview=cleartool lsview $* -llview=cleartool lsview -long $* -lsregion=cleartool lsregion $* -register=cleartool register $* -unregister=cleartool unregister $* -mktag=cleartool mktag $* -rmtag=cleartool rmtag $* -setview=cleartool setview $* -setcs=cleartool setcs $* -edcs=cleartool edcs $* -catcs=cleartool catcs $* -pwv=cleartool pwv $* -startview=cleartool startview $* -endview=cleartool endview $* -killview=cleartool endview -server $* -rmtag=cleartool rmtag $* -mktag=cleartool mktag $* -mkview=cleartool mkview $* -rmview=cleartool rmview $* -lsregion=cleartool lsregion $* -describe=cleartool describe $* -vtree=cleartool lsvtree $* -unco=cleartool unco -rm $* -cdiff=cleartool diff $* -space=cleartool space $* -register=cleartool register $* -unregister=cleartool unregister $* -lslic=clearlicense -product ClearCase -rellic=clearlicense -release -product ClearCase $* -lstype=cleartool lstype $* -lltype=cleartool lstype -long $* -lsbranch=cleartoollstype brtype:$1 -llbranch=cleartool lstype -long brtype:$1 -lslabel=cleartool lstype lbtype:$1 -lllabel=cleartool lstype -long lbtype:$1 -lstrigger=cleartool lstype trtype:$1 -lltrigger=cleartool lstype -long trtype:$1 -lslock=cleartool lslock $* -mt=multitool $* -lspacket=multitool lspacket $* -llpacket=multitool lspacket -long $* -lsreplica=multitool lsreplica -vob $* -llreplica=multitool lsreplica -long -vob $* -lsepoch=multitool lsepoch -vob $* -llepoch=multitool lsepoch -long -vob $* -chepoch=multitool chepoch -vob $* +;= rem doskey macros to make cmd more Linux like +ls=dir /w $* +ll=dir /n /-c /q /ta $* +ct=cleartool $* +grep=findstr $* +rm=del $* +cp=copy $* +mv=copy $* +cat=type $* +history=doskey /history +asperl=C:\Perl\bin\perl $* +lsvob=cleartool lsvob $* +llvob=cleartool lsvob -long $* +lsview=cleartool lsview $* +llview=cleartool lsview -long $* +lsregion=cleartool lsregion $* +register=cleartool register $* +unregister=cleartool unregister $* +mktag=cleartool mktag $* +rmtag=cleartool rmtag $* +setview=cleartool setview $* +setcs=cleartool setcs $* +edcs=cleartool edcs $* +catcs=cleartool catcs $* +pwv=cleartool pwv $* +startview=cleartool startview $* +endview=cleartool endview $* +killview=cleartool endview -server $* +rmtag=cleartool rmtag $* +mktag=cleartool mktag $* +mkview=cleartool mkview $* +rmview=cleartool rmview $* +lsregion=cleartool lsregion $* +describe=cleartool describe $* +vtree=cleartool lsvtree $* +unco=cleartool unco -rm $* +cdiff=cleartool diff $* +space=cleartool space $* +register=cleartool register $* +unregister=cleartool unregister $* +lslic=clearlicense -product ClearCase +rellic=clearlicense -release -product ClearCase $* +lstype=cleartool lstype $* +lltype=cleartool lstype -long $* +lsbranch=cleartoollstype brtype:$1 +llbranch=cleartool lstype -long brtype:$1 +lslabel=cleartool lstype lbtype:$1 +lllabel=cleartool lstype -long lbtype:$1 +lstrigger=cleartool lstype trtype:$1 +lltrigger=cleartool lstype -long trtype:$1 +lslock=cleartool lslock $* +mt=multitool $* +lspacket=multitool lspacket $* +llpacket=multitool lspacket -long $* +lsreplica=multitool lsreplica -vob $* +llreplica=multitool lsreplica -long -vob $* +lsepoch=multitool lsepoch -vob $* +llepoch=multitool lsepoch -long -vob $* +chepoch=multitool chepoch -vob $* syncreplica=multitool syncreplica $* \ No newline at end of file diff --git a/etc/startup.cmd b/etc/startup.cmd new file mode 100644 index 0000000..e3f5ed8 --- /dev/null +++ b/etc/startup.cmd @@ -0,0 +1,2 @@ +@echo off +doskey /macrofile=C:\Cygwin\opt\clearscm\etc\doskey.mac \ No newline at end of file diff --git a/lib/Utils.pm b/lib/Utils.pm index a523461..acebce5 100644 --- a/lib/Utils.pm +++ b/lib/Utils.pm @@ -529,7 +529,7 @@ Returns: =cut - open my $pipe, '|', $to + open my $pipe, '|-', $to or error "Unable to open pipe - $!", 1; foreach (@output) { @@ -827,7 +827,8 @@ and the values of the hash will be the counters. =item $log -Logger object to log stats to (if specified) +Logger object to log stats to (if specified). Note: if the Logger object has +errors or warnings then they will be automatically included in the output. =back @@ -849,7 +850,12 @@ Returns: my $msg = "$FindBin::Script Run Statistics:"; - if (scalar keys %$total) { + if ($log and ref $log eq 'Logger') { + $total->{errors} = $log->{errors}; + $total->{warnings} = $log->{warnings}; + } # if + + if (keys %$total) { # Display statistics (if any) if ($log) { $log->msg ($msg); @@ -858,10 +864,10 @@ Returns: } # if foreach (sort keys %$total) { - $msg = $$total{$_} . "\t $_"; + $msg = $total->{$_} . "\t $_"; if ($log) { - $log->msg ($$total{$_} . "\t $_"); + $log->msg ($total->{$_} . "\t $_"); } else { display $msg; } # if diff --git a/maps/bin/MAPSDB.pm b/maps/bin/MAPSDB.pm index 74b1302..8dd79ac 100644 --- a/maps/bin/MAPSDB.pm +++ b/maps/bin/MAPSDB.pm @@ -814,10 +814,10 @@ sub OpenDB ($$) { my $dbname = 'MAPS'; my $dbdriver = 'mysql'; - my $dbserver = $ENV{MAPS_SERVER} ? $ENV{MAPS_SERVER} : 'jupiter'; + my $dbserver = $ENV{MAPS_SERVER} || 'jupiter'; if (!$DB || $DB eq '') { - $dbserver='localhost'; + #$dbserver='localhost'; $DB = DBI->connect("DBI:$dbdriver:$dbname:$dbserver", $username, $password, {PrintError => 0}) or die "Couldn't connect to $dbname database as $username\n" . $DBI::errstr; } # if diff --git a/rc/bash_login b/rc/bash_login index 89ae5a8..1084f41 100644 --- a/rc/bash_login +++ b/rc/bash_login @@ -11,6 +11,22 @@ # (c) Copyright 2000-2005, Andrew@DeFaria.com, all rights reserved. # ################################################################################ +# The following will set HOME, temporarily, to the directory of my $HOME so that +# somebody other than me can envoke my startup scripts. I use this mainly for +# when I become root with sudo -s and what a better environment that the bare +# bones root environment gives you. Note that for us to be effective one must +# source (or .) this file and if so the ${BASH_ARGV[0]} is the normal $0 +home=$(dirname ${BASH_ARGV[0]}) + +# Strip off .rc +home=${home/%\/\.rc/} + +# Now set $HOME if $home is different. +saved_home=$HOME +if [ "$HOME" != "$home" ]; then + HOME=$home +fi + # Set ARCHITECTURE of the machine KERNEL=$(uname -s) if [[ $KERNEL = CYGWIN* ]]; then @@ -129,6 +145,11 @@ set -o emacs set -o monitor set +u +# Shell options +shopt -s autocd +shopt -s cdspell +shopt -s dirspell +shopt -s histappend shopt -s lithist # Aliases: @@ -164,8 +185,12 @@ fi if [ -f "$SYSTEMDRIVE/Perl/bin/perl" ]; then alias asperl="$SYSTEMDRIVE/Perl64/bin/perl" fi - -export LANG=C + +if [ $(locale -a | grep -c en_US.utf8) != 0 ]; then + export LANG=en_US.utf8 +else + export LANG=C +fi if [ $(type -p vim) ]; then alias vi=vim @@ -239,6 +264,9 @@ if [ -d /opt/clearscm/lib ]; then export PERL5LIB="$PERL5LIB:/opt/clearscm/lib" fi +# Lessfile +eval $(lessfile) + # Client specific customizations for script in $(\ls ~/.rc/client_scripts); do # This is not working as long as ACLs are not supported from the remote @@ -255,6 +283,9 @@ done # Set display to local export DISPLAY=${DISPLAY:-:0} +# Reset home in case it changed +HOME=$saved_home + # Now go home (in case we were not autmatically cd'ed there) if [ $(id -u) -ne 0 ]; then cd diff --git a/rc/client_scripts/Audience b/rc/client_scripts/Audience new file mode 100644 index 0000000..071261e --- /dev/null +++ b/rc/client_scripts/Audience @@ -0,0 +1,24 @@ +#!/bin/bash +################################################################################ +# +# File: $RCSfile: Audience,v $ +# Revision: $Revision: 1.0 $ +# Description: Client specific start up for Audience +# Author: Andrew@ClearSCM.com +# Created: Fri Nov 8 13:50:01 PST 2013 +# Modified: $Date: $ +# Language: bash +# +# (c) Copyright 2013, ClearSCM, Inc., all rights reserved. +# +################################################################################ +export SMTPHOST=cas.audience.local +export SMTPFROM=cm@audience.com +export CLEARADM_SERVER=adefaria-lt +export P4CONFIG=.p4config + +append_to_path /cygdrive/c/Program\ Files/Perforce +append_to_path /cygdrive/c/Program\ Files\ \(x86\)/MATLAB/R2013a/bin +append_to_path /mnt/fs-ReleaseEng/Dev/bin + +export CDPATH=~/p4/prod/AudEngr/Import/VSS/ReleaseEng diff --git a/rc/functions b/rc/functions index 69f8aa1..39f7058 100644 --- a/rc/functions +++ b/rc/functions @@ -19,7 +19,13 @@ view_name= # Function to set the title bar. Works on the terminal emulators listed. function title_bar { + if [ $# -gt 1 ]; then + ROOT=shift + ROOT="${NORMAL}\($ROOT\)" + fi + prefix="$@" + # Change $HOME -> ~ if [ "${PWD#$HOME*}" != "$PWD" ]; then current_dir="~${PWD#$HOME*}" @@ -53,7 +59,8 @@ function title_bar { string=$(echo "${SYSNAME##*:}:$@") echo -n "${ESC}]2;$string\007" elif [ "$TERM" = "cygwin" -o "$TERM" = "vt100" -o "$TERM" = "xterm" ]; then - PS1="\[\e]0;$prefix$current_dir\a\e[01;33m\]$SYSNAME:\[\e[0m\]" + # Here's where we set the prompt with root + PS1="\[\e]0;$prefix$current_dir\a$AQUA\]$ROOT\[$YELLOW\]$SYSNAME:\[$NORMAL\]" fi } # title_bar @@ -96,7 +103,7 @@ function set_title { view_name="" title_bar "$ROOT" else - title_bar "${ROOT}View: $view_name: " + title_bar "$ROOT" "View: $view_name: " fi icon_name "${SYSNAME##*:}" diff --git a/songbook/20141221.html b/songbook/20141221.html new file mode 100644 index 0000000..72669f7 --- /dev/null +++ b/songbook/20141221.html @@ -0,0 +1,12 @@ + + +Jam at Andrew's + + +

What

Jam at Andrew's House (Add to your Google Calender  )

Where

1676 Hope Dr #1915
Santa Clara, California, 95054

When

Sunday, 12/21/2014
1-4 Pm

Parking

My community is gated. If you can get in the gate you can park in any uncovered parking space but there are pretty limited. My apt is close to the street and there's usually some parking around there.

When you get here call me (408-596-4937). I can open the gate and show you in.

I have room for about 4-5 people in my house for an acoustic jam. As +this will be introductory I will suggest a few songs here. Note that you + can go to http://defaria.com/songbook to get copies of the words and +chords for these, and even the MP3 on some songs. Of course there are lots of other songs we can do or just straight out jamming.

+
  1. Amie (Pure Prairie League)
  2. Desperado (Eagles)
  3. I Saw Her Standing There (Beatles)
  4. Swingtown (Steve Miller)
  5. Take it Easy (Eagles)
+ + diff --git a/songbook/20150111.html b/songbook/20150111.html new file mode 100644 index 0000000..31df4fc --- /dev/null +++ b/songbook/20150111.html @@ -0,0 +1,12 @@ + + +Jam at Andrew's + + +

What

Jam at Andrew's House (Add to your Google Calender  )

Where

1676 Hope Dr #1915
Santa Clara, California, 95054

When

Sunday, 1/11/2015
1-4 Pm

Parking

My community is gated. If you can get in the gate you can park in any uncovered parking space but there are pretty limited. My apt is close to the street and there's usually some parking around there.

When you get here call me (408-596-4937). I can open the gate and show you in.

I have room for about 4-5 people in my house for an acoustic jam. As +this will be introductory I will suggest a few songs here. Note that you + can go to http://defaria.com/songbook to get copies of the words and +chords for these, and even the MP3 on some songs. Of course there are lots of other songs we can do or just straight out jamming.

+
  1. Country Roads (John Denver)
  2. Desperado (Eagles)
  3. Hotel California (Eagles)
  4. Imagine (John Lennon)
  5. Maggie May (Rod Stewart)
+ + diff --git a/web/Resumes/Andrew/Resume.doc b/web/Resumes/Andrew/Resume.doc index 599c067..a9b799c 100644 Binary files a/web/Resumes/Andrew/Resume.doc and b/web/Resumes/Andrew/Resume.doc differ diff --git a/web/Resumes/Andrew/index.php b/web/Resumes/Andrew/index.php index 33456f6..25dc339 100644 --- a/web/Resumes/Andrew/index.php +++ b/web/Resumes/Andrew/index.php @@ -177,12 +177,16 @@ function stoptimer () { Contract
Workblog

-

Worked as a Linux Admin, Perforce Admin maintaining an inhouse developed build system - that utilizes Perl/PHP/Apache/Linux/Windows servers to allow engineers in the field to - perform customized builds around the world. Helped set up and establish Swarm installation, - integration between Bugzilla and Perforce (P4DTG) on various vituralized hardware. Established - and maintained documentation of various Dev/Test/Prod environments throughout the organization.

- +

Worked in the Devops group performing Linux Admin, Perforce Admin and + maintaining an inhouse developed build system that utilizes Perl/PHP/Apache/Linux/Windows + servers to allow engineers in the field to perform customized builds around the + world. Helped set up and establish Perforce Swarm installation, integration between + Bugzilla and Perforce (P4DTG) on various vituralized hardware. Established and + maintained documentation of various Dev/Test/Prod environments throughout the + organization. Wrote scripts to import data from Bugzilla to JIRA, assisted with + JIRA setup and integration of Salesforce with JIRA.

+ +

Also helped administer and support a wide array of engineering tools such as Perforce, Jama, Jira, Git, Gerrit, Bugzilla, Autobuilder, Build Script, Jenkins, Confluence and Swarm.


Axcient