dcuddeback-octopi 0.2.8
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.
- data/.gitignore +4 -0
- data/.yardoc +0 -0
- data/CHANGELOG.md +9 -0
- data/LICENSE +20 -0
- data/README.markdown +144 -0
- data/Rakefile +94 -0
- data/VERSION.yml +4 -0
- data/contrib/backup.rb +100 -0
- data/dcuddeback-octopi.gemspec +108 -0
- data/examples/authenticated.rb +20 -0
- data/examples/issues.rb +18 -0
- data/examples/overall.rb +50 -0
- data/lib/ext/string_ext.rb +5 -0
- data/lib/octopi/api.rb +213 -0
- data/lib/octopi/base.rb +115 -0
- data/lib/octopi/blob.rb +25 -0
- data/lib/octopi/branch.rb +31 -0
- data/lib/octopi/branch_set.rb +11 -0
- data/lib/octopi/comment.rb +20 -0
- data/lib/octopi/commit.rb +69 -0
- data/lib/octopi/deploy_key.rb +27 -0
- data/lib/octopi/deploy_key_set.rb +18 -0
- data/lib/octopi/error.rb +35 -0
- data/lib/octopi/file_object.rb +16 -0
- data/lib/octopi/gist.rb +28 -0
- data/lib/octopi/issue.rb +111 -0
- data/lib/octopi/issue_comment.rb +7 -0
- data/lib/octopi/issue_set.rb +21 -0
- data/lib/octopi/key.rb +25 -0
- data/lib/octopi/key_set.rb +14 -0
- data/lib/octopi/plan.rb +5 -0
- data/lib/octopi/repository.rb +136 -0
- data/lib/octopi/repository_set.rb +9 -0
- data/lib/octopi/resource.rb +70 -0
- data/lib/octopi/self.rb +33 -0
- data/lib/octopi/tag.rb +23 -0
- data/lib/octopi/user.rb +131 -0
- data/lib/octopi.rb +135 -0
- data/test/api_test.rb +58 -0
- data/test/authenticated_test.rb +39 -0
- data/test/base_test.rb +20 -0
- data/test/blob_test.rb +23 -0
- data/test/branch_test.rb +20 -0
- data/test/commit_test.rb +82 -0
- data/test/file_object_test.rb +39 -0
- data/test/gist_test.rb +16 -0
- data/test/issue_comment.rb +19 -0
- data/test/issue_set_test.rb +33 -0
- data/test/issue_test.rb +120 -0
- data/test/key_set_test.rb +29 -0
- data/test/key_test.rb +35 -0
- data/test/repository_set_test.rb +23 -0
- data/test/repository_test.rb +151 -0
- data/test/stubs/commits/fcoury/octopi/octopi.rb +818 -0
- data/test/tag_test.rb +20 -0
- data/test/test_helper.rb +246 -0
- data/test/user_test.rb +92 -0
- metadata +151 -0
data/lib/octopi/api.rb
ADDED
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
require File.join(File.dirname(__FILE__), "self")
|
3
|
+
module Octopi
|
4
|
+
# Dummy class, so AnonymousApi and AuthApi have somewhere to inherit from
|
5
|
+
class Api
|
6
|
+
include Self
|
7
|
+
attr_accessor :format, :login, :token, :trace_level, :read_only
|
8
|
+
end
|
9
|
+
|
10
|
+
# Used for accessing the Github API anonymously
|
11
|
+
class AnonymousApi < Api
|
12
|
+
include HTTParty
|
13
|
+
include Singleton
|
14
|
+
base_uri "http://github.com/api/v2"
|
15
|
+
|
16
|
+
def read_only?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
def auth_parameters
|
21
|
+
{ }
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class AuthApi < Api
|
26
|
+
include HTTParty
|
27
|
+
include Singleton
|
28
|
+
base_uri "https://github.com/api/v2"
|
29
|
+
|
30
|
+
def read_only?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
def auth_parameters
|
35
|
+
{ :login => Api.me.login, :token => Api.me.token }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is the real API class.
|
40
|
+
#
|
41
|
+
# API requests are limited to 60 per minute.
|
42
|
+
#
|
43
|
+
# Sets up basic methods for accessing the API.
|
44
|
+
class Api
|
45
|
+
@@api = Octopi::AnonymousApi.instance
|
46
|
+
@@authenticated = false
|
47
|
+
|
48
|
+
include Singleton
|
49
|
+
CONTENT_TYPE = {
|
50
|
+
'yaml' => ['application/x-yaml', 'text/yaml', 'text/x-yaml', 'application/yaml'],
|
51
|
+
'json' => 'application/json',
|
52
|
+
'xml' => 'application/xml',
|
53
|
+
# Unexpectedly, Github returns resources such as blobs as text/html!
|
54
|
+
# Thus, plain == text/html.
|
55
|
+
'plain' => ['text/plain', 'text/html']
|
56
|
+
}
|
57
|
+
RETRYABLE_STATUS = [403]
|
58
|
+
MAX_RETRIES = 10
|
59
|
+
# Would be nice if cattr_accessor was available, oh well.
|
60
|
+
|
61
|
+
# We use this to check if we use the auth or anonymous api
|
62
|
+
def self.authenticated
|
63
|
+
@@authenticated
|
64
|
+
end
|
65
|
+
|
66
|
+
# We set this to true when the user has auth'd.
|
67
|
+
def self.authenticated=(value)
|
68
|
+
@@authenticated = value
|
69
|
+
end
|
70
|
+
|
71
|
+
# The API we're using
|
72
|
+
def self.api
|
73
|
+
@@api
|
74
|
+
end
|
75
|
+
|
76
|
+
class << self
|
77
|
+
alias_method :me, :api
|
78
|
+
end
|
79
|
+
|
80
|
+
# set the API we're using
|
81
|
+
def self.api=(value)
|
82
|
+
@@api = value
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def user
|
87
|
+
user_data = get("/user/show/#{login}")
|
88
|
+
raise "Unexpected response for user command" unless user_data and user_data['user']
|
89
|
+
User.new(user_data['user'])
|
90
|
+
end
|
91
|
+
|
92
|
+
def save(resource_path, data)
|
93
|
+
traslate resource_path, data
|
94
|
+
#still can't figure out on what format values are expected
|
95
|
+
post("#{resource_path}", { :query => data })
|
96
|
+
end
|
97
|
+
|
98
|
+
|
99
|
+
def find(path, result_key, resource_id, klass=nil, cache=true)
|
100
|
+
result = get(path, { :id => resource_id, :cache => cache }, klass)
|
101
|
+
result
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def find_all(path, result_key, query, klass=nil, cache=true)
|
106
|
+
{ :query => query, :id => query, :cache => cache }
|
107
|
+
result = get(path, { :query => query, :id => query, :cache => cache }, klass)
|
108
|
+
result[result_key]
|
109
|
+
end
|
110
|
+
|
111
|
+
def get_raw(path, params, klass=nil)
|
112
|
+
get(path, params, klass, 'plain')
|
113
|
+
end
|
114
|
+
|
115
|
+
def get(path, params = {}, klass=nil, format = :yaml)
|
116
|
+
@@retries = 0
|
117
|
+
begin
|
118
|
+
submit(path, params, klass, format) do |path, params, format, query|
|
119
|
+
self.class.get "/#{format}#{path}", { :format => format, :query => query }
|
120
|
+
end
|
121
|
+
rescue RetryableAPIError => e
|
122
|
+
if @@retries < MAX_RETRIES
|
123
|
+
# $stderr.puts e.message
|
124
|
+
if e.code != 403
|
125
|
+
@@retries += 1
|
126
|
+
sleep 6
|
127
|
+
retry
|
128
|
+
else
|
129
|
+
raise APIError, "Github returned status #{e.code}, you may not have access to this resource."
|
130
|
+
end
|
131
|
+
else
|
132
|
+
raise APIError, "GitHub returned status #{e.code}, despite" +
|
133
|
+
" repeating the request #{MAX_RETRIES} times. Giving up."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def post(path, params = {}, klass=nil, format = :yaml)
|
139
|
+
@@retries = 0
|
140
|
+
begin
|
141
|
+
trace "POST", "/#{format}#{path}", params
|
142
|
+
submit(path, params, klass, format) do |path, params, format, query|
|
143
|
+
resp = self.class.post "/#{format}#{path}", { :body => params, :format => format, :query => query }
|
144
|
+
resp
|
145
|
+
end
|
146
|
+
rescue RetryableAPIError => e
|
147
|
+
if @@retries < MAX_RETRIES
|
148
|
+
# $stderr.puts e.message
|
149
|
+
@@retries += 1
|
150
|
+
sleep 6
|
151
|
+
retry
|
152
|
+
else
|
153
|
+
raise APIError, "GitHub returned status #{e.code}, despite" +
|
154
|
+
" repeating the request #{MAX_RETRIES} times. Giving up."
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
private
|
160
|
+
|
161
|
+
def method_missing(method, *args)
|
162
|
+
api.send(method, *args)
|
163
|
+
end
|
164
|
+
|
165
|
+
def submit(path, params = {}, klass=nil, format = :yaml, &block)
|
166
|
+
# Ergh. Ugly way to do this. Find a better one!
|
167
|
+
cache = params.delete(:cache)
|
168
|
+
cache = true if cache.nil?
|
169
|
+
params.each_pair do |k,v|
|
170
|
+
if path =~ /:#{k.to_s}/
|
171
|
+
params.delete(k)
|
172
|
+
path = path.gsub(":#{k.to_s}", v)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
begin
|
176
|
+
key = "#{Api.api.class.to_s}:#{path}"
|
177
|
+
resp = if cache
|
178
|
+
APICache.get(key, :cache => 61) do
|
179
|
+
yield(path, params, format, auth_parameters)
|
180
|
+
end
|
181
|
+
else
|
182
|
+
yield(path, params, format, auth_parameters)
|
183
|
+
end
|
184
|
+
rescue Net::HTTPBadResponse
|
185
|
+
raise RetryableAPIError
|
186
|
+
end
|
187
|
+
|
188
|
+
raise RetryableAPIError, resp.code.to_i if RETRYABLE_STATUS.include? resp.code.to_i
|
189
|
+
# puts resp.code.inspect
|
190
|
+
raise NotFound, klass || self.class if resp.code.to_i == 404
|
191
|
+
raise APIError,
|
192
|
+
"GitHub returned status #{resp.code}" unless resp.code.to_i == 200
|
193
|
+
# FIXME: This fails for showing raw Git data because that call returns
|
194
|
+
# text/html as the content type. This issue has been reported.
|
195
|
+
|
196
|
+
# It happens, in tests.
|
197
|
+
return resp if resp.headers.empty?
|
198
|
+
ctype = resp.headers['content-type'].first.split(";").first
|
199
|
+
raise FormatError, [ctype, format] unless CONTENT_TYPE[format.to_s].include?(ctype)
|
200
|
+
if format == 'yaml' && resp['error']
|
201
|
+
raise APIError, resp['error']
|
202
|
+
end
|
203
|
+
resp
|
204
|
+
end
|
205
|
+
|
206
|
+
def trace(oper, url, params)
|
207
|
+
return unless trace_level
|
208
|
+
par_str = " params: " + params.map { |p| "#{p[0]}=#{p[1]}" }.join(", ") if params && !params.empty?
|
209
|
+
puts "#{oper}: #{url}#{par_str}"
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
213
|
+
end
|
data/lib/octopi/base.rb
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
module Octopi
|
2
|
+
class Base
|
3
|
+
VALID = {
|
4
|
+
:repo => {
|
5
|
+
# FIXME: API currently chokes on repository names containing periods,
|
6
|
+
# but presumably this will be fixed.
|
7
|
+
:pat => /^[A-Za-z0-9_\.-]+$/,
|
8
|
+
:msg => "%s is an invalid repository name"},
|
9
|
+
:user => {
|
10
|
+
:pat => /^[A-Za-z0-9_\.-]+$/,
|
11
|
+
:msg => "%s is an invalid username"},
|
12
|
+
:sha => {
|
13
|
+
:pat => /^[a-f0-9]{40}$/,
|
14
|
+
:msg => "%s is an invalid SHA hash"},
|
15
|
+
:state => {
|
16
|
+
# FIXME: Any way to access Issue::STATES from here?
|
17
|
+
:pat => /^(open|closed)$/,
|
18
|
+
:msg => "%s is an invalid state; should be 'open' or 'closed'."
|
19
|
+
}
|
20
|
+
}
|
21
|
+
|
22
|
+
attr_accessor :api
|
23
|
+
|
24
|
+
def initialize(attributes={})
|
25
|
+
# Useful for finding out what attr_accessor needs for classes
|
26
|
+
# puts caller.first.inspect
|
27
|
+
# puts "#{self.class.inspect} #{attributes.keys.map { |s| s.to_sym }.inspect}"
|
28
|
+
attributes.each do |key, value|
|
29
|
+
method = "#{key}="
|
30
|
+
self.send(method, value) if respond_to? method
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def error=(error)
|
35
|
+
if /\w+ not found/.match(error)
|
36
|
+
raise NotFound, self.class
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def property(p, v)
|
41
|
+
path = "#{self.class.path_for(:resource)}/#{p}"
|
42
|
+
Api.api.find(path, self.class.resource_name(:singular), v)
|
43
|
+
end
|
44
|
+
|
45
|
+
def save
|
46
|
+
hash = {}
|
47
|
+
@keys.each { |k| hash[k] = send(k) }
|
48
|
+
Api.api.save(self.path_for(:resource), hash)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.gather_name(options)
|
54
|
+
options[:repository] || options[:repo] || options[:name]
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.gather_details(options)
|
58
|
+
repo = self.gather_name(options)
|
59
|
+
repo = Repository.find(:user => options[:user], :name => repo) if !repo.is_a?(Repository)
|
60
|
+
user = repo.owner.to_s
|
61
|
+
user ||= options[:user].to_s
|
62
|
+
branch = options[:branch] || "master"
|
63
|
+
self.validate_args(user => :user, repo.name => :repo)
|
64
|
+
[user, repo, branch, options[:sha]].compact
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.extract_user_repository(*args)
|
68
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
69
|
+
if options.empty?
|
70
|
+
if args.length > 1
|
71
|
+
repo, user = *args
|
72
|
+
else
|
73
|
+
repo = args.pop
|
74
|
+
end
|
75
|
+
else
|
76
|
+
options[:repo] = options[:repository] if options[:repository]
|
77
|
+
repo = args.pop || options[:repo]
|
78
|
+
user = options[:user]
|
79
|
+
end
|
80
|
+
|
81
|
+
user = repo.owner if repo.is_a? Repository
|
82
|
+
|
83
|
+
if repo.is_a?(String) && !user
|
84
|
+
raise "Need user argument when repository is identified by name"
|
85
|
+
end
|
86
|
+
ret = extract_names(user, repo)
|
87
|
+
ret << options
|
88
|
+
ret
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.extract_names(*args)
|
92
|
+
args.map do |v|
|
93
|
+
v = v.name if v.is_a? Repository
|
94
|
+
v = v.login if v.is_a? User
|
95
|
+
v
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.ensure_hash(spec)
|
100
|
+
raise ArgumentMustBeHash, "find takes a hash of options as a solitary argument" if !spec.is_a?(Hash)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.validate_args(spec)
|
104
|
+
m = caller[0].match(/\/([a-z0-9_]+)\.rb:\d+:in `([a-z_0-9]+)'/)
|
105
|
+
meth = m ? "#{m[1].camel_case}.#{m[2]}" : 'method'
|
106
|
+
raise ArgumentError, "Invalid spec" unless
|
107
|
+
spec.values.all? { |s| VALID.key? s }
|
108
|
+
errors = spec.reject{|arg, spec| arg.nil?}.
|
109
|
+
reject{|arg, spec| arg.to_s.match(VALID[spec][:pat])}.
|
110
|
+
map {|arg, spec| "Invalid argument '%s' for %s (%s)" %
|
111
|
+
[arg, meth, VALID[spec][:msg] % arg]}
|
112
|
+
raise ArgumentError, "\n" + errors.join("\n") unless errors.empty?
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/octopi/blob.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "resource")
|
2
|
+
module Octopi
|
3
|
+
class Blob < Base
|
4
|
+
attr_accessor :text, :data, :name, :sha, :size, :mode, :mime_type
|
5
|
+
include Resource
|
6
|
+
set_resource_name "blob"
|
7
|
+
|
8
|
+
resource_path "/blob/show/:id"
|
9
|
+
|
10
|
+
def self.find(options={})
|
11
|
+
ensure_hash(options)
|
12
|
+
user, repo = gather_details(options)
|
13
|
+
sha = options[:sha]
|
14
|
+
path = options[:path]
|
15
|
+
|
16
|
+
self.validate_args(sha => :sha, user => :user)
|
17
|
+
|
18
|
+
if path
|
19
|
+
super [user, repo, sha, path]
|
20
|
+
else
|
21
|
+
Api.api.get_raw(path_for(:resource), {:id => [user, repo, sha].join('/')})
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Octopi
|
2
|
+
class Branch < Base
|
3
|
+
attr_accessor :name, :sha
|
4
|
+
include Resource
|
5
|
+
set_resource_name "branch", "branches"
|
6
|
+
|
7
|
+
resource_path "/repos/show/:id"
|
8
|
+
|
9
|
+
# Called when we ask for a resource.
|
10
|
+
# Arguments are passed in like [<name>, <sha>]
|
11
|
+
# TODO: Find out why args are doubly nested
|
12
|
+
def initialize(*args)
|
13
|
+
args = args.flatten!
|
14
|
+
self.name = args.first
|
15
|
+
self.sha = args.last
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
name
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.all(options={})
|
23
|
+
ensure_hash(options)
|
24
|
+
user, repo = gather_details(options)
|
25
|
+
self.validate_args(user => :user, repo => :repo)
|
26
|
+
BranchSet.new(find_plural([user, repo, 'branches'], :resource)) do |i|
|
27
|
+
{ :name => i.first, :hash => i.last }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "branch")
|
2
|
+
class Octopi::BranchSet < Array
|
3
|
+
include Octopi
|
4
|
+
attr_accessor :user, :repository
|
5
|
+
# Takes a name, returns a branch if it exists
|
6
|
+
def find(name)
|
7
|
+
branch = detect { |b| b.name == name }
|
8
|
+
raise NotFound, Branch if branch.nil?
|
9
|
+
branch
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Octopi
|
2
|
+
class Comment < Base
|
3
|
+
attr_accessor :content, :author, :title, :updated, :link, :published, :id, :repository
|
4
|
+
include Resource
|
5
|
+
set_resource_name "tree"
|
6
|
+
|
7
|
+
resource_path "/tree/show/:id"
|
8
|
+
|
9
|
+
def self.find(options={})
|
10
|
+
ensure_hash(options)
|
11
|
+
user, repo, branch, sha = gather_details(options)
|
12
|
+
self.validate_args(sha => :sha, user => :user, repo => :repo)
|
13
|
+
super [user, repo, sha]
|
14
|
+
end
|
15
|
+
|
16
|
+
def commit
|
17
|
+
Commit.find(:user => repository.owner, :repo => repository, :sha => /commit\/(.*?)#/.match(link)[1])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Octopi
|
2
|
+
class Commit < Base
|
3
|
+
include Resource
|
4
|
+
find_path "/commits/list/:query"
|
5
|
+
resource_path "/commits/show/:id"
|
6
|
+
|
7
|
+
attr_accessor :repository, :message, :parents, :author, :url, :id, :committed_date, :authored_date, :tree, :committer, :added, :removed, :modified
|
8
|
+
|
9
|
+
|
10
|
+
# Finds all commits for the given options:
|
11
|
+
#
|
12
|
+
# :repo or :repository or :name - A repository object or the name of a repository
|
13
|
+
# :user - A user object or the login of a user
|
14
|
+
# :branch - A branch object or the name of a branch. Defaults to master.
|
15
|
+
#
|
16
|
+
# Sample usage:
|
17
|
+
#
|
18
|
+
# >> find_all(:user => "fcoury", :repo => "octopi")
|
19
|
+
# => <Latest 30 commits for master branch>
|
20
|
+
#
|
21
|
+
# => find_all(:user => "fcoury", :repo => "octopi", :branch => "lazy") # branch is set to lazy.
|
22
|
+
# => <Latest 30 commits for lazy branch>
|
23
|
+
#
|
24
|
+
def self.find_all(options={})
|
25
|
+
ensure_hash(options)
|
26
|
+
user, repo, branch = gather_details(options)
|
27
|
+
commits = if options[:path]
|
28
|
+
super user, repo.name, branch, options[:path]
|
29
|
+
else
|
30
|
+
super user, repo.name, branch
|
31
|
+
end
|
32
|
+
# Repository is not passed in from the data, set it manually.
|
33
|
+
commits.each { |c| c.repository = repo }
|
34
|
+
commits
|
35
|
+
end
|
36
|
+
|
37
|
+
# Finds all commits for the given options:
|
38
|
+
#
|
39
|
+
# :repo or :repository or :name - A repository object or the name of a repository
|
40
|
+
# :user - A user object or the login of a user
|
41
|
+
# :branch - A branch object or the name of a branch. Defaults to master.
|
42
|
+
# :sha - The commit ID
|
43
|
+
#
|
44
|
+
# Sample usage:
|
45
|
+
#
|
46
|
+
# >> find(:user => "fcoury", :repo => "octopi", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae")
|
47
|
+
# => <Commit f6609209c3ac0badd004512d318bfaa508ea10ae for branch master>
|
48
|
+
#
|
49
|
+
# >> find(:user => "fcoury", :repo => "octopi", :branch => "lazy", :sha => "f6609209c3ac0badd004512d318bfaa508ea10ae") # branch is set to lazy.
|
50
|
+
# => <Commit f6609209c3ac0badd004512d318bfaa508ea10ae for branch lazy>
|
51
|
+
#
|
52
|
+
def self.find(options={})
|
53
|
+
ensure_hash(options)
|
54
|
+
user, repo, branch, sha = gather_details(options)
|
55
|
+
super [user, repo, sha]
|
56
|
+
end
|
57
|
+
|
58
|
+
def repo_identifier
|
59
|
+
url_parts = url.split('/')
|
60
|
+
if @repository
|
61
|
+
parts = [@repository.owner, @repository.name, url_parts[6]]
|
62
|
+
else
|
63
|
+
parts = [url_parts[3], url_parts[4], url_parts[6]]
|
64
|
+
end
|
65
|
+
|
66
|
+
parts.join('/')
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Octopi
|
2
|
+
class DeployKey < Base
|
3
|
+
include Resource
|
4
|
+
set_resource_name "public_key"
|
5
|
+
|
6
|
+
attr_accessor :title, :id, :key
|
7
|
+
|
8
|
+
create_path "/repos/key/:id/add"
|
9
|
+
delete_path "/repos/key/:id/remove"
|
10
|
+
find_path "/repos/keys/:id"
|
11
|
+
|
12
|
+
def self.all(options={})
|
13
|
+
ensure_hash(options)
|
14
|
+
user, repo = gather_details(options)
|
15
|
+
self.validate_args(repo => :repo)
|
16
|
+
DeployKeySet.new(repo, find_plural([repo], :find))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.create(options={})
|
20
|
+
ensure_hash(options)
|
21
|
+
repo = options.delete(:repo)
|
22
|
+
options[:id] = repo.name
|
23
|
+
resp = Api.api.post(path_for(:create), options.merge(:cache => false))
|
24
|
+
DeployKeySet.new(repo, resp[resource_name(:plural)].map {|k| self.new(k)})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "key")
|
2
|
+
class Octopi::DeployKeySet < Array
|
3
|
+
include Octopi
|
4
|
+
attr_accessor :repository
|
5
|
+
|
6
|
+
def initialize(repo, keys)
|
7
|
+
self.repository = repo
|
8
|
+
super keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(title)
|
12
|
+
detect {|key| key.title == title}
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(options={})
|
16
|
+
DeployKey.create(options.merge(:repo => repository))
|
17
|
+
end
|
18
|
+
end
|
data/lib/octopi/error.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Octopi
|
2
|
+
|
3
|
+
class FormatError < StandardError
|
4
|
+
def initialize(f)
|
5
|
+
super("Got unexpected format (got #{f.first} for #{f.last})")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class AuthenticationRequired < StandardError
|
10
|
+
end
|
11
|
+
|
12
|
+
class APIError < StandardError
|
13
|
+
end
|
14
|
+
|
15
|
+
class InvalidLogin < StandardError
|
16
|
+
end
|
17
|
+
|
18
|
+
class RetryableAPIError < RuntimeError
|
19
|
+
attr_reader :code
|
20
|
+
def initialize(code=nil)
|
21
|
+
@code = code.nil? ? '???' : code
|
22
|
+
@message = "GitHub returned status #{@code}. Retrying request."
|
23
|
+
super @message
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class ArgumentMustBeHash < Exception; end
|
28
|
+
|
29
|
+
|
30
|
+
class NotFound < Exception
|
31
|
+
def initialize(klass)
|
32
|
+
super "The #{klass.to_s.split("::").last} you were looking for could not be found, or is private."
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Octopi
|
2
|
+
class FileObject < Base
|
3
|
+
attr_accessor :name, :sha, :mode, :type
|
4
|
+
|
5
|
+
include Resource
|
6
|
+
set_resource_name "tree"
|
7
|
+
resource_path "/tree/show/:id"
|
8
|
+
|
9
|
+
def self.find(options={})
|
10
|
+
ensure_hash(options)
|
11
|
+
user, repo, branch, sha = gather_details(options)
|
12
|
+
self.validate_args(sha => :sha, user => :user, repo => :repo)
|
13
|
+
super [user, repo, sha]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/octopi/gist.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Octopi
|
2
|
+
# Gist API is... lacking at the moment.
|
3
|
+
# This class serves only as a reminder to implement it later
|
4
|
+
class Gist < Base
|
5
|
+
include HTTParty
|
6
|
+
attr_accessor :description, :repo, :public, :created_at
|
7
|
+
|
8
|
+
include Resource
|
9
|
+
set_resource_name "tree"
|
10
|
+
resource_path ":id"
|
11
|
+
|
12
|
+
def self.base_uri
|
13
|
+
"http://gist.github.com/api/v1/yaml"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find(id)
|
17
|
+
result = get("#{base_uri}/#{id}")
|
18
|
+
# This returns an array of Gists, rather than a single record.
|
19
|
+
new(result["gists"].first)
|
20
|
+
end
|
21
|
+
|
22
|
+
# def files
|
23
|
+
# gists_folder = File.join(ENV['HOME'], ".octopi", "gists")
|
24
|
+
# File.mkdir_p(gists_folder)
|
25
|
+
# `git clone git://`
|
26
|
+
# end
|
27
|
+
end
|
28
|
+
end
|