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,36 @@
1
+ require File.expand_path(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,85 @@
1
+ require File.expand_path(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
+
66
+ it "should allow for an alias on a commad" do
67
+ GitHub.command 'some-command', :aliases => 'an-alias' do
68
+ end
69
+ GitHub.commands['an-alias'].should_not be_nil
70
+ GitHub.commands['an-alias'].should_not == GitHub.commands['non-existant-command']
71
+ GitHub.commands['an-alias'].should == GitHub.commands['some-command']
72
+ end
73
+
74
+ it "should allow for an array of aliases on a commad" do
75
+ GitHub.command 'another-command', :aliases => ['some-alias-1', 'some-alias-2'] do
76
+ end
77
+ GitHub.commands['some-alias-1'].should_not be_nil
78
+ GitHub.commands['some-alias-1'].should_not == GitHub.commands['non-existant-command']
79
+ GitHub.commands['some-alias-1'].should_not be_nil
80
+ GitHub.commands['some-alias-1'].should_not == GitHub.commands['non-existant-command']
81
+ GitHub.commands['some-alias-1'].should == GitHub.commands['another-command']
82
+ GitHub.commands['some-alias-2'].should == GitHub.commands['another-command']
83
+ end
84
+
85
+ end
@@ -0,0 +1,368 @@
1
+ require File.expand_path(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
+
18
+ def before(symbol=:each, &block)
19
+ @parent.send :before, symbol, &block
20
+ end
21
+ end
22
+
23
+ describe GitHub::Helper do
24
+ include SetupMethods
25
+
26
+ def self.helper(name, &block)
27
+ HelperRunner.new(self, name).run(&block)
28
+ end
29
+
30
+ before(:each) do
31
+ @helper = GitHub::Helper.new
32
+ end
33
+
34
+ helper :format_list do
35
+ it "should format an array of hashes with name,description keys" do
36
+ list = [{"name" => "aaa", "description" => "description for aaa"},
37
+ {"name" => "a long name", "description" => "help"},
38
+ {"name" => "no desc"},
39
+ {"name" => "empty desc", "description" => ""}]
40
+ expected = <<-EOS.gsub(/^ /, '')
41
+ aaa # description for aaa
42
+ a long name # help
43
+ no desc
44
+ empty desc
45
+ EOS
46
+ @helper.format_list(list).should == expected.gsub(/\n$/,'')
47
+ end
48
+ end
49
+
50
+ helper :print_issues_help do
51
+ it "should exist" do
52
+ @helper.should respond_to(:print_issues_help)
53
+ end
54
+ end
55
+
56
+ helper :format_issue do
57
+ before(:each) do
58
+ @issue = {}
59
+ @issue['number'] = 1234
60
+ @issue['title'] = "Isaac Asimov's Science Fiction Magazine"
61
+ @issue['votes'] = 99
62
+ end
63
+
64
+ specify "the title, number of votes and ticket number should appear" do
65
+ @helper.format_issue(@issue, {}).should =~ /Issue #1234 \(99 votes\): Isaac Asimov's Science Fiction Magazine/
66
+ end
67
+
68
+ specify "the url should appear" do
69
+ setup_url_for("origin", "hamilton", "foo")
70
+ @helper.format_issue(@issue, {:user => 'hamilton'}).should =~ /http:\/\/github.com\/hamilton\/foo\/issues\/#issue\/#{@issue['number']}/
71
+ end
72
+
73
+ specify "created_at should appear" do
74
+ @issue['created_at'] = Time.now - 3600
75
+ @issue['user'] = 'Ray Bradbury'
76
+ @helper.format_issue(@issue, {}).should =~ /Opened about 1 hour ago by Ray Bradbury/
77
+ end
78
+
79
+ specify "closed_at should appear" do
80
+ @issue['closed_at'] = Time.now - 3600
81
+ @helper.format_issue(@issue, {}).should =~ /Closed about 1 hour ago/
82
+ end
83
+
84
+ specify "updated_at should appear" do
85
+ @issue['updated_at'] = Time.now - 3600
86
+ @helper.format_issue(@issue, {}).should =~ /Last updated about 1 hour ago/
87
+ end
88
+
89
+ specify "labels should appear" do
90
+ @issue['labels'] = ['Horror','Sci-Fi','Fan Fic']
91
+ @helper.format_issue(@issue, {}).should =~ /Labels: Horror, Sci-Fi, Fan Fic/
92
+ end
93
+
94
+ specify "the body should appear" do
95
+ @issue['body'] = <<-EOF
96
+ It was the best of times,
97
+ It was the worst of times.
98
+ EOF
99
+ report = @helper.format_issue(@issue, {})
100
+ report.should =~ /It was the best of times,/
101
+ report.should =~ /It was the worst of times\./
102
+ end
103
+ end
104
+
105
+ helper :filter_issue do
106
+ specify "when the after option is present, show only issues updated on or after that date" do
107
+ issue = {'updated_at' => Time.parse('2009-01-02 12:00:00')}
108
+ @helper.filter_issue(issue, :after => '2009-01-02').should be_false
109
+ @helper.filter_issue(issue, :after => '2009-01-03').should be_true
110
+ end
111
+
112
+ specify "when a label is specified, show only issues that have that label" do
113
+ @helper.filter_issue({'labels' => nil}, :label => 'foo').should be_true
114
+ @helper.filter_issue({'labels' => []}, :label => 'foo').should be_true
115
+ @helper.filter_issue({'labels' => ['foo']}, :label => 'foo').should be_false
116
+ @helper.filter_issue({'labels' => ['quux','foo','bar']}, :label => 'foo').should be_false
117
+ end
118
+ end
119
+
120
+ helper :owner do
121
+ it "should return repo owner" do
122
+ setup_url_for "origin", "hacker"
123
+ @helper.owner.should == "hacker"
124
+ end
125
+ end
126
+
127
+ helper :private_url_for do
128
+ it "should return an ssh-style url" do
129
+ setup_url_for "origin", "user", "merb-core"
130
+ @helper.private_url_for("wycats").should == "git@github.com:wycats/merb-core.git"
131
+ end
132
+ end
133
+
134
+ helper :private_url_for_user_and_repo do
135
+ it "should return an ssh-style url" do
136
+ @helper.should_not_receive(:project)
137
+ @helper.private_url_for_user_and_repo("defunkt", "github-gem").should == "git@github.com:defunkt/github-gem.git"
138
+ end
139
+ end
140
+
141
+ helper :public_url_for do
142
+ it "should return a git:// URL" do
143
+ setup_url_for "origin", "user", "merb-core"
144
+ @helper.public_url_for("wycats").should == "git://github.com/wycats/merb-core.git"
145
+ end
146
+ end
147
+
148
+ helper :public_url_for_user_and_repo do
149
+ it "should return a git:// URL" do
150
+ @helper.should_not_receive(:project)
151
+ @helper.public_url_for_user_and_repo("defunkt", "github-gem").should == "git://github.com/defunkt/github-gem.git"
152
+ end
153
+ end
154
+
155
+ helper :project do
156
+ it "should return project-awesome" do
157
+ setup_url_for "origin", "user", "project-awesome"
158
+ @helper.project.should == "project-awesome"
159
+ end
160
+
161
+ it "should exit due to missing origin" do
162
+ @helper.should_receive(:url_for).twice.with("origin").and_return("")
163
+ @helper.should_receive(:origin).twice.and_return("origin")
164
+ STDERR.should_receive(:puts).with("Error: missing remote 'origin'")
165
+ lambda { @helper.project }.should raise_error(SystemExit)
166
+ end
167
+
168
+ it "should exit due to non-github origin" do
169
+ @helper.should_receive(:url_for).twice.with("origin").and_return("home:path/to/repo.git")
170
+ @helper.should_receive(:origin).twice.and_return("origin")
171
+ STDERR.should_receive(:puts).with("Error: remote 'origin' is not a github URL")
172
+ lambda { @helper.project }.should raise_error(SystemExit)
173
+ end
174
+ end
175
+
176
+ helper :repo_for do
177
+ it "should return mephisto.git" do
178
+ setup_url_for "mojombo", "mojombo", "mephisto"
179
+ @helper.repo_for("mojombo").should == "mephisto.git"
180
+ end
181
+ end
182
+
183
+ helper :user_and_repo_from do
184
+ it "should parse a git:// url" do
185
+ @helper.user_and_repo_from("git://github.com/defunkt/github.git").should == ["defunkt", "github.git"]
186
+ end
187
+
188
+ it "should parse a ssh-based url" do
189
+ @helper.user_and_repo_from("git@github.com:mojombo/god.git").should == ["mojombo", "god.git"]
190
+ end
191
+
192
+ it "should parse a non-standard ssh-based url" do
193
+ @helper.user_and_repo_from("ssh://git@github.com:mojombo/god.git").should == ["mojombo", "god.git"]
194
+ @helper.user_and_repo_from("github.com:mojombo/god.git").should == ["mojombo", "god.git"]
195
+ @helper.user_and_repo_from("ssh://github.com:mojombo/god.git").should == ["mojombo", "god.git"]
196
+ end
197
+
198
+ it "should return nothing for other urls" do
199
+ @helper.user_and_repo_from("home:path/to/repo.git").should == nil
200
+ end
201
+
202
+ it "should return nothing for invalid git:// urls" do
203
+ @helper.user_and_repo_from("git://github.com/foo").should == nil
204
+ end
205
+
206
+ it "should return nothing for invalid ssh-based urls" do
207
+ @helper.user_and_repo_from("git@github.com:kballard").should == nil
208
+ @helper.user_and_repo_from("git@github.com:kballard/test/repo.git").should == nil
209
+ @helper.user_and_repo_from("ssh://git@github.com:kballard").should == nil
210
+ @helper.user_and_repo_from("github.com:kballard").should == nil
211
+ @helper.user_and_repo_from("ssh://github.com:kballard").should == nil
212
+ end
213
+ end
214
+
215
+ helper :user_for do
216
+ it "should return defunkt" do
217
+ setup_url_for "origin", "defunkt"
218
+ @helper.user_for("origin").should == "defunkt"
219
+ end
220
+ end
221
+
222
+ helper :url_for do
223
+ it "should call out to the shell" do
224
+ @helper.should_receive(:`).with("git config --get remote.origin.url").and_return "git://github.com/user/project.git\n"
225
+ @helper.url_for("origin").should == "git://github.com/user/project.git"
226
+ end
227
+ end
228
+
229
+ helper :remotes do
230
+ it "should return a list of remotes" do
231
+ @helper.should_receive(:`).with('git config --get-regexp \'^remote\.(.+)\.url$\'').and_return <<-EOF
232
+ remote.origin.url git@github.com:kballard/github-gem.git
233
+ remote.defunkt.url git://github.com/defunkt/github-gem.git
234
+ remote.nex3.url git://github.com/nex3/github-gem.git
235
+ EOF
236
+ @helper.remotes.should == {
237
+ :origin => "git@github.com:kballard/github-gem.git",
238
+ :defunkt => "git://github.com/defunkt/github-gem.git",
239
+ :nex3 => "git://github.com/nex3/github-gem.git"
240
+ }
241
+ end
242
+ end
243
+
244
+ helper :remote_branches_for do
245
+ it "should return an empty list because no user was provided" do
246
+ @helper.remote_branches_for(nil).should == nil
247
+ end
248
+
249
+ it "should return a list of remote branches for defunkt" do
250
+ @helper.should_receive(:`).with('git ls-remote -h defunkt 2> /dev/null').and_return <<-EOF
251
+ fe1f852f3cf719c7cd86147031732f570ad89619 refs/heads/kballard/master
252
+ f8a6bb42b0ed43ac7336bfcda246e59a9da949d6 refs/heads/master
253
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/heads/restart
254
+ EOF
255
+ @helper.remote_branches_for("defunkt").should == {
256
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
257
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
258
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
259
+ }
260
+ end
261
+
262
+ it "should return an empty list of remote branches for nex3 and nex4" do
263
+ # the following use-case should never happen as the -h parameter should only return heads on remote branches
264
+ # however, we are testing this particular case to verify how remote_branches_for would respond if random
265
+ # git results
266
+ @helper.should_receive(:`).with('git ls-remote -h nex3 2> /dev/null').and_return <<-EOF
267
+ fe1f852f3cf719c7cd86147031732f570ad89619 HEAD
268
+ a1a392369e5b7842d01cce965272d4b96c2fd343 refs/tags/v0.1.3
269
+ 624d9c2f742ff24a79353a7e02bf289235c72ff1 refs/remotes/origin/master
270
+ random
271
+ random_again
272
+ EOF
273
+ @helper.remote_branches_for("nex3").should be_empty
274
+
275
+ @helper.should_receive(:`).with('git ls-remote -h nex4 2> /dev/null').and_return ""
276
+ @helper.remote_branches_for("nex4").should be_empty
277
+ end
278
+ end
279
+
280
+ helper :remote_branch? do
281
+ it "should return whether the branch exists at the remote user" do
282
+ @helper.should_receive(:remote_branches_for).with("defunkt").any_number_of_times.and_return({
283
+ "master" => "f8a6bb42b0ed43ac7336bfcda246e59a9da949d6",
284
+ "kballard/master" => "fe1f852f3cf719c7cd86147031732f570ad89619",
285
+ "restart" => "624d9c2f742ff24a79353a7e02bf289235c72ff1"
286
+ })
287
+ @helper.remote_branch?("defunkt", "master").should == true
288
+ @helper.remote_branch?("defunkt", "not_master").should == false
289
+ end
290
+ end
291
+
292
+ helper :branch_dirty? do
293
+ it "should return false" do
294
+ @helper.should_receive(:system).with(/^git diff/).and_return(true)
295
+ @helper.branch_dirty?.should == false
296
+ end
297
+
298
+ it "should return true" do
299
+ @helper.should_receive(:system).with(/^git diff/).and_return(false, true)
300
+ @helper.branch_dirty?.should == true
301
+ end
302
+ end
303
+
304
+ helper :tracking do
305
+ it "should return a list of remote/user_or_url pairs" do
306
+ @helper.should_receive(:remotes).and_return({
307
+ :origin => "git@github.com:kballard/github-gem.git",
308
+ :defunkt => "git://github.com/defunkt/github-gem.git",
309
+ :external => "server:path/to/github-gem.git"
310
+ })
311
+ @helper.tracking.should == {
312
+ :origin => "kballard",
313
+ :defunkt => "defunkt",
314
+ :external => "server:path/to/github-gem.git"
315
+ }
316
+ end
317
+ end
318
+
319
+ helper :tracking? do
320
+ it "should return whether the user is tracked" do
321
+ @helper.should_receive(:tracking).any_number_of_times.and_return({
322
+ :origin => "kballard",
323
+ :defunkt => "defunkt",
324
+ :external => "server:path/to/github-gem.git"
325
+ })
326
+ @helper.tracking?("kballard").should == true
327
+ @helper.tracking?("defunkt").should == true
328
+ @helper.tracking?("nex3").should == false
329
+ end
330
+ end
331
+
332
+ helper :user_and_branch do
333
+ it "should return owner and branch for unqualified branches" do
334
+ setup_url_for
335
+ @helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/master"
336
+ @helper.user_and_branch.should == ["user", "master"]
337
+ end
338
+
339
+ it "should return user and branch for user/branch-style branches" do
340
+ @helper.should_receive(:`).with("git rev-parse --symbolic-full-name HEAD").and_return "refs/heads/defunkt/wip"
341
+ @helper.user_and_branch.should == ["defunkt", "wip"]
342
+ end
343
+ end
344
+
345
+ helper :open do
346
+ it "should launch the URL when Launchy is installed" do
347
+ begin
348
+ # tricking launchy into thinking there is always a browser
349
+ ENV['LAUNCHY_BROWSER'] = dummy_browser = __FILE__
350
+ require 'launchy'
351
+
352
+ @helper.should_receive(:gem).with('launchy')
353
+ Launchy::Browser.next_instance.tap do |browser|
354
+ browser.should_receive(:run).with(dummy_browser, "http://www.google.com")
355
+ @helper.open "http://www.google.com"
356
+ end
357
+ rescue LoadError
358
+ fail "Launchy is required for this spec"
359
+ end
360
+ end
361
+
362
+ it "should fail when Launchy is not installed" do
363
+ @helper.should_receive(:gem).with('launchy').and_raise(Gem::LoadError)
364
+ STDERR.should_receive(:puts).with("Sorry, you need to install launchy: `gem install launchy`")
365
+ @helper.open "http://www.google.com"
366
+ end
367
+ end
368
+ end