gitomator-github 0.1.1
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/.gitignore +56 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +10 -0
- data/Rakefile +9 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/gitomator-github.gemspec +28 -0
- data/lib/gitomator/github/base_provider.rb +28 -0
- data/lib/gitomator/github/hosting_provider.rb +402 -0
- data/lib/gitomator/github/model/hosted_repo.rb +43 -0
- data/lib/gitomator/github/model/pull_request.rb +81 -0
- data/lib/gitomator/github/model/team.rb +26 -0
- data/lib/gitomator/github/model/user.rb +21 -0
- data/lib/gitomator/github/tagging_provider.rb +95 -0
- data/lib/gitomator/github/version.rb +5 -0
- data/lib/gitomator/github.rb +31 -0
- metadata +134 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 48039cc1c1db7659a763068cbba64b83eba5db2c
|
4
|
+
data.tar.gz: e73d715a625a98e08145314f312fc03dfb3eaab6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ec36dac4420f30b9f940e170df89ca9aea7c0152338151e1deed2cf00c79198f30a4cfd6400ad3fe02357a82799ded98f5a6b27535cfa7e38f91963e8d50d29b
|
7
|
+
data.tar.gz: 04359cb8cb41e088ca37afa6db42a214912f2a5af8602d0ffd6b4a0ee22c2037e7bca5fd2fc6d4b8255a933ac1c41cd4781187d2be9e5cc5fa757fec149ad524
|
data/.gitignore
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
|
2
|
+
# Created by https://www.gitignore.io/api/ruby
|
3
|
+
|
4
|
+
### Ruby ###
|
5
|
+
*.gem
|
6
|
+
*.rbc
|
7
|
+
/.config
|
8
|
+
/coverage/
|
9
|
+
/InstalledFiles
|
10
|
+
/pkg/
|
11
|
+
/spec/reports/
|
12
|
+
/spec/examples.txt
|
13
|
+
/test/tmp/
|
14
|
+
/test/version_tmp/
|
15
|
+
/tmp/
|
16
|
+
|
17
|
+
# Used by dotenv library to load environment variables.
|
18
|
+
# .env
|
19
|
+
|
20
|
+
## Specific to RubyMotion:
|
21
|
+
.dat*
|
22
|
+
.repl_history
|
23
|
+
build/
|
24
|
+
*.bridgesupport
|
25
|
+
build-iPhoneOS/
|
26
|
+
build-iPhoneSimulator/
|
27
|
+
|
28
|
+
## Specific to RubyMotion (use of CocoaPods):
|
29
|
+
#
|
30
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
31
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
32
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
33
|
+
#
|
34
|
+
# vendor/Pods/
|
35
|
+
|
36
|
+
## Documentation cache and generated files:
|
37
|
+
/.yardoc/
|
38
|
+
/_yardoc/
|
39
|
+
/doc/
|
40
|
+
/rdoc/
|
41
|
+
|
42
|
+
## Environment normalization:
|
43
|
+
/.bundle/
|
44
|
+
/vendor/bundle
|
45
|
+
/lib/bundler/man/
|
46
|
+
|
47
|
+
# for a library or gem, you might want to ignore these files since the code is
|
48
|
+
# intended to run in multiple environments; otherwise, check them in:
|
49
|
+
Gemfile.lock
|
50
|
+
.ruby-version
|
51
|
+
.ruby-gemset
|
52
|
+
|
53
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
54
|
+
.rvmrc
|
55
|
+
|
56
|
+
# End of https://www.gitignore.io/api/ruby
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.2.2
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Joey Freund
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
> **IMPORTANT:** All Gitomator projects are currently in pre-alpha stage, which means that:
|
2
|
+
>
|
3
|
+
> * Some parts are not implemented
|
4
|
+
> * API's may change significantly
|
5
|
+
> * There are not a lot of tests
|
6
|
+
>
|
7
|
+
|
8
|
+
# Gitomator::Github
|
9
|
+
|
10
|
+
See https://gitomator.github.io/
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "gitomator/github"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'gitomator/github/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "gitomator-github"
|
8
|
+
spec.version = Gitomator::Github::VERSION
|
9
|
+
spec.authors = ["Joey Freund"]
|
10
|
+
spec.email = ["joeyfreund@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Gitomator GitHub provider}
|
13
|
+
spec.description = %q{Automate the management of GitHub organizations}
|
14
|
+
spec.homepage = "https://github.com/gitomator/gitomator-github"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "bin"
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.11"
|
23
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'octokit', '~> 4.2'
|
27
|
+
spec.add_runtime_dependency 'gitomator', '>= 0.1.1'
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'gitomator/github'
|
2
|
+
require 'gitomator/util/repo/name_resolver'
|
3
|
+
|
4
|
+
|
5
|
+
module Gitomator
|
6
|
+
module GitHub
|
7
|
+
class BaseProvider
|
8
|
+
|
9
|
+
|
10
|
+
#
|
11
|
+
# @param github_client [Octokit::Client]
|
12
|
+
# @param github_organization [String]
|
13
|
+
#
|
14
|
+
def initialize(github_client, github_organization=nil)
|
15
|
+
@gh = github_client
|
16
|
+
@org = github_organization
|
17
|
+
@repo_name_resolver = Gitomator::Util::Repo::NameResolver.new(@org)
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def name
|
22
|
+
:github
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require 'gitomator/github'
|
2
|
+
require 'gitomator/github/base_provider'
|
3
|
+
require 'gitomator/github/model/hosted_repo'
|
4
|
+
require 'gitomator/github/model/pull_request'
|
5
|
+
require 'gitomator/github/model/team'
|
6
|
+
require 'gitomator/github/model/user'
|
7
|
+
require 'gitomator/util/repo/name_resolver'
|
8
|
+
|
9
|
+
|
10
|
+
module Gitomator
|
11
|
+
module GitHub
|
12
|
+
class HostingProvider < Gitomator::GitHub::BaseProvider
|
13
|
+
|
14
|
+
#
|
15
|
+
# @param config [Hash<String,Object>]
|
16
|
+
# @return [Gitomator::GitHub::HostingProvider] GitHub hosting provider.
|
17
|
+
#
|
18
|
+
def self.from_config(config = {})
|
19
|
+
org = config['organization'] || config[:organization]
|
20
|
+
return new(Gitomator::GitHub::github_client_from_config(config), org)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
#=========================================================================
|
25
|
+
|
26
|
+
#
|
27
|
+
# @param github_client [Octokit::Client]
|
28
|
+
# @param github_organization [String]
|
29
|
+
# @param opts [Hash]
|
30
|
+
#
|
31
|
+
def initialize(github_client, github_organization=nil)
|
32
|
+
super
|
33
|
+
# GitHub API doesn't have a straight forward way to get a team by name, so we'll keep an in-memory cache
|
34
|
+
@name2team_cache = {}
|
35
|
+
end
|
36
|
+
|
37
|
+
def name
|
38
|
+
:github
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# ------------ Helper Methods, Dealing With Naming Conventions -------
|
43
|
+
|
44
|
+
def repo_name_full(repo_name)
|
45
|
+
@repo_name_resolver.full_name(repo_name)
|
46
|
+
end
|
47
|
+
|
48
|
+
#---------------------------------------------------------------------
|
49
|
+
|
50
|
+
def _fetch_teams
|
51
|
+
with_auto_paginate do
|
52
|
+
@name2team_cache = @gh.org_teams(@org).map {|t| [t.name, t]} .to_h
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
#---------------------------- REPO -----------------------------------
|
58
|
+
|
59
|
+
SUPPORTED_CREATE_OPTS = [:description, :homepage, :private, :has_issues,
|
60
|
+
:has_wiki, :has_downloads, :auto_init]
|
61
|
+
#
|
62
|
+
# @option opts [String] :description
|
63
|
+
# @option opts [String] :homepage
|
64
|
+
# @option opts [Boolean] :private
|
65
|
+
# @option opts [Boolean] :has_issues
|
66
|
+
# @option opts [Boolean] :has_wiki
|
67
|
+
# @option opts [Boolean] :has_downloads
|
68
|
+
# @option opts [Boolean] :auto_init
|
69
|
+
#
|
70
|
+
def create_repo(name, opts = {})
|
71
|
+
opts = opts.select {|k,_| SUPPORTED_CREATE_OPTS.include? k }
|
72
|
+
|
73
|
+
# Decide whether this is an organization-repo or a user-repo ...
|
74
|
+
org = @repo_name_resolver.namespace(name)
|
75
|
+
unless org.nil? || org == @gh.user.login
|
76
|
+
opts[:organization] = org
|
77
|
+
end
|
78
|
+
|
79
|
+
return Gitomator::GitHub::Model::HostedRepo.new(
|
80
|
+
@gh.create_repo(@repo_name_resolver.name_only(name), opts)
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
def read_repo(name)
|
86
|
+
begin
|
87
|
+
return Gitomator::GitHub::Model::HostedRepo.new(@gh.repo repo_name_full(name))
|
88
|
+
rescue Octokit::NotFound
|
89
|
+
return nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
SUPPORTED_UPDATE_OPTS = [:name, :description, :homepage, :private,
|
95
|
+
:has_issues, :has_wiki, :has_downloads,
|
96
|
+
:default_branch]
|
97
|
+
#
|
98
|
+
# @option opts [String] :name — Name of the repo
|
99
|
+
# @option opts [String] :description — Description of the repo
|
100
|
+
# @option opts [String] :homepage — Home page of the repo
|
101
|
+
# @option opts [Boolean] :private — true makes the repository private, and false makes it public.
|
102
|
+
# @option opts [Boolean] :has_issues — true enables issues for this repo, false disables issues.
|
103
|
+
# @option opts [Boolean] :has_wiki — true enables wiki for this repo, false disables wiki.
|
104
|
+
# @option opts [Boolean] :has_downloads — true enables downloads for this repo, false disables downloads.
|
105
|
+
# @option opts [String] :default_branch — Update the default branch for this repository.
|
106
|
+
#
|
107
|
+
def update_repo(name, opts = {})
|
108
|
+
opts = opts.select {|k,_| SUPPORTED_UPDATE_OPTS.include? k }
|
109
|
+
unless opts.empty?
|
110
|
+
return Gitomator::GitHub::Model::HostedRepo.new(
|
111
|
+
@gh.edit_repository repo_name_full(name), opts)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def delete_repo(name)
|
117
|
+
@gh.delete_repo repo_name_full(name)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# For opts see http://www.rubydoc.info/gems/octokit/Octokit%2FClient%2FSearch%3Asearch_issues
|
122
|
+
#
|
123
|
+
def search_repos(query, opts = {})
|
124
|
+
gh_repos = nil
|
125
|
+
with_auto_paginate do
|
126
|
+
gh_repos = @gh.search_repos("#{query} user:#{@org}", opts).items
|
127
|
+
end
|
128
|
+
|
129
|
+
gh_repos.map {|r| Gitomator::GitHub::Model::HostedRepo.new(r)}
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
#---------------------------- TEAMS ----------------------------------
|
134
|
+
|
135
|
+
def create_team(name, opts = {})
|
136
|
+
Gitomator::GitHub::Model::Team.new(@gh.create_team(@org, {name: name}))
|
137
|
+
end
|
138
|
+
|
139
|
+
def read_team(name)
|
140
|
+
unless @name2team_cache.has_key? name
|
141
|
+
_fetch_teams()
|
142
|
+
end
|
143
|
+
if @name2team_cache[name]
|
144
|
+
Gitomator::GitHub::Model::Team.new(@name2team_cache[name])
|
145
|
+
else
|
146
|
+
return nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# opts:
|
152
|
+
# - :name (String)
|
153
|
+
# - :permission (String, one of 'pull', 'push' or 'admin')
|
154
|
+
#
|
155
|
+
def update_team(name, opts)
|
156
|
+
unless @name2team_cache.has_key? name
|
157
|
+
_fetch_teams()
|
158
|
+
end
|
159
|
+
raise "No such team, '#{name}'" unless @name2team_cache.has_key? name
|
160
|
+
|
161
|
+
t = @gh.update_team(@name2team_cache[name].id, opts)
|
162
|
+
@name2team_cache[name] = t
|
163
|
+
return Gitomator::GitHub::Model::Team.new(t)
|
164
|
+
end
|
165
|
+
|
166
|
+
def delete_team(name)
|
167
|
+
unless @name2team_cache.has_key? name
|
168
|
+
_fetch_teams()
|
169
|
+
end
|
170
|
+
if @name2team_cache.has_key? name
|
171
|
+
@gh.delete_team @name2team_cache[name].id
|
172
|
+
@name2team_cache.delete(name)
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
return false
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def search_teams(query, opts={})
|
180
|
+
result = @name2team_cache.select {|k,_| k.downcase.include? query} .values
|
181
|
+
if result.empty?
|
182
|
+
_fetch_teams()
|
183
|
+
result = @name2team_cache.select {|k,_| k.downcase.include? query} .values
|
184
|
+
end
|
185
|
+
return result.map {|t| Gitomator::GitHub::Model::Team.new(t)}
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
#---------------------------------------------------------------------
|
190
|
+
|
191
|
+
def set_user_permission(user, repo, permission)
|
192
|
+
permission = _strinigify_permission(permission)
|
193
|
+
if permission.nil?
|
194
|
+
@gh.remove_collab(repo_name_full(repo), user)
|
195
|
+
else
|
196
|
+
@gh.add_collab(repo_name_full(repo), user, {permission: permission})
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
def set_team_permission(team, repo, permission)
|
202
|
+
permission = _strinigify_permission(permission)
|
203
|
+
|
204
|
+
t = read_team(team)
|
205
|
+
raise "No such team, #{team}" if t.nil?
|
206
|
+
|
207
|
+
if permission.nil?
|
208
|
+
@gh.remove_team_repo(t.id, repo_name_full(repo))
|
209
|
+
else
|
210
|
+
@gh.add_team_repo(t.id, repo_name_full(repo),
|
211
|
+
{
|
212
|
+
permission: permission,
|
213
|
+
accept: 'application/vnd.github.ironman-preview+json'
|
214
|
+
}
|
215
|
+
)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
def _strinigify_permission(permission)
|
221
|
+
if permission.nil?
|
222
|
+
return nil
|
223
|
+
end
|
224
|
+
|
225
|
+
case permission.to_s
|
226
|
+
when 'read' || 'pull'
|
227
|
+
return 'pull'
|
228
|
+
when 'write' || 'push'
|
229
|
+
return 'push'
|
230
|
+
else
|
231
|
+
raise "Invalid permission '#{permission}'"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
|
237
|
+
#--------------------------- Team Membership -------------------------
|
238
|
+
|
239
|
+
#----------- Helpers ---------
|
240
|
+
|
241
|
+
def gitomator_role_2_github_role(role)
|
242
|
+
if ['admin', 'maintainer'].include? role.to_s.downcase
|
243
|
+
return 'maintainer'
|
244
|
+
else
|
245
|
+
return 'member'
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def github_role_2_gitomator_role(role)
|
250
|
+
role == 'maintainer' ? 'admin' : role
|
251
|
+
end
|
252
|
+
|
253
|
+
def team_id(team_name)
|
254
|
+
team = read_team(team_name)
|
255
|
+
raise "No such team, #{team_name}" if team.nil?
|
256
|
+
return team.id
|
257
|
+
end
|
258
|
+
|
259
|
+
#-----------------------------
|
260
|
+
|
261
|
+
|
262
|
+
def create_team_membership(team_name, user_name, role='member')
|
263
|
+
@gh.add_team_membership(team_id(team_name), user_name,
|
264
|
+
{ :role => gitomator_role_2_github_role(role) }
|
265
|
+
)
|
266
|
+
return role
|
267
|
+
end
|
268
|
+
|
269
|
+
|
270
|
+
def read_team_membership(team_name, user_name)
|
271
|
+
begin
|
272
|
+
m = @gh.team_membership(team_id(team_name), user_name)
|
273
|
+
return m.nil? ? nil : m.role
|
274
|
+
rescue Octokit::NotFound
|
275
|
+
return nil
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
|
280
|
+
def update_team_membership(team_name, user_name, role)
|
281
|
+
@gh.add_team_membership(team_id(team_name), user_name,
|
282
|
+
{ :role => gitomator_role_2_github_role(role) } )
|
283
|
+
return role
|
284
|
+
end
|
285
|
+
|
286
|
+
|
287
|
+
def delete_team_membership(team_name, user_name)
|
288
|
+
@gh.remove_team_membership(team_id(team_name), user_name)
|
289
|
+
end
|
290
|
+
|
291
|
+
|
292
|
+
#---------------------------------------------------------------------
|
293
|
+
|
294
|
+
def with_auto_paginate
|
295
|
+
raise "You must supply a block" unless block_given?
|
296
|
+
begin
|
297
|
+
@gh.auto_paginate = true # We want to get all team members
|
298
|
+
yield
|
299
|
+
ensure
|
300
|
+
@gh.auto_paginate = nil # We don't want to hit GitHub's API rate-limit
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
|
305
|
+
def search_users(query, opts={})
|
306
|
+
|
307
|
+
# If the team's name is specified ...
|
308
|
+
gh_users = nil
|
309
|
+
if opts[:team_name]
|
310
|
+
team = read_team(opts[:team_name])
|
311
|
+
gh_users = with_auto_paginate { @gh.team_members(team.id) }
|
312
|
+
else
|
313
|
+
gh_users = with_auto_paginate { @gh.org_members(@org) }
|
314
|
+
end
|
315
|
+
|
316
|
+
result = gh_users.map { |u| Gitomator::GitHub::Model::User.new(u) }
|
317
|
+
|
318
|
+
if query.is_a?(String) && (! query.empty?)
|
319
|
+
result = result.select {|u| u.username.include? query }
|
320
|
+
elsif query.is_a? Regexp
|
321
|
+
result = result.select {|u| query.match(u.username) }
|
322
|
+
end
|
323
|
+
|
324
|
+
return result
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
#---------------------------------------------------------------------
|
329
|
+
|
330
|
+
|
331
|
+
#
|
332
|
+
# @param src (String) of the following format 'org/repo:branch'.
|
333
|
+
# @param dst (String) of the following format 'org/repo:branch'.
|
334
|
+
#
|
335
|
+
def create_pull_request(src, dst, opts = {})
|
336
|
+
|
337
|
+
def extract_org_repo_and_branch(src_or_dst)
|
338
|
+
match = src_or_dst.match(/(.+)\/(.+):(.+)/i)
|
339
|
+
raise "Invalid src/dst, #{src_or_dst} (expected: `org_or_user/repo:branch`)" if match.nil?
|
340
|
+
return match.captures
|
341
|
+
end
|
342
|
+
|
343
|
+
src_org, src_repo, src_branch = extract_org_repo_and_branch(src)
|
344
|
+
dst_org, dst_repo, dst_branch = extract_org_repo_and_branch(dst)
|
345
|
+
|
346
|
+
unless src_repo == dst_repo
|
347
|
+
raise "Cannot create pull-request from #{src} to #{dst} (must be the same repo or a fork)."
|
348
|
+
end
|
349
|
+
|
350
|
+
Gitomator::GitHub::Model::PullRequest.new(
|
351
|
+
@gh.create_pull_request("#{dst_org}/#{dst_repo}", dst_branch,
|
352
|
+
(src_org == dst_org ? '' : "#{src_org}:") + src_branch,
|
353
|
+
opts[:title] || 'New Pull Request',
|
354
|
+
opts[:body] || 'Pull-request created using Gitomator.'
|
355
|
+
)
|
356
|
+
)
|
357
|
+
end
|
358
|
+
|
359
|
+
|
360
|
+
def read_pull_request(dst_repo, id)
|
361
|
+
begin
|
362
|
+
return Gitomator::GitHub::Model::PullRequest.new(
|
363
|
+
@gh.pull_request(repo_name_full(dst_repo), id))
|
364
|
+
rescue Octokit::NotFound
|
365
|
+
return nil
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
|
370
|
+
#
|
371
|
+
# @param opts [Hash]
|
372
|
+
# => @param :state [Symbol] One of :open, :close or :all (default: :open)
|
373
|
+
#
|
374
|
+
def read_pull_requests(dst_repo, opts = {})
|
375
|
+
@gh.pulls(repo_name_full(dst_repo), opts)
|
376
|
+
.map {|pr| Gitomator::GitHub::Model::PullRequest.new(pr, @gh)}
|
377
|
+
end
|
378
|
+
|
379
|
+
|
380
|
+
def merge_pull_request(dst_repo, id, message='')
|
381
|
+
Gitomator::GitHub::Model::PullRequest.new(
|
382
|
+
@gh.merge_pull_request(repo_name_full(dst_repo), id, message), @gh)
|
383
|
+
end
|
384
|
+
|
385
|
+
def close_pull_request(dst_repo, id)
|
386
|
+
Gitomator::GitHub::Model::PullRequest.new(
|
387
|
+
@gh.close_pull_request(repo_name_full(dst_repo), id), @gh)
|
388
|
+
end
|
389
|
+
|
390
|
+
def open_pull_request(dst_repo, id)
|
391
|
+
Gitomator::GitHub::Model::PullRequest.new(
|
392
|
+
@gh.update_pull_request(repo_name_full(dst_repo), id, {state: :open}), @gh)
|
393
|
+
end
|
394
|
+
|
395
|
+
|
396
|
+
#---------------------------------------------------------------------
|
397
|
+
|
398
|
+
|
399
|
+
|
400
|
+
end
|
401
|
+
end
|
402
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Gitomator
|
2
|
+
module GitHub
|
3
|
+
module Model
|
4
|
+
class HostedRepo
|
5
|
+
|
6
|
+
|
7
|
+
#
|
8
|
+
# @param gh_repo [Sawyer::Resource]
|
9
|
+
#
|
10
|
+
def initialize(gh_repo)
|
11
|
+
@r = gh_repo
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def name
|
16
|
+
@r.name
|
17
|
+
end
|
18
|
+
|
19
|
+
def full_name
|
20
|
+
@r.full_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def url
|
24
|
+
@r.clone_url
|
25
|
+
end
|
26
|
+
|
27
|
+
def properties
|
28
|
+
return {
|
29
|
+
:description => @r.description,
|
30
|
+
:homepage => @r.homepage,
|
31
|
+
:private => @r.private?,
|
32
|
+
:has_issues => @r.has_issues?,
|
33
|
+
:has_wiki => @r.has_wiki?,
|
34
|
+
:has_downloads => @r.has_downloads?,
|
35
|
+
:default_branch => @r.default_branch
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'gitomator/github/model/hosted_repo'
|
2
|
+
|
3
|
+
module Gitomator
|
4
|
+
module GitHub
|
5
|
+
module Model
|
6
|
+
class PullRequest
|
7
|
+
|
8
|
+
|
9
|
+
#
|
10
|
+
# @param gh_pull_request [Sawyer::Resource]
|
11
|
+
# @param octokit [Octokit::Client]
|
12
|
+
#
|
13
|
+
def initialize(gh_pull_request, octokit)
|
14
|
+
@r = gh_pull_request
|
15
|
+
@octokit = octokit
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def id
|
20
|
+
@r.number
|
21
|
+
end
|
22
|
+
|
23
|
+
def title
|
24
|
+
@r.title
|
25
|
+
end
|
26
|
+
|
27
|
+
def created_by
|
28
|
+
@r.user.login
|
29
|
+
end
|
30
|
+
|
31
|
+
def created_at
|
32
|
+
@r.created_at
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String] One of 'open', 'close'
|
36
|
+
#
|
37
|
+
def state
|
38
|
+
@r.state
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# @return true/false/nil
|
43
|
+
#
|
44
|
+
def mergeable?
|
45
|
+
# In Octokit, the two methods `pull_request` and `pull_requests` return
|
46
|
+
# different type of objects (the one returned by `pull_requests` is missing
|
47
|
+
# the mergeable? method)
|
48
|
+
if (not @r.respond_to? :mergeable?)
|
49
|
+
@r = @octokit.pull_request(@r.base.repo.full_name, @r.number)
|
50
|
+
end
|
51
|
+
|
52
|
+
if @r.mergeable_state == 'clean'
|
53
|
+
return @r.mergeable?
|
54
|
+
else
|
55
|
+
return nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# The "source repo"
|
60
|
+
def head_repo
|
61
|
+
Gitomator::GitHub::Model::HostedRepo.new(@r.head.repo)
|
62
|
+
end
|
63
|
+
|
64
|
+
def head_branch
|
65
|
+
@r.head.label.split(':').last
|
66
|
+
end
|
67
|
+
|
68
|
+
# The "destination repo"
|
69
|
+
def base_repo
|
70
|
+
Gitomator::GitHub::Model::HostedRepo.new(@r.base.repo)
|
71
|
+
end
|
72
|
+
|
73
|
+
def base_branch
|
74
|
+
@r.base.label.split(':').last
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Gitomator
|
2
|
+
module GitHub
|
3
|
+
module Model
|
4
|
+
class Team
|
5
|
+
|
6
|
+
|
7
|
+
#
|
8
|
+
# @param gh_team [Sawyer::Resource]
|
9
|
+
#
|
10
|
+
def initialize(gh_team)
|
11
|
+
@r = gh_team
|
12
|
+
end
|
13
|
+
|
14
|
+
def id
|
15
|
+
@r.id
|
16
|
+
end
|
17
|
+
|
18
|
+
def name
|
19
|
+
@r.name
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'gitomator/github'
|
2
|
+
require 'gitomator/github/base_provider'
|
3
|
+
|
4
|
+
|
5
|
+
module Gitomator
|
6
|
+
module GitHub
|
7
|
+
class TaggingProvider < Gitomator::GitHub::BaseProvider
|
8
|
+
|
9
|
+
|
10
|
+
# ---------------------- Static Factory Methods --------------------------
|
11
|
+
|
12
|
+
#
|
13
|
+
# @param config [Hash<String,Object>]
|
14
|
+
# @return [Gitomator::GitHub::HostingProvider] GitHub hosting provider.
|
15
|
+
#
|
16
|
+
def self.from_config(config = {})
|
17
|
+
return new(Gitomator::GitHub::github_client_from_config(config),
|
18
|
+
config['organization'])
|
19
|
+
end
|
20
|
+
|
21
|
+
#-------------------------------------------------------------------------
|
22
|
+
|
23
|
+
|
24
|
+
def add_tags(repo, issue_or_pr_id, *tags)
|
25
|
+
@gh.add_labels_to_an_issue(@repo_name_resolver.full_name(repo),
|
26
|
+
issue_or_pr_id, tags
|
27
|
+
).map { |r| r.to_h }
|
28
|
+
end
|
29
|
+
|
30
|
+
def remove_tag(repo, id_or_name, tag)
|
31
|
+
@gh.remove_label(@repo_name_resolver.full_name(repo), id_or_name, tag)
|
32
|
+
.map { |r| r.to_h } # Make the result a regular Ruby Hash
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
def tags(repo, id_or_name)
|
37
|
+
repo = @repo_name_resolver.full_name(repo)
|
38
|
+
@gh.labels_for_issue(repo, id_or_name).map {|r| r.name }
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return Enumerable of object identifiers.
|
44
|
+
#
|
45
|
+
def search(repo, label)
|
46
|
+
if label.is_a? String
|
47
|
+
q = "repo:#{@repo_name_resolver.full_name(repo)} type:issue|pr label:\"#{label}\""
|
48
|
+
@gh.search_issues(q)
|
49
|
+
.items.map {|item| item.number} # Make the result an array of issue/or id's
|
50
|
+
else
|
51
|
+
raise "Unimplemented! Search only supports a single tag at the moment."
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
def metadata(repo, tag=nil)
|
58
|
+
repo = @repo_name_resolver.full_name(repo)
|
59
|
+
|
60
|
+
if tag
|
61
|
+
begin
|
62
|
+
@gh.label(repo, tag).to_h # Return metadata (Hash<Symbol,String>)
|
63
|
+
rescue Octokit::NotFound
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
else
|
67
|
+
@gh.labels(repo).map {|r| [r.name, r.to_h]}.to_h # Return Hash<String,Hash<Symbol,String>>, mapping tags to their metadata
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
def set_metadata(repo, tag, metadata)
|
74
|
+
repo = @repo_name_resolver.full_name(repo)
|
75
|
+
color = metadata[:color] || metadata['color']
|
76
|
+
raise "The only supported metadata property is 'color'" if color.nil?
|
77
|
+
# TODO: Validate the color string (6-char-long Hex string. Any other formats supproted by GitHub?)
|
78
|
+
|
79
|
+
if metadata(repo, tag).nil?
|
80
|
+
@gh.add_label(repo, tag, color).to_h
|
81
|
+
else
|
82
|
+
@gh.update_label(repo, tag, {:color => color}).to_h
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def delete_metadata(repo, tag)
|
89
|
+
@gh.delete_label!(@repo_name_resolver.full_name(repo), tag)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "gitomator/github/version"
|
2
|
+
|
3
|
+
module Gitomator
|
4
|
+
module GitHub
|
5
|
+
|
6
|
+
|
7
|
+
def self.github_client_from_config(config = {})
|
8
|
+
# Convert keys yo strings (to handle keys whose type is Symbol)
|
9
|
+
config = config.map {|k,v| [k.to_s, v]} .to_h
|
10
|
+
|
11
|
+
opts = {}
|
12
|
+
|
13
|
+
if config['access_token']
|
14
|
+
opts[:access_token] = config['access_token']
|
15
|
+
elsif config['username'] && config['password']
|
16
|
+
opts[:login] = config['username']
|
17
|
+
opts[:password] = config['password']
|
18
|
+
elsif config['client_id'] && config['client_secret']
|
19
|
+
opts[:client_id] = config['client_id']
|
20
|
+
opts[:client_secret] = config['client_secret']
|
21
|
+
else
|
22
|
+
raise "Invalid GitHub hosting configuration - #{config}"
|
23
|
+
end
|
24
|
+
|
25
|
+
require 'octokit'
|
26
|
+
return Octokit::Client.new(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,134 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitomator-github
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joey Freund
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-12-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.11'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.11'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: octokit
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.2'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '4.2'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: gitomator
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.1.1
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.1.1
|
83
|
+
description: Automate the management of GitHub organizations
|
84
|
+
email:
|
85
|
+
- joeyfreund@gmail.com
|
86
|
+
executables:
|
87
|
+
- console
|
88
|
+
- setup
|
89
|
+
extensions: []
|
90
|
+
extra_rdoc_files: []
|
91
|
+
files:
|
92
|
+
- ".gitignore"
|
93
|
+
- ".ruby-version"
|
94
|
+
- Gemfile
|
95
|
+
- LICENSE.txt
|
96
|
+
- README.md
|
97
|
+
- Rakefile
|
98
|
+
- bin/console
|
99
|
+
- bin/setup
|
100
|
+
- gitomator-github.gemspec
|
101
|
+
- lib/gitomator/github.rb
|
102
|
+
- lib/gitomator/github/base_provider.rb
|
103
|
+
- lib/gitomator/github/hosting_provider.rb
|
104
|
+
- lib/gitomator/github/model/hosted_repo.rb
|
105
|
+
- lib/gitomator/github/model/pull_request.rb
|
106
|
+
- lib/gitomator/github/model/team.rb
|
107
|
+
- lib/gitomator/github/model/user.rb
|
108
|
+
- lib/gitomator/github/tagging_provider.rb
|
109
|
+
- lib/gitomator/github/version.rb
|
110
|
+
homepage: https://github.com/gitomator/gitomator-github
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.6.2
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Gitomator GitHub provider
|
134
|
+
test_files: []
|