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