octopolo 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +21 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/Gemfile +3 -0
  7. data/Guardfile +5 -0
  8. data/MIT-LICENSE +20 -0
  9. data/README.markdown +55 -0
  10. data/Rakefile +38 -0
  11. data/bash_completion.sh +13 -0
  12. data/bin/octopolo +21 -0
  13. data/bin/op +21 -0
  14. data/lib/octopolo.rb +15 -0
  15. data/lib/octopolo/changelog.rb +27 -0
  16. data/lib/octopolo/cli.rb +210 -0
  17. data/lib/octopolo/commands/accept_pull.rb +8 -0
  18. data/lib/octopolo/commands/compare_release.rb +9 -0
  19. data/lib/octopolo/commands/deployable.rb +8 -0
  20. data/lib/octopolo/commands/github_auth.rb +5 -0
  21. data/lib/octopolo/commands/new_branch.rb +9 -0
  22. data/lib/octopolo/commands/new_deployable.rb +8 -0
  23. data/lib/octopolo/commands/new_staging.rb +8 -0
  24. data/lib/octopolo/commands/octopolo_setup.rb +5 -0
  25. data/lib/octopolo/commands/pivotal_auth.rb +5 -0
  26. data/lib/octopolo/commands/pull_request.rb +13 -0
  27. data/lib/octopolo/commands/signoff.rb +10 -0
  28. data/lib/octopolo/commands/stage_up.rb +8 -0
  29. data/lib/octopolo/commands/stale_branches.rb +11 -0
  30. data/lib/octopolo/commands/sync_branch.rb +11 -0
  31. data/lib/octopolo/commands/tag_release.rb +13 -0
  32. data/lib/octopolo/config.rb +146 -0
  33. data/lib/octopolo/convenience_wrappers.rb +46 -0
  34. data/lib/octopolo/dated_branch_creator.rb +81 -0
  35. data/lib/octopolo/git.rb +262 -0
  36. data/lib/octopolo/github.rb +95 -0
  37. data/lib/octopolo/github/commit.rb +45 -0
  38. data/lib/octopolo/github/pull_request.rb +126 -0
  39. data/lib/octopolo/github/pull_request_creator.rb +127 -0
  40. data/lib/octopolo/github/user.rb +40 -0
  41. data/lib/octopolo/jira/story_commenter.rb +26 -0
  42. data/lib/octopolo/pivotal.rb +44 -0
  43. data/lib/octopolo/pivotal/story_commenter.rb +19 -0
  44. data/lib/octopolo/pull_request_merger.rb +99 -0
  45. data/lib/octopolo/renderer.rb +37 -0
  46. data/lib/octopolo/reports.rb +18 -0
  47. data/lib/octopolo/scripts.rb +23 -0
  48. data/lib/octopolo/scripts/accept_pull.rb +67 -0
  49. data/lib/octopolo/scripts/compare_release.rb +52 -0
  50. data/lib/octopolo/scripts/deployable.rb +27 -0
  51. data/lib/octopolo/scripts/github_auth.rb +87 -0
  52. data/lib/octopolo/scripts/new_branch.rb +34 -0
  53. data/lib/octopolo/scripts/new_deployable.rb +14 -0
  54. data/lib/octopolo/scripts/new_staging.rb +15 -0
  55. data/lib/octopolo/scripts/octopolo_setup.rb +55 -0
  56. data/lib/octopolo/scripts/pivotal_auth.rb +44 -0
  57. data/lib/octopolo/scripts/pull_request.rb +127 -0
  58. data/lib/octopolo/scripts/signoff.rb +85 -0
  59. data/lib/octopolo/scripts/stage_up.rb +26 -0
  60. data/lib/octopolo/scripts/stale_branches.rb +54 -0
  61. data/lib/octopolo/scripts/sync_branch.rb +37 -0
  62. data/lib/octopolo/scripts/tag_release.rb +70 -0
  63. data/lib/octopolo/templates/pull_request_body.erb +24 -0
  64. data/lib/octopolo/user_config.rb +112 -0
  65. data/lib/octopolo/version.rb +3 -0
  66. data/lib/octopolo/week.rb +130 -0
  67. data/octopolo.gemspec +31 -0
  68. data/spec/.DS_Store +0 -0
  69. data/spec/octopolo/cli_spec.rb +310 -0
  70. data/spec/octopolo/config_spec.rb +344 -0
  71. data/spec/octopolo/convenience_wrappers_spec.rb +80 -0
  72. data/spec/octopolo/dated_branch_creator_spec.rb +143 -0
  73. data/spec/octopolo/git_spec.rb +419 -0
  74. data/spec/octopolo/github/commit_spec.rb +59 -0
  75. data/spec/octopolo/github/pull_request_creator_spec.rb +174 -0
  76. data/spec/octopolo/github/pull_request_spec.rb +291 -0
  77. data/spec/octopolo/github/user_spec.rb +65 -0
  78. data/spec/octopolo/github_spec.rb +169 -0
  79. data/spec/octopolo/jira/stor_commenter_spec.rb +30 -0
  80. data/spec/octopolo/pivotal/story_commenter_spec.rb +34 -0
  81. data/spec/octopolo/pivotal_spec.rb +61 -0
  82. data/spec/octopolo/pull_request_merger_spec.rb +144 -0
  83. data/spec/octopolo/renderer_spec.rb +35 -0
  84. data/spec/octopolo/scripts/accept_pull_spec.rb +76 -0
  85. data/spec/octopolo/scripts/compare_release_spec.rb +115 -0
  86. data/spec/octopolo/scripts/deployable_spec.rb +52 -0
  87. data/spec/octopolo/scripts/github_auth_spec.rb +156 -0
  88. data/spec/octopolo/scripts/new_branch_spec.rb +41 -0
  89. data/spec/octopolo/scripts/new_deployable_spec.rb +18 -0
  90. data/spec/octopolo/scripts/new_staging_spec.rb +18 -0
  91. data/spec/octopolo/scripts/octopolo_setup_spec.rb +120 -0
  92. data/spec/octopolo/scripts/pivotal_auth_spec.rb +77 -0
  93. data/spec/octopolo/scripts/pull_request_spec.rb +217 -0
  94. data/spec/octopolo/scripts/signoff_spec.rb +139 -0
  95. data/spec/octopolo/scripts/stage_up_spec.rb +52 -0
  96. data/spec/octopolo/scripts/stale_branches_spec.rb +81 -0
  97. data/spec/octopolo/scripts/sync_branch_spec.rb +57 -0
  98. data/spec/octopolo/scripts/tag_release_spec.rb +108 -0
  99. data/spec/octopolo/user_config_spec.rb +167 -0
  100. data/spec/octopolo_spec.rb +7 -0
  101. data/spec/spec_helper.rb +29 -0
  102. data/spec/support/engine_yard.cache +0 -0
  103. data/spec/support/sample_octopolo.yml +2 -0
  104. data/spec/support/sample_user.yml +2 -0
  105. data/templates/lib.erb +23 -0
  106. data/templates/script.erb +7 -0
  107. data/templates/spec.erb +29 -0
  108. metadata +344 -0
@@ -0,0 +1,419 @@
1
+ require "spec_helper"
2
+ require_relative "../../lib/octopolo/git"
3
+
4
+ module Octopolo
5
+ describe Git do
6
+ let(:cli) { stub(:CLI) }
7
+
8
+ context ".perform(subcommand)" do
9
+ let(:command) { "status" }
10
+
11
+ before { Git.cli = cli }
12
+
13
+ it "performs the given subcommand" do
14
+ cli.should_receive(:perform).with("git #{command}")
15
+ Git.perform command
16
+ end
17
+ end
18
+
19
+ context ".perform_quietly(subcommand)" do
20
+ let(:command) { "status" }
21
+
22
+ before { Git.cli = cli }
23
+
24
+ it "performs the given subcommand quietly" do
25
+ cli.should_receive(:perform_quietly).with("git #{command}")
26
+ Git.perform_quietly command
27
+ end
28
+ end
29
+
30
+ context ".current_branch" do
31
+ let(:name) { "foo" }
32
+ let(:output) { "#{name}\n" }
33
+ let(:nobranch_output) { "#{Git::NO_BRANCH}\n" }
34
+ before { Git.cli = cli }
35
+
36
+ it "performs a command to filter current branch from list of branches" do
37
+ cli.should_receive(:perform_quietly).with("git branch | grep '^* ' | cut -c 3-") { output }
38
+ Git.current_branch.should == name
39
+ end
40
+
41
+ it "raises NotOnBranch if not on a branch" do
42
+ cli.should_receive(:perform_quietly) { nobranch_output }
43
+ expect { Git.current_branch }.to raise_error(Git::NotOnBranch, "Not currently checked out to a particular branch")
44
+ end
45
+
46
+ it "staging and deploy should be reserved branches" do
47
+ Git.stub(:current_branch).and_return "staging.05.12"
48
+ Git.reserved_branch?.should be_true
49
+
50
+ Git.stub(:current_branch).and_return "deployable.05.12"
51
+ Git.reserved_branch?.should be_true
52
+
53
+ Git.stub(:current_branch).and_return "qaready.05.12"
54
+ Git.reserved_branch?.should be_true
55
+ end
56
+
57
+ it "other branches should not be reserved branches" do
58
+ Git.stub(:current_branch).and_return "not_staging.05.12"
59
+ Git.reserved_branch?.should_not be_true
60
+
61
+ Git.stub(:current_branch).and_return "not_deployable.05.12"
62
+ Git.reserved_branch?.should_not be_true
63
+
64
+ Git.stub(:current_branch).and_return "not_qaready.05.12"
65
+ Git.reserved_branch?.should_not be_true
66
+ end
67
+ end
68
+
69
+
70
+
71
+ context ".check_out(branch_name)" do
72
+ let(:name) { "foo" }
73
+
74
+ it "checks out the given branch name" do
75
+ Git.should_receive(:fetch)
76
+ Git.should_receive(:perform).with("checkout #{name}")
77
+ Git.should_receive(:pull)
78
+ Git.should_receive(:current_branch) { name }
79
+ Git.check_out name
80
+ end
81
+
82
+ it "raises an exception if the current branch is not the requested branch afterward" do
83
+ Git.should_receive(:fetch)
84
+ Git.should_receive(:perform)
85
+ Git.should_receive(:pull)
86
+ Git.should_receive(:current_branch) { "other" }
87
+ expect { Git.check_out name }.to raise_error(Git::CheckoutFailed, "Failed to check out '#{name}'")
88
+ end
89
+ end
90
+
91
+ context ".clean?" do
92
+ let(:cmd) { "git status --short" }
93
+
94
+ before { Git.cli = cli }
95
+
96
+ it "returns true if everything is checked in" do
97
+ cli.should_receive(:perform_quietly).with(cmd) { "" }
98
+ Git.should be_clean
99
+ end
100
+
101
+ it "returns false if the index has untracked files" do
102
+ cli.should_receive(:perform_quietly).with(cmd) { "?? foo.txt" }
103
+ Git.should_not be_clean
104
+ end
105
+
106
+ it "returns false if the index has missing files" do
107
+ cli.should_receive(:perform_quietly).with(cmd) { "D foo.txt" }
108
+ Git.should_not be_clean
109
+ end
110
+
111
+ it "returns false if the index has changed files" do
112
+ cli.should_receive(:perform_quietly).with(cmd) { "M foo.txt" }
113
+ Git.should_not be_clean
114
+ end
115
+ end
116
+
117
+ context ".if_clean" do
118
+ let(:custom_message) { "Some other message" }
119
+
120
+ it "performs the block if the git index is clean" do
121
+ Git.should_receive(:clean?) { true }
122
+ Math.should_receive(:log).with(1)
123
+
124
+ Git.if_clean do
125
+ Math.log 1
126
+ end
127
+ end
128
+
129
+ it "does not perform the block if the git index is not clean" do
130
+ Git.should_receive(:clean?) { false }
131
+ Math.should_not_receive(:log)
132
+ Git.should_receive(:alert_dirty_index).with(Git::DEFAULT_DIRTY_MESSAGE)
133
+
134
+ Git.if_clean do
135
+ Math.log 1
136
+ end
137
+ end
138
+
139
+ it "prints a custom message if git index is not clean" do
140
+ Git.should_receive(:clean?) { false }
141
+ Math.should_not_receive(:log)
142
+ Git.should_receive(:alert_dirty_index).with(custom_message)
143
+
144
+ Git.if_clean custom_message do
145
+ Math.log 1
146
+ end
147
+ end
148
+ end
149
+
150
+ context ".alert_dirty_index(message)" do
151
+ let(:message) { "Some message" }
152
+
153
+ before { Git.cli = cli }
154
+
155
+ it "prints the given message and shows the git status" do
156
+ cli.should_receive(:say).with(" ")
157
+ cli.should_receive(:say).with(message)
158
+ cli.should_receive(:say).with(" ")
159
+ Git.should_receive(:perform).with("status")
160
+
161
+ Git.alert_dirty_index message
162
+ end
163
+ end
164
+
165
+ context ".merge(branch_name)" do
166
+ let(:branch_name) { "foo" }
167
+
168
+ it "fetches the latest code and merges the given branch name" do
169
+ Git.should_receive(:if_clean).and_yield
170
+ Git.should_receive(:fetch)
171
+ Git.should_receive(:perform).with("merge --no-ff origin/#{branch_name}")
172
+ Git.should_receive(:clean?) { true }
173
+ Git.should_receive(:push)
174
+
175
+ Git.merge branch_name
176
+ end
177
+
178
+ it "does not push and raises MergeFailed if the merge failed" do
179
+ Git.should_receive(:if_clean).and_yield
180
+ Git.should_receive(:fetch)
181
+ Git.should_receive(:perform).with("merge --no-ff origin/#{branch_name}")
182
+ Git.should_receive(:clean?) { false }
183
+ Git.should_not_receive(:push)
184
+
185
+ expect { Git.merge branch_name }.to raise_error(Git::MergeFailed)
186
+ end
187
+ end
188
+
189
+ context ".fetch" do
190
+ it "fetches and prunes remote branches" do
191
+ Git.should_receive(:perform_quietly).with("fetch --prune")
192
+
193
+ Git.fetch
194
+ end
195
+ end
196
+
197
+ context ".push" do
198
+ let(:branch) { "current_branch" }
199
+
200
+ it "pushes the current branch" do
201
+ Git.stub(current_branch: branch)
202
+ Git.should_receive(:if_clean).and_yield
203
+ Git.should_receive(:perform).with("push origin #{branch}")
204
+
205
+ Git.push
206
+ end
207
+ end
208
+
209
+ context ".pull" do
210
+ it "performs a pull if the index is clean" do
211
+ Git.should_receive(:if_clean).and_yield
212
+ Git.should_receive(:perform).with("pull")
213
+ Git.pull
214
+ end
215
+ end
216
+
217
+ context ".remote_branches" do
218
+ let(:raw_output) { raw_names.join("\n ") }
219
+ let(:raw_names) { %w(origin/foo origin/bar) }
220
+ let(:cleaned_names) { %w(foo bar) }
221
+
222
+ it "prunes the remote branch list and grabs all the branch names" do
223
+ Git.should_receive(:fetch)
224
+ Git.should_receive(:perform_quietly).with("branch --remote") { raw_output }
225
+ Git.remote_branches.should == cleaned_names.sort
226
+ end
227
+ end
228
+
229
+ context ".branches_for branch_type" do
230
+ let(:remote_branches) { [depl1, rando, stage1, depl2].sort }
231
+ let(:depl1) { "deployable.12.20" }
232
+ let(:depl2) { "deployable.11.05" }
233
+ let(:stage1) { "staging.04.05" }
234
+ let(:rando) { "something-else" }
235
+
236
+ before do
237
+ Git.should_receive(:remote_branches) { remote_branches }
238
+ end
239
+
240
+ it "can find deployable branches" do
241
+ deployables = Git.branches_for(Git::DEPLOYABLE_PREFIX)
242
+ deployables.should include depl1
243
+ deployables.should include depl2
244
+ deployables.should == [depl1, depl2].sort
245
+ deployables.count.should == 2
246
+ end
247
+
248
+ it "can find staging branches" do
249
+ stagings = Git.branches_for(Git::STAGING_PREFIX)
250
+ stagings.should include stage1
251
+ stagings.count.should == 1
252
+ end
253
+ end
254
+
255
+ context ".deployable_branch" do
256
+ let(:depl1) { "deployable.12.05" }
257
+ let(:depl2) { "deployable.12.25" }
258
+
259
+ it "returns the last deployable branch" do
260
+ Git.should_receive(:branches_for).with(Git::DEPLOYABLE_PREFIX) { [depl1, depl2] }
261
+ Git.deployable_branch.should == depl2
262
+ end
263
+
264
+ it "raises an exception if none exist" do
265
+ Git.should_receive(:branches_for).with(Git::DEPLOYABLE_PREFIX) { [] }
266
+ expect { Git.deployable_branch.should }.to raise_error(Git::NoBranchOfType, "No #{Git::DEPLOYABLE_PREFIX} branch")
267
+ end
268
+ end
269
+
270
+ context ".staging_branch" do
271
+ let(:stage1) { "stage1" }
272
+ let(:stage2) { "stage2" }
273
+
274
+ it "returns the last staging branch" do
275
+ Git.should_receive(:branches_for).with(Git::STAGING_PREFIX) { [stage1, stage2] }
276
+ Git.staging_branch.should == stage2
277
+ end
278
+
279
+ it "raises an exception if none exist" do
280
+ Git.should_receive(:branches_for).with(Git::STAGING_PREFIX) { [] }
281
+ expect { Git.staging_branch}.to raise_error(Git::NoBranchOfType, "No #{Git::STAGING_PREFIX} branch")
282
+ end
283
+ end
284
+
285
+ context ".qaready_branch" do
286
+ let(:qaready1) { "qaready1" }
287
+ let(:qaready2) { "qaready2" }
288
+
289
+ it "returns the last qaready branch" do
290
+ Git.should_receive(:branches_for).with(Git::QAREADY_PREFIX) { [qaready1, qaready2] }
291
+ Git.qaready_branch.should == qaready2
292
+ end
293
+
294
+ it "raises an exception if none exist" do
295
+ Git.should_receive(:branches_for).with(Git::QAREADY_PREFIX) { [] }
296
+ expect { Git.qaready_branch }.to raise_error(Git::NoBranchOfType, "No #{Git::QAREADY_PREFIX} branch")
297
+ end
298
+ end
299
+
300
+ context ".release_tags" do
301
+ let(:valid1) { "2012.02.28" }
302
+ let(:valid2) { "2012.11.10" }
303
+ let(:invalid) { "foothing" }
304
+ let(:tags) { [valid1, invalid, valid2].join("\n") }
305
+
306
+ it "returns all the tags for releases" do
307
+ Git.should_receive(:perform_quietly).with("tag") { tags }
308
+ release_tags = Git.release_tags
309
+ release_tags.should_not include invalid
310
+ release_tags.should include valid1
311
+ release_tags.should include valid2
312
+ end
313
+ end
314
+
315
+ context ".recent_release_tags" do
316
+ let(:long_list) { Array.new(100, "sometag#{rand(1000)}") } # big-ass list
317
+
318
+ it "returns the last #{Git::RECENT_TAG_LIMIT} tags" do
319
+ Git.should_receive(:release_tags) { long_list }
320
+ tags = Git.recent_release_tags
321
+ tags.count.should == Git::RECENT_TAG_LIMIT
322
+ tags.should == long_list.last(Git::RECENT_TAG_LIMIT)
323
+ end
324
+ end
325
+
326
+ context ".new_branch(new_branch_name, source_branch_name)" do
327
+ let(:new_branch_name) { "foo" }
328
+ let(:source_branch_name) { "bar" }
329
+
330
+ it "creates and pushes a new branch from the source branch" do
331
+ Git.should_receive(:fetch)
332
+ Git.should_receive(:perform).with("branch --no-track #{new_branch_name} origin/#{source_branch_name}")
333
+ Git.should_receive(:check_out).with(new_branch_name)
334
+ Git.should_receive(:perform).with("push --set-upstream origin #{new_branch_name}")
335
+
336
+ Git.new_branch(new_branch_name, source_branch_name)
337
+ end
338
+ end
339
+
340
+ context ".new_tag(tag_name)" do
341
+ let(:tag) { "asdf" }
342
+
343
+ it "creates a new tag with the given name and pushes it" do
344
+ Git.should_receive(:perform).with("tag #{tag}")
345
+ Git.should_receive(:push)
346
+ Git.should_receive(:perform).with("push --tag")
347
+
348
+ Git.new_tag(tag)
349
+ end
350
+ end
351
+
352
+ context ".stale_branches(destination_branch, branches_to_ignore)" do
353
+ let(:ignored) { %w(foo bar) }
354
+ let(:branch_name) { "master" }
355
+ let(:sha) { "asdf123" }
356
+ let(:raw_result) do
357
+ %Q(
358
+ origin/bing
359
+ origin/bang
360
+ )
361
+ end
362
+
363
+ it "checks for stale branches for the given branch, less branches to ignore" do
364
+ Git.should_receive(:fetch)
365
+ Git.should_receive(:stale_branches_to_ignore).with(ignored) { ignored }
366
+ Git.should_receive(:recent_sha).with(branch_name) { sha }
367
+ Git.should_receive(:perform_quietly).with("branch --remote --merged #{sha} | grep -E -v '(foo|bar)'") { raw_result }
368
+
369
+ expect(Git.stale_branches(branch_name, ignored)).to eq(%w(bing bang))
370
+ end
371
+
372
+ it "defaults to master branch and no extra branches to ignore" do
373
+ Git.should_receive(:fetch)
374
+ Git.should_receive(:stale_branches_to_ignore).with([]) { ignored }
375
+ Git.should_receive(:recent_sha).with("master") { sha }
376
+ Git.should_receive(:perform_quietly).with("branch --remote --merged #{sha} | grep -E -v '(foo|bar)'") { raw_result }
377
+
378
+ Git.stale_branches
379
+ end
380
+ end
381
+
382
+ context "#branches_to_ignore(custom_branch_list)" do
383
+ it "ignores some branches by default" do
384
+ expect(Git.send(:stale_branches_to_ignore)).to include "HEAD"
385
+ expect(Git.send(:stale_branches_to_ignore)).to include "master"
386
+ expect(Git.send(:stale_branches_to_ignore)).to include "staging"
387
+ expect(Git.send(:stale_branches_to_ignore)).to include "deployable"
388
+ end
389
+
390
+ it "accepts an optional list of additional branches to ignore" do
391
+ expect(Git.send(:stale_branches_to_ignore, ["foo"])).to include "HEAD"
392
+ expect(Git.send(:stale_branches_to_ignore, ["foo"])).to include "master"
393
+ expect(Git.send(:stale_branches_to_ignore, ["foo"])).to include "staging"
394
+ expect(Git.send(:stale_branches_to_ignore, ["foo"])).to include "deployable"
395
+ expect(Git.send(:stale_branches_to_ignore, ["foo"])).to include "foo"
396
+ end
397
+ end
398
+
399
+ context "#recent_sha(branch_name)" do
400
+ let(:branch_name) { "foo" }
401
+ let(:raw_sha) { "asdf123\n" }
402
+
403
+ it "grabs the SHA of the given branch from 1 day ago" do
404
+ Git.should_receive(:perform_quietly).with("rev-list `git rev-parse remotes/origin/#{branch_name} --before=1.day.ago` --max-count=1") { raw_sha }
405
+ expect(Git.send(:recent_sha, branch_name)).to eq("asdf123")
406
+ end
407
+ end
408
+
409
+ context ".delete_branch(branch_name)" do
410
+ let(:branch_name) { "foo" }
411
+
412
+ it "leverages git-extra's delete-branch command" do
413
+ Git.should_receive(:perform).with("push origin :#{branch_name}")
414
+ Git.should_receive(:perform).with("branch -D #{branch_name}")
415
+ Git.delete_branch branch_name
416
+ end
417
+ end
418
+ end
419
+ end
@@ -0,0 +1,59 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/octopolo/github/commit"
3
+
4
+ module Octopolo
5
+ module GitHub
6
+ describe Commit do
7
+ context ".new" do
8
+ let(:commit_data) { stub }
9
+
10
+ it "remembers the commit data from GitHub API" do
11
+ commit = Commit.new commit_data
12
+ commit.commit_data.should == commit_data
13
+ end
14
+ end
15
+
16
+ context "#author_name" do
17
+ let(:commit) do
18
+ Commit.new stub
19
+ end
20
+
21
+ it "fetches the author name from the author" do
22
+ commit.stub(:author => stub(:author_name => "pbyrne"))
23
+ commit.author_name.should == "pbyrne"
24
+ end
25
+ end
26
+
27
+ context "#author" do
28
+ let(:commit_data) do
29
+ stub(:author => stub(:login => "pbyrne"))
30
+ end
31
+
32
+ it "fetches the User from github" do
33
+ commit = Commit.new commit_data
34
+ GitHub::User.should_receive(:new).with("pbyrne")
35
+ commit.author
36
+ end
37
+
38
+ it "gracefully handles a commit without an author" do
39
+ commit = Commit.new author: nil
40
+ User.should_receive(:new).with(GitHub::UNKNOWN_USER)
41
+ commit.author
42
+ end
43
+ end
44
+
45
+ context ".for_pull_request pull_request" do
46
+ let(:pull_request) { stub(repo_name: "foo/bar", number: 123) }
47
+ let(:raw_commit1) { stub }
48
+ let(:raw_commits) { [raw_commit1] }
49
+ let(:wrapper_commit) { stub }
50
+
51
+ it "fetches from octokit and returns Commit wrappers" do
52
+ GitHub.should_receive(:pull_request_commits).with(pull_request.repo_name, pull_request.number) { raw_commits }
53
+ Commit.should_receive(:new).with(raw_commit1) { wrapper_commit }
54
+ Commit.for_pull_request(pull_request).should == [wrapper_commit]
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end