jqr-github 0.3.4

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,397 @@
1
+ DEV_NULL = File.exist?("/dev/null") ? "/dev/null" : "nul:"
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
+ next if shown_commits[sha] || ignores[sha]
124
+ next if options[:project] && !ref_name.match(Regexp.new(options[:project]))
125
+ ref_name = ref_name.gsub('remotes/', '')
126
+ if status == '+' && commit
127
+ next if options[:author] && !commit[1].match(Regexp.new(options[:author]))
128
+ next if options[:before] && before && (before < Date.parse(commit[4])) rescue false
129
+ next if options[:after] && after && (after > Date.parse(commit[4])) rescue false
130
+ applies = applies_cleanly(sha)
131
+ next if options[:applies] && !applies
132
+ next if options[:noapply] && applies
133
+ if options[:shas]
134
+ puts sha
135
+ else
136
+ common = options[:common] ? get_common(sha) : ''
137
+ puts [sha[0,6], ref_name.ljust(25), commit[1][0,20].ljust(21),
138
+ commit[2][0, 36].ljust(38), commit[3][0,15], common].join(" ")
139
+ end
140
+ end
141
+ shown_commits[sha] = true
142
+ end
143
+ end
144
+
145
+ helper :applies_cleanly do |sha|
146
+ `git diff ...#{sha} | git apply --check >#{DEV_NULL} 2>#{DEV_NULL}`
147
+ $?.exitstatus == 0
148
+ end
149
+
150
+ helper :remotes do
151
+ regexp = '^remote\.(.+)\.url$'
152
+ `git config --get-regexp '#{regexp}'`.split("\n").inject({}) do |memo, line|
153
+ name_string, url = line.split(/ /, 2)
154
+ m, name = *name_string.match(/#{regexp}/)
155
+ memo[name.to_sym] = url
156
+ memo
157
+ end
158
+ end
159
+
160
+ helper :remote_branches_for do |user|
161
+ `git ls-remote -h #{user} 2> #{DEV_NULL}`.split(/\n/).inject({}) do |memo, line|
162
+ hash, head = line.split(/\t/, 2)
163
+ head = head[%r{refs/heads/(.+)$},1] unless head.nil?
164
+ memo[head] = hash unless head.nil?
165
+ memo
166
+ end if !(user.nil? || user.strip.empty?)
167
+ end
168
+
169
+ helper :remote_branch? do |user, branch|
170
+ remote_branches_for(user).key?(branch)
171
+ end
172
+
173
+ helper :branch_dirty? do
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
+ !(system("git diff --quiet 2>#{DEV_NULL}") or !system("git diff --cached --quiet 2>#{DEV_NULL}"))
179
+ end
180
+
181
+ helper :tracking do
182
+ remotes.inject({}) do |memo, (name, url)|
183
+ if ur = user_and_repo_from(url)
184
+ memo[name] = ur.first
185
+ else
186
+ memo[name] = url
187
+ end
188
+ memo
189
+ end
190
+ end
191
+
192
+ helper :tracking? do |user|
193
+ tracking.values.include?(user)
194
+ end
195
+
196
+ helper :owner do
197
+ user_for(origin)
198
+ end
199
+
200
+ helper :current_branch do
201
+ `git rev-parse --symbolic-full-name HEAD`.chomp.sub(/^refs\/heads\//, '')
202
+ end
203
+
204
+ helper :user_and_branch do
205
+ raw_branch = current_branch
206
+ user, branch = raw_branch.split(/\//, 2)
207
+ if branch
208
+ [user, branch]
209
+ else
210
+ [owner, user]
211
+ end
212
+ end
213
+
214
+ helper :branch_user do
215
+ user_and_branch.first
216
+ end
217
+
218
+ helper :branch_name do
219
+ user_and_branch.last
220
+ end
221
+
222
+ helper :public_url_for_user_and_repo do |user, repo|
223
+ "git://github.com/#{user}/#{repo}.git"
224
+ end
225
+
226
+ helper :private_url_for_user_and_repo do |user, repo|
227
+ "git@github.com:#{user}/#{repo}.git"
228
+ end
229
+
230
+ helper :public_url_for do |user|
231
+ public_url_for_user_and_repo user, project
232
+ end
233
+
234
+ helper :private_url_for do |user|
235
+ private_url_for_user_and_repo user, project
236
+ end
237
+
238
+ helper :homepage_for do |user, branch|
239
+ "https://github.com/#{user}/#{project}/tree/#{branch}"
240
+ end
241
+
242
+ helper :network_page_for do |user|
243
+ "https://github.com/#{user}/#{project}/network"
244
+ end
245
+
246
+ helper :network_meta_for do |user|
247
+ "http://github.com/#{user}/#{project}/network_meta"
248
+ end
249
+
250
+ helper :network_members_for do |user|
251
+ "http://github.com/#{user}/#{project}/network/members.json"
252
+ end
253
+
254
+ helper :has_launchy? do |blk|
255
+ begin
256
+ gem 'launchy'
257
+ require 'launchy'
258
+ blk.call
259
+ rescue Gem::LoadError
260
+ STDERR.puts "Sorry, you need to install launchy: `gem install launchy`"
261
+ end
262
+ end
263
+
264
+ helper :open do |url|
265
+ has_launchy? proc {
266
+ Launchy::Browser.new.visit url
267
+ }
268
+ end
269
+
270
+ helper :print_network_help do
271
+ puts "
272
+ You have to provide a command :
273
+
274
+ web [user] - opens your web browser to the network graph page for this
275
+ project, or for the graph page for [user] if provided
276
+
277
+ list - shows the projects in your network that have commits
278
+ that you have not pulled in yet, and branch names
279
+
280
+ fetch - adds all projects in your network as remotes and fetches
281
+ any objects from them that you don't have yet
282
+
283
+ commits - will show you a list of all commits in your network that
284
+ you have not ignored or have not merged or cherry-picked.
285
+ This will automatically fetch objects you don't have yet.
286
+
287
+ --project (user/branch) - only show projects that match string
288
+ --author (email) - only show projects that match string
289
+ --after (date) - only show commits after date
290
+ --before (date) - only show commits before date
291
+ --shas - only print shas (can pipe through 'github ignore')
292
+ --applies - filter to patches that still apply cleanly
293
+ --sort - how to sort the commits (date, branch, author)
294
+ "
295
+ end
296
+
297
+ helper :print_network_cherry_help do
298
+ $stderr.puts "
299
+ =========================================================================================
300
+ These are all the commits that other people have pushed that you have not
301
+ applied or ignored yet (see 'github ignore'). Some things you might want to do:
302
+
303
+ * You can run 'github fetch user/branch' (sans '~N') to pull into a local branch for testing
304
+ * You can run 'github cherry-pick [SHA]' to apply a single patch
305
+ * You can run 'github merge user/branch' to merge a commit and all the '~N' variants.
306
+ * You can ignore all commits from a branch with 'github ignore ..user/branch'
307
+ =========================================================================================
308
+
309
+ "
310
+ end
311
+
312
+ helper :argv do
313
+ GitHub.original_args
314
+ end
315
+
316
+ helper :network_members do
317
+ get_network_members(owner, {}).map {|member| member['owner']['login'] }
318
+ end
319
+
320
+
321
+ helper :get_network_data do |user, options|
322
+ if options[:cache] && has_cache?
323
+ return get_cache
324
+ end
325
+ if cache_network_data(options)
326
+ begin
327
+ return cache_data(user)
328
+ rescue SocketError
329
+ STDERR.puts "*** Warning: There was a problem accessing the network."
330
+ rv = get_cache
331
+ STDERR.puts "Using cached data."
332
+ rv
333
+ end
334
+ else
335
+ return get_cache
336
+ end
337
+ end
338
+
339
+ helper :get_network_members do |user, options|
340
+ json = Kernel.open(network_members_for(user)).read
341
+ JSON.parse(json)["users"]
342
+ end
343
+
344
+ helper :cache_commits do |commits|
345
+ File.open( commits_cache_path, 'w' ) do |out|
346
+ out.write(commits.to_yaml)
347
+ end
348
+ end
349
+
350
+ helper :commits_cache do
351
+ YAML.load(File.open(commits_cache_path))
352
+ end
353
+
354
+ helper :cache_commits_data do |options|
355
+ cache_expired? || options[:nocache] || !has_commits_cache?
356
+ end
357
+
358
+ helper :cache_network_data do |options|
359
+ cache_expired? || options[:nocache] || !has_cache?
360
+ end
361
+
362
+ helper :network_cache_path do
363
+ dir = `git rev-parse --git-dir`.chomp
364
+ File.join(dir, 'network-cache')
365
+ end
366
+
367
+ helper :commits_cache_path do
368
+ dir = `git rev-parse --git-dir`.chomp
369
+ File.join(dir, 'commits-cache')
370
+ end
371
+
372
+ helper :cache_data do |user|
373
+ raw_data = Kernel.open(network_meta_for(user)).read
374
+ File.open( network_cache_path, 'w' ) do |out|
375
+ out.write(raw_data)
376
+ end
377
+ data = JSON.parse(raw_data)
378
+ end
379
+
380
+ helper :cache_expired? do
381
+ return true if !has_cache?
382
+ age = Time.now - File.stat(network_cache_path).mtime
383
+ return true if age > (60 * 60) # 1 hour
384
+ false
385
+ end
386
+
387
+ helper :has_cache? do
388
+ File.file?(network_cache_path)
389
+ end
390
+
391
+ helper :has_commits_cache? do
392
+ File.file?(commits_cache_path)
393
+ end
394
+
395
+ helper :get_cache do
396
+ JSON.parse(File.read(network_cache_path))
397
+ end
@@ -0,0 +1,113 @@
1
+ desc "Project network tools - sub-commands : web [user], list, fetch, commits"
2
+ flags :after => "Only show commits after a certain date"
3
+ flags :before => "Only show commits before a certain date"
4
+ flags :shas => "Only show shas"
5
+ flags :project => "Filter commits on a certain project"
6
+ flags :author => "Filter commits on a email address of author"
7
+ flags :applies => "Filter commits to patches that apply cleanly"
8
+ flags :noapply => "Filter commits to patches that do not apply cleanly"
9
+ flags :nocache => "Do not use the cached network data"
10
+ flags :cache => "Use the network data even if it's expired"
11
+ flags :sort => "How to sort : date(*), branch, author"
12
+ flags :common => "Show common branch point"
13
+ flags :thisbranch => "Look at branches that match the current one"
14
+ flags :limit => "Only look through the first X heads - useful for really large projects"
15
+ command :network do |command, user|
16
+ return if !helper.project
17
+ user ||= helper.owner
18
+
19
+ case command
20
+ when 'web'
21
+ helper.open helper.network_page_for(user)
22
+ when 'list'
23
+ members = helper.get_network_members(user, options)
24
+ members.each do |hsh|
25
+ puts hsh["owner"]["login"]
26
+ end
27
+ when 'fetch'
28
+ # fetch each remote we don't have
29
+ data = helper.get_network_data(user, options)
30
+ data['users'].each do |hsh|
31
+ u = hsh['name']
32
+ GitHub.invoke(:track, u) unless helper.tracking?(u)
33
+ puts "fetching #{u}"
34
+ GitHub.invoke(:fetch_all, u)
35
+ end
36
+ when 'commits'
37
+ # show commits we don't have yet
38
+
39
+ $stderr.puts 'gathering heads'
40
+ cherry = []
41
+
42
+ if helper.cache_commits_data(options)
43
+ ids = []
44
+ data = helper.get_network_data(user, options)
45
+ data['users'].each do |hsh|
46
+ u = hsh['name']
47
+ if options[:thisbranch]
48
+ user_ids = hsh['heads'].map { |a| a['id'] if a['name'] == helper.current_branch }.compact
49
+ else
50
+ user_ids = hsh['heads'].map { |a| a['id'] }
51
+ end
52
+ user_ids.each do |id|
53
+ if !helper.has_commit?(id) && helper.cache_expired?
54
+ GitHub.invoke(:track, u) unless helper.tracking?(u)
55
+ puts "fetching #{u}"
56
+ GitHub.invoke(:fetch_all, u)
57
+ end
58
+ end
59
+ ids += user_ids
60
+ end
61
+ ids.uniq!
62
+
63
+ $stderr.puts 'has heads'
64
+
65
+ # check that we have all these shas locally
66
+ local_heads = helper.local_heads
67
+ local_heads_not = local_heads.map { |a| "^#{a}"}
68
+ looking_for = (ids - local_heads) + local_heads_not
69
+ commits = helper.get_commits(looking_for)
70
+
71
+ $stderr.puts 'ID SIZE:' + ids.size.to_s
72
+
73
+ ignores = helper.ignore_sha_array
74
+
75
+ ids.each do |id|
76
+ next if ignores[id] || !commits.assoc(id)
77
+ cherries = helper.get_cherry(id)
78
+ cherries = helper.remove_ignored(cherries, ignores)
79
+ cherry += cherries
80
+ helper.ignore_shas([id]) if cherries.size == 0
81
+ $stderr.puts "checking head #{id} : #{cherry.size.to_s}"
82
+ break if options[:limit] && cherry.size > options[:limit].to_i
83
+ end
84
+ end
85
+
86
+ if cherry.size > 0 || !helper.cache_commits_data(options)
87
+ helper.print_network_cherry_help if !options[:shas]
88
+
89
+ if helper.cache_commits_data(options)
90
+ $stderr.puts "caching..."
91
+ $stderr.puts "commits: " + cherry.size.to_s
92
+ our_commits = cherry.map { |item| c = commits.assoc(item[1]); [item, c] if c }
93
+ our_commits.delete_if { |item| item == nil }
94
+ helper.cache_commits(our_commits)
95
+ else
96
+ $stderr.puts "using cached..."
97
+ our_commits = helper.commits_cache
98
+ end
99
+
100
+ helper.print_commits(our_commits, options)
101
+ else
102
+ puts "no unapplied commits"
103
+ end
104
+ else
105
+ helper.print_network_help
106
+ end
107
+ end
108
+
109
+ desc "Ignore a SHA (from 'github network commits')"
110
+ command :ignore do |sha|
111
+ commits = helper.resolve_commits(sha)
112
+ helper.ignore_shas(commits) # add to .git/ignore-shas file
113
+ end
@@ -0,0 +1,129 @@
1
+ require 'fileutils'
2
+
3
+ if RUBY_PLATFORM =~ /mswin|mingw/
4
+ begin
5
+ require 'win32/open3'
6
+ rescue LoadError
7
+ warn "You must 'gem install win32-open3' to use the github command on Windows"
8
+ exit 1
9
+ end
10
+ else
11
+ require 'open3'
12
+ end
13
+
14
+ module GitHub
15
+ class Command
16
+ include FileUtils
17
+
18
+ def initialize(block)
19
+ (class << self;self end).send :define_method, :command, &block
20
+ end
21
+
22
+ def call(*args)
23
+ arity = method(:command).arity
24
+ args << nil while args.size < arity
25
+ send :command, *args
26
+ end
27
+
28
+ def helper
29
+ @helper ||= Helper.new
30
+ end
31
+
32
+ def options
33
+ GitHub.options
34
+ end
35
+
36
+ def pgit(*command)
37
+ puts git(*command)
38
+ end
39
+
40
+ def git(command)
41
+ run :sh, command
42
+ end
43
+
44
+ def git_exec(command)
45
+ run :exec, command
46
+ end
47
+
48
+ def run(method, command)
49
+ if command.is_a? Array
50
+ command = [ 'git', command ].flatten
51
+ GitHub.learn command.join(' ')
52
+ else
53
+ command = 'git ' + command
54
+ GitHub.learn command
55
+ end
56
+
57
+ send method, *command
58
+ end
59
+
60
+ def sh(*command)
61
+ Shell.new(*command).run
62
+ end
63
+
64
+ def die(message)
65
+ puts "=> #{message}"
66
+ exit!
67
+ end
68
+
69
+ def github_user
70
+ git("config --get github.user")
71
+ end
72
+
73
+ def github_token
74
+ git("config --get github.token")
75
+ end
76
+
77
+ def shell_user
78
+ ENV['USER']
79
+ end
80
+
81
+ def current_user?(user)
82
+ user == github_user || user == shell_user
83
+ end
84
+
85
+ class Shell < String
86
+ attr_reader :error
87
+ attr_reader :out
88
+
89
+ def initialize(*command)
90
+ @command = command
91
+ end
92
+
93
+ def run
94
+ GitHub.debug "sh: #{command}"
95
+ _, out, err = Open3.popen3(*@command)
96
+
97
+ out = out.read.strip
98
+ err = err.read.strip
99
+
100
+ replace @error = err if err.any?
101
+ replace @out = out if out.any?
102
+
103
+ self
104
+ end
105
+
106
+ def command
107
+ @command.join(' ')
108
+ end
109
+
110
+ def error?
111
+ !!@error
112
+ end
113
+
114
+ def out?
115
+ !!@out
116
+ end
117
+ end
118
+ end
119
+
120
+ class GitCommand < Command
121
+ def initialize(name)
122
+ @name = name
123
+ end
124
+
125
+ def command(*args)
126
+ git_exec [ @name, args ]
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,39 @@
1
+ # define #try
2
+ class Object
3
+ def try
4
+ self
5
+ end
6
+ end
7
+
8
+ class NilClass
9
+ klass = Class.new
10
+ klass.class_eval do
11
+ instance_methods.each { |meth| undef_method meth.to_sym unless meth =~ /^__(id|send)__$/ }
12
+ def method_missing(*args)
13
+ self
14
+ end
15
+ end
16
+ NilProxy = klass.new
17
+ def try
18
+ NilProxy
19
+ end
20
+ end
21
+
22
+ # define #tap
23
+ class Object
24
+ def tap(&block)
25
+ block.call(self)
26
+ self
27
+ end
28
+ end
29
+
30
+ # cute
31
+ module Color
32
+ COLORS = { :clear => 0, :red => 31, :green => 32, :yellow => 33 }
33
+ def self.method_missing(color_name, *args)
34
+ color(color_name) + args.first + color(:clear)
35
+ end
36
+ def self.color(color)
37
+ "\e[#{COLORS[color.to_sym]}m"
38
+ end
39
+ end
@@ -0,0 +1,4 @@
1
+ module GitHub
2
+ class Helper
3
+ end
4
+ end