github_repo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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