octopi 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/.yardoc +0 -0
- data/README.rdoc +16 -41
- data/Rakefile +9 -0
- data/VERSION.yml +2 -2
- data/examples/overall.rb +1 -1
- data/lib/ext/hash_ext.rb +5 -0
- data/lib/ext/string_ext.rb +5 -0
- data/lib/octopi.rb +101 -202
- data/lib/octopi/api.rb +209 -0
- data/lib/octopi/base.rb +42 -38
- data/lib/octopi/blob.rb +12 -8
- data/lib/octopi/branch.rb +20 -7
- data/lib/octopi/branch_set.rb +11 -0
- data/lib/octopi/comment.rb +20 -0
- data/lib/octopi/commit.rb +39 -35
- data/lib/octopi/error.rb +17 -5
- data/lib/octopi/file_object.rb +6 -5
- data/lib/octopi/gist.rb +28 -0
- data/lib/octopi/issue.rb +49 -40
- data/lib/octopi/issue_comment.rb +7 -0
- data/lib/octopi/issue_set.rb +21 -0
- data/lib/octopi/key.rb +14 -7
- data/lib/octopi/key_set.rb +14 -0
- data/lib/octopi/plan.rb +5 -0
- data/lib/octopi/repository.rb +66 -45
- data/lib/octopi/repository_set.rb +9 -0
- data/lib/octopi/resource.rb +11 -16
- data/lib/octopi/self.rb +33 -0
- data/lib/octopi/tag.rb +12 -6
- data/lib/octopi/user.rb +62 -38
- data/octopi.gemspec +43 -12
- data/test/api_test.rb +58 -0
- data/test/authenticated_test.rb +39 -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 +141 -0
- data/test/stubs/commits/fcoury/octopi/octopi.rb +818 -0
- data/test/tag_test.rb +20 -0
- data/test/test_helper.rb +236 -0
- data/test/user_test.rb +92 -0
- metadata +54 -12
- data/examples/github.yml.example +0 -14
- data/test/octopi_test.rb +0 -46
data/lib/octopi/api.rb
ADDED
@@ -0,0 +1,209 @@
|
|
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
|
+
@@retries += 1
|
125
|
+
sleep 6
|
126
|
+
retry
|
127
|
+
else
|
128
|
+
raise APIError, "GitHub returned status #{e.code}, despite" +
|
129
|
+
" repeating the request #{MAX_RETRIES} times. Giving up."
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def post(path, params = {}, klass=nil, format = :yaml)
|
135
|
+
@@retries = 0
|
136
|
+
begin
|
137
|
+
trace "POST", "/#{format}#{path}", params
|
138
|
+
submit(path, params, klass, format) do |path, params, format, query|
|
139
|
+
resp = self.class.post "/#{format}#{path}", { :body => params, :format => format, :query => query }
|
140
|
+
resp
|
141
|
+
end
|
142
|
+
rescue RetryableAPIError => e
|
143
|
+
if @@retries < MAX_RETRIES
|
144
|
+
$stderr.puts e.message
|
145
|
+
@@retries += 1
|
146
|
+
sleep 6
|
147
|
+
retry
|
148
|
+
else
|
149
|
+
raise APIError, "GitHub returned status #{e.code}, despite" +
|
150
|
+
" repeating the request #{MAX_RETRIES} times. Giving up."
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
private
|
156
|
+
|
157
|
+
def method_missing(method, *args)
|
158
|
+
api.send(method, *args)
|
159
|
+
end
|
160
|
+
|
161
|
+
def submit(path, params = {}, klass=nil, format = :yaml, &block)
|
162
|
+
# Ergh. Ugly way to do this. Find a better one!
|
163
|
+
cache = params.delete(:cache)
|
164
|
+
cache = true if cache.nil?
|
165
|
+
params.each_pair do |k,v|
|
166
|
+
if path =~ /:#{k.to_s}/
|
167
|
+
params.delete(k)
|
168
|
+
path = path.gsub(":#{k.to_s}", v)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
begin
|
172
|
+
key = "#{Api.api.class.to_s}:#{path}"
|
173
|
+
resp = if cache
|
174
|
+
APICache.get(key, :cache => 61) do
|
175
|
+
yield(path, params, format, auth_parameters)
|
176
|
+
end
|
177
|
+
else
|
178
|
+
yield(path, params, format, auth_parameters)
|
179
|
+
end
|
180
|
+
rescue Net::HTTPBadResponse
|
181
|
+
raise RetryableAPIError
|
182
|
+
end
|
183
|
+
|
184
|
+
raise RetryableAPIError, resp.code.to_i if RETRYABLE_STATUS.include? resp.code.to_i
|
185
|
+
# puts resp.code.inspect
|
186
|
+
raise NotFound, klass || self.class if resp.code.to_i == 404
|
187
|
+
raise APIError,
|
188
|
+
"GitHub returned status #{resp.code}" unless resp.code.to_i == 200
|
189
|
+
# FIXME: This fails for showing raw Git data because that call returns
|
190
|
+
# text/html as the content type. This issue has been reported.
|
191
|
+
|
192
|
+
# It happens, in tests.
|
193
|
+
return resp if resp.headers.empty?
|
194
|
+
ctype = resp.headers['content-type'].first.split(";").first
|
195
|
+
raise FormatError, [ctype, format] unless CONTENT_TYPE[format.to_s].include?(ctype)
|
196
|
+
if format == 'yaml' && resp['error']
|
197
|
+
raise APIError, resp['error']
|
198
|
+
end
|
199
|
+
resp
|
200
|
+
end
|
201
|
+
|
202
|
+
def trace(oper, url, params)
|
203
|
+
return unless trace_level
|
204
|
+
par_str = " params: " + params.map { |p| "#{p[0]}=#{p[1]}" }.join(", ") if params && !params.empty?
|
205
|
+
puts "#{oper}: #{url}#{par_str}"
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
data/lib/octopi/base.rb
CHANGED
@@ -1,8 +1,3 @@
|
|
1
|
-
class String
|
2
|
-
def camel_case
|
3
|
-
self.gsub(/(^|_)(.)/) { $2.upcase }
|
4
|
-
end
|
5
|
-
end
|
6
1
|
module Octopi
|
7
2
|
class Base
|
8
3
|
VALID = {
|
@@ -14,9 +9,6 @@ module Octopi
|
|
14
9
|
:user => {
|
15
10
|
:pat => /^[A-Za-z0-9_\.-]+$/,
|
16
11
|
:msg => "%s is an invalid username"},
|
17
|
-
:file => {
|
18
|
-
:pat => /^[^ \/]+$/,
|
19
|
-
:msg => "%s is an invalid filename"},
|
20
12
|
:sha => {
|
21
13
|
:pat => /^[a-f0-9]{40}$/,
|
22
14
|
:msg => "%s is an invalid SHA hash"},
|
@@ -29,62 +21,70 @@ module Octopi
|
|
29
21
|
|
30
22
|
attr_accessor :api
|
31
23
|
|
32
|
-
def initialize(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
self.class.send :define_method, "#{method}=" do |v|
|
46
|
-
instance_variable_set("@#{k}", v)
|
47
|
-
end
|
48
|
-
|
49
|
-
self.class.send :define_method, method do
|
50
|
-
instance_variable_get("@#{k}")
|
51
|
-
end
|
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
|
+
raise "no attr_accessor set for #{key} on #{self.class}" if !respond_to?("#{key}=")
|
30
|
+
self.send("#{key}=", value)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def error=(error)
|
35
|
+
if /\w+ not found/.match(error)
|
36
|
+
raise NotFound, self.class
|
52
37
|
end
|
53
38
|
end
|
54
39
|
|
55
40
|
def property(p, v)
|
56
41
|
path = "#{self.class.path_for(:resource)}/#{p}"
|
57
|
-
|
42
|
+
Api.api.find(path, self.class.resource_name(:singular), v)
|
58
43
|
end
|
59
44
|
|
60
45
|
def save
|
61
46
|
hash = {}
|
62
47
|
@keys.each { |k| hash[k] = send(k) }
|
63
|
-
|
48
|
+
Api.api.save(self.path_for(:resource), hash)
|
64
49
|
end
|
65
50
|
|
66
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
67
|
def self.extract_user_repository(*args)
|
68
|
-
|
69
|
-
if
|
68
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
69
|
+
if options.empty?
|
70
70
|
if args.length > 1
|
71
71
|
repo, user = *args
|
72
72
|
else
|
73
73
|
repo = args.pop
|
74
74
|
end
|
75
75
|
else
|
76
|
-
|
77
|
-
repo = args.pop ||
|
78
|
-
user =
|
76
|
+
options[:repo] = options[:repository] if options[:repository]
|
77
|
+
repo = args.pop || options[:repo]
|
78
|
+
user = options[:user]
|
79
79
|
end
|
80
80
|
|
81
81
|
user = repo.owner if repo.is_a? Repository
|
82
82
|
|
83
|
-
if repo.is_a?(String)
|
83
|
+
if repo.is_a?(String) && !user
|
84
84
|
raise "Need user argument when repository is identified by name"
|
85
85
|
end
|
86
86
|
ret = extract_names(user, repo)
|
87
|
-
ret <<
|
87
|
+
ret << options
|
88
88
|
ret
|
89
89
|
end
|
90
90
|
|
@@ -95,7 +95,11 @@ module Octopi
|
|
95
95
|
v
|
96
96
|
end
|
97
97
|
end
|
98
|
-
|
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
|
+
|
99
103
|
def self.validate_args(spec)
|
100
104
|
m = caller[0].match(/\/([a-z0-9_]+)\.rb:\d+:in `([a-z_0-9]+)'/)
|
101
105
|
meth = m ? "#{m[1].camel_case}.#{m[2]}" : 'method'
|
data/lib/octopi/blob.rb
CHANGED
@@ -1,20 +1,24 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "resource")
|
1
2
|
module Octopi
|
2
3
|
class Blob < Base
|
4
|
+
attr_accessor :text, :data, :name, :sha, :size, :mode, :mime_type
|
3
5
|
include Resource
|
4
6
|
set_resource_name "blob"
|
5
7
|
|
6
8
|
resource_path "/blob/show/:id"
|
7
9
|
|
8
|
-
def self.find(
|
9
|
-
|
10
|
-
repo =
|
11
|
-
|
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
|
+
|
12
18
|
if path
|
13
|
-
super [user,repo,sha,path]
|
19
|
+
super [user, repo, sha, path]
|
14
20
|
else
|
15
|
-
|
16
|
-
{:id => [user,repo,sha].join('/')})
|
17
|
-
new(ANONYMOUS_API, {:text => blob})
|
21
|
+
Api.api.get_raw(path_for(:resource), {:id => [user, repo, sha].join('/')})
|
18
22
|
end
|
19
23
|
end
|
20
24
|
end
|
data/lib/octopi/branch.rb
CHANGED
@@ -1,18 +1,31 @@
|
|
1
1
|
module Octopi
|
2
2
|
class Branch < Base
|
3
|
+
attr_accessor :name, :sha
|
3
4
|
include Resource
|
4
5
|
set_resource_name "branch", "branches"
|
5
6
|
|
6
7
|
resource_path "/repos/show/:id"
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
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)
|
11
25
|
self.validate_args(user => :user, repo => :repo)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
}
|
26
|
+
BranchSet.new(find_plural([user, repo, 'branches'], :resource)) do |i|
|
27
|
+
{ :name => i.first, :hash => i.last }
|
28
|
+
end
|
16
29
|
end
|
17
30
|
end
|
18
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
|
data/lib/octopi/commit.rb
CHANGED
@@ -4,51 +4,55 @@ module Octopi
|
|
4
4
|
find_path "/commits/list/:query"
|
5
5
|
resource_path "/commits/show/:id"
|
6
6
|
|
7
|
-
attr_accessor :repository
|
7
|
+
attr_accessor :repository, :message, :parents, :author, :url, :id, :committed_date, :authored_date, :tree, :committer, :added, :removed, :modified
|
8
8
|
|
9
|
-
|
9
|
+
|
10
|
+
# Finds all commits for the given options:
|
10
11
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
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.
|
14
15
|
#
|
15
|
-
# If no branch is given, "master" is assumed.
|
16
|
-
#
|
17
16
|
# Sample usage:
|
18
17
|
#
|
19
|
-
# find_all(
|
20
|
-
#
|
21
|
-
#
|
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>
|
22
23
|
#
|
23
|
-
def self.find_all(
|
24
|
-
|
25
|
-
repo =
|
26
|
-
|
27
|
-
|
28
|
-
self.validate_args(user => :user, repo_name => :repo)
|
29
|
-
branch = opts[:branch] || "master"
|
30
|
-
api = ANONYMOUS_API if repo.is_a?(Repository) && !repo.private
|
31
|
-
commits = super user, repo_name, branch, api
|
32
|
-
commits.each { |c| c.repository = repo } if repo.is_a? Repository
|
33
|
-
commits
|
34
|
-
end
|
35
|
-
|
36
|
-
# TODO: Make find use hashes like find_all
|
37
|
-
def self.find(*args)
|
38
|
-
if args.last.is_a?(Commit)
|
39
|
-
commit = args.pop
|
40
|
-
super "#{commit.repo_identifier}"
|
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]
|
41
29
|
else
|
42
|
-
user, name,
|
43
|
-
user = user.login if user.is_a? User
|
44
|
-
name = repo.name if name.is_a? Repository
|
45
|
-
self.validate_args(user => :user, name => :repo, sha => :sha)
|
46
|
-
super [user, name, sha]
|
30
|
+
super user, repo.name, branch
|
47
31
|
end
|
32
|
+
# Repository is not passed in from the data, set it manually.
|
33
|
+
commits.each { |c| c.repository = repo }
|
34
|
+
commits
|
48
35
|
end
|
49
36
|
|
50
|
-
|
51
|
-
|
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]
|
52
56
|
end
|
53
57
|
|
54
58
|
def repo_identifier
|