package Net::GitHub::V3::Repos;
use Moo;
our $VERSION = '0.64';
our $AUTHORITY = 'cpan:FAYLAND';
use Carp;
use URI::Escape;
use URI;
use HTTP::Request::Common qw(POST);
with 'Net::GitHub::V3::Query';
sub list {
my ( $self, $args ) = @_;
# for old
unless (ref($args) eq 'HASH') {
$args = { type => $args };
}
my $uri = URI->new('/user/repos');
$uri->query_form($args);
return $self->query($uri->as_string);
}
sub list_all {
my ( $self, $since ) = @_;
$since ||= 'first';
my $u = '/repositories';
$u .= '?since=' . $since if $since ne 'first';
return $self->query($u);
}
sub list_user {
my ($self, $user, $args) = @_;
$user ||= $self->u;
# for old
unless (ref($args) eq 'HASH') {
$args = { type => $args };
}
my $uri = URI->new("/users/" . uri_escape($user) . "/repos");
$uri->query_form($args);
return $self->query($uri->as_string);
}
sub list_org {
my ($self, $org, $type) = @_;
$type ||= 'all';
my $u = "/orgs/" . uri_escape($org) . "/repos";
$u .= '?type=' . $type if $type ne 'all';
return $self->query($u);
}
sub create {
my ( $self, $data ) = @_;
my $u = '/user/repos';
if (exists $data->{org}) {
my $o = delete $data->{org};
$u = "/orgs/" . uri_escape($o) . "/repos";
}
return $self->query('POST', $u, $data);
}
sub upload_asset {
my $self = shift;
unshift @_, $self->u, $self->repo if @_ < 5;
my ($user, $repos, $release_id, $name, $content_type, $file_content) = @_;
my $ua = $self->ua;
my $url = $self->upload_url . "/repos/$user/$repos/releases/$release_id/assets?name=" . uri_escape($name);
my $req = HTTP::Request->new( 'POST', $url );
$req->accept_decodable;
$req->content($file_content);
$req->header( 'Content-Type', $content_type );
my $res = $ua->request($req);
my $data;
if ($res->header('Content-Type') and $res->header('Content-Type') =~ 'application/json') {
my $json = $res->decoded_content;
$data = eval { $self->json->jsonToObj($json) };
unless ($data) {
# We tolerate bad JSON for errors,
# otherwise we just rethrow the JSON parsing problem.
die unless $res->is_error;
$data = { message => $res->message };
}
} else {
$data = { message => $res->message };
}
return wantarray ? %$data : $data;
}
sub commits {
my $self = shift;
if (@_ < 2) {
unshift @_, $self->repo;
unshift @_, $self->u;
}
my ($user, $repos, $args) = @_;
my $uri = URI->new("/repos/" . uri_escape($user) . "/" . uri_escape($repos) . '/commits');
$uri->query_form($args);
return $self->query($uri->as_string);
}
## build methods on fly
my %__methods = (
get => { url => "/repos/%s/%s" },
update => { url => "/repos/%s/%s", method => 'PATCH', args => 1 },
contributors => { url => "/repos/%s/%s/contributors" },
languages => { url => "/repos/%s/%s/languages" },
teams => { url => "/repos/%s/%s/teams" },
tags => { url => "/repos/%s/%s/tags" },
branches => { url => "/repos/%s/%s/branches" },
branch => { url => "/repos/%s/%s/branches/%s" },
delete => { url => "/repos/%s/%s", method => 'DELETE', check_status => 204 },
# http://developer.github.com/v3/repos/collaborators/
collaborators => { url => "/repos/%s/%s/collaborators" },
is_collaborator => { url => "/repos/%s/%s/collaborators/%s", check_status => 204 },
add_collaborator => { url => "/repos/%s/%s/collaborators/%s", method => 'PUT', check_status => 204 },
delete_collaborator => { url => "/repos/%s/%s/collaborators/%s", method => 'DELETE', check_status => 204 },
# http://developer.github.com/v3/repos/commits/
commit => { url => "/repos/%s/%s/commits/%s" },
comments => { url => "/repos/%s/%s/comments" },
comment => { url => "/repos/%s/%s/comments/%s" },
commit_comments => { url => "/repos/%s/%s/commits/%s/comments" },
create_comment => { url => "/repos/%s/%s/commits/%s/comments", method => 'POST', args => 1 },
update_comment => { url => "/repos/%s/%s/comments/%s", method => 'PATCH', args => 1 },
delete_comment => { url => "/repos/%s/%s/comments/%s", method => 'DELETE', check_status => 204 },
compare_commits => { url => "/repos/%s/%s/compare/%s...%s" },
# http://developer.github.com/v3/repos/contents/
readme => { url => "/repos/%s/%s/readme" },
get_content => { url => "/repos/%s/%s/contents/%s" },
# http://developer.github.com/v3/repos/downloads/
downloads => { url => "/repos/%s/%s/downloads" },
download => { url => "/repos/%s/%s/downloads/%s" },
delete_download => { url => "/repos/%s/%s/downloads/%s", method => 'DELETE', check_status => 204 },
# http://developer.github.com/v3/repos/releases/
releases => { url => "/repos/%s/%s/releases" },
release => { url => "/repos/%s/%s/releases/%s" },
create_release => { url => "/repos/%s/%s/releases", method => 'POST', args => 1 },
update_release => { url => "/repos/%s/%s/releases/%s", method => 'PATCH', args => 1 },
delete_release => { url => "/repos/%s/%s/releases/%s", method => 'DELETE', check_status => 204 },
release_assets => { url => "/repos/%s/%s/releases/%s/assets" },
release_asset => { url => "/repos/%s/%s/releases/%s/assets/%s" },
update_release_asset => { url => "/repos/%s/%s/releases/%s/assets/%s", method => 'PATCH', args => 1 },
delete_release_asset => { url => "/repos/%s/%s/releases/%s/assets/%s", method => 'DELETE', check_status => 204 },
forks => { url => "/repos/%s/%s/forks" },
# http://developer.github.com/v3/repos/keys/
keys => { url => "/repos/%s/%s/keys" },
key => { url => "/repos/%s/%s/keys/%s" },
create_key => { url => "/repos/%s/%s/keys", method => 'POST', args => 1 },
update_key => { url => "/repos/%s/%s/keys/%s", method => 'PATCH', check_status => 204, args => 1 },
delete_key => { url => "/repos/%s/%s/keys/%s", method => 'DELETE', check_status => 204 },
# http://developer.github.com/v3/repos/watching/
watchers => { url => "/repos/%s/%s/watchers" },
is_watching => { url => "/user/watched/%s/%s", is_u_repo => 1, check_status => 204 },
watch => { url => "/user/watched/%s/%s", is_u_repo => 1, method => 'PUT', check_status => 204 },
unwatch => { url => "/user/watched/%s/%s", is_u_repo => 1, method => 'DELETE', check_status => 204 },
subscribers => { url => "/repos/%s/%s/subscribers" },
subscription => { url => "/repos/%s/%s/subscription" },
is_subscribed => { url => "/repos/%s/%s/subscription", check_status => 200 },
subscribe => { url => "/repos/%s/%s/subscription", method => 'PUT',
check_status => 200, args => 1 },
unsubscribe => { url => "/repos/%s/%s/subscription", method => 'DELETE', check_status => 204 },
# http://developer.github.com/v3/repos/hooks/
hooks => { url => "/repos/%s/%s/hooks" },
hook => { url => "/repos/%s/%s/hooks/%s" },
delete_hook => { url => "/repos/%s/%s/hooks/%s", method => 'DELETE', check_status => 204 },
test_hook => { url => "/repos/%s/%s/hooks/%s/test", method => 'POST', check_status => 204 },
create_hook => { url => "/repos/%s/%s/hooks", method => 'POST', args => 1 },
update_hook => { url => "/repos/%s/%s/hooks/%s", method => 'PATCH', args => 1 },
# http://developer.github.com/v3/repos/merging/
merges => { url => "/repos/%s/%s/merges", method => 'POST', args => 1 },
# http://developer.github.com/v3/repos/statuses/
list_statuses => { url => "/repos/%s/%s/statuses/%s" },
create_status => { url => "/repos/%s/%s/statuses/%s", method => 'POST', args => 1 },
);
__build_methods(__PACKAGE__, %__methods);
sub create_download {
my $self = shift;
if (@_ == 1) {
unshift @_, $self->repo;
unshift @_, $self->u;
}
my ($user, $repos, $download) = @_;
my $file = delete $download->{file};
my $u = "/repos/" . uri_escape($user) . "/" . uri_escape($repos) . '/downloads';
my $d = $self->query('POST', $u, $download);
if (defined $file) {
return $self->upload_download($d, $file);
} else {
return $d;
}
}
sub upload_download {
my $self = shift;
if (@_ < 3) {
unshift @_, $self->repo;
unshift @_, $self->u;
}
my ($user, $repos, $download, $file) = @_;
# must successful on create_download
return 0 unless exists $download->{s3_url};
## POST form-data
my %data = (
Content_Type => 'form-data',
Content => [
'key' => $download->{path},
'acl' => $download->{acl},
'success_action_status' => 201,
'Filename' => $download->{name},
'AWSAccessKeyId' => $download->{accesskeyid},
'Policy' => $download->{policy},
'Signature' => $download->{signature},
'Content-Type' => $download->{mime_type},
'file' => [ $file ],
],
);
my $request = POST $download->{s3_url}, %data;
my $res = $self->ua->request($request);
return $res->code == 201 ? 1 : 0;
}
## http://developer.github.com/v3/repos/forks/
sub create_fork {
my $self = shift;
if (@_ < 2) {
unshift @_, $self->repo;
unshift @_, $self->u;
}
my ($user, $repos, $org) = @_;
my $u = "/repos/" . uri_escape($user) . "/" . uri_escape($repos) . '/forks';
$u .= '?org=' . $org if defined $org;
return $self->query('POST', $u);
}
## http://developer.github.com/v3/repos/watching/
sub watched {
my ($self, $user) = @_;
my $u = $user ? '/users/' . uri_escape($user). '/watched' : '/user/watched';
return $self->query($u);
}
no Moo;
1;
__END__
=head1 NAME
Net::GitHub::V3::Repos - GitHub Repos API
=head1 SYNOPSIS
use Net::GitHub::V3;
my $gh = Net::GitHub::V3->new; # read L<Net::GitHub::V3> to set right authentication info
my $repos = $gh->repos;
# set :user/:repo for simple calls
$repos->set_default_user_repo('fayland', 'perl-net-github');
my @contributors = $repos->contributors; # don't need pass user and repos
=head1 DESCRIPTION
=head2 METHODS
=head3 Repos
L<http://developer.github.com/v3/repos/>
=over 4
=item list
=item list_all
# All public repositories on Github
my @rp = $repos->list_all;
# starting at id 500
my @rp = $repos->list_all(500);
=item list_user
=item list_org
my @rp = $repos->list; # or my $rp = $repos->list;
my @rp = $repos->list({
type => 'private'
sort => 'updated'
});
my @rp = $repos->list_user('c9s');
my @rp = $repos->list_user('c9s', {
type => 'member'
});
my @rp = $repos->list_org('perlchina');
my @rp = $repos->list_org('perlchina', 'public');
=item create
# create for yourself
my $rp = $repos->create( {
"name" => "Hello-World",
"description" => "This is your first repo",
"homepage" => "https://github.com"
} );
# create for organization
my $rp = $repos->create( {
"org" => "perlchina", ## the organization
"name" => "Hello-World",
"description" => "This is your first repo",
"homepage" => "https://github.com"
} );
=item get
my $rp = $repos->get('fayland', 'perl-net-github');
=back
B<To ease the keyboard, we provied two ways to call any method which starts with :user/:repo>
1. SET user/repos before call methods below
$gh->set_default_user_repo('fayland', 'perl-net-github'); # take effects for all $gh->
$repos->set_default_user_repo('fayland', 'perl-net-github'); # only take effect to $gh->repos
my @contributors = $repos->contributors;
2. If it is just for once, we can pass :user, :repo before any arguments
my @contributors = $repos->contributors($user, $repo);
=over 4
=item update
$repos->update({ homepage => 'https://metacpan.org/module/Net::GitHub' });
=item delete
$repos->delete();
=item contributors
=item languages
=item teams
=item tags
=item contributors
my @contributors = $repos->contributors;
my @languages = $repos->languages;
my @teams = $repos->teams;
my @tags = $repos->tags;
my @branches = $repos->branches;
my $branch = $repos->branch('master');
=back
=head3 Repo Collaborators API
L<http://developer.github.com/v3/repos/collaborators/>
=over 4
=item collaborators
=item is_collaborator
=item add_collaborator
=item delete_collaborator
my @collaborators = $repos->collaborators;
my $is = $repos->is_collaborator('fayland');
$repos->add_collaborator('fayland');
$repos->delete_collaborator('fayland');
=back
=head3 Commits API
L<http://developer.github.com/v3/repos/commits/>
=over 4
=item commits
=item commit
my @commits = $repos->commits;
my @commits = $repos->commits({
author => 'fayland'
});
my $commit = $repos->commit($sha);
=item comments
=item commit_comments
=item create_comment
=item comment
=item update_comment
=item delete_comment
my @comments = $repos->comments;
my @comments = $repos->commit_comments($sha);
my $comment = $repos->create_comment($sha, {
"body" => "Nice change",
"commit_id" => "6dcb09b5b57875f334f61aebed695e2e4193db5e",
"line" => 1,
"path" => "file1.txt",
"position" => 4
});
my $comment = $repos->comment($comment_id);
my $comment = $repos->update_comment($comment_id, {
"body" => "Nice change"
});
my $st = $repos->delete_comment($comment_id);
=item compare_commits
my $diffs = $repos->compare_commits($base, $head);
=back
=head3 Forks API
L<http://developer.github.com/v3/repos/forks/>
=over 4
=item forks
=item create_fork
my @forks = $repos->forks;
my $fork = $repos->create_fork;
my $fork = $repos->create_fork($org);
=back
=head3 Repos Deploy Keys API
L<http://developer.github.com/v3/repos/keys/>
=over 4
=item keys
=item key
=item create_key
=item update_key
=item delete_key
my @keys = $repos->keys;
my $key = $repos->key($key_id); # get key
$repos->create_key( {
title => 'title',
key => $key
} );
$repos->update_key($key_id, {
title => $title,
key => $key
});
$repos->delete_key($key_id);
=back
=head3 Repo Watching API
L<http://developer.github.com/v3/repos/watching/>
=over 4
=item watchers
my @watchers = $repos->watchers;
=item watched
my @repos = $repos->watched; # what I watched
my @repos = $repos->watched('c9s');
=item is_watching
my $is_watching = $repos->is_watching;
my $is_watching = $repos->is_watching('fayland', 'perl-net-github');
=item watch
=item unwatch
my $st = $repos->watch();
my $st = $repos->watch('fayland', 'perl-net-github');
my $st = $repos->unwatch();
my $st = $repos->unwatch('fayland', 'perl-net-github');
=back
=head3 Subscriptions
Github changed the ideas of Watchers (stars) and Subscriptions (new watchers).
https://github.com/blog/1204-notifications-stars
The Watchers code in this module predates the terminology change, so the new
Watcher methods use the GitHub 'subscription' terminology.
=over 4
=item subscribers
Returns a list of subscriber data hashes.
=item is_subscribed
Returns true or false if you are subscribed
$repos->is_subscribed();
$repos->is_subscribed('fayland','perl-net-github');
=item subscription
Returns more information about your subscription to a repo.
is_subscribed is a shortcut to calling this and checking for
subscribed => 1.
=item subscribe
Required argument telling github if you want to subscribe or if you want
to ignore mentions. If you want to change from subscribed to ignores you
need to unsubscribe first.
$repos->subscribe('fayland','perl-net-github', { subscribed => 1 })
$repos->subscribe('fayland','perl-net-github', { ignored => 1 })
=item unsubscribe
$repos->unsubscribe('fayland','perl-net-github');
=back
=head3 Hooks API
L<http://developer.github.com/v3/repos/hooks/>
=over 4
=item hooks
=item hook
=item create_hook
=item update_hook
=item test_hook
=item delete_hook
my @hooks = $repos->hooks;
my $hook = $repos->hook($hook_id);
my $hook = $repos->create_hook($hook_hash);
my $hook = $repos->update_hook($hook_id, $new_hook_hash);
my $st = $repos->test_hook($hook_id);
my $st = $repos->delete_hook($hook_id);
=back
=head3 Repo Merging API
L<http://developer.github.com/v3/repos/merging/>
=over 4
=item merges
my $status = $repos->merges( {
"base" => "master",
"head" => "cool_feature",
"commit_message" => "Shipped cool_feature!"
} );
=back
=head3 Repo Statuses API
L<http://developer.github.com/v3/repos/statuses/>
=over 4
=item list_statuses
my @statuses = $repos->list_statuses($sha1);
=item create_status
my $status = $repos->create_status( {
"state" => "success",
"target_url" => "https://example.com/build/status",
"description" => "The build succeeded!"
} );
=back
=head3 Repo Releases API
L<http://developer.github.com/v3/repos/releases/>
=over 4
=item releases
my @releases = $repos->releases();
=item release
my $release = $repos->release($release_id);
=item create_release
my $release = $repos->create_release({
"tag_name" => "v1.0.0",
"target_commitish" => "master",
"name" => "v1.0.0",
"body" => "Description of the release",
"draft" => \1,
});
=item update_release
my $release = $repos->update_release($release_id, {
"tag_name" => "v1.0.0",
"target_commitish" => "master",
"name" => "v1.0.0",
"body" => "Description of the release",
});
=item delete_release
$repos->delete_release($release_id);
=item release_assets
my @release_assets = $repos->release_assets($release_id);
=item upload_asset
my $asset = $repos->upload_asset($release_id, $name, $content_type, $file_content);
Check examples/upload_asset.pl for a working example.
=item release_asset
my $release_asset = $repos->release_asset($release_id, $asset_id);
=item update_release_asset
my $release_asset = $repos->update_release_asset($release_id, $asset_id, {
name" => "foo-1.0.0-osx.zip",
"label" => "Mac binary"
});
=item delete_release_asset
my $ok = $repos->delete_release_asset($release_id, $asset_id);
=back
=head1 AUTHOR & COPYRIGHT & LICENSE
Refer L<Net::GitHub>