wfarr-github 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +41 -0
- data/LICENSE +18 -0
- data/Manifest +38 -0
- data/README.md +187 -0
- data/Rakefile +44 -0
- data/bin/gh +8 -0
- data/bin/github +8 -0
- data/github.gemspec +38 -0
- data/lib/commands/commands.rb +249 -0
- data/lib/commands/helpers.rb +486 -0
- data/lib/commands/issues.rb +17 -0
- data/lib/commands/network.rb +110 -0
- data/lib/github.rb +185 -0
- data/lib/github/command.rb +131 -0
- data/lib/github/extensions.rb +39 -0
- data/lib/github/helper.rb +4 -0
- data/lib/github/ui.rb +19 -0
- data/setup.rb +1551 -0
- data/spec/command_spec.rb +82 -0
- data/spec/commands/command_browse_spec.rb +36 -0
- data/spec/commands/command_clone_spec.rb +87 -0
- data/spec/commands/command_create-from-local_spec.rb +7 -0
- data/spec/commands/command_fetch_spec.rb +56 -0
- data/spec/commands/command_fork_spec.rb +44 -0
- data/spec/commands/command_helper.rb +170 -0
- data/spec/commands/command_home_spec.rb +20 -0
- data/spec/commands/command_info_spec.rb +23 -0
- data/spec/commands/command_issues_spec.rb +97 -0
- data/spec/commands/command_network_spec.rb +30 -0
- data/spec/commands/command_pull-request_spec.rb +51 -0
- data/spec/commands/command_pull_spec.rb +82 -0
- data/spec/commands/command_search_spec.rb +34 -0
- data/spec/commands/command_track_spec.rb +82 -0
- data/spec/commands_spec.rb +49 -0
- data/spec/extensions_spec.rb +36 -0
- data/spec/github_spec.rb +85 -0
- data/spec/helper_spec.rb +368 -0
- data/spec/spec_helper.rb +165 -0
- data/spec/windoze_spec.rb +38 -0
- metadata +128 -0
@@ -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
|