drnic-github 0.3.9

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,473 @@
1
+ DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:" unless const_defined?("DEV_NULL")
2
+
3
+ helper :user_and_repo_from do |url|
4
+ case url
5
+ when %r|^git://github\.com/([^/]+/[^/]+)$|: $1.split('/')
6
+ when %r|^(?:ssh://)?(?:git@)?github\.com:([^/]+/[^/]+)$|: $1.split('/')
7
+ end
8
+ end
9
+
10
+ helper :user_and_repo_for do |remote|
11
+ user_and_repo_from(url_for(remote))
12
+ end
13
+
14
+ helper :user_for do |remote|
15
+ user_and_repo_for(remote).try.first
16
+ end
17
+
18
+ helper :repo_for do |remote|
19
+ user_and_repo_for(remote).try.last
20
+ end
21
+
22
+ helper :origin do
23
+ orig = `git config --get github.origin`.chomp
24
+ orig = nil if orig.empty?
25
+ orig || 'origin'
26
+ end
27
+
28
+ helper :project do
29
+ repo = repo_for(origin)
30
+ if repo.nil?
31
+ if url_for(origin) == ""
32
+ STDERR.puts "Error: missing remote 'origin'"
33
+ else
34
+ STDERR.puts "Error: remote 'origin' is not a github URL"
35
+ end
36
+ exit 1
37
+ end
38
+ repo.chomp('.git')
39
+ end
40
+
41
+ helper :url_for do |remote|
42
+ `git config --get remote.#{remote}.url`.chomp
43
+ end
44
+
45
+ helper :local_heads do
46
+ `git show-ref --heads --hash`.split("\n")
47
+ end
48
+
49
+ helper :has_commit? do |sha|
50
+ `git show #{sha} >#{DEV_NULL} 2>#{DEV_NULL}`
51
+ $?.exitstatus == 0
52
+ end
53
+
54
+ helper :resolve_commits do |treeish|
55
+ if treeish
56
+ if treeish.match(/\.\./)
57
+ commits = `git rev-list #{treeish}`.split("\n")
58
+ else
59
+ commits = `git rev-parse #{treeish}`.split("\n")
60
+ end
61
+ else
62
+ # standard in
63
+ puts 'reading from stdin...'
64
+ commits = $stdin.read.split("\n")
65
+ end
66
+ commits.select { |a| a.size == 40 } # only the shas, not the ^SHAs
67
+ end
68
+
69
+ helper :ignore_file_path do
70
+ dir = `git rev-parse --git-dir`.chomp
71
+ File.join(dir, 'ignore-shas')
72
+ end
73
+
74
+ helper :ignore_sha_array do
75
+ File.open( ignore_file_path ) { |yf| YAML::load( yf ) } rescue {}
76
+ end
77
+
78
+ helper :remove_ignored do |array, ignore_array|
79
+ array.reject { |id| ignore_array[id] }
80
+ end
81
+
82
+ helper :ignore_shas do |shas|
83
+ ignores = ignore_sha_array
84
+ shas.each do |sha|
85
+ puts 'ignoring ' + sha
86
+ ignores[sha] = true
87
+ end
88
+ File.open( ignore_file_path, 'w' ) do |out|
89
+ YAML.dump( ignores, out )
90
+ end
91
+ end
92
+
93
+ helper :get_commits do |rev_array|
94
+ list = rev_array.select { |a| has_commit?(a) }.join(' ')
95
+ `git log --pretty=format:"%H::%ae::%s::%ar::%ad" --no-merges #{list}`.split("\n").map { |a| a.split('::') }
96
+ end
97
+
98
+ helper :get_cherry do |branch|
99
+ `git cherry HEAD #{branch} | git name-rev --stdin`.split("\n").map { |a| a.split(' ') }
100
+ end
101
+
102
+ helper :get_common do |branch|
103
+ `git rev-list ..#{branch} --boundary | tail -1 | git name-rev --stdin`.split(' ')[1] rescue 'unknown'
104
+ end
105
+
106
+ helper :print_commits do |our_commits, options|
107
+ ignores = ignore_sha_array
108
+
109
+ case options[:sort]
110
+ when 'branch'
111
+ our_commits.sort! { |a, b| a[0][2] <=> b[0][2] }
112
+ when 'author'
113
+ our_commits.sort! { |a, b| a[1][1] <=> b[1][1] }
114
+ else
115
+ our_commits.sort! { |a, b| Date.parse(a[1][4]) <=> Date.parse(b[1][4]) } rescue 'cant parse dates'
116
+ end
117
+
118
+ shown_commits = {}
119
+ before = Date.parse(options[:before]) if options[:before] rescue puts 'cant parse before date'
120
+ after = Date.parse(options[:after]) if options[:after] rescue puts 'cant parse after date'
121
+ our_commits.each do |cherry, commit|
122
+ status, sha, ref_name = cherry
123
+ ref_name ||= ""
124
+ next if shown_commits[sha] || ignores[sha]
125
+ next if options[:project] && !ref_name.match(Regexp.new(options[:project]))
126
+ ref_name = ref_name.gsub('remotes/', '')
127
+ if status == '+' && commit
128
+ next if options[:author] && !commit[1].match(Regexp.new(options[:author]))
129
+ next if options[:before] && before && (before < Date.parse(commit[4])) rescue false
130
+ next if options[:after] && after && (after > Date.parse(commit[4])) rescue false
131
+ applies = applies_cleanly(sha)
132
+ next if options[:applies] && !applies
133
+ next if options[:noapply] && applies
134
+ if options[:shas]
135
+ puts sha
136
+ else
137
+ common = options[:common] ? get_common(sha) : ''
138
+ puts [sha[0,6], ref_name.ljust(25), commit[1][0,20].ljust(21),
139
+ commit[2][0, 36].ljust(38), commit[3][0,15], common].join(" ")
140
+ end
141
+ end
142
+ shown_commits[sha] = true
143
+ end
144
+ end
145
+
146
+ helper :applies_cleanly do |sha|
147
+ `git diff ...#{sha} | git apply --check >#{DEV_NULL} 2>#{DEV_NULL}`
148
+ $?.exitstatus == 0
149
+ end
150
+
151
+ helper :remotes do
152
+ regexp = '^remote\.(.+)\.url$'
153
+ `git config --get-regexp '#{regexp}'`.split("\n").inject({}) do |memo, line|
154
+ name_string, url = line.split(/ /, 2)
155
+ m, name = *name_string.match(/#{regexp}/)
156
+ memo[name.to_sym] = url
157
+ memo
158
+ end
159
+ end
160
+
161
+ helper :remote_branches_for do |user|
162
+ `git ls-remote -h #{user} 2> #{DEV_NULL}`.split(/\n/).inject({}) do |memo, line|
163
+ hash, head = line.split(/\t/, 2)
164
+ head = head[%r{refs/heads/(.+)$},1] unless head.nil?
165
+ memo[head] = hash unless head.nil?
166
+ memo
167
+ end if !(user.nil? || user.strip.empty?)
168
+ end
169
+
170
+ helper :remote_branch? do |user, branch|
171
+ remote_branches_for(user).key?(branch)
172
+ end
173
+
174
+ # see if there are any cached or tracked files that have been modified
175
+ # originally, we were going to use git-ls-files but that could only
176
+ # report modified track files...not files that have been staged
177
+ # for committal
178
+ helper :branch_dirty? do
179
+ !( system("git diff --quiet 2>#{DEV_NULL}") ||
180
+ !system("git diff --cached --quiet 2>#{DEV_NULL}")
181
+ )
182
+ end
183
+
184
+ helper :tracking do
185
+ remotes.inject({}) do |memo, (name, url)|
186
+ if ur = user_and_repo_from(url)
187
+ memo[name] = ur.first
188
+ else
189
+ memo[name] = url
190
+ end
191
+ memo
192
+ end
193
+ end
194
+
195
+ helper :tracking? do |user|
196
+ tracking.values.include?(user)
197
+ end
198
+
199
+ helper :owner do
200
+ user_for(origin)
201
+ end
202
+
203
+ helper :current_branch do
204
+ `git rev-parse --symbolic-full-name HEAD`.chomp.sub(/^refs\/heads\//, '')
205
+ end
206
+
207
+ helper :user_and_branch do
208
+ raw_branch = current_branch
209
+ user, branch = raw_branch.split(/\//, 2)
210
+ if branch
211
+ [user, branch]
212
+ else
213
+ [owner, user]
214
+ end
215
+ end
216
+
217
+ helper :branch_user do
218
+ user_and_branch.first
219
+ end
220
+
221
+ helper :branch_name do
222
+ user_and_branch.last
223
+ end
224
+
225
+ helper :public_url_for_user_and_repo do |user, repo|
226
+ "git://github.com/#{user}/#{repo}.git"
227
+ end
228
+
229
+ helper :private_url_for_user_and_repo do |user, repo|
230
+ "git@github.com:#{user}/#{repo}.git"
231
+ end
232
+
233
+ helper :public_url_for do |user|
234
+ public_url_for_user_and_repo user, project
235
+ end
236
+
237
+ helper :private_url_for do |user|
238
+ private_url_for_user_and_repo user, project
239
+ end
240
+
241
+ helper :homepage_for do |user, branch|
242
+ "https://github.com/#{user}/#{project}/tree/#{branch}"
243
+ end
244
+
245
+ helper :network_page_for do |user|
246
+ "https://github.com/#{user}/#{project}/network"
247
+ end
248
+
249
+ helper :network_meta_for do |user|
250
+ "http://github.com/#{user}/#{project}/network_meta"
251
+ end
252
+
253
+ helper :network_members_for do |user|
254
+ "http://github.com/#{user}/#{project}/network/members.json"
255
+ end
256
+
257
+ helper :list_issues_for do |user, state|
258
+ "http://github.com/api/v2/yaml/issues/list/#{user}/#{project}/#{state}"
259
+ end
260
+
261
+ helper :has_launchy? do |blk|
262
+ begin
263
+ gem 'launchy'
264
+ require 'launchy'
265
+ blk.call
266
+ rescue Gem::LoadError
267
+ STDERR.puts "Sorry, you need to install launchy: `gem install launchy`"
268
+ end
269
+ end
270
+
271
+ helper :open do |url|
272
+ has_launchy? proc {
273
+ Launchy::Browser.new.visit url
274
+ }
275
+ end
276
+
277
+ helper :print_network_help do
278
+ puts "
279
+ You have to provide a command :
280
+
281
+ web [user] - opens your web browser to the network graph page for this
282
+ project, or for the graph page for [user] if provided
283
+
284
+ list - shows the projects in your network that have commits
285
+ that you have not pulled in yet, and branch names
286
+
287
+ fetch - adds all projects in your network as remotes and fetches
288
+ any objects from them that you don't have yet
289
+
290
+ commits - will show you a list of all commits in your network that
291
+ you have not ignored or have not merged or cherry-picked.
292
+ This will automatically fetch objects you don't have yet.
293
+
294
+ --project (user/branch) - only show projects that match string
295
+ --author (email) - only show projects that match string
296
+ --after (date) - only show commits after date
297
+ --before (date) - only show commits before date
298
+ --shas - only print shas (can pipe through 'github ignore')
299
+ --applies - filter to patches that still apply cleanly
300
+ --sort - how to sort the commits (date, branch, author)
301
+ "
302
+ end
303
+
304
+ helper :print_network_cherry_help do
305
+ $stderr.puts "
306
+ =========================================================================================
307
+ These are all the commits that other people have pushed that you have not
308
+ applied or ignored yet (see 'github ignore'). Some things you might want to do:
309
+
310
+ * You can run 'github fetch user/branch' (sans '~N') to pull into a local branch for testing
311
+ * You can run 'github cherry-pick [SHA]' to apply a single patch
312
+ * You can run 'github merge user/branch' to merge a commit and all the '~N' variants.
313
+ * You can ignore all commits from a branch with 'github ignore ..user/branch'
314
+ =========================================================================================
315
+
316
+ "
317
+ end
318
+
319
+ helper :argv do
320
+ GitHub.original_args
321
+ end
322
+
323
+ helper :network_members do
324
+ get_network_members(owner, {}).map {|member| member['owner']['login'] }
325
+ end
326
+
327
+
328
+ helper :get_network_data do |user, options|
329
+ if options[:cache] && has_cache?
330
+ return get_cache
331
+ end
332
+ if cache_network_data(options)
333
+ begin
334
+ return cache_data(user)
335
+ rescue SocketError
336
+ STDERR.puts "*** Warning: There was a problem accessing the network."
337
+ rv = get_cache
338
+ STDERR.puts "Using cached data."
339
+ rv
340
+ end
341
+ else
342
+ return get_cache
343
+ end
344
+ end
345
+
346
+ helper :get_network_members do |user, options|
347
+ json = Kernel.open(network_members_for(user)).read
348
+ JSON.parse(json)["users"]
349
+ end
350
+
351
+ helper :cache_commits do |commits|
352
+ File.open( commits_cache_path, 'w' ) do |out|
353
+ out.write(commits.to_yaml)
354
+ end
355
+ end
356
+
357
+ helper :commits_cache do
358
+ YAML.load(File.open(commits_cache_path))
359
+ end
360
+
361
+ helper :cache_commits_data do |options|
362
+ cache_expired? || options[:nocache] || !has_commits_cache?
363
+ end
364
+
365
+ helper :cache_network_data do |options|
366
+ cache_expired? || options[:nocache] || !has_cache?
367
+ end
368
+
369
+ helper :network_cache_path do
370
+ dir = `git rev-parse --git-dir`.chomp
371
+ File.join(dir, 'network-cache')
372
+ end
373
+
374
+ helper :commits_cache_path do
375
+ dir = `git rev-parse --git-dir`.chomp
376
+ File.join(dir, 'commits-cache')
377
+ end
378
+
379
+ helper :cache_data do |user|
380
+ raw_data = Kernel.open(network_meta_for(user)).read
381
+ File.open( network_cache_path, 'w' ) do |out|
382
+ out.write(raw_data)
383
+ end
384
+ data = JSON.parse(raw_data)
385
+ end
386
+
387
+ helper :cache_expired? do
388
+ return true if !has_cache?
389
+ age = Time.now - File.stat(network_cache_path).mtime
390
+ return true if age > (60 * 60) # 1 hour
391
+ false
392
+ end
393
+
394
+ helper :has_cache? do
395
+ File.file?(network_cache_path)
396
+ end
397
+
398
+ helper :has_commits_cache? do
399
+ File.file?(commits_cache_path)
400
+ end
401
+
402
+ helper :get_cache do
403
+ JSON.parse(File.read(network_cache_path))
404
+ end
405
+
406
+ helper :print_issues_help do
407
+ puts <<-EOHELP
408
+ You have to provide a command :
409
+
410
+ open - shows open tickets for this project
411
+ closed - shows closed tickets for this project
412
+
413
+ --user=<username> - show issues from <username>'s repository
414
+ --after=<date> - only show issues updated after <date>
415
+
416
+ EOHELP
417
+ end
418
+
419
+ helper :distance_of_time do |from_time, to_time|
420
+ # this is a dumbed-down version of actionpack's helper.
421
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
422
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
423
+
424
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
425
+
426
+ words = case distance_in_minutes
427
+ when 0 then "less than 1 minute"
428
+ when 2..44 then "%d minutes" % distance_in_minutes
429
+ when 45..89 then "about 1 hour"
430
+ when 90..1439 then "about %d hours" % (distance_in_minutes.to_f / 60.0).round
431
+ when 1440..2879 then "1 day"
432
+ when 2880..43199 then "%d days" % (distance_in_minutes / 1440).round
433
+ when 43200..86399 then "about 1 month"
434
+ when 86400..525599 then "%d months" % (distance_in_minutes / 43200).round
435
+ when 525600..1051199 then "about 1 year"
436
+ else "over %d years" % (distance_in_minutes / 525600).round
437
+ end
438
+
439
+ "#{words} ago"
440
+ end
441
+
442
+ helper :format_issue do |issue, options|
443
+ options ||= {}
444
+ report = []
445
+ report << "Issue ##{issue['number']} (#{issue['votes']} votes): #{issue['title']}"
446
+ report << "* URL: http://github.com/#{options[:user]}/#{project}/issues/#issue/#{issue['number']}" if options[:user]
447
+ report << "* Opened #{distance_of_time(issue['created_at'], Time.now)} by #{issue['user']}" if issue['created_at']
448
+ report << "* Closed #{distance_of_time(issue['closed_at'], Time.now)}" if issue['closed_at']
449
+ report << "* Last updated #{distance_of_time(issue['updated_at'], Time.now)}" if issue['updated_at']
450
+ report << "* Labels: #{issue['labels'].join(', ')}" if issue['labels'] && issue['labels'].length > 0
451
+ report << ""
452
+ report << issue['body']
453
+ report << ""
454
+ report.join("\n")
455
+ end
456
+
457
+ helper :filter_issue do |issue, options|
458
+ if options[:after] && ! options[:after].instance_of?(Time)
459
+ options[:after] = Time.parse(options[:after]) rescue (puts 'cant parse after date')
460
+ end
461
+ return true if options[:after] && (options[:after] > issue['updated_at']) rescue false
462
+ return true if options[:label] && (issue['labels'].nil? || issue['labels'].empty? || ! issue['labels'].include?(options[:label]))
463
+ return false
464
+ end
465
+
466
+ helper :print_issues do |issues, options|
467
+ issues.sort_by {|issue| issue['updated_at']}.reverse.each do |issue|
468
+ next if filter_issue(issue, options)
469
+ puts "-----"
470
+ puts format_issue(issue, options)
471
+ end
472
+ puts "-----"
473
+ end