ehahn-github 0.1.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 +17 -0
- data/README +54 -0
- data/bin/github +5 -0
- data/commands/commands.rb +108 -0
- data/commands/helpers.rb +123 -0
- data/github-gem.gemspec +24 -0
- data/lib/github.rb +127 -0
- data/lib/github/command.rb +87 -0
- data/lib/github/extensions.rb +28 -0
- data/lib/github/helper.rb +4 -0
- data/spec/command_spec.rb +66 -0
- data/spec/extensions_spec.rb +36 -0
- data/spec/github_spec.rb +65 -0
- data/spec/helper_spec.rb +212 -0
- data/spec/spec_helper.rb +138 -0
- data/spec/ui_spec.rb +506 -0
- data/spec/windoze_spec.rb +36 -0
- metadata +89 -0
@@ -0,0 +1,87 @@
|
|
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
|
+
sh ['git', command].flatten.join(' ')
|
38
|
+
end
|
39
|
+
|
40
|
+
def git_exec(*command)
|
41
|
+
cmdstr = ['git', command].flatten.join(' ')
|
42
|
+
GitHub.debug "exec: #{cmdstr}"
|
43
|
+
exec cmdstr
|
44
|
+
end
|
45
|
+
|
46
|
+
def sh(*command)
|
47
|
+
Shell.new(*command).run
|
48
|
+
end
|
49
|
+
|
50
|
+
def die(message)
|
51
|
+
puts "=> #{message}"
|
52
|
+
exit!
|
53
|
+
end
|
54
|
+
|
55
|
+
class Shell < String
|
56
|
+
def initialize(*command)
|
57
|
+
@command = command
|
58
|
+
end
|
59
|
+
|
60
|
+
def run
|
61
|
+
GitHub.debug "sh: #{command}"
|
62
|
+
_, out, err = Open3.popen3(*@command)
|
63
|
+
|
64
|
+
out = out.read.strip
|
65
|
+
err = err.read.strip
|
66
|
+
|
67
|
+
if out.any?
|
68
|
+
replace @out = out
|
69
|
+
elsif err.any?
|
70
|
+
replace @error = err
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def command
|
75
|
+
@command.join(' ')
|
76
|
+
end
|
77
|
+
|
78
|
+
def error?
|
79
|
+
!!@error
|
80
|
+
end
|
81
|
+
|
82
|
+
def out?
|
83
|
+
!!@out
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe GitHub::Command do
|
4
|
+
before(:each) do
|
5
|
+
@command = GitHub::Command.new(proc { |x| puts x })
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should return a GitHub::Helper" do
|
9
|
+
@command.helper.should be_instance_of(GitHub::Helper)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should call successfully" do
|
13
|
+
@command.should_receive(:puts).with("test").once
|
14
|
+
@command.call("test")
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should return options" do
|
18
|
+
GitHub.should_receive(:options).with().once.and_return({:ssh => true})
|
19
|
+
@command.options.should == {:ssh => true}
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should successfully call out to the shell" do
|
23
|
+
unguard(Kernel, :fork)
|
24
|
+
unguard(Kernel, :exec)
|
25
|
+
hi = @command.sh("echo hi")
|
26
|
+
hi.should == "hi"
|
27
|
+
hi.out?.should be(true)
|
28
|
+
hi.error?.should be(false)
|
29
|
+
hi.command.should == "echo hi"
|
30
|
+
bye = @command.sh("echo bye >&2")
|
31
|
+
bye.should == "bye"
|
32
|
+
bye.out?.should be(false)
|
33
|
+
bye.error?.should be(true)
|
34
|
+
bye.command.should == "echo bye >&2"
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should return the results of a git operation" do
|
38
|
+
GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
|
39
|
+
s = mock("GitHub::Commands::Shell")
|
40
|
+
s.should_receive(:run).once.and_return("sha1")
|
41
|
+
s
|
42
|
+
end
|
43
|
+
@command.git("rev-parse master").should == "sha1"
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should print the results of a git operation" do
|
47
|
+
@command.should_receive(:puts).with("sha1").once
|
48
|
+
GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
|
49
|
+
s = mock("GitHub::Commands::Shell")
|
50
|
+
s.should_receive(:run).once.and_return("sha1")
|
51
|
+
s
|
52
|
+
end
|
53
|
+
@command.pgit("rev-parse master")
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should exec a git command" do
|
57
|
+
@command.should_receive(:exec).with("git rev-parse master").once
|
58
|
+
@command.git_exec "rev-parse master"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should die" do
|
62
|
+
@command.should_receive(:puts).once.with("=> message")
|
63
|
+
@command.should_receive(:exit!).once
|
64
|
+
@command.die "message"
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/spec_helper"
|
2
|
+
|
3
|
+
describe "When calling #try" do
|
4
|
+
specify "objects should return themselves" do
|
5
|
+
obj = 1; obj.try.should equal(obj)
|
6
|
+
obj = "foo"; obj.try.should equal(obj)
|
7
|
+
obj = { :foo => "bar" }; obj.try.should equal(obj)
|
8
|
+
end
|
9
|
+
|
10
|
+
specify "objects should behave as if #try wasn't called" do
|
11
|
+
"foo".try.size.should == 3
|
12
|
+
{ :foo => :bar }.try.fetch(:foo).should == :bar
|
13
|
+
[1, 2, 3].try.map { |x| x + 1 }.should == [2, 3, 4]
|
14
|
+
end
|
15
|
+
|
16
|
+
specify "nil should return the singleton NilClass::NilProxy" do
|
17
|
+
nil.try.should equal(NilClass::NilProxy)
|
18
|
+
end
|
19
|
+
|
20
|
+
specify "nil should ignore any calls made past #try" do
|
21
|
+
nil.try.size.should equal(NilClass::NilProxy)
|
22
|
+
nil.try.sdlfj.should equal(NilClass::NilProxy)
|
23
|
+
nil.try.one.two.three.should equal(NilClass::NilProxy)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify "classes should respond just like objects" do
|
27
|
+
String.try.should equal(String)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "When calling #tap" do
|
32
|
+
specify "objects should behave like Ruby 1.9's #tap" do
|
33
|
+
obj = "foo"
|
34
|
+
obj.tap { |obj| obj.size.should == 3 }.should equal(obj)
|
35
|
+
end
|
36
|
+
end
|
data/spec/github_spec.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe "GitHub.parse_options" do
|
4
|
+
it "should parse --bare options" do
|
5
|
+
args = ["--bare", "--test"]
|
6
|
+
GitHub.parse_options(args).should == {:bare => true, :test => true}
|
7
|
+
args.should == []
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should parse options intermixed with non-options" do
|
11
|
+
args = ["text", "--bare", "more text", "--option", "--foo"]
|
12
|
+
GitHub.parse_options(args).should == {:bare => true, :option => true, :foo => true}
|
13
|
+
args.should == ["text", "more text"]
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should parse --foo=bar style options" do
|
17
|
+
args = ["--foo=bar", "--bare"]
|
18
|
+
GitHub.parse_options(args).should == {:bare => true, :foo => "bar"}
|
19
|
+
args.should == []
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should stop parsing options at --" do
|
23
|
+
args = ["text", "--bare", "--", "--foo"]
|
24
|
+
GitHub.parse_options(args).should == {:bare => true}
|
25
|
+
args.should == ["text", "--foo"]
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should handle duplicate options" do
|
29
|
+
args = ["text", "--foo=bar", "--bare", "--foo=baz"]
|
30
|
+
GitHub.parse_options(args).should == {:foo => "baz", :bare => true}
|
31
|
+
args.should == ["text"]
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should handle duplicate --bare options surrounding --" do
|
35
|
+
args = ["text", "--bare", "--", "--bare"]
|
36
|
+
GitHub.parse_options(args).should == {:bare => true}
|
37
|
+
args.should == ["text", "--bare"]
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should handle no options" do
|
41
|
+
args = ["text", "more text"]
|
42
|
+
GitHub.parse_options(args).should == {}
|
43
|
+
args.should == ["text", "more text"]
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should handle no args" do
|
47
|
+
args = []
|
48
|
+
GitHub.parse_options(args).should == {}
|
49
|
+
args.should == []
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not set up debugging when --debug not passed" do
|
53
|
+
GitHub.stub!(:load)
|
54
|
+
GitHub.stub!(:invoke)
|
55
|
+
GitHub.activate(['default'])
|
56
|
+
GitHub.should_not be_debug
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should set up debugging when passed --debug" do
|
60
|
+
GitHub.stub!(:load)
|
61
|
+
GitHub.stub!(:invoke)
|
62
|
+
GitHub.activate(['default', '--debug'])
|
63
|
+
GitHub.should be_debug
|
64
|
+
end
|
65
|
+
end
|
data/spec/helper_spec.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class HelperRunner
|
4
|
+
def initialize(parent, name)
|
5
|
+
@parent = parent
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def run(&block)
|
10
|
+
self.instance_eval(&block)
|
11
|
+
end
|
12
|
+
|
13
|
+
def it(str, &block)
|
14
|
+
@parent.send :it, "#{@name} #{str}", &block
|
15
|
+
end
|
16
|
+
alias specify it
|
17
|
+
end
|
18
|
+
|
19
|
+
describe GitHub::Helper do
|
20
|
+
include SetupMethods
|
21
|
+
|
22
|
+
def self.helper(name, &block)
|
23
|
+
HelperRunner.new(self, name).run(&block)
|
24
|
+
end
|
25
|
+
|
26
|
+
before(:each) do
|
27
|
+
@helper = GitHub::Helper.new
|
28
|
+
end
|
29
|
+
|
30
|
+
helper :owner do
|
31
|
+
it "should return repo owner" do
|
32
|
+
setup_url_for :origin, "hacker"
|
33
|
+
@helper.owner.should == "hacker"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
helper :private_url_for do
|
38
|
+
it "should return an ssh-style url" do
|
39
|
+
setup_url_for :origin, "user", "merb-core"
|
40
|
+
@helper.private_url_for("wycats").should == "git@github.com:wycats/merb-core.git"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
helper :private_url_for_user_and_repo do
|
45
|
+
it "should return an ssh-style url" do
|
46
|
+
@helper.should_not_receive(:project)
|
47
|
+
@helper.private_url_for_user_and_repo("defunkt", "github-gem").should == "git@github.com:defunkt/github-gem.git"
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
helper :public_url_for do
|
52
|
+
it "should return a git:// URL" do
|
53
|
+
setup_url_for :origin, "user", "merb-core"
|
54
|
+
@helper.public_url_for("wycats").should == "git://github.com/wycats/merb-core.git"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
helper :public_url_for_user_and_repo do
|
59
|
+
it "should return a git:// URL" do
|
60
|
+
@helper.should_not_receive(:project)
|
61
|
+
@helper.public_url_for_user_and_repo("defunkt", "github-gem").should == "git://github.com/defunkt/github-gem.git"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
helper :project do
|
66
|
+
it "should return project-awesome" do
|
67
|
+
setup_url_for :origin, "user", "project-awesome"
|
68
|
+
@helper.project.should == "project-awesome"
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should exit due to missing origin" do
|
72
|
+
@helper.should_receive(:url_for).twice.with(:origin).and_return("")
|
73
|
+
STDERR.should_receive(:puts).with("Error: missing remote 'origin'")
|
74
|
+
lambda { @helper.project }.should raise_error(SystemExit)
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should exit due to non-github origin" do
|
78
|
+
@helper.should_receive(:url_for).twice.with(:origin).and_return("home:path/to/repo.git")
|
79
|
+
STDERR.should_receive(:puts).with("Error: remote 'origin' is not a github URL")
|
80
|
+
lambda { @helper.project }.should raise_error(SystemExit)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
helper :repo_for do
|
85
|
+
it "should return mephisto.git" do
|
86
|
+
setup_url_for :mojombo, "mojombo", "mephisto"
|
87
|
+
@helper.repo_for(:mojombo).should == "mephisto.git"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
helper :user_and_repo_from do
|
92
|
+
it "should parse a git:// url" do
|
93
|
+
@helper.user_and_repo_from("git://github.com/defunkt/github.git").should == ["defunkt", "github.git"]
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should parse a ssh-based url" do
|
97
|
+
@helper.user_and_repo_from("git@github.com:mojombo/god.git").should == ["mojombo", "god.git"]
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should parse a non-standard ssh-based url" do
|
101
|
+
@helper.user_and_repo_from("ssh://git@github.com:mojombo/god.git").should == ["mojombo", "god.git"]
|
102
|
+
@helper.user_and_repo_from("github.com:mojombo/god.git").should == ["mojombo", "god.git"]
|
103
|
+
@helper.user_and_repo_from("ssh://github.com:mojombo/god.git").should == ["mojombo", "god.git"]
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should return nothing for other urls" do
|
107
|
+
@helper.user_and_repo_from("home:path/to/repo.git").should == nil
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should return nothing for invalid git:// urls" do
|
111
|
+
@helper.user_and_repo_from("git://github.com/foo").should == nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should return nothing for invalid ssh-based urls" do
|
115
|
+
@helper.user_and_repo_from("git@github.com:kballard").should == nil
|
116
|
+
@helper.user_and_repo_from("git@github.com:kballard/test/repo.git").should == nil
|
117
|
+
@helper.user_and_repo_from("ssh://git@github.com:kballard").should == nil
|
118
|
+
@helper.user_and_repo_from("github.com:kballard").should == nil
|
119
|
+
@helper.user_and_repo_from("ssh://github.com:kballard").should == nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
helper :user_for do
|
124
|
+
it "should return defunkt" do
|
125
|
+
setup_url_for :origin, "defunkt"
|
126
|
+
@helper.user_for(:origin).should == "defunkt"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
helper :url_for do
|
131
|
+
it "should call out to the shell" do
|
132
|
+
@helper.should_receive(:`).with("git config --get remote.origin.url").and_return "git://github.com/user/project.git\n"
|
133
|
+
@helper.url_for(:origin).should == "git://github.com/user/project.git"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
helper :remotes do
|
138
|
+
it "should return a list of remotes" do
|
139
|
+
@helper.should_receive(:`).with('git config --get-regexp \'^remote\.(.+)\.url$\'').and_return <<-EOF
|
140
|
+
remote.origin.url git@github.com:kballard/github-gem.git
|
141
|
+
remote.defunkt.url git://github.com/defunkt/github-gem.git
|
142
|
+
remote.nex3.url git://github.com/nex3/github-gem.git
|
143
|
+
EOF
|
144
|
+
@helper.remotes.should == {
|
145
|
+
:origin => "git@github.com:kballard/github-gem.git",
|
146
|
+
:defunkt => "git://github.com/defunkt/github-gem.git",
|
147
|
+
:nex3 => "git://github.com/nex3/github-gem.git"
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
helper :tracking do
|
153
|
+
it "should return a list of remote/user_or_url pairs" do
|
154
|
+
@helper.should_receive(:remotes).and_return({
|
155
|
+
:origin => "git@github.com:kballard/github-gem.git",
|
156
|
+
:defunkt => "git://github.com/defunkt/github-gem.git",
|
157
|
+
:external => "server:path/to/github-gem.git"
|
158
|
+
})
|
159
|
+
@helper.tracking.should == {
|
160
|
+
:origin => "kballard",
|
161
|
+
:defunkt => "defunkt",
|
162
|
+
:external => "server:path/to/github-gem.git"
|
163
|
+
}
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
helper :tracking? do
|
168
|
+
it "should return whether the user is tracked" do
|
169
|
+
@helper.should_receive(:tracking).any_number_of_times.and_return({
|
170
|
+
:origin => "kballard",
|
171
|
+
:defunkt => "defunkt",
|
172
|
+
:external => "server:path/to/github-gem.git"
|
173
|
+
})
|
174
|
+
@helper.tracking?("kballard").should == true
|
175
|
+
@helper.tracking?("defunkt").should == true
|
176
|
+
@helper.tracking?("nex3").should == false
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
helper :user_and_branch do
|
181
|
+
it "should return owner and branch for unqualified branches" do
|
182
|
+
setup_url_for
|
183
|
+
@helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/master"
|
184
|
+
@helper.user_and_branch.should == ["user", "master"]
|
185
|
+
end
|
186
|
+
|
187
|
+
it "should return user and branch for user/branch-style branches" do
|
188
|
+
@helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/defunkt/wip"
|
189
|
+
@helper.user_and_branch.should == ["defunkt", "wip"]
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
helper :open do
|
194
|
+
it "should launch the URL when Launchy is installed" do
|
195
|
+
begin
|
196
|
+
require 'launchy'
|
197
|
+
@helper.should_receive(:gem).with('launchy')
|
198
|
+
# @helper.should_receive(:has_launchy?).and_return { |blk| blk.call }
|
199
|
+
Launchy::Browser.next_instance.should_receive(:visit).with("http://www.google.com")
|
200
|
+
@helper.open "http://www.google.com"
|
201
|
+
rescue LoadError
|
202
|
+
fail "Launchy is required for this spec"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should fail when Launchy is not installed" do
|
207
|
+
@helper.should_receive(:gem).with('launchy').and_raise(Gem::LoadError)
|
208
|
+
STDERR.should_receive(:puts).with("Sorry, you need to install launchy: `gem install launchy`")
|
209
|
+
@helper.open "http://www.google.com"
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|