universal-git-client 1.2.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +40 -0
- data/VERSION +1 -0
- data/lib/universal-git-client.rb +18 -0
- data/lib/universal-git-client/client.rb +72 -0
- data/lib/universal-git-client/client_factory.rb +26 -0
- data/lib/universal-git-client/configuration.rb +13 -0
- data/lib/universal-git-client/errors.rb +29 -0
- data/lib/universal-git-client/http/base.rb +69 -0
- data/lib/universal-git-client/http/bitbucket.rb +118 -0
- data/lib/universal-git-client/http/bitbucket_server.rb +134 -0
- data/lib/universal-git-client/http/github.rb +123 -0
- data/lib/universal-git-client/http/gitlab.rb +130 -0
- data/lib/universal-git-client/http/helpers/dummy_response.rb +14 -0
- data/lib/universal-git-client/http/response_validation.rb +28 -0
- data/lib/universal-git-client/normalizers/base.rb +103 -0
- data/lib/universal-git-client/normalizers/bitbucket.rb +93 -0
- data/lib/universal-git-client/normalizers/bitbucket_server.rb +110 -0
- data/lib/universal-git-client/normalizers/github.rb +35 -0
- data/lib/universal-git-client/normalizers/gitlab.rb +53 -0
- metadata +320 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'universal-git-client/http/base'
|
2
|
+
|
3
|
+
module UniversalGitClient
|
4
|
+
module Http
|
5
|
+
class Github < Base
|
6
|
+
def user
|
7
|
+
with_response_validation! do
|
8
|
+
self.class.get('/user', default_options)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def organizations(page: 1, per_page: nil)
|
13
|
+
with_response_validation! do
|
14
|
+
self.class.get(
|
15
|
+
'/user/orgs',
|
16
|
+
default_options.merge(
|
17
|
+
query: {
|
18
|
+
page: page,
|
19
|
+
per_page: per_page || default_elements_per_page,
|
20
|
+
},
|
21
|
+
)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_repos(page: 1, per_page: nil)
|
27
|
+
with_response_validation! do
|
28
|
+
self.class.get(
|
29
|
+
'/user/repos',
|
30
|
+
default_options.merge(
|
31
|
+
query: {
|
32
|
+
affiliation: 'owner',
|
33
|
+
page: page,
|
34
|
+
per_page: per_page || default_elements_per_page,
|
35
|
+
},
|
36
|
+
)
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def orga_repos(organization:, page: 1, per_page: nil)
|
42
|
+
# TODO: Handle permissions
|
43
|
+
with_response_validation! do
|
44
|
+
self.class.get(
|
45
|
+
"/orgs/#{organization}/repos",
|
46
|
+
default_options.merge(
|
47
|
+
query: {
|
48
|
+
page: page,
|
49
|
+
per_page: per_page || default_elements_per_page,
|
50
|
+
},
|
51
|
+
)
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def repository(owner:, repo:)
|
57
|
+
with_response_validation! do
|
58
|
+
self.class.get("/repos/#{owner}/#{repo}", default_options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def branches(owner:, repo:, page: 1, per_page: nil)
|
63
|
+
with_response_validation! do
|
64
|
+
self.class.get(
|
65
|
+
"/repos/#{owner}/#{repo}/branches",
|
66
|
+
default_options.merge(
|
67
|
+
query: {
|
68
|
+
page: page,
|
69
|
+
per_page: per_page || default_elements_per_page,
|
70
|
+
},
|
71
|
+
)
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def branch(owner:, repo:, branch:)
|
77
|
+
with_response_validation! do
|
78
|
+
self.class.get(
|
79
|
+
"/repos/#{owner}/#{repo}/branches/#{branch}",
|
80
|
+
default_options
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def download_repo_archive(owner:, repo:, branch: nil)
|
86
|
+
path = "#{base_url}/repos/#{owner}/#{repo}/zipball"
|
87
|
+
path << "/#{branch}" if branch
|
88
|
+
Down.download(path, down_default_options)
|
89
|
+
end
|
90
|
+
|
91
|
+
def setup_repo_webhook(owner:, repo:, webhook_url:, webhook_secret: nil)
|
92
|
+
with_response_validation! do
|
93
|
+
self.class.post(
|
94
|
+
"/repos/#{owner}/#{repo}/hooks",
|
95
|
+
default_options.merge(
|
96
|
+
body: {
|
97
|
+
name: 'web',
|
98
|
+
active: true,
|
99
|
+
events: [
|
100
|
+
'push',
|
101
|
+
],
|
102
|
+
config: {
|
103
|
+
url: webhook_url,
|
104
|
+
secret: webhook_secret,
|
105
|
+
content_type: 'json',
|
106
|
+
}.compact,
|
107
|
+
}.to_json,
|
108
|
+
)
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete_repo_webhook(owner:, repo:, webhook_id:)
|
114
|
+
with_response_validation! do
|
115
|
+
self.class.delete(
|
116
|
+
"/repos/#{owner}/#{repo}/hooks/#{webhook_id}",
|
117
|
+
default_options
|
118
|
+
)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'universal-git-client/http/base'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module UniversalGitClient
|
5
|
+
module Http
|
6
|
+
class Gitlab < Base
|
7
|
+
def user
|
8
|
+
with_response_validation! do
|
9
|
+
self.class.get('/user', default_options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def organizations(page: 1, per_page: nil)
|
14
|
+
with_response_validation! do
|
15
|
+
self.class.get(
|
16
|
+
'/groups',
|
17
|
+
default_options.merge(
|
18
|
+
query: {
|
19
|
+
page: page,
|
20
|
+
per_page: per_page || default_elements_per_page,
|
21
|
+
},
|
22
|
+
)
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def user_repos(page: 1, per_page: nil)
|
28
|
+
current_user_id = self.user['id']
|
29
|
+
with_response_validation! do
|
30
|
+
self.class.get(
|
31
|
+
"/users/#{current_user_id}/projects",
|
32
|
+
default_options.merge(
|
33
|
+
query: {
|
34
|
+
page: page,
|
35
|
+
per_page: per_page || default_elements_per_page,
|
36
|
+
},
|
37
|
+
)
|
38
|
+
)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def orga_repos(organization:, page: 1, per_page: nil)
|
43
|
+
encoded_namespace = URI.encode_www_form_component(organization)
|
44
|
+
with_response_validation! do
|
45
|
+
self.class.get(
|
46
|
+
"/groups/#{encoded_namespace}/projects",
|
47
|
+
default_options.merge(
|
48
|
+
query: {
|
49
|
+
page: page,
|
50
|
+
per_page: per_page || default_elements_per_page,
|
51
|
+
},
|
52
|
+
)
|
53
|
+
)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def repository(owner:, repo:)
|
58
|
+
encoded_namespace = encode_namespace(owner, repo)
|
59
|
+
with_response_validation! do
|
60
|
+
self.class.get("/projects/#{encoded_namespace}", default_options)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def branches(owner:, repo:, page: 1, per_page: nil)
|
65
|
+
encoded_namespace = encode_namespace(owner, repo)
|
66
|
+
with_response_validation! do
|
67
|
+
self.class.get(
|
68
|
+
"/projects/#{encoded_namespace}/repository/branches",
|
69
|
+
default_options.merge(
|
70
|
+
query: {
|
71
|
+
page: page,
|
72
|
+
per_page: per_page || default_elements_per_page,
|
73
|
+
},
|
74
|
+
)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def branch(owner:, repo:, branch:)
|
80
|
+
encoded_namespace = encode_namespace(owner, repo)
|
81
|
+
with_response_validation! do
|
82
|
+
self.class.get(
|
83
|
+
"/projects/#{encoded_namespace}/repository/branches/#{branch}",
|
84
|
+
default_options
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def download_repo_archive(owner:, repo:, branch: nil)
|
90
|
+
encoded_namespace = encode_namespace(owner, repo)
|
91
|
+
path = "#{base_url}/projects/#{encoded_namespace}/repository/archive.zip"
|
92
|
+
path << "?sha=#{branch}" if branch
|
93
|
+
Down.download(path, down_default_options)
|
94
|
+
end
|
95
|
+
|
96
|
+
def setup_repo_webhook(owner:, repo:, webhook_url:, webhook_secret: nil)
|
97
|
+
encoded_namespace = encode_namespace(owner, repo)
|
98
|
+
with_response_validation! do
|
99
|
+
self.class.post(
|
100
|
+
"/projects/#{encoded_namespace}/hooks",
|
101
|
+
default_options.merge(
|
102
|
+
body: {
|
103
|
+
url: webhook_url,
|
104
|
+
token: webhook_secret,
|
105
|
+
push_events: true,
|
106
|
+
enable_ssl_verification: true,
|
107
|
+
}.compact.to_json,
|
108
|
+
)
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def delete_repo_webhook(owner:, repo:, webhook_id:)
|
114
|
+
encoded_namespace = encode_namespace(owner, repo)
|
115
|
+
with_response_validation! do
|
116
|
+
self.class.delete(
|
117
|
+
"/projects/#{encoded_namespace}/hooks/#{webhook_id}",
|
118
|
+
default_options
|
119
|
+
)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def encode_namespace(owner, repo)
|
126
|
+
URI.encode_www_form_component("#{owner}/#{repo}")
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module UniversalGitClient
|
2
|
+
module Http
|
3
|
+
module Helpers
|
4
|
+
class DummyResponse
|
5
|
+
def self.response(request: nil, response: nil, body: {}, options: {})
|
6
|
+
request ||= HTTParty::Request.new(Net::HTTP::Get, '/', options)
|
7
|
+
response ||= Net::HTTPSuccess.new('1.1', 200, 'Success')
|
8
|
+
|
9
|
+
HTTParty::Response.new(request, response, -> { '' }, body: body.to_json)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'universal-git-client/errors'
|
2
|
+
|
3
|
+
module UniversalGitClient
|
4
|
+
module Http
|
5
|
+
module ResponseValidation
|
6
|
+
def with_response_validation!
|
7
|
+
response = yield
|
8
|
+
case response.code
|
9
|
+
when 200..399 then response
|
10
|
+
when 400 then raise Errors::BadRequest.new('BadRequest', response)
|
11
|
+
when 401 then raise Errors::Unauthorized.new('Unauthorized', response)
|
12
|
+
when 403 then raise Errors::Forbidden.new('Forbidden', response)
|
13
|
+
when 404 then raise Errors::NotFound.new('NotFound', response)
|
14
|
+
when 405 then raise Errors::MethodNotAllowed.new('MethodNotAllowed', response)
|
15
|
+
when 406 then raise Errors::NotAcceptable.new('NotAcceptable', response)
|
16
|
+
when 409 then raise Errors::Conflict.new('Conflict', response)
|
17
|
+
when 415 then raise Errors::UnsupportedMediaType.new('UnsupportedMediaType', response)
|
18
|
+
when 422 then raise Errors::UnprocessableEntity.new('UnprocessableEntity', response)
|
19
|
+
when 400..499 then raise Errors::ClientError.new('ClientError', response)
|
20
|
+
when 500 then raise Errors::InternalServerError.new('InternalServerError', response)
|
21
|
+
when 502 then raise Errors::BadGateway.new('BadGateway', response)
|
22
|
+
when 503 then raise Errors::ServiceUnavailable.new('ServiceUnavailable', response)
|
23
|
+
when 500..599 then raise Errors::ServerError.new('ServerError', response)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fast_jsonapi'
|
3
|
+
require 'deep_merge'
|
4
|
+
require 'uri'
|
5
|
+
require 'nitlink'
|
6
|
+
|
7
|
+
module UniversalGitClient
|
8
|
+
module Normalizers
|
9
|
+
class Base
|
10
|
+
attr_reader :response, :resource
|
11
|
+
|
12
|
+
def initialize(response, resource)
|
13
|
+
@response = response
|
14
|
+
@resource = resource
|
15
|
+
end
|
16
|
+
|
17
|
+
def normalize
|
18
|
+
object = body_as_object
|
19
|
+
serializer = serializer_for_resource
|
20
|
+
serializer_options = {}
|
21
|
+
if object.is_a?(Array)
|
22
|
+
serializer_options.deep_merge!(pagination_options)
|
23
|
+
end
|
24
|
+
serializer.new(object, serializer_options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def serializer_for_resource
|
28
|
+
self.class.const_get("#{resource}Serializer")
|
29
|
+
end
|
30
|
+
|
31
|
+
def body_as_object
|
32
|
+
@body_as_object ||= begin
|
33
|
+
struct = JSON.parse(response.body, object_class: OpenStruct)
|
34
|
+
if !struct.is_a?(Array) && struct.values
|
35
|
+
# Bitbucket embeds collections inside values
|
36
|
+
struct.values
|
37
|
+
else
|
38
|
+
struct
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def pagination_options
|
44
|
+
{
|
45
|
+
meta: {
|
46
|
+
pagination: {
|
47
|
+
self: current_page_index,
|
48
|
+
first: first_page_index,
|
49
|
+
prev: prev_page_index,
|
50
|
+
next: next_page_index,
|
51
|
+
last: last_page_index,
|
52
|
+
per_page: per_page_index,
|
53
|
+
},
|
54
|
+
},
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
def current_page_index
|
59
|
+
response.request.options[:query][:page].to_s
|
60
|
+
rescue StandardError
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
|
64
|
+
def first_page_index
|
65
|
+
fetch_rel_value('first')
|
66
|
+
end
|
67
|
+
|
68
|
+
def prev_page_index
|
69
|
+
fetch_rel_value('prev')
|
70
|
+
end
|
71
|
+
|
72
|
+
def next_page_index
|
73
|
+
fetch_rel_value('next')
|
74
|
+
end
|
75
|
+
|
76
|
+
def last_page_index
|
77
|
+
fetch_rel_value('last')
|
78
|
+
end
|
79
|
+
|
80
|
+
def per_page_index
|
81
|
+
response.request.options[:query][:per_page].to_s
|
82
|
+
rescue StandardError
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def fetch_rel_value(rel)
|
89
|
+
Hash[URI::decode_www_form(links.by_rel(rel).target.query)]['page']
|
90
|
+
rescue StandardError
|
91
|
+
nil
|
92
|
+
end
|
93
|
+
|
94
|
+
def links
|
95
|
+
@links ||= Nitlink::Parser.new.parse(response)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class BaseSerializer
|
100
|
+
include FastJsonapi::ObjectSerializer
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'universal-git-client/normalizers/base'
|
2
|
+
|
3
|
+
module UniversalGitClient
|
4
|
+
module Normalizers
|
5
|
+
class Bitbucket < Base
|
6
|
+
def first_page_index
|
7
|
+
'1'
|
8
|
+
end
|
9
|
+
|
10
|
+
def prev_page_index
|
11
|
+
get_page_index('previous')
|
12
|
+
end
|
13
|
+
|
14
|
+
def next_page_index
|
15
|
+
get_page_index('next')
|
16
|
+
end
|
17
|
+
|
18
|
+
def last_page_index
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def per_page_index
|
23
|
+
response.request.options[:query][:pagelen].to_s
|
24
|
+
rescue StandardError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def get_page_index(rel)
|
31
|
+
Hash[
|
32
|
+
URI::decode_www_form(
|
33
|
+
URI.parse(response[rel]).query
|
34
|
+
)
|
35
|
+
]['page']
|
36
|
+
rescue StandardError
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
class UserSerializer < BaseSerializer
|
41
|
+
set_id :account_id
|
42
|
+
attribute :login, &:username
|
43
|
+
attribute :name, &:display_name
|
44
|
+
|
45
|
+
attribute :avatar_url do |object|
|
46
|
+
object.links.avatar.href
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class OrganizationSerializer < BaseSerializer
|
51
|
+
set_id :uuid
|
52
|
+
attribute :login, &:username
|
53
|
+
|
54
|
+
attribute :avatar_url do |object|
|
55
|
+
object.links.avatar.href
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class RepositorySerializer < BaseSerializer
|
60
|
+
set_id :uuid
|
61
|
+
attributes :name, :full_name
|
62
|
+
attribute :full_path, &:full_name
|
63
|
+
attribute :private, &:is_private
|
64
|
+
attribute :archived, &:is_archived
|
65
|
+
|
66
|
+
attribute :default_branch do |object|
|
67
|
+
object.mainbranch&.name
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class BranchSerializer < BaseSerializer
|
72
|
+
set_id :name
|
73
|
+
attributes :name, :protected
|
74
|
+
|
75
|
+
attribute :commit do |object|
|
76
|
+
object.target.to_h[:hash]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class WebhookSerializer < BaseSerializer
|
81
|
+
set_id :uuid
|
82
|
+
attributes :url, :active
|
83
|
+
attribute :events do |object|
|
84
|
+
events = []
|
85
|
+
events << 'push' if object.events.include?('repo:push')
|
86
|
+
end
|
87
|
+
attribute :type do |object|
|
88
|
+
object.subject.type.capitalize
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|