universal-git-client 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|