github_repo 0.1.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,39 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # jeweler generated
12
+ pkg
13
+
14
+ # Have editor/IDE/OS specific files you need to ignore? Consider using a global gitignore:
15
+ #
16
+ # * Create a file at ~/.gitignore
17
+ # * Include files you want ignored
18
+ # * Run: git config --global core.excludesfile ~/.gitignore
19
+ #
20
+ # After doing this, these files will be ignored in all your git projects,
21
+ # saving you from having to 'pollute' every project you touch with them
22
+ #
23
+ # Not sure what to needs to be ignored for particular editors/OSes? Here's some ideas to get you started. (Remember, remove the leading # of the line)
24
+ #
25
+ # For MacOS:
26
+ #
27
+ #.DS_Store
28
+ #
29
+ # For TextMate
30
+ #*.tmproj
31
+ #tmtags
32
+ #
33
+ # For emacs:
34
+ #*~
35
+ #\#*
36
+ #.\#*
37
+ #
38
+ # For vim:
39
+ #*.swp
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Kristian Mandrup
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ # github_repo ##
2
+
3
+ Library to enable automation of common github repository tasks.
4
+ This library is a response to some problems I had getting Octopi to work correctly for repository tasks.
5
+ I use the Octopi API in some cases, but implement my own Http post calls in most cases.
6
+
7
+ ## Rename ##
8
+
9
+ Renames a github repo
10
+
11
+ Rename [old-name] [new-name]
12
+
13
+ 1. Deletes any existing github repository [new-name] (if overwrite option)
14
+ 2. Creates a new repository called [new-name]
15
+ 3. Clones the github repository [old-name] locally
16
+ 4. Deletes the github repository [old-name]
17
+ 5. Changes origin of the local [old-name] repository to point to the github repository [new-name]
18
+ 6. Push the local repository to the github repository [new-name]
19
+ 7. Makes sure the github repository [old-name] was deleted!
20
+ 8. If the repository [new-name] exists, delete the local repo (only if option set to do so!)
21
+
22
+ Note: Currently this task contains a lot of code to retry when things go wrong some some reason.
23
+ The github API is still pretty unstable! And suffers from some timeout and caching issues, which requires a lot of care and exception handling!
24
+ Feel free to improve it!
25
+
26
+ ## Other Github tasks ##
27
+
28
+ * Delete
29
+ * Create
30
+ * Get clone url
31
+ * Clone
32
+ * Fork
33
+ * Collaborators
34
+ * Languages
35
+ * Tags
36
+ * Branches
37
+ * First commit
38
+ * First origin push
39
+ * Init repository
40
+
41
+ ## Note on Patches/Pull Requests ##
42
+
43
+ * Fork the project.
44
+ * Make your feature addition or bug fix.
45
+ * Add tests for it. This is important so I don't break it in a
46
+ future version unintentionally.
47
+ * Commit, do not mess with rakefile, version, or history.
48
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
49
+ * Send me a pull request. Bonus points for topic branches.
50
+
51
+ ## Copyright ##
52
+
53
+ Copyright (c) 2010 Kristian Mandrup. See LICENSE for details.
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gem|
4
+ gem.name = "github_repo"
5
+ gem.summary = %Q{Github API v2 wrapper for common github tasks}
6
+ gem.description = %Q{Github API v2 wrapper for common github tasks. Builds on Octopi gem where possible but works at a higher level.}
7
+ gem.email = "kmandrup@gmail.com"
8
+ gem.homepage = "http://github.com/kristianmandrup/github_repo"
9
+ gem.authors = ["Kristian Mandrup"]
10
+ gem.add_development_dependency "rspec", ">= 2.0.0"
11
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
12
+
13
+ # add more gem options here
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
17
+ end
18
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,303 @@
1
+ require 'octopi'
2
+ require 'git'
3
+ require 'yaml'
4
+ require 'github_util'
5
+
6
+ module GithubParser
7
+ def parse_single_result(key)
8
+ self.body[/#{Regexp.escape(key)}:\s(.*)/, 1]
9
+ end
10
+
11
+ def parse_result(key)
12
+ result = self.body.gsub("\n", '')
13
+ token = result[/#{Regexp.escape(key)}:(.*)/, 1]
14
+ token.split("- ").reject{|e| e == ' ' || e.match('{}') }.collect{|e| e.strip}
15
+ end
16
+ end
17
+
18
+ class GithubApi
19
+ include Octopi
20
+ include GithubUtil
21
+
22
+ class DeleteError < StandardError
23
+ end
24
+ class CreateError < StandardError
25
+ end
26
+ class RenameError < StandardError
27
+ end
28
+ class CloneError < StandardError
29
+ end
30
+ class FirstCommitError < StandardError
31
+ end
32
+ class FirstPushOriginError < StandardError
33
+ end
34
+ class InitRepoError < StandardError
35
+ end
36
+ class FindError < StandardError
37
+ end
38
+
39
+
40
+ attr_accessor :config, :log_level
41
+
42
+ def initialize(options = {})
43
+ @config = configure
44
+ @log_level = options[:log_level] || 0
45
+ end
46
+
47
+ def log_on
48
+ self.log_level = 1
49
+ end
50
+
51
+ def verbose_log_on
52
+ self.log_level = 2
53
+ end
54
+
55
+
56
+ def log_off
57
+ self.log_level = 0
58
+ end
59
+
60
+ def delete!(name)
61
+ begin
62
+ if !user.repositories.find(name)
63
+ info "repo #{name} not found"
64
+ return nil
65
+ end
66
+ delete_it!(name)
67
+ rescue Octopi::APIError
68
+ return nil
69
+ end
70
+ end
71
+
72
+ def delete_it!(name)
73
+ begin
74
+ info "deleting repo: #{name}"
75
+ result = post "http://github.com/api/v2/yaml/repos/delete/#{name}"
76
+ token = result.parse_single_result('delete_token')
77
+ return true if token && token.length > 20
78
+ raise DeleteError, "delete error" if !token
79
+ status = post "http://github.com/api/v2/yaml/repos/delete/#{name}", 'delete_token' => token
80
+ return true if status.to_s.length > 100
81
+ log "repo #{name} deleted ok"
82
+ status
83
+ rescue Octopi::APIError
84
+ log "delete error!"
85
+ raise DeleteError, "delete error"
86
+ end
87
+ end
88
+
89
+ def user(user_name = nil)
90
+ if user_name
91
+ User.find(user_name)
92
+ else
93
+ authenticated do
94
+ Api.api.user
95
+ end
96
+ end
97
+ end
98
+
99
+ def clone(repo_name, user_name = nil, clone_name = nil)
100
+ begin
101
+ info "cloning: #{repo_name}"
102
+ clone_user = user(user_name)
103
+ repo = clone_user.repositories.find(repo_name)
104
+ url = get_clone_url(repo, clone_user)
105
+ name = clone_name ? clone_name : repo_name
106
+ `git clone #{url} #{name}`
107
+ log "cloned #{repo_name} ok"
108
+ return url
109
+ rescue Octopi::APIError
110
+ raise CloneError
111
+ end
112
+ end
113
+
114
+ def get_clone_url(repo, user)
115
+ # log "user: '#{user}' == repo_user: '#{repo.owner.login}', #{user.to_s == repo.owner.login.to_s}"
116
+ url = user.to_s == repo.owner.login.to_s ? "git@github.com:" : "git://github.com/"
117
+ url += "#{repo.owner}/#{repo.name}.git"
118
+ end
119
+
120
+ def clone_url(repo_name, user_name = nil, options = {:retries => 3} )
121
+ begin
122
+ authenticated do
123
+ clone_user = user(user_name)
124
+ repo = clone_user.repositories.find(repo_name)
125
+ get_clone_url(repo, clone_user)
126
+ end
127
+ rescue Octopi::APIError
128
+ return "git://github.com/#{repo.owner}/#{repo.name}.git" if options[:retries] == 0
129
+ info "retry get clone url for #{repo_name} in 10 secs"
130
+ sleep 10
131
+ options.merge! :retries => options[:retries] -1
132
+ clone_url(repo_name, user_name, options)
133
+ end
134
+ end
135
+
136
+ def create(name, options = {})
137
+ log "creating repo: #{name}"
138
+ authenticated do
139
+ begin
140
+ if user.repositories.find(name)
141
+ unless options[:overwrite]
142
+ info "repo #{name} not created since it already exists"
143
+ return true
144
+ else
145
+ info "repo #{name} already exists, but will be overwritten with new repo!"
146
+ delete!(name, options)
147
+ create(name, options)
148
+ end
149
+ end
150
+ rescue Octopi::APIError
151
+ create_it(name, options)
152
+ end
153
+ end
154
+ end
155
+
156
+ def create_it(name, options = {:retries => 2})
157
+ begin
158
+ status = nil
159
+ authenticated do
160
+ repo_options = {:name => name}
161
+ [:description, :homepage ].each{|o| repo_options[o] = options[o] if options[o]}
162
+ repo_options[:public] = 0 if options[:private]
163
+ log repo_options.inspect
164
+ status = Repository.create(repo_options)
165
+ status.to_s == name ? status : nil
166
+ end
167
+ rescue Octopi::APIError => e
168
+ info "create error: #{e}"
169
+ ensure
170
+ options.merge! :retries => options[:retries] -1
171
+ if status.to_s == name
172
+ log "created repo ok"
173
+ return status
174
+ else
175
+ info "bad status #{status}, should be #{name} - try again!"
176
+ sleep 32
177
+ return create_it(name, options )
178
+ end
179
+ end
180
+ end
181
+
182
+ def first_commit(msg = 'first commit')
183
+ begin
184
+ info "first commit"
185
+ `git init`
186
+ `touch README`
187
+ `git add .`
188
+ `git commit -m '#{msg}'`
189
+ log "first push commit completed ok"
190
+ rescue
191
+ raise FirstCommitError
192
+ end
193
+ end
194
+
195
+ def first_push_origin(name)
196
+ begin
197
+ info "first push origin"
198
+ origin = clone_url(name)
199
+ `git remote rm origin`
200
+ `git remote add origin #{origin}`
201
+ `git push origin master`
202
+ log "first push origin completed ok"
203
+ rescue
204
+ raise FirstPushOriginError
205
+ end
206
+ end
207
+
208
+ def init_repo(name, options = {:overwrite => true})
209
+ begin
210
+ if File.directory?(name) && options[:overwrite]
211
+ log "removing local repo: #{name}"
212
+ FileUtils.rm_rf(name)
213
+ end
214
+ clone name
215
+ FileUtils.cd name do
216
+ first_commit
217
+ first_push_origin name
218
+ end
219
+ log "init repo complete!"
220
+ rescue
221
+ raise InitRepoError
222
+ end
223
+ end
224
+
225
+
226
+ def rename!(repo_name, new_repo_name, user_name = nil, options = {:overwrite => true})
227
+ begin
228
+ delete!(new_repo_name) if options[:overwrite]
229
+ info "waiting 20 secs for delete to take effect before creating new repo"
230
+ sleep 20
231
+ return nil if !create(new_repo_name)
232
+ log "created new repo: #{new_repo_name}"
233
+
234
+ # clone old repo
235
+ info "current dir: #{Dir.pwd}"
236
+ if File.directory?(repo_name)
237
+ if overwrite
238
+ info "removing local repo: #{repo_name}"
239
+ FileUtils.rm_rf(repo_name)
240
+ old_clone_url = clone(repo_name, user_name)
241
+ else
242
+ old_clone_url = clone_url(repo_name, user_name)
243
+ end
244
+ else
245
+ old_clone_url = clone(repo_name, user_name)
246
+ end
247
+ log "cloned old repo from : #{old_clone_url}"
248
+ # create new repo with new name
249
+ raise RenameError, "Error getting hold of old repository" if !old_clone_url
250
+
251
+ info "get clone_url for new repo: #{repo_name}"
252
+ new_clone_url = clone_url(new_repo_name, user_name)
253
+ raise RenameError, "Error getting new repository url for: #{new_repo_name}" if !new_clone_url
254
+
255
+ # change remote origin of repo
256
+ info "current dir: #{Dir.pwd}"
257
+ return "no local clone dir for #{repo_name}" if !File.directory? repo_name
258
+ FileUtils.cd repo_name do
259
+ info "update old local repo to point to new repo"
260
+ `git remote rm origin`
261
+ info "removed old origin"
262
+ `git remote add origin #{new_clone_url}`
263
+ info "added new origin"
264
+ `git push origin master --force`
265
+ log "pushed to new origin master: #{new_clone_url}"
266
+ status = delete!(repo_name)
267
+ log "old repo #{repo_name} deleted" if status
268
+ end
269
+
270
+ FileUtils.rm_rf repo_name if options[:delete_local]
271
+ log "old local repo directory deleted"
272
+
273
+ info "making sure old repo is deleted"
274
+ log delete!(repo_name)
275
+ rescue StandardError => e
276
+ log e
277
+ true
278
+ end
279
+ end
280
+
281
+ def fork(repo, options = {})
282
+ post_repo_user 'repos/fork', options
283
+ end
284
+
285
+ def collaborators(repo, options = {})
286
+ post_repo_show repo, 'collaborators', options
287
+ end
288
+
289
+ def languages(repo, options = {})
290
+ post_repo_show repo, 'languages', options
291
+ end
292
+
293
+ def tags(repo, options = {})
294
+ post_repo_show repo, 'tags', options
295
+ end
296
+
297
+ def branches(repo, options = {})
298
+ post_repo_show repo, 'branches', options
299
+ end
300
+
301
+ end
302
+
303
+
@@ -0,0 +1,41 @@
1
+ module GithubUtil
2
+ def configure
3
+
4
+ git_config = Git.global_config
5
+ user_name = git_config['user.name']
6
+ user_email = git_config['user.email']
7
+ github_username = git_config['github.user']
8
+ github_token = git_config['github.token']
9
+
10
+ {:name => github_username, :token => github_token, :login => github_username}
11
+ end
12
+
13
+ def info(msg)
14
+ puts msg if log_level > 0
15
+ end
16
+
17
+ def log(msg)
18
+ puts msg if log_level > 1
19
+ end
20
+
21
+
22
+ def post(path, options = {})
23
+ opts = {'login' => config[:login], 'token' => config[:token]}
24
+ res = Net::HTTP.post_form URI.parse(path), opts.merge(options)
25
+ res.extend(GithubParser)
26
+ end
27
+
28
+ def post_repo_user(api_path, repo, action, options = {})
29
+ user = options[:user] ? options[:user] : config[:name]
30
+ path = 'http://github.com/api/v2/yaml/'
31
+ path << api_path
32
+ path << "/#{user}/#{repo}"
33
+ path << "/#{action}" if action
34
+ post path, options
35
+ end
36
+
37
+ def post_repo_show(repo, action, options = {})
38
+ res = post_repo_user 'repos/show', repo, action, options
39
+ res.parse_result(action)
40
+ end
41
+ end
@@ -0,0 +1,69 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ # describe "GithubRepo" do
4
+ # it "works" do
5
+ # api = GithubApi.new
6
+ # res = api.tags 'github_thor_tasks'
7
+ # puts res.inspect
8
+ #
9
+ # res = api.branches 'github_thor_tasks'
10
+ # puts res.inspect
11
+ # end
12
+ # end
13
+
14
+ describe "GithubRepo" do
15
+ it "works" do
16
+ api = GithubApi.new
17
+ api.log_on
18
+ # res = api.create 'hello2', :description => 'my hello2', :homepage => 'my homepage2'
19
+ # puts api.delete! 'hello2'
20
+ # api.authenticated do
21
+ # clone_user = api.user
22
+ # repo = clone_user.repositories.find 'new_hello'
23
+ # url = api.get_clone_url(repo, clone_user)
24
+ # puts repo
25
+ # puts url
26
+ # end
27
+
28
+ res = api.create 'hello', :description => 'my hello', :homepage => 'my homepage'
29
+ # puts res
30
+ # # puts Dir.pwd
31
+ # api.init_repo 'hello'
32
+ # puts res
33
+ # api.rename! 'hello', 'new_hello'
34
+ # res = api.rename 'hello', 'new_hello'
35
+ # puts res
36
+ end
37
+ end
38
+
39
+
40
+ # Running “github_repo_spec.rb”…
41
+ # ruby 1.9.1p378 (2010-01-10 revision 26273) [i386-darwin10.2.0]
42
+ # Theme:
43
+ # W, [2010-04-13T15:40:17.884494 #91255] WARN -- : Using in memory store
44
+ # F
45
+ #
46
+ # 1) GithubRepo works
47
+ # Failure/Error: res = api.create 'hello', :description => 'my hello', :homepage => 'my homepage'
48
+ # Github returned status 403, you may not have access to this resource.
49
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/api.rb:129:in `rescue in get'
50
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/api.rb:117:in `get'
51
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/api.rb:100:in `find'
52
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/resource.rb:41:in `find'
53
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/repository.rb:82:in `find'
54
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi/repository_set.rb:7:in `find'
55
+ # # /Users/kristianconsult/Development/Languages/Ruby/Apps/Gems/github_repo/lib/github_repo.rb:74:in `block in create'
56
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi.rb:33:in `block in authenticated'
57
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi.rb:63:in `authenticated_with'
58
+ # # /Users/kristianconsult/.rvm/gems/ruby-1.9.1-p378/gems/octopi-0.2.8/lib/octopi.rb:32:in `authenticated'
59
+ # # /Users/kristianconsult/Development/Languages/Ruby/Apps/Gems/github_repo/lib/github_repo.rb:73:in `create'
60
+ # # /Users/kristianconsult/Development/Languages/Ruby/Apps/Gems/github_repo/spec/github_repo_spec.rb:19:in `block (2 levels) in <main>'
61
+ #
62
+ #
63
+ # Finished in 3.542207 seconds
64
+ # 1 example, 1 failures
65
+ # GitHub returned status 403. Retrying request.
66
+ # copy output
67
+ # Program exited with code #1 after 4.91 seconds.
68
+ #
69
+ #
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,10 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'github_repo'
4
+ require 'rspec'
5
+ require 'rspec/autorun'
6
+
7
+ Rspec.configure do |c|
8
+ # c.mock_with :rspec
9
+ end
10
+
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: github_repo
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Kristian Mandrup
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-13 00:00:00 -04:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 2
29
+ - 0
30
+ - 0
31
+ version: 2.0.0
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: Github API v2 wrapper for common github tasks. Builds on Octopi gem where possible but works at a higher level.
35
+ email: kmandrup@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - LICENSE
42
+ - README.markdown
43
+ files:
44
+ - .document
45
+ - .gitignore
46
+ - LICENSE
47
+ - README.markdown
48
+ - Rakefile
49
+ - VERSION
50
+ - lib/github_repo.rb
51
+ - lib/github_util.rb
52
+ - spec/github_repo_spec.rb
53
+ - spec/spec.opts
54
+ - spec/spec_helper.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/kristianmandrup/github_repo
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --charset=UTF-8
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ segments:
76
+ - 0
77
+ version: "0"
78
+ requirements: []
79
+
80
+ rubyforge_project:
81
+ rubygems_version: 1.3.6
82
+ signing_key:
83
+ specification_version: 3
84
+ summary: Github API v2 wrapper for common github tasks
85
+ test_files:
86
+ - spec/github_repo_spec.rb
87
+ - spec/spec_helper.rb