tekkub-github 0.3.3
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.
- data/LICENSE +18 -0
- data/Manifest +22 -0
- data/README +164 -0
- data/bin/gh +5 -0
- data/bin/github +5 -0
- data/commands/commands.rb +147 -0
- data/commands/helpers.rb +376 -0
- data/commands/network.rb +113 -0
- data/github-gem.gemspec +26 -0
- data/lib/github.rb +172 -0
- data/lib/github/command.rb +121 -0
- data/lib/github/extensions.rb +39 -0
- data/lib/github/helper.rb +4 -0
- data/spec/command_spec.rb +82 -0
- data/spec/extensions_spec.rb +36 -0
- data/spec/github_spec.rb +85 -0
- data/spec/helper_spec.rb +280 -0
- data/spec/spec_helper.rb +138 -0
- data/spec/ui_spec.rb +604 -0
- data/spec/windoze_spec.rb +36 -0
- metadata +93 -0
data/commands/network.rb
ADDED
@@ -0,0 +1,113 @@
|
|
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
|
+
data = helper.get_network_data(user, options)
|
24
|
+
data['users'].each do |hsh|
|
25
|
+
puts [ hsh['name'].ljust(20), hsh['heads'].map {|a| a['name']}.uniq.join(', ') ].join(' ')
|
26
|
+
end
|
27
|
+
when 'fetch'
|
28
|
+
# fetch each remote we don't have
|
29
|
+
data = helper.get_network_data(user, options)
|
30
|
+
data['users'].each do |hsh|
|
31
|
+
u = hsh['name']
|
32
|
+
GitHub.invoke(:track, u) unless helper.tracking?(u)
|
33
|
+
puts "fetching #{u}"
|
34
|
+
GitHub.invoke(:fetch_all, u)
|
35
|
+
end
|
36
|
+
when 'commits'
|
37
|
+
# show commits we don't have yet
|
38
|
+
|
39
|
+
$stderr.puts 'gathering heads'
|
40
|
+
cherry = []
|
41
|
+
|
42
|
+
if helper.cache_commits_data(options)
|
43
|
+
ids = []
|
44
|
+
data = helper.get_network_data(user, options)
|
45
|
+
data['users'].each do |hsh|
|
46
|
+
u = hsh['name']
|
47
|
+
if options[:thisbranch]
|
48
|
+
user_ids = hsh['heads'].map { |a| a['id'] if a['name'] == helper.current_branch }.compact
|
49
|
+
else
|
50
|
+
user_ids = hsh['heads'].map { |a| a['id'] }
|
51
|
+
end
|
52
|
+
user_ids.each do |id|
|
53
|
+
if !helper.has_commit?(id) && helper.cache_expired?
|
54
|
+
GitHub.invoke(:track, u) unless helper.tracking?(u)
|
55
|
+
puts "fetching #{u}"
|
56
|
+
GitHub.invoke(:fetch_all, u)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
ids += user_ids
|
60
|
+
end
|
61
|
+
ids.uniq!
|
62
|
+
|
63
|
+
$stderr.puts 'has heads'
|
64
|
+
|
65
|
+
# check that we have all these shas locally
|
66
|
+
local_heads = helper.local_heads
|
67
|
+
local_heads_not = local_heads.map { |a| "^#{a}"}
|
68
|
+
looking_for = (ids - local_heads) + local_heads_not
|
69
|
+
commits = helper.get_commits(looking_for)
|
70
|
+
|
71
|
+
$stderr.puts 'ID SIZE:' + ids.size.to_s
|
72
|
+
|
73
|
+
ignores = helper.ignore_sha_array
|
74
|
+
|
75
|
+
ids.each do |id|
|
76
|
+
next if ignores[id] || !commits.assoc(id)
|
77
|
+
cherries = helper.get_cherry(id)
|
78
|
+
cherries = helper.remove_ignored(cherries, ignores)
|
79
|
+
cherry += cherries
|
80
|
+
helper.ignore_shas([id]) if cherries.size == 0
|
81
|
+
$stderr.puts "checking head #{id} : #{cherry.size.to_s}"
|
82
|
+
break if options[:limit] && cherry.size > options[:limit].to_i
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if cherry.size > 0 || !helper.cache_commits_data(options)
|
87
|
+
helper.print_network_cherry_help if !options[:shas]
|
88
|
+
|
89
|
+
if helper.cache_commits_data(options)
|
90
|
+
$stderr.puts "caching..."
|
91
|
+
$stderr.puts "commits: " + cherry.size.to_s
|
92
|
+
our_commits = cherry.map { |item| c = commits.assoc(item[1]); [item, c] if c }
|
93
|
+
our_commits.delete_if { |item| item == nil }
|
94
|
+
helper.cache_commits(our_commits)
|
95
|
+
else
|
96
|
+
$stderr.puts "using cached..."
|
97
|
+
our_commits = helper.commits_cache
|
98
|
+
end
|
99
|
+
|
100
|
+
helper.print_commits(our_commits, options)
|
101
|
+
else
|
102
|
+
puts "no unapplied commits"
|
103
|
+
end
|
104
|
+
else
|
105
|
+
helper.print_network_help
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
desc "Ignore a SHA (from 'github network commits')"
|
110
|
+
command :ignore do |sha|
|
111
|
+
commits = helper.resolve_commits(sha)
|
112
|
+
helper.ignore_shas(commits) # add to .git/ignore-shas file
|
113
|
+
end
|
data/github-gem.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "github"
|
3
|
+
s.version = "0.3.3"
|
4
|
+
|
5
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Chris Wanstrath, Kevin Ballard, Scott Chacon"]
|
9
|
+
s.date = %q{2008-05-18}
|
10
|
+
s.default_executable = %q{gh}
|
11
|
+
s.description = %q{The official `github` command line helper for simplifying your GitHub experience.}
|
12
|
+
s.email = %q{chris@ozmm.org}
|
13
|
+
s.executables = ["github", "gh"]
|
14
|
+
s.extra_rdoc_files = ["bin/github", "bin/gh", "lib/github/extensions.rb", "lib/github/command.rb", "lib/github/helper.rb", "lib/github.rb", "LICENSE", "README"]
|
15
|
+
s.files = ["bin/github", "commands/network.rb", "commands/commands.rb", "commands/helpers.rb", "lib/github/extensions.rb", "lib/github/command.rb", "lib/github/helper.rb", "lib/github.rb", "LICENSE", "Manifest", "README", "spec/command_spec.rb", "spec/extensions_spec.rb", "spec/github_spec.rb", "spec/helper_spec.rb", "spec/spec_helper.rb", "spec/ui_spec.rb", "spec/windoze_spec.rb", "github-gem.gemspec"]
|
16
|
+
s.has_rdoc = true
|
17
|
+
s.homepage = %q{http://github.com/}
|
18
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Github", "--main", "README"]
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
s.rubyforge_project = %q{github}
|
21
|
+
s.rubygems_version = %q{1.1.1}
|
22
|
+
s.summary = %q{The official `github` command line helper for simplifying your GitHub experience.}
|
23
|
+
|
24
|
+
# s.add_dependency(%q<launchy>, [">= 0"])
|
25
|
+
s.add_dependency('json_pure', [">= 0"])
|
26
|
+
end
|
data/lib/github.rb
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
$:.unshift File.dirname(__FILE__)
|
2
|
+
require 'github/extensions'
|
3
|
+
require 'github/command'
|
4
|
+
require 'github/helper'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'rubygems'
|
7
|
+
require 'open-uri'
|
8
|
+
require 'json'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
##
|
12
|
+
# Starting simple.
|
13
|
+
#
|
14
|
+
# $ github <command> <args>
|
15
|
+
#
|
16
|
+
# GitHub.command <command> do |*args|
|
17
|
+
# whatever
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
|
21
|
+
module GitHub
|
22
|
+
extend self
|
23
|
+
|
24
|
+
BasePath = File.expand_path(File.dirname(__FILE__) + '/..')
|
25
|
+
|
26
|
+
def command(command, options = {}, &block)
|
27
|
+
debug "Registered `#{command}`"
|
28
|
+
descriptions[command] = @next_description if @next_description
|
29
|
+
@next_description = nil
|
30
|
+
flag_descriptions[command].update @next_flags if @next_flags
|
31
|
+
usage_descriptions[command] = @next_usage if @next_usage
|
32
|
+
@next_flags = nil
|
33
|
+
@next_usage = []
|
34
|
+
commands[command.to_s] = Command.new(block)
|
35
|
+
Array(options[:alias] || options[:aliases]).each do |command_alias|
|
36
|
+
commands[command_alias.to_s] = commands[command.to_s]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def desc(str)
|
41
|
+
@next_description = str
|
42
|
+
end
|
43
|
+
|
44
|
+
def flags(hash)
|
45
|
+
@next_flags ||= {}
|
46
|
+
@next_flags.update hash
|
47
|
+
end
|
48
|
+
|
49
|
+
def usage(string)
|
50
|
+
@next_usage ||= []
|
51
|
+
@next_usage << string
|
52
|
+
end
|
53
|
+
|
54
|
+
def helper(command, &block)
|
55
|
+
debug "Helper'd `#{command}`"
|
56
|
+
Helper.send :define_method, command, &block
|
57
|
+
end
|
58
|
+
|
59
|
+
def activate(args)
|
60
|
+
@@original_args = args.clone
|
61
|
+
@options = parse_options(args)
|
62
|
+
@debug = @options.delete(:debug)
|
63
|
+
@learn = @options.delete(:learn)
|
64
|
+
Dir[BasePath + '/commands/*.rb'].each do |command|
|
65
|
+
load command
|
66
|
+
end
|
67
|
+
invoke(args.shift, *args)
|
68
|
+
end
|
69
|
+
|
70
|
+
def invoke(command, *args)
|
71
|
+
block = find_command(command)
|
72
|
+
debug "Invoking `#{command}`"
|
73
|
+
block.call(*args)
|
74
|
+
end
|
75
|
+
|
76
|
+
def find_command(name)
|
77
|
+
name = name.to_s
|
78
|
+
commands[name] || GitCommand.new(name) || commands['default']
|
79
|
+
end
|
80
|
+
|
81
|
+
def commands
|
82
|
+
@commands ||= {}
|
83
|
+
end
|
84
|
+
|
85
|
+
def descriptions
|
86
|
+
@descriptions ||= {}
|
87
|
+
end
|
88
|
+
|
89
|
+
def flag_descriptions
|
90
|
+
@flagdescs ||= Hash.new { |h, k| h[k] = {} }
|
91
|
+
end
|
92
|
+
|
93
|
+
def usage_descriptions
|
94
|
+
@usage_descriptions ||= Hash.new { |h, k| h[k] = [] }
|
95
|
+
end
|
96
|
+
|
97
|
+
def options
|
98
|
+
@options
|
99
|
+
end
|
100
|
+
|
101
|
+
def original_args
|
102
|
+
@@original_args ||= []
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_options(args)
|
106
|
+
idx = 0
|
107
|
+
args.clone.inject({}) do |memo, arg|
|
108
|
+
case arg
|
109
|
+
when /^--(.+?)=(.*)/
|
110
|
+
args.delete_at(idx)
|
111
|
+
memo.merge($1.to_sym => $2)
|
112
|
+
when /^--(.+)/
|
113
|
+
args.delete_at(idx)
|
114
|
+
memo.merge($1.to_sym => true)
|
115
|
+
when "--"
|
116
|
+
args.delete_at(idx)
|
117
|
+
return memo
|
118
|
+
else
|
119
|
+
idx += 1
|
120
|
+
memo
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def debug(*messages)
|
126
|
+
puts *messages.map { |m| "== #{m}" } if debug?
|
127
|
+
end
|
128
|
+
|
129
|
+
def learn(message)
|
130
|
+
if learn?
|
131
|
+
puts "== " + Color.yellow(message)
|
132
|
+
else
|
133
|
+
debug(message)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def learn?
|
138
|
+
!!@learn
|
139
|
+
end
|
140
|
+
|
141
|
+
def debug?
|
142
|
+
!!@debug
|
143
|
+
end
|
144
|
+
|
145
|
+
def load(file)
|
146
|
+
file[0] == ?/ ? path = file : path = BasePath + "/commands/#{File.basename(file)}"
|
147
|
+
data = File.read(path)
|
148
|
+
GitHub.module_eval data, path
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
GitHub.command :default, :aliases => ['', '-h', 'help', '-help', '--help'] do
|
153
|
+
puts "Usage: github command <space separated arguments>", ''
|
154
|
+
puts "Available commands:", ''
|
155
|
+
longest = GitHub.descriptions.map { |d,| d.to_s.size }.max
|
156
|
+
GitHub.descriptions.sort {|a,b| a.to_s <=> b.to_s }.each do |command, desc|
|
157
|
+
cmdstr = "%-#{longest}s" % command
|
158
|
+
puts " #{cmdstr} => #{desc}"
|
159
|
+
flongest = GitHub.flag_descriptions[command].map { |d,| "--#{d}".size }.max
|
160
|
+
GitHub.usage_descriptions[command].each do |usage_descriptions|
|
161
|
+
usage_descriptions.each do |usage|
|
162
|
+
usage_str = "#{" " * longest} %% %-#{flongest}s" % usage
|
163
|
+
puts usage_str
|
164
|
+
end
|
165
|
+
end
|
166
|
+
GitHub.flag_descriptions[command].each do |flag, fdesc|
|
167
|
+
flagstr = "#{" " * longest} %-#{flongest}s" % "--#{flag}"
|
168
|
+
puts " #{flagstr}: #{fdesc}"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
puts
|
172
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
if RUBY_PLATFORM =~ /mswin|mingw/
|
2
|
+
begin
|
3
|
+
require 'win32/open3'
|
4
|
+
rescue LoadError
|
5
|
+
warn "You must 'gem install win32-open3' to use the github command on Windows"
|
6
|
+
exit 1
|
7
|
+
end
|
8
|
+
else
|
9
|
+
require 'open3'
|
10
|
+
end
|
11
|
+
|
12
|
+
module GitHub
|
13
|
+
class Command
|
14
|
+
def initialize(block)
|
15
|
+
(class << self;self end).send :define_method, :command, &block
|
16
|
+
end
|
17
|
+
|
18
|
+
def call(*args)
|
19
|
+
arity = method(:command).arity
|
20
|
+
args << nil while args.size < arity
|
21
|
+
send :command, *args
|
22
|
+
end
|
23
|
+
|
24
|
+
def helper
|
25
|
+
@helper ||= Helper.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def options
|
29
|
+
GitHub.options
|
30
|
+
end
|
31
|
+
|
32
|
+
def pgit(*command)
|
33
|
+
puts git(*command)
|
34
|
+
end
|
35
|
+
|
36
|
+
def git(command)
|
37
|
+
run :sh, command
|
38
|
+
end
|
39
|
+
|
40
|
+
def git_exec(command)
|
41
|
+
run :exec, command
|
42
|
+
end
|
43
|
+
|
44
|
+
def run(method, command)
|
45
|
+
if command.is_a? Array
|
46
|
+
command = [ 'git', command ].flatten
|
47
|
+
GitHub.learn command.join(' ')
|
48
|
+
else
|
49
|
+
command = 'git ' + command
|
50
|
+
GitHub.learn command
|
51
|
+
end
|
52
|
+
|
53
|
+
send method, *command
|
54
|
+
end
|
55
|
+
|
56
|
+
def sh(*command)
|
57
|
+
Shell.new(*command).run
|
58
|
+
end
|
59
|
+
|
60
|
+
def die(message)
|
61
|
+
puts "=> #{message}"
|
62
|
+
exit!
|
63
|
+
end
|
64
|
+
|
65
|
+
def github_user
|
66
|
+
git("config --get github.user")
|
67
|
+
end
|
68
|
+
|
69
|
+
def shell_user
|
70
|
+
ENV['USER']
|
71
|
+
end
|
72
|
+
|
73
|
+
def current_user?(user)
|
74
|
+
user == github_user || user == shell_user
|
75
|
+
end
|
76
|
+
|
77
|
+
class Shell < String
|
78
|
+
attr_reader :error
|
79
|
+
attr_reader :out
|
80
|
+
|
81
|
+
def initialize(*command)
|
82
|
+
@command = command
|
83
|
+
end
|
84
|
+
|
85
|
+
def run
|
86
|
+
GitHub.debug "sh: #{command}"
|
87
|
+
_, out, err = Open3.popen3(*@command)
|
88
|
+
|
89
|
+
out = out.read.strip
|
90
|
+
err = err.read.strip
|
91
|
+
|
92
|
+
replace @error = err if err.any?
|
93
|
+
replace @out = out if out.any?
|
94
|
+
|
95
|
+
self
|
96
|
+
end
|
97
|
+
|
98
|
+
def command
|
99
|
+
@command.join(' ')
|
100
|
+
end
|
101
|
+
|
102
|
+
def error?
|
103
|
+
!!@error
|
104
|
+
end
|
105
|
+
|
106
|
+
def out?
|
107
|
+
!!@out
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
class GitCommand < Command
|
113
|
+
def initialize(name)
|
114
|
+
@name = name
|
115
|
+
end
|
116
|
+
|
117
|
+
def command(*args)
|
118
|
+
git_exec [ @name, args ]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
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)__$/ }
|
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
|