wfarr-github 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ desc "Project issues tools - sub-commands : open [user], closed [user]"
2
+ flags :after => "Only show issues updated after a certain date"
3
+ flags :label => "Only show issues with a certain label"
4
+ command :issues do |command, user|
5
+ return if !helper.project
6
+ user ||= helper.owner
7
+
8
+ case command
9
+ when 'open', 'closed'
10
+ report = YAML.load(open(@helper.list_issues_for(user, command)))
11
+ @helper.print_issues(report['issues'], options)
12
+ when 'web'
13
+ helper.open helper.issues_page_for(user)
14
+ else
15
+ helper.print_issues_help
16
+ end
17
+ end
@@ -0,0 +1,110 @@
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
+ helper.network_members(user, options).each { |user| puts user }
24
+ when 'fetch'
25
+ # fetch each remote we don't have
26
+ data = helper.get_network_data(user, options)
27
+ data['users'].each do |hsh|
28
+ u = hsh['name']
29
+ GitHub.invoke(:track, u) unless helper.tracking?(u)
30
+ puts "fetching #{u}"
31
+ GitHub.invoke(:fetch_all, u)
32
+ end
33
+ when 'commits'
34
+ # show commits we don't have yet
35
+
36
+ $stderr.puts 'gathering heads'
37
+ cherry = []
38
+
39
+ if helper.cache_commits_data(options)
40
+ ids = []
41
+ data = helper.get_network_data(user, options)
42
+ data['users'].each do |hsh|
43
+ u = hsh['name']
44
+ if options[:thisbranch]
45
+ user_ids = hsh['heads'].map { |a| a['id'] if a['name'] == helper.current_branch }.compact
46
+ else
47
+ user_ids = hsh['heads'].map { |a| a['id'] }
48
+ end
49
+ user_ids.each do |id|
50
+ if !helper.has_commit?(id) && helper.cache_expired?
51
+ GitHub.invoke(:track, u) unless helper.tracking?(u)
52
+ puts "fetching #{u}"
53
+ GitHub.invoke(:fetch_all, u)
54
+ end
55
+ end
56
+ ids += user_ids
57
+ end
58
+ ids.uniq!
59
+
60
+ $stderr.puts 'has heads'
61
+
62
+ # check that we have all these shas locally
63
+ local_heads = helper.local_heads
64
+ local_heads_not = local_heads.map { |a| "^#{a}"}
65
+ looking_for = (ids - local_heads) + local_heads_not
66
+ commits = helper.get_commits(looking_for)
67
+
68
+ $stderr.puts 'ID SIZE:' + ids.size.to_s
69
+
70
+ ignores = helper.ignore_sha_array
71
+
72
+ ids.each do |id|
73
+ next if ignores[id] || !commits.assoc(id)
74
+ cherries = helper.get_cherry(id)
75
+ cherries = helper.remove_ignored(cherries, ignores)
76
+ cherry += cherries
77
+ helper.ignore_shas([id]) if cherries.size == 0
78
+ $stderr.puts "checking head #{id} : #{cherry.size.to_s}"
79
+ break if options[:limit] && cherry.size > options[:limit].to_i
80
+ end
81
+ end
82
+
83
+ if cherry.size > 0 || !helper.cache_commits_data(options)
84
+ helper.print_network_cherry_help if !options[:shas]
85
+
86
+ if helper.cache_commits_data(options)
87
+ $stderr.puts "caching..."
88
+ $stderr.puts "commits: " + cherry.size.to_s
89
+ our_commits = cherry.map { |item| c = commits.assoc(item[1]); [item, c] if c }
90
+ our_commits.delete_if { |item| item == nil }
91
+ helper.cache_commits(our_commits)
92
+ else
93
+ $stderr.puts "using cached..."
94
+ our_commits = helper.commits_cache
95
+ end
96
+
97
+ helper.print_commits(our_commits, options)
98
+ else
99
+ puts "no unapplied commits"
100
+ end
101
+ else
102
+ helper.print_network_help
103
+ end
104
+ end
105
+
106
+ desc "Ignore a SHA (from 'github network commits')"
107
+ command :ignore do |sha|
108
+ commits = helper.resolve_commits(sha)
109
+ helper.ignore_shas(commits) # add to .git/ignore-shas file
110
+ end
data/lib/github.rb ADDED
@@ -0,0 +1,185 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+ require 'github/extensions'
3
+ require 'github/command'
4
+ require 'github/helper'
5
+ require 'github/ui'
6
+ require 'fileutils'
7
+ require 'rubygems'
8
+ require 'open-uri'
9
+ require 'json'
10
+ require 'yaml'
11
+ require 'text/format'
12
+
13
+ ##
14
+ # Starting simple.
15
+ #
16
+ # $ github <command> <args>
17
+ #
18
+ # GitHub.command <command> do |*args|
19
+ # whatever
20
+ # end
21
+ #
22
+
23
+ module GitHub
24
+ extend self
25
+
26
+ BasePath = File.expand_path(File.dirname(__FILE__))
27
+
28
+ def command(command, options = {}, &block)
29
+ command = command.to_s
30
+ debug "Registered `#{command}`"
31
+ descriptions[command] = @next_description if @next_description
32
+ @next_description = nil
33
+ flag_descriptions[command].update @next_flags if @next_flags
34
+ usage_descriptions[command] = @next_usage if @next_usage
35
+ @next_flags = nil
36
+ @next_usage = []
37
+ commands[command] = Command.new(block)
38
+ Array(options[:alias] || options[:aliases]).each do |command_alias|
39
+ commands[command_alias.to_s] = commands[command.to_s]
40
+ end
41
+ end
42
+
43
+ def desc(str)
44
+ @next_description = str
45
+ end
46
+
47
+ def flags(hash)
48
+ @next_flags ||= {}
49
+ @next_flags.update hash
50
+ end
51
+
52
+ def usage(string)
53
+ @next_usage ||= []
54
+ @next_usage << string
55
+ end
56
+
57
+ def helper(command, &block)
58
+ debug "Helper'd `#{command}`"
59
+ Helper.send :define_method, command, &block
60
+ end
61
+
62
+ def activate(args)
63
+ @@original_args = args.clone
64
+ @options = parse_options(args)
65
+ @debug = @options.delete(:debug)
66
+ @learn = @options.delete(:learn)
67
+ Dir[BasePath + '/commands/*.rb'].each do |command|
68
+ load command
69
+ end
70
+ invoke(args.shift, *args)
71
+ end
72
+
73
+ def invoke(command, *args)
74
+ block = find_command(command)
75
+ debug "Invoking `#{command}`"
76
+ block.call(*args)
77
+ end
78
+
79
+ def find_command(name)
80
+ name = name.to_s
81
+ commands[name] || (commands[name] = GitCommand.new(name)) || commands['default']
82
+ end
83
+
84
+ def commands
85
+ @commands ||= {}
86
+ end
87
+
88
+ def descriptions
89
+ @descriptions ||= {}
90
+ end
91
+
92
+ def flag_descriptions
93
+ @flagdescs ||= Hash.new { |h, k| h[k] = {} }
94
+ end
95
+
96
+ def usage_descriptions
97
+ @usage_descriptions ||= Hash.new { |h, k| h[k] = [] }
98
+ end
99
+
100
+ def options
101
+ @options
102
+ end
103
+
104
+ def original_args
105
+ @@original_args ||= []
106
+ end
107
+
108
+ def parse_options(args)
109
+ idx = 0
110
+ args.clone.inject({}) do |memo, arg|
111
+ case arg
112
+ when /^--(.+?)=(.*)/
113
+ args.delete_at(idx)
114
+ memo.merge($1.to_sym => $2)
115
+ when /^--(.+)/
116
+ args.delete_at(idx)
117
+ memo.merge($1.to_sym => true)
118
+ when "--"
119
+ args.delete_at(idx)
120
+ return memo
121
+ else
122
+ idx += 1
123
+ memo
124
+ end
125
+ end
126
+ end
127
+
128
+ def debug(*messages)
129
+ puts *messages.map { |m| "== #{m}" } if debug?
130
+ end
131
+
132
+ def learn(message)
133
+ if learn?
134
+ puts "== " + Color.yellow(message)
135
+ else
136
+ debug(message)
137
+ end
138
+ end
139
+
140
+ def learn?
141
+ !!@learn
142
+ end
143
+
144
+ def debug?
145
+ !!@debug
146
+ end
147
+
148
+ def load(file)
149
+ file[0] =~ /^\// ? path = file : path = BasePath + "/commands/#{File.basename(file)}"
150
+ data = File.read(path)
151
+ GitHub.module_eval data, path
152
+ end
153
+ end
154
+
155
+ GitHub.command :default, :aliases => ['', '-h', 'help', '-help', '--help'] do
156
+ message = []
157
+ message << "Usage: github command <space separated arguments>"
158
+ message << "Available commands:"
159
+ longest = GitHub.descriptions.map { |d,| d.to_s.size }.max
160
+ indent = longest + 6 # length of " " + " => "
161
+ fmt = Text::Format.new(
162
+ :first_indent => indent,
163
+ :body_indent => indent,
164
+ :columns => 79 # be a little more lenient than the default
165
+ )
166
+ GitHub.descriptions.sort {|a,b| a.to_s <=> b.to_s }.each do |command, desc|
167
+ cmdstr = "%-#{longest}s" % command
168
+ desc = fmt.format(desc).strip # strip to eat first "indent"
169
+ message << " #{cmdstr} => #{desc}"
170
+ flongest = GitHub.flag_descriptions[command].map { |d,| "--#{d}".size }.max
171
+ ffmt = fmt.clone
172
+ ffmt.body_indent += 2 # length of "% " and/or "--"
173
+ GitHub.usage_descriptions[command].each do |usage_descriptions|
174
+ usage_descriptions.each_line do |usage|
175
+ usage_str = "%% %-#{flongest}s" % usage
176
+ message << ffmt.format(usage_str)
177
+ end
178
+ end
179
+ GitHub.flag_descriptions[command].sort {|a,b| a.to_s <=> b.to_s }.each do |flag, fdesc|
180
+ flagstr = "#{" " * longest} %-#{flongest}s" % "--#{flag}"
181
+ message << ffmt.format(" #{flagstr}: #{fdesc}")
182
+ end
183
+ end
184
+ puts message.map { |m| m.gsub(/\n$/,'') }.join("\n") + "\n"
185
+ end
@@ -0,0 +1,131 @@
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
+
96
+ out = err = nil
97
+ Open3.popen3(*@command) do |_, pout, perr|
98
+ out = pout.read.strip
99
+ err = perr.read.strip
100
+ end
101
+
102
+ replace @error = err if !err.empty?
103
+ replace @out = out if !out.empty?
104
+
105
+ self
106
+ end
107
+
108
+ def command
109
+ @command.join(' ')
110
+ end
111
+
112
+ def error?
113
+ !!@error
114
+ end
115
+
116
+ def out?
117
+ !!@out
118
+ end
119
+ end
120
+ end
121
+
122
+ class GitCommand < Command
123
+ def initialize(name)
124
+ @name = name
125
+ end
126
+
127
+ def command(*args)
128
+ git_exec [ @name, args ]
129
+ end
130
+ end
131
+ 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)__|object_id)$/ }
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