Skip to content

Commit 5e6f2b4

Browse files
committed
Merge pull request #8 from Ensembl/feature/github_pullrequests
Feature/github pullrequests
2 parents c35e1b1 + 8499ac6 commit 5e6f2b4

2 files changed

Lines changed: 221 additions & 2 deletions

File tree

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env perl
2+
3+
use strict;
4+
use warnings;
5+
use DateTime::Format::ISO8601;
6+
7+
BEGIN {
8+
use Cwd;
9+
use File::Basename;
10+
use File::Spec;
11+
my $dirname = dirname(Cwd::realpath(__FILE__));
12+
my $lib = File::Spec->catdir($dirname, File::Spec->updir(), 'lib');
13+
if(-d $lib) {
14+
unshift(@INC, $lib);
15+
}
16+
else {
17+
die "Cannot find the lib directory in the expected location $lib";
18+
}
19+
};
20+
21+
use EnsEMBL::GitHub qw/parse_oauth_token rest_request public_repositories/;
22+
23+
use Pod::Usage;
24+
use Getopt::Long;
25+
use JSON;
26+
use HTTP::Tiny;
27+
use File::Spec;
28+
use DateTime;
29+
use DateTime::Format::ISO8601;
30+
use DateTime::Duration;
31+
use DateTime::Format::Duration;
32+
33+
run();
34+
35+
sub run {
36+
my $opts = parse_command_line();
37+
my $repos = get_repos($opts);
38+
39+
my $now = DateTime->now();
40+
my $delta = DateTime::Duration->new(days => 7);
41+
my $cutoff = $now->subtract_duration($delta);
42+
43+
my $duration_format = DateTime::Format::Duration->new(pattern => '%Y years, %m months, %e days');
44+
45+
foreach my $repo (@{$repos}) {
46+
my $pulls = get_pull_requests($opts, $repo);
47+
my @old_pulls =
48+
sort { DateTime->compare($a->{recent_date}, $b->{recent_date}) }
49+
grep { DateTime->compare($_->{recent_date}, $cutoff) < 0 } #filter anything which is as old or older than the cutoff
50+
@{$pulls};
51+
52+
next unless @old_pulls;
53+
print "* ${repo}: old pull requests found\n\n";
54+
foreach my $pull (@old_pulls) {
55+
my $url = $pull->{html_url};
56+
my $title = $pull->{title};
57+
my $user = $pull->{user}->{login};
58+
my $recent_date = $pull->{recent_date};
59+
my $age_of_pull = $duration_format->format_duration($now->subtract_datetime($recent_date));
60+
my $base = $pull->{base}->{label};
61+
62+
print "\t* USER : ${user}\n";
63+
print "\t* TITLE: ${title}\n";
64+
print "\t* AGE : ${age_of_pull}\n";
65+
print "\t* DATE : ${recent_date}\n";
66+
print "\t* BASE : ${base}\n";
67+
print "\t* URL : ${url}\n";
68+
69+
print "\n";
70+
}
71+
print "\n----------------------------\n";
72+
}
73+
return;
74+
}
75+
76+
sub get_repos {
77+
my ($opts) = @_;
78+
return $opts->{repository} if @{$opts->{repository}};
79+
return public_repositories($opts->{organisation}, get_oauth_token($opts));
80+
}
81+
82+
sub get_pull_requests {
83+
my ($opts, $repo) = @_;
84+
my $org = $opts->{organisation};
85+
my $pulls = rest_request('GET', "/repos/${org}/${repo}/pulls?state=open", get_oauth_token($opts));
86+
foreach my $pull (@{$pulls}) {
87+
my @sorted_dates =
88+
sort { DateTime->compare($a, $b) }
89+
map { DateTime::Format::ISO8601->parse_datetime($_) }
90+
grep { defined $_ }
91+
map { $pull->{$_} }
92+
qw/created_at updated_at closed_at merged_at/;
93+
$pull->{recent_date} = pop(@sorted_dates);
94+
}
95+
return $pulls;
96+
}
97+
98+
sub get_oauth_token {
99+
my ($opts) = @_;
100+
return $opts->{oauth} if defined $opts->{oauth};
101+
my $path = $opts->{oauth_file};
102+
if($path && -f $path) {
103+
my $token = parse_oauth_token($path);
104+
return $opts->{oauth} = $token;
105+
}
106+
return;
107+
}
108+
109+
sub parse_command_line {
110+
my $opts = {
111+
repository => [],
112+
organisation => 'Ensembl',
113+
help => 0,
114+
man => 0
115+
};
116+
117+
GetOptions($opts, qw/
118+
repository|repo=s@
119+
oauth=s
120+
oauth_file=s
121+
organisation|organization=s
122+
help|?
123+
man
124+
/) or pod2usage(2);
125+
126+
pod2usage(1) if $opts->{help};
127+
pod2usage(-exitval => 0, -verbose => 2) if $opts->{man};
128+
129+
my $oauth_file = $opts->{oauth_file};
130+
if($oauth_file && ! -f $oauth_file) {
131+
pod2usage(-exitval => 1, -verbose => 1, -msg => 'Cannot find the file specified in your --oauth_file param: '.$oauth_file);
132+
}
133+
134+
return $opts;
135+
}
136+
137+
__END__
138+
=pod
139+
140+
=head1 NAME
141+
142+
github-pullrequestcheck - Check for old pull requests
143+
144+
=head1 SYNOPSIS
145+
146+
github-pullrequestcheck [--oauth OAUTH] [--oauth_file FILE] [--organisation ORG] [--repository REPO] [-h] [-m]
147+
148+
# Search for all open old pull requests with no oauth authentication
149+
150+
github-pullrequestcheck
151+
152+
# Search for all open old pull requests in a single repo
153+
154+
github-pullrequestcheck --repository ensembl
155+
156+
# Search for all open old pull requests with oauth authentication
157+
158+
github-pullrequestcheck --oauth XXXXXXXXXXXXXXXXXXXXXX
159+
160+
# Using a OAuth file
161+
162+
github-pullrequestcheck --oauth_file ~/.private/github-oauth
163+
164+
# Using a different organisation
165+
166+
github-switchbranch --organisation EnsemblGenomes
167+
168+
=head1 DESCRIPTION
169+
170+
171+
All of this is done via GitHub's REST API and requires the generation of an oauth token for authentication purposes. You can do this via your account's setting page under Applications and generate a B<Personal Access Token>.
172+
173+
The code can save an OAUTH token in a file and use this for authentication. To do so give it using the B<--oauth_file> option. The file must be readable only by the user (we are strict that access settings must be rw------- for user)
174+
175+
=head1 OPTIONS
176+
177+
=over 8
178+
179+
=item B<--oauth>
180+
181+
The OAuth token to use. More information is available from L<http://developer.github.com/v3/#authentication> and can be generated from your personal settings page.
182+
183+
=item B<--oauth_file>
184+
185+
The file which contains the OAuth key. Must be just the OAuth token (any whitespace will be removed). The file must be read/write only by the user and its containing directory must be read/write/execute by the user alone.
186+
187+
=item B<--organisation|organization>
188+
189+
The GitHub organisation to list repositories for. Defaults to Ensembl
190+
191+
=item B<--repository|repo>
192+
193+
The repository to use. If not specified we use all public repositories
194+
195+
=item B<--help>
196+
197+
Print the help information
198+
199+
=item B<--man>
200+
201+
Print a man page
202+
203+
=back
204+
205+
=cut
206+

lib/EnsEMBL/GitHub.pm

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ limitations under the License.
1717
=cut
1818

1919
package EnsEMBL::GitHub;
20+
use strict;
21+
use warnings;
2022

2123
use parent qw/Exporter/;
2224
use Carp;
2325
use Cwd;
2426
use File::Spec;
2527
use HTTP::Tiny;
28+
use Fcntl ':mode';
2629

2730
my $base_url = 'https://api.github.com';
2831

@@ -37,12 +40,19 @@ eval {
3740
our @EXPORT = qw/
3841
rest_request
3942
parse_oauth_token
43+
public_repositories
4044
/;
4145

46+
sub public_repositories {
47+
my ($organisation, $oauth) = @_;
48+
my $json = rest_request('GET', "/orgs/${organisation}/repos?type=public", $oauth);
49+
return [ sort map { $_->{name} } @{$json} ];
50+
}
51+
4252
# Performs a REST request. You can specify the METHOD, extension URL, oauth token
4353
# and body content
4454
sub rest_request {
45-
my ($method, $url, $oauth, $content) = @_;
55+
my ($method, $url, $oauth_token, $content) = @_;
4656
die 'No method specified' if ! $method;
4757
die 'No URL specified' if ! $url;
4858
my $http = HTTP::Tiny->new();
@@ -53,7 +63,10 @@ sub rest_request {
5363
$options->{content} = JSON::encode_json($content);
5464
}
5565
my $response = $http->request($method, $base_url.$url, $options);
56-
die "Failed to process $method (${url})! STATUS: $response->{status} REASON: $response->{reason}\n" unless $response->{success};
66+
if(! $response->{success}) {
67+
use Data::Dumper; warn Dumper $response->{headers};
68+
die "Failed to process $method (${url})! STATUS: $response->{status} REASON: $response->{reason}\n";
69+
}
5770
return JSON::decode_json($response->{content});
5871
}
5972

0 commit comments

Comments
 (0)