suse-git-pulls 0.3.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/LICENSE +20 -0
  2. data/bin/git-pulls +6 -0
  3. data/lib/git-pulls.rb +306 -0
  4. metadata +112 -0
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Scott Chacon
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.
data/bin/git-pulls ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'git-pulls'
5
+
6
+ GitPulls.start(ARGV)
data/lib/git-pulls.rb ADDED
@@ -0,0 +1,306 @@
1
+ require 'json'
2
+ require 'launchy'
3
+ require 'octokit'
4
+
5
+ class GitPulls
6
+
7
+ PULLS_CACHE_FILE = '.git/pulls_cache.json'
8
+
9
+ def initialize(args)
10
+ @command = args.shift
11
+ @user, @repo = repo_info
12
+ @args = args
13
+ end
14
+
15
+ def self.start(args)
16
+ GitPulls.new(args).run
17
+ end
18
+
19
+ def run
20
+ configure
21
+ if @command && self.respond_to?(@command)
22
+ # If the cache file doesn't exist, make sure we run update
23
+ # before any other command. git-pulls will otherwise crash
24
+ # with an exception.
25
+ update unless File.exists?(PULLS_CACHE_FILE) || @command == 'update'
26
+
27
+ self.send @command
28
+ elsif %w(-h --help).include?(@command)
29
+ usage
30
+ else
31
+ help
32
+ end
33
+ end
34
+
35
+ ## COMMANDS ##
36
+
37
+ def help
38
+ puts "No command: #{@command}"
39
+ puts "Try: update, list, show, merge, browse"
40
+ puts "or call with '-h' for usage information"
41
+ end
42
+
43
+ def usage
44
+ puts <<-USAGE
45
+ Usage: git pulls update
46
+ or: git pulls list [--reverse]
47
+ or: git pulls show <number> [--full]
48
+ or: git pulls browse <number>
49
+ or: git pulls merge <number>
50
+ USAGE
51
+ end
52
+
53
+ def merge
54
+ num = @args.shift
55
+ option = @args.shift
56
+ if p = pull_num(num)
57
+ if p['head']['repository']
58
+ o = p['head']['repository']['owner']
59
+ r = p['head']['repository']['name']
60
+ else # they deleted the source repo
61
+ o = p['head']['user']['login']
62
+ purl = p['patch_url']
63
+ puts "Sorry, #{o} deleted the source repository, git-pulls doesn't support this."
64
+ puts "You can manually patch your repo by running:"
65
+ puts
66
+ puts " curl #{purl} | git am"
67
+ puts
68
+ puts "Tell the contributor not to do this."
69
+ return false
70
+ end
71
+ s = p['head']['sha']
72
+
73
+ message = "Merge pull request ##{num} from #{o}/#{r}\n\n---\n\n"
74
+ message += p['body'].gsub("'", '')
75
+ cmd = ''
76
+ if option == '--log'
77
+ message += "\n\n---\n\nMerge Log:\n"
78
+ puts cmd = "git merge --no-ff --log -m '#{message}' #{s}"
79
+ else
80
+ puts cmd = "git merge --no-ff -m '#{message}' #{s}"
81
+ end
82
+ exec(cmd)
83
+ else
84
+ puts "No such number"
85
+ end
86
+ end
87
+
88
+ def show
89
+ num = @args.shift
90
+ option = @args.shift
91
+ if p = pull_num(num)
92
+ puts "Number : #{p['number']}"
93
+ puts "Label : #{p['head']['label']}"
94
+ puts "Created : #{p['created_at']}"
95
+ puts "Votes : #{p['votes']}"
96
+ puts "Comments : #{p['comments']}"
97
+ puts
98
+ puts "Title : #{p['title']}"
99
+ puts "Body :"
100
+ puts
101
+ puts p['body']
102
+ puts
103
+ puts '------------'
104
+ puts
105
+ if option == '--full'
106
+ exec "git diff --color=always HEAD...#{p['head']['sha']}"
107
+ else
108
+ puts "cmd: git diff HEAD...#{p['head']['sha']}"
109
+ puts git("diff --stat --color=always HEAD...#{p['head']['sha']}")
110
+ end
111
+ else
112
+ puts "No such number"
113
+ end
114
+ end
115
+
116
+ def browse
117
+ num = @args.shift
118
+ if p = pull_num(num)
119
+ Launchy.open(p['html_url'])
120
+ else
121
+ puts "No such number"
122
+ end
123
+ end
124
+
125
+ def list
126
+ option = @args.shift
127
+ puts "Open Pull Requests for #{@user}/#{@repo}"
128
+ pulls = get_pull_info
129
+ pulls.reverse! if option == '--reverse'
130
+ count = 0
131
+ pulls.each do |pull|
132
+ line = []
133
+ line << l(pull['number'], 4)
134
+ line << l(Date.parse(pull['created_at']).strftime("%m/%d"), 5)
135
+ line << l(pull['comments'], 2)
136
+ line << l(pull['title'], 35)
137
+ line << l(pull['head']['label'], 20)
138
+ sha = pull['head']['sha']
139
+ if not_merged?(sha)
140
+ puts line.join ' '
141
+ count += 1
142
+ end
143
+ end
144
+ if count == 0
145
+ puts ' -- no open pull requests --'
146
+ end
147
+ end
148
+
149
+ def update
150
+ puts "Updating #{@user}/#{@repo}"
151
+ cache_pull_info
152
+ fetch_stale_forks
153
+ list
154
+ end
155
+
156
+ def fetch_stale_forks
157
+ puts "Checking for forks in need of fetching"
158
+ pulls = get_pull_info
159
+ repos = {}
160
+ pulls.each do |pull|
161
+ next if pull['head']['repository'].nil? # Fork has been deleted
162
+ o = pull['head']['repository']['owner']
163
+ r = pull['head']['repository']['name']
164
+ s = pull['head']['sha']
165
+ if !has_sha(s)
166
+ repo = "#{o}/#{r}"
167
+ repos[repo] = true
168
+ end
169
+ end
170
+ if github_credentials_provided?
171
+ endpoint = "git@github.com:"
172
+ else
173
+ endpoint = github_endpoint + "/"
174
+ end
175
+ repos.each do |repo, bool|
176
+ puts " fetching #{repo}"
177
+ git("fetch #{endpoint}#{repo}.git +refs/heads/*:refs/pr/#{repo}/*")
178
+ end
179
+ end
180
+
181
+ def has_sha(sha)
182
+ git("show #{sha} 2>&1")
183
+ $?.exitstatus == 0
184
+ end
185
+
186
+ def not_merged?(sha)
187
+ commits = git("rev-list #{sha} ^HEAD 2>&1")
188
+ commits.split("\n").size > 0
189
+ end
190
+
191
+ # DISPLAY HELPER FUNCTIONS #
192
+
193
+ def l(info, size)
194
+ clean(info)[0, size].ljust(size)
195
+ end
196
+
197
+ def r(info, size)
198
+ clean(info)[0, size].rjust(size)
199
+ end
200
+
201
+ def clean(info)
202
+ info.to_s.gsub("\n", ' ')
203
+ end
204
+
205
+ # PRIVATE REPOSITORIES ACCESS
206
+
207
+ def configure
208
+ Octokit.configure do |config|
209
+ config.login = github_login
210
+ config.token = github_token
211
+ config.endpoint = github_endpoint
212
+ end
213
+ end
214
+
215
+ def github_login
216
+ git("config --get-all github.user")
217
+ end
218
+
219
+ def github_token
220
+ git("config --get-all github.token")
221
+ end
222
+
223
+ def github_endpoint
224
+ host = git("config --get-all github.host")
225
+ if host.size > 0
226
+ host
227
+ else
228
+ 'https://github.com'
229
+ end
230
+ end
231
+
232
+ # API/DATA HELPER FUNCTIONS #
233
+
234
+ def github_credentials_provided?
235
+ if github_token.empty? && github_login.empty?
236
+ return false
237
+ end
238
+ true
239
+ end
240
+
241
+ def get_pull_info
242
+ get_data(PULLS_CACHE_FILE)['pulls']
243
+ end
244
+
245
+ def get_data(file)
246
+ data = JSON.parse(File.read(file))
247
+ end
248
+
249
+ def cache_pull_info
250
+ response = Octokit.pull_requests("#{@user}/#{@repo}")
251
+ save_data({'pulls' => response}, PULLS_CACHE_FILE)
252
+ end
253
+
254
+ def save_data(data, file)
255
+ File.open(file, "w+") do |f|
256
+ f.puts data.to_json
257
+ end
258
+ end
259
+
260
+ def pull_num(num)
261
+ data = get_pull_info
262
+ data.select { |p| p['number'].to_s == num.to_s }.first
263
+ end
264
+
265
+ def github_insteadof_matching(c, u)
266
+ first = c.collect {|k,v| [v, /url\.(.*github\.com.*)\.insteadof/.match(k)]}.
267
+ find {|v,m| u.index(v) and m != nil}
268
+ if first
269
+ return first[0], first[1][1]
270
+ end
271
+ return nil, nil
272
+ end
273
+
274
+ def github_user_and_proj(u)
275
+ # Trouble getting optional ".git" at end to work, so put that logic below
276
+ m = /github\.com.(.*?)\/(.*)/.match(u)
277
+ if m
278
+ return m[1], m[2].sub(/\.git\Z/, "")
279
+ end
280
+ return nil, nil
281
+ end
282
+
283
+ def repo_info
284
+ c = {}
285
+ config = git('config --list')
286
+ config.split("\n").each do |line|
287
+ k, v = line.split('=')
288
+ c[k] = v
289
+ end
290
+ u = c['remote.origin.url']
291
+
292
+ user, proj = github_user_and_proj(u)
293
+ if !(user and proj)
294
+ short, base = github_insteadof_matching(c, u)
295
+ if short and base
296
+ u = u.sub(short, base)
297
+ user, proj = github_user_and_proj(u)
298
+ end
299
+ end
300
+ [user, proj]
301
+ end
302
+
303
+ def git(command)
304
+ `git #{command}`.chomp
305
+ end
306
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: suse-git-pulls
3
+ version: !ruby/object:Gem::Version
4
+ hash: 25
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 3
9
+ - 5
10
+ version: 0.3.5
11
+ platform: ruby
12
+ authors:
13
+ - Cristian Messel, Dominik Bamberger
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-12 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: json
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: launchy
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :runtime
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: octokit
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - "="
56
+ - !ruby/object:Gem::Version
57
+ hash: 9
58
+ segments:
59
+ - 0
60
+ - 5
61
+ - 1
62
+ version: 0.5.1
63
+ type: :runtime
64
+ version_requirements: *id003
65
+ description: git-pulls facilitates github pull requests.
66
+ email: bamberger.dominik@gmail.com
67
+ executables:
68
+ - git-pulls
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - LICENSE
75
+ - lib/git-pulls.rb
76
+ - bin/git-pulls
77
+ has_rdoc: true
78
+ homepage: http://github.com/b4mboo/git-pulls
79
+ licenses: []
80
+
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 3
92
+ segments:
93
+ - 0
94
+ version: "0"
95
+ required_rubygems_version: !ruby/object:Gem::Requirement
96
+ none: false
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ hash: 3
101
+ segments:
102
+ - 0
103
+ version: "0"
104
+ requirements: []
105
+
106
+ rubyforge_project:
107
+ rubygems_version: 1.5.2
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: facilitates github pull requests
111
+ test_files: []
112
+