github 0.1.1 → 0.4.0

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.
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