github 0.1.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/History.txt +37 -0
  2. data/Manifest +33 -12
  3. data/README.md +187 -0
  4. data/Rakefile +44 -0
  5. data/bin/gh +8 -0
  6. data/bin/github +4 -1
  7. data/github.gemspec +29 -34
  8. data/lib/commands/commands.rb +249 -0
  9. data/lib/commands/helpers.rb +486 -0
  10. data/lib/commands/issues.rb +17 -0
  11. data/lib/commands/network.rb +110 -0
  12. data/lib/github.rb +117 -29
  13. data/lib/github/command.rb +69 -14
  14. data/lib/github/extensions.rb +39 -0
  15. data/lib/github/ui.rb +19 -0
  16. data/setup.rb +1551 -0
  17. data/spec/command_spec.rb +82 -0
  18. data/spec/commands/command_browse_spec.rb +36 -0
  19. data/spec/commands/command_clone_spec.rb +87 -0
  20. data/spec/commands/command_create-from-local_spec.rb +7 -0
  21. data/spec/commands/command_fetch_spec.rb +56 -0
  22. data/spec/commands/command_fork_spec.rb +44 -0
  23. data/spec/commands/command_helper.rb +170 -0
  24. data/spec/commands/command_home_spec.rb +20 -0
  25. data/spec/commands/command_info_spec.rb +23 -0
  26. data/spec/commands/command_issues_spec.rb +97 -0
  27. data/spec/commands/command_network_spec.rb +30 -0
  28. data/spec/commands/command_pull-request_spec.rb +51 -0
  29. data/spec/commands/command_pull_spec.rb +82 -0
  30. data/spec/commands/command_search_spec.rb +34 -0
  31. data/spec/commands/command_track_spec.rb +82 -0
  32. data/spec/commands_spec.rb +49 -0
  33. data/spec/extensions_spec.rb +36 -0
  34. data/spec/github_spec.rb +85 -0
  35. data/spec/helper_spec.rb +368 -0
  36. data/spec/spec_helper.rb +160 -4
  37. data/spec/windoze_spec.rb +38 -0
  38. metadata +114 -47
  39. data/README +0 -49
  40. data/commands/commands.rb +0 -54
  41. data/commands/helpers.rb +0 -79
  42. data/spec/helpers/owner_spec.rb +0 -12
  43. data/spec/helpers/project_spec.rb +0 -12
  44. data/spec/helpers/public_url_for_spec.rb +0 -12
  45. data/spec/helpers/repo_for_spec.rb +0 -12
  46. data/spec/helpers/user_and_repo_from_spec.rb +0 -15
  47. 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.register <command> do |*args|
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 register(command, &block)
28
+ def command(command, options = {}, &block)
29
+ command = command.to_s
24
30
  debug "Registered `#{command}`"
25
- commands[command.to_s] = Command.new(block)
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 describe(hash)
29
- descriptions.update hash
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
- load 'helpers.rb'
40
- load 'commands.rb'
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 = commands[command.to_s] || commands['default']
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
- @debug = args.delete('--debug')
64
- args.inject({}) do |memo, arg|
65
- if arg =~ /^--([^=]+)=(.+)/
66
- args.delete(arg)
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
- elsif arg =~ /^--(.+)/
69
- args.delete(arg)
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.register :default do
91
- puts "Usage: github command <space separated arguments>", ''
92
- puts "Available commands:", ''
93
- GitHub.descriptions.each do |command, desc|
94
- puts " #{command} => #{desc}"
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
@@ -1,7 +1,20 @@
1
- require 'open3'
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(*command)
28
- sh ['git', command].flatten.join(' ')
40
+ def git(command)
41
+ run :sh, command
29
42
  end
30
43
 
31
- def git_exec(*command)
32
- exec ['git', command].flatten.join(' ')
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
- if out.any?
57
- replace @out = out
58
- elsif err.any?
59
- replace @error = err
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