github 0.1.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +37 -0
- data/Manifest +33 -12
- data/README.md +187 -0
- data/Rakefile +44 -0
- data/bin/gh +8 -0
- data/bin/github +4 -1
- data/github.gemspec +29 -34
- 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 +117 -29
- data/lib/github/command.rb +69 -14
- data/lib/github/extensions.rb +39 -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 +160 -4
- data/spec/windoze_spec.rb +38 -0
- metadata +114 -47
- data/README +0 -49
- data/commands/commands.rb +0 -54
- data/commands/helpers.rb +0 -79
- data/spec/helpers/owner_spec.rb +0 -12
- data/spec/helpers/project_spec.rb +0 -12
- data/spec/helpers/public_url_for_spec.rb +0 -12
- data/spec/helpers/repo_for_spec.rb +0 -12
- data/spec/helpers/user_and_repo_from_spec.rb +0 -15
- data/spec/helpers/user_for_spec.rb +0 -12
@@ -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
CHANGED
@@ -1,32 +1,57 @@
|
|
1
1
|
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'github/extensions'
|
2
3
|
require 'github/command'
|
3
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'
|
4
12
|
|
5
13
|
##
|
6
14
|
# Starting simple.
|
7
15
|
#
|
8
16
|
# $ github <command> <args>
|
9
17
|
#
|
10
|
-
# GitHub.
|
11
|
-
# whatever
|
18
|
+
# GitHub.command <command> do |*args|
|
19
|
+
# whatever
|
12
20
|
# end
|
13
21
|
#
|
14
|
-
# We'll probably want to use the `choice` gem for concise, tasty DSL
|
15
|
-
# arg parsing action.
|
16
|
-
#
|
17
22
|
|
18
23
|
module GitHub
|
19
24
|
extend self
|
20
25
|
|
21
|
-
BasePath = File.expand_path(File.dirname(__FILE__)
|
26
|
+
BasePath = File.expand_path(File.dirname(__FILE__))
|
22
27
|
|
23
|
-
def
|
28
|
+
def command(command, options = {}, &block)
|
29
|
+
command = command.to_s
|
24
30
|
debug "Registered `#{command}`"
|
25
|
-
|
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
|
26
45
|
end
|
27
46
|
|
28
|
-
def
|
29
|
-
|
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
|
30
55
|
end
|
31
56
|
|
32
57
|
def helper(command, &block)
|
@@ -35,18 +60,27 @@ module GitHub
|
|
35
60
|
end
|
36
61
|
|
37
62
|
def activate(args)
|
63
|
+
@@original_args = args.clone
|
38
64
|
@options = parse_options(args)
|
39
|
-
|
40
|
-
|
65
|
+
@debug = @options.delete(:debug)
|
66
|
+
@learn = @options.delete(:learn)
|
67
|
+
Dir[BasePath + '/commands/*.rb'].each do |command|
|
68
|
+
load command
|
69
|
+
end
|
41
70
|
invoke(args.shift, *args)
|
42
71
|
end
|
43
72
|
|
44
73
|
def invoke(command, *args)
|
45
|
-
block =
|
74
|
+
block = find_command(command)
|
46
75
|
debug "Invoking `#{command}`"
|
47
76
|
block.call(*args)
|
48
77
|
end
|
49
78
|
|
79
|
+
def find_command(name)
|
80
|
+
name = name.to_s
|
81
|
+
commands[name] || (commands[name] = GitCommand.new(name)) || commands['default']
|
82
|
+
end
|
83
|
+
|
50
84
|
def commands
|
51
85
|
@commands ||= {}
|
52
86
|
end
|
@@ -55,43 +89,97 @@ module GitHub
|
|
55
89
|
@descriptions ||= {}
|
56
90
|
end
|
57
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
|
+
|
58
100
|
def options
|
59
101
|
@options
|
60
102
|
end
|
61
103
|
|
104
|
+
def original_args
|
105
|
+
@@original_args ||= []
|
106
|
+
end
|
107
|
+
|
62
108
|
def parse_options(args)
|
63
|
-
|
64
|
-
args.inject({}) do |memo, arg|
|
65
|
-
|
66
|
-
|
109
|
+
idx = 0
|
110
|
+
args.clone.inject({}) do |memo, arg|
|
111
|
+
case arg
|
112
|
+
when /^--(.+?)=(.*)/
|
113
|
+
args.delete_at(idx)
|
67
114
|
memo.merge($1.to_sym => $2)
|
68
|
-
|
69
|
-
args.
|
115
|
+
when /^--(.+)/
|
116
|
+
args.delete_at(idx)
|
70
117
|
memo.merge($1.to_sym => true)
|
118
|
+
when "--"
|
119
|
+
args.delete_at(idx)
|
120
|
+
return memo
|
71
121
|
else
|
122
|
+
idx += 1
|
72
123
|
memo
|
73
124
|
end
|
74
125
|
end
|
75
126
|
end
|
76
127
|
|
77
|
-
def load(file)
|
78
|
-
file[0] == ?/ ? super : super(BasePath + "/commands/#{file}")
|
79
|
-
end
|
80
|
-
|
81
128
|
def debug(*messages)
|
82
129
|
puts *messages.map { |m| "== #{m}" } if debug?
|
83
130
|
end
|
84
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
|
+
|
85
144
|
def debug?
|
86
145
|
!!@debug
|
87
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
|
88
153
|
end
|
89
154
|
|
90
|
-
GitHub.
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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 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
|
95
183
|
end
|
96
|
-
puts
|
184
|
+
puts message.map { |m| m.gsub(/\n$/,'') }.join("\n") + "\n"
|
97
185
|
end
|
data/lib/github/command.rb
CHANGED
@@ -1,7 +1,20 @@
|
|
1
|
-
require '
|
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
|
2
13
|
|
3
14
|
module GitHub
|
4
15
|
class Command
|
16
|
+
include FileUtils
|
17
|
+
|
5
18
|
def initialize(block)
|
6
19
|
(class << self;self end).send :define_method, :command, &block
|
7
20
|
end
|
@@ -11,7 +24,7 @@ module GitHub
|
|
11
24
|
args << nil while args.size < arity
|
12
25
|
send :command, *args
|
13
26
|
end
|
14
|
-
|
27
|
+
|
15
28
|
def helper
|
16
29
|
@helper ||= Helper.new
|
17
30
|
end
|
@@ -24,12 +37,24 @@ module GitHub
|
|
24
37
|
puts git(*command)
|
25
38
|
end
|
26
39
|
|
27
|
-
def git(
|
28
|
-
sh
|
40
|
+
def git(command)
|
41
|
+
run :sh, command
|
29
42
|
end
|
30
43
|
|
31
|
-
def git_exec(
|
32
|
-
exec
|
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
|
33
58
|
end
|
34
59
|
|
35
60
|
def sh(*command)
|
@@ -41,23 +66,43 @@ module GitHub
|
|
41
66
|
exit!
|
42
67
|
end
|
43
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
|
+
|
44
85
|
class Shell < String
|
86
|
+
attr_reader :error
|
87
|
+
attr_reader :out
|
88
|
+
|
45
89
|
def initialize(*command)
|
46
90
|
@command = command
|
47
91
|
end
|
48
92
|
|
49
93
|
def run
|
50
94
|
GitHub.debug "sh: #{command}"
|
51
|
-
_, out, err = Open3.popen3(*@command)
|
52
|
-
|
53
|
-
out = out.read.strip
|
54
|
-
err = err.read.strip
|
55
95
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
96
|
+
out = err = nil
|
97
|
+
Open3.popen3(*@command) do |_, pout, perr|
|
98
|
+
out = pout.read.strip
|
99
|
+
err = perr.read.strip
|
60
100
|
end
|
101
|
+
|
102
|
+
replace @error = err if err.any?
|
103
|
+
replace @out = out if out.any?
|
104
|
+
|
105
|
+
self
|
61
106
|
end
|
62
107
|
|
63
108
|
def command
|
@@ -73,4 +118,14 @@ module GitHub
|
|
73
118
|
end
|
74
119
|
end
|
75
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
|
76
131
|
end
|