schacon-github 0.3.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.
@@ -0,0 +1,99 @@
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
+ attr_reader :error
57
+ attr_reader :out
58
+
59
+ def initialize(*command)
60
+ @command = command
61
+ end
62
+
63
+ def run
64
+ GitHub.debug "sh: #{command}"
65
+ _, out, err = Open3.popen3(*@command)
66
+
67
+ out = out.read.strip
68
+ err = err.read.strip
69
+
70
+ replace @error = err if err.any?
71
+ replace @out = out if out.any?
72
+
73
+ self
74
+ end
75
+
76
+ def command
77
+ @command.join(' ')
78
+ end
79
+
80
+ def error?
81
+ !!@error
82
+ end
83
+
84
+ def out?
85
+ !!@out
86
+ end
87
+ end
88
+ end
89
+
90
+ class GitCommand < Command
91
+ def initialize(name)
92
+ @name = name
93
+ end
94
+
95
+ def command(*args)
96
+ git_exec *[ @name, args ]
97
+ end
98
+ end
99
+ 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,4 @@
1
+ module GitHub
2
+ class Helper
3
+ end
4
+ end
@@ -0,0 +1,82 @@
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 == "hi"
28
+ hi.out?.should be(true)
29
+ hi.error.should be_nil
30
+ hi.error?.should be(false)
31
+ hi.command.should == "echo hi"
32
+ if RUBY_PLATFORM =~ /mingw|mswin/
33
+ command = "cmd /c echo bye >&2"
34
+ else
35
+ command = "echo bye >&2"
36
+ end
37
+ bye = @command.sh(command)
38
+ bye.should == "bye"
39
+ bye.out.should be_nil
40
+ bye.out?.should be(false)
41
+ bye.error.should == "bye"
42
+ bye.error?.should be(true)
43
+ bye.command.should == command
44
+ hi_and_bye = @command.sh("echo hi; echo bye >&2")
45
+ hi_and_bye.should == "hi"
46
+ hi_and_bye.out.should == "hi"
47
+ hi_and_bye.out?.should be(true)
48
+ hi_and_bye.error.should == "bye"
49
+ hi_and_bye.error?.should be(true)
50
+ hi_and_bye.command.should == "echo hi; echo bye >&2"
51
+ end
52
+
53
+ it "should return the results of a git operation" do
54
+ GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
55
+ s = mock("GitHub::Commands::Shell")
56
+ s.should_receive(:run).once.and_return("sha1")
57
+ s
58
+ end
59
+ @command.git("rev-parse master").should == "sha1"
60
+ end
61
+
62
+ it "should print the results of a git operation" do
63
+ @command.should_receive(:puts).with("sha1").once
64
+ GitHub::Command::Shell.should_receive(:new).with("git rev-parse master").once.and_return do |*cmds|
65
+ s = mock("GitHub::Commands::Shell")
66
+ s.should_receive(:run).once.and_return("sha1")
67
+ s
68
+ end
69
+ @command.pgit("rev-parse master")
70
+ end
71
+
72
+ it "should exec a git command" do
73
+ @command.should_receive(:exec).with("git rev-parse master").once
74
+ @command.git_exec "rev-parse master"
75
+ end
76
+
77
+ it "should die" do
78
+ @command.should_receive(:puts).once.with("=> message")
79
+ @command.should_receive(:exit!).once
80
+ @command.die "message"
81
+ end
82
+ 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
@@ -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
@@ -0,0 +1,280 @@
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 :remote_branches_for do
153
+ it "should return an empty list because no user was provided" do
154
+ @helper.remote_branches_for(nil).should == nil
155
+ end
156
+
157
+ it "should return a list of remote branches for defunkt" do
158
+ @helper.should_receive(:`).with('git ls-remote -h defunkt 2> /dev/null').and_return <<-EOF
159
+ fe1f852f3cf719c7cd86147031732f570ad89619 refs/heads/kballard/master
160
+ f8a6bb42b0ed43ac7336bfcda246e59a9da949d6 refs/heads/master
161
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/heads/restart
162
+ EOF
163
+ @helper.remote_branches_for("defunkt").should == {
164
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
165
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
166
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
167
+ }
168
+ end
169
+
170
+ it "should return an empty list of remote branches for nex3 and nex4" do
171
+ # the following use-case should never happen as the -h parameter should only return heads on remote branches
172
+ # however, we are testing this particular case to verify how remote_branches_for would respond if random
173
+ # git results
174
+ @helper.should_receive(:`).with('git ls-remote -h nex3 2> /dev/null').and_return <<-EOF
175
+ fe1f852f3cf719c7cd86147031732f570ad89619 HEAD
176
+ a1a392369e5b7842d01cce965272d4b96c2fd343 refs/tags/v0.1.3
177
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/remotes/origin/master
178
+ random
179
+ random_again
180
+ EOF
181
+ @helper.remote_branches_for("nex3").should be_empty
182
+
183
+ @helper.should_receive(:`).with('git ls-remote -h nex4 2> /dev/null').and_return ""
184
+ @helper.remote_branches_for("nex4").should be_empty
185
+ end
186
+ end
187
+
188
+ helper :remote_branch? do
189
+ it "should return whether the branch exists at the remote user" do
190
+ @helper.should_receive(:remote_branches_for).with("defunkt").any_number_of_times.and_return({
191
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
192
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
193
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
194
+ })
195
+ @helper.remote_branch?("defunkt", "master").should == true
196
+ @helper.remote_branch?("defunkt", "not_master").should == false
197
+ end
198
+ end
199
+
200
+ helper :branch_dirty? do
201
+ it "should return false" do
202
+ @helper.should_receive(:system).with(/^git diff/).and_return(0, 0)
203
+ @helper.branch_dirty?.should == 0
204
+ end
205
+
206
+ it "should return true" do
207
+ @helper.should_receive(:system).with(/^git diff/).and_return(1, 1, 0, 1)
208
+ @helper.branch_dirty?.should == 1
209
+ @helper.branch_dirty?.should == 1
210
+ end
211
+ end
212
+
213
+ helper :tracking do
214
+ it "should return a list of remote/user_or_url pairs" do
215
+ @helper.should_receive(:remotes).and_return({
216
+ :origin => "git@github.com:kballard/github-gem.git",
217
+ :defunkt => "git://github.com/defunkt/github-gem.git",
218
+ :external => "server:path/to/github-gem.git"
219
+ })
220
+ @helper.tracking.should == {
221
+ :origin => "kballard",
222
+ :defunkt => "defunkt",
223
+ :external => "server:path/to/github-gem.git"
224
+ }
225
+ end
226
+ end
227
+
228
+ helper :tracking? do
229
+ it "should return whether the user is tracked" do
230
+ @helper.should_receive(:tracking).any_number_of_times.and_return({
231
+ :origin => "kballard",
232
+ :defunkt => "defunkt",
233
+ :external => "server:path/to/github-gem.git"
234
+ })
235
+ @helper.tracking?("kballard").should == true
236
+ @helper.tracking?("defunkt").should == true
237
+ @helper.tracking?("nex3").should == false
238
+ end
239
+ end
240
+
241
+ helper :user_and_branch do
242
+ it "should return owner and branch for unqualified branches" do
243
+ setup_url_for
244
+ @helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/master"
245
+ @helper.user_and_branch.should == ["user", "master"]
246
+ end
247
+
248
+ it "should return user and branch for user/branch-style branches" do
249
+ @helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/defunkt/wip"
250
+ @helper.user_and_branch.should == ["defunkt", "wip"]
251
+ end
252
+ end
253
+
254
+ helper :open do
255
+ it "should launch the URL when Launchy is installed" do
256
+ begin
257
+ require 'launchy'
258
+
259
+ @helper.should_receive(:gem).with('launchy')
260
+ Launchy::Browser.next_instance.tap do |browser|
261
+ browser.should_receive(:my_os_family).any_number_of_times.and_return :windows # avoid forking
262
+ if RUBY_PLATFORM =~ /mingw|mswin/
263
+ browser.should_receive(:system).with("start http://www.google.com")
264
+ else
265
+ browser.should_receive(:system).with("/usr/bin/open http://www.google.com")
266
+ end
267
+ @helper.open "http://www.google.com"
268
+ end
269
+ rescue LoadError
270
+ fail "Launchy is required for this spec"
271
+ end
272
+ end
273
+
274
+ it "should fail when Launchy is not installed" do
275
+ @helper.should_receive(:gem).with('launchy').and_raise(Gem::LoadError)
276
+ STDERR.should_receive(:puts).with("Sorry, you need to install launchy: `gem install launchy`")
277
+ @helper.open "http://www.google.com"
278
+ end
279
+ end
280
+ end