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,18 @@
1
+ require "spec_helper"
2
+ require "octopolo/scripts/new_deployable"
3
+
4
+ module Octopolo
5
+ module Scripts
6
+ describe NewDeployable do
7
+ subject { NewDeployable.new }
8
+
9
+ context "#execute" do
10
+ it "delegates the work to DatedBranchCreator" do
11
+ DatedBranchCreator.should_receive(:perform).with(Git::DEPLOYABLE_PREFIX)
12
+ subject.execute
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+ require "octopolo/scripts/new_staging"
3
+
4
+ module Octopolo
5
+ module Scripts
6
+ describe NewStaging do
7
+ subject { NewStaging.new }
8
+
9
+ context "#execute" do
10
+ it "delegates to DatedBranchCreator to create the branch" do
11
+ DatedBranchCreator.should_receive(:perform).with(Git::STAGING_PREFIX)
12
+ subject.execute
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,120 @@
1
+ require "spec_helper"
2
+ require "octopolo/scripts/octopolo_setup"
3
+
4
+ module Octopolo
5
+ module Scripts
6
+ describe OctopoloSetup do
7
+ let(:config) { stub(:config) }
8
+ let(:cli) { stub(:cli) }
9
+ let(:user_config) { stub(:user_config) }
10
+
11
+ subject { OctopoloSetup }
12
+
13
+ before do
14
+ subject.config = config
15
+ subject.cli = cli
16
+ subject.user_config = user_config
17
+ end
18
+
19
+ context ".invoke" do
20
+ it "ensures that git extras is installed" do
21
+ subject.should_receive(:verify_git_extras_setup)
22
+ subject.should_receive(:verify_user_setup)
23
+
24
+ subject.invoke
25
+ end
26
+ end
27
+
28
+ context ".verify_git_extras_setup" do
29
+ it "installs git-extras if it is not already installed" do
30
+ subject.should_receive(:git_extras_installed?) { false }
31
+ subject.should_receive(:install_git_extras)
32
+ subject.verify_git_extras_setup
33
+ end
34
+
35
+ it "does nothing if git-extras is already installed" do
36
+ subject.should_receive(:git_extras_installed?) { true }
37
+ subject.should_receive(:install_git_extras).never
38
+ subject.verify_git_extras_setup
39
+ end
40
+ end
41
+
42
+ context ".git_extras_installed?" do
43
+ it "returns true if git-extras command exists" do
44
+ cli.should_receive(:perform).with("which git-extras", false) { "/usr/local/lib/git-extras" }
45
+ subject.git_extras_installed?.should be_true
46
+ end
47
+
48
+ it "returns false if git-extras command does not exist" do
49
+ cli.should_receive(:perform).with("which git-extras", false) { "" }
50
+ subject.git_extras_installed?.should be_false
51
+ end
52
+ end
53
+
54
+ context ".install_git_extras" do
55
+ it "installs git-extras through homebrew" do
56
+ cli.should_receive(:say).with("Updating Homebrew to ensure latest git-extras formula.")
57
+ cli.should_receive(:perform).with("brew update")
58
+ cli.should_receive(:say).with("Installing git-extras")
59
+ cli.should_receive(:perform).with("brew install git-extras")
60
+
61
+ subject.install_git_extras
62
+ end
63
+ end
64
+
65
+ context ".verify_user_setup" do
66
+ it "verifies that the user's full name and github credentials are set up" do
67
+ subject.should_receive(:verify_user_full_name)
68
+ subject.should_receive(:verify_user_github_credentials)
69
+
70
+ subject.verify_user_setup
71
+ end
72
+ end
73
+
74
+ context ".verify_user_full_name" do
75
+ let(:name) { "Joe Person" }
76
+
77
+ it "does nothing if the full name is configured" do
78
+ user_config.stub(full_name: name)
79
+ cli.should_receive(:say).with("Full name '#{name}' already configured.")
80
+ cli.should_receive(:prompt).never
81
+ user_config.should_receive(:full_name=).never
82
+
83
+ subject.verify_user_full_name
84
+ end
85
+
86
+ it "asks and stores the full name if not configured" do
87
+ user_config.stub(full_name: ENV["USER"])
88
+ cli.should_receive(:prompt).with("Your full name:") { name }
89
+ user_config.should_receive(:full_name=).with(name)
90
+
91
+ subject.verify_user_full_name
92
+ end
93
+ end
94
+
95
+ context ".verify_user_github_credentials" do
96
+ it "does nothing if github credentials are set" do
97
+ GitHub.should_receive(:check_connection)
98
+ GithubAuth.should_not_receive(:invoke)
99
+ cli.should_receive(:say).with("Successfully configured API token.")
100
+
101
+ subject.verify_user_github_credentials
102
+ end
103
+
104
+ it "prompts to set up authentication otherwise" do
105
+ GitHub.should_receive(:check_connection).and_raise(GitHub::BadCredentials.new "token rejected")
106
+ cli.should_receive(:say).with("token rejected")
107
+ GithubAuth.should_not_receive(:invoke)
108
+
109
+ subject.verify_user_github_credentials
110
+ end
111
+
112
+ it "does nothing if it gets TryAgain" do
113
+ GitHub.should_receive(:check_connection).and_raise(GitHub::TryAgain.new "no token. make one and try again.")
114
+ cli.should_receive(:say).with("no token. make one and try again.")
115
+ expect { subject.verify_user_github_credentials }.to_not raise_error
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,77 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/octopolo/scripts/pivotal_auth"
3
+
4
+ module Octopolo
5
+ module Scripts
6
+ describe PivotalAuth do
7
+ # stub any attributes in the config that you need
8
+ let(:user_config) { stub(:user_config) }
9
+ let(:cli) { stub(:cli) }
10
+ let(:email) { "example@example.com" }
11
+ let(:password) { "don't tell anyone" }
12
+ let(:client) { stub(:pivotal_client) }
13
+ let(:token) { "deadbeef" }
14
+
15
+ subject { PivotalAuth.new }
16
+
17
+ before do
18
+ subject.cli = cli
19
+ subject.user_config = user_config
20
+ end
21
+
22
+ context "#execute" do
23
+ let(:bad_credentials_message) { "OMG TYPE IT RIGHT" }
24
+
25
+ it "asks for username and password, generates the token, and writes to disk" do
26
+ subject.should_receive(:ask_credentials)
27
+ subject.should_receive(:request_token)
28
+ subject.should_receive(:store_token)
29
+
30
+ subject.execute
31
+ end
32
+
33
+ it "gracefully handles invalid credentials" do
34
+ subject.should_receive(:ask_credentials)
35
+ subject.should_receive(:request_token).and_raise(Pivotal::BadCredentials.new(bad_credentials_message))
36
+ subject.should_not_receive(:store_token)
37
+ cli.should_receive(:say).with(bad_credentials_message)
38
+
39
+ expect { subject.execute }.to_not raise_error
40
+ end
41
+ end
42
+
43
+ context "#ask_credentials" do
44
+ it "asks for and captures the user's Pivotal Tracker credentials" do
45
+ cli.should_receive(:prompt).with("Your Pivotal Tracker email: ") { email }
46
+ cli.should_receive(:prompt_secret).with("Your Pivotal Tracker password (never stored): ") { password }
47
+ subject.send(:ask_credentials)
48
+ expect(subject.email).to eq(email)
49
+ expect(subject.password).to eq(password)
50
+ end
51
+ end
52
+
53
+ context "#request_token" do
54
+ before do
55
+ subject.email = email
56
+ subject.password = password
57
+ end
58
+
59
+ it "leverages the Pivotal Tracker client to fetch the token via API" do
60
+ Pivotal::Client.should_receive(:fetch_token).with(email, password) { token }
61
+ subject.send(:request_token)
62
+ expect(subject.token).to eq(token)
63
+ end
64
+ end
65
+
66
+ context "#store_token" do
67
+ it "stores the token in the config" do
68
+ subject.token = token
69
+ user_config.should_receive(:set).with(:pivotal_token, token)
70
+ subject.send(:store_token)
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ # vim: set ft=ruby : #
@@ -0,0 +1,217 @@
1
+ require "spec_helper"
2
+ require_relative "../../../lib/octopolo/scripts/pull_request"
3
+
4
+ module Octopolo
5
+ module Scripts
6
+ describe PullRequest do
7
+ let(:config) do
8
+ stub(:config, {
9
+ deploy_branch: "production",
10
+ github_repo: "tstmedia/foo",
11
+ use_pivotal_tracker: true,
12
+ use_jira: true
13
+ })
14
+ end
15
+ let(:cli) { stub(:cli) }
16
+ let(:git) { stub(:Git, current_branch: "bug-123-something", reserved_branch?: false) }
17
+ let(:pull_request_url) { "http://github.com/tstmedia/octopolo/pull/0" }
18
+ let(:pull_request) { stub(:pull_request) }
19
+
20
+ subject { PullRequest.new }
21
+
22
+ before do
23
+ PullRequest.any_instance.stub({
24
+ :cli => cli,
25
+ :config => config,
26
+ :git => git
27
+ })
28
+ end
29
+
30
+ context "#new" do
31
+ it "defaults the destination branch to the deploy branch" do
32
+ expect(subject.destination_branch).to eq(config.deploy_branch)
33
+ end
34
+
35
+ it "accepts an alternate destination branch" do
36
+ expect(PullRequest.new('foo').destination_branch).to eq("foo")
37
+ end
38
+ end
39
+
40
+ context "#execute" do
41
+ it "if connected to GitHub, asks some questions, creates the pull request, and opens it" do
42
+ GitHub.should_receive(:connect).and_yield
43
+ subject.should_receive(:ask_questionaire)
44
+ subject.should_receive(:create_pull_request)
45
+ subject.should_receive(:update_pivotal)
46
+ subject.should_receive(:update_jira)
47
+ subject.should_receive(:open_pull_request)
48
+
49
+ subject.execute
50
+ end
51
+
52
+ it "if not connected to GitHub, does nothing" do
53
+ GitHub.should_receive(:connect) # and not yield, no github credentials
54
+ expect { subject.execute }.to_not raise_error
55
+ end
56
+ end
57
+
58
+ context "#ask_questionaire" do
59
+ it "asks appropriate questions to create a pull request" do
60
+ subject.should_receive(:announce)
61
+ subject.should_receive(:ask_title)
62
+ subject.should_receive(:ask_pivotal_ids)
63
+ subject.should_receive(:ask_jira_ids)
64
+
65
+ subject.send(:ask_questionaire)
66
+ end
67
+
68
+ context "when checking for staging branch" do
69
+ before do
70
+ subject.stub(:announce)
71
+ subject.stub(:ask_title)
72
+ subject.stub(:ask_pivotal_ids)
73
+ subject.stub(:ask_jira_ids)
74
+ end
75
+ it "exits when branch name is reserved" do
76
+ subject.git.stub(:reserved_branch?).and_return true
77
+
78
+ subject.should_receive(:alert_reserved_and_exit)
79
+
80
+ subject.send(:ask_questionaire)
81
+ end
82
+
83
+ it "should not ask if the branch is not staging" do
84
+ subject.git.stub(:reserved_branch?).and_return false
85
+
86
+ subject.should_not_receive(:alert_reserved_and_exit)
87
+
88
+ subject.send(:ask_questionaire)
89
+ end
90
+ end
91
+ end
92
+
93
+ context "#announce" do
94
+ before do
95
+ subject.destination_branch = "foo"
96
+ end
97
+
98
+ it "displays information about the pull request to be created" do
99
+ cli.should_receive(:say).with("Preparing a pull request for #{config.github_repo}/#{git.current_branch} to #{config.github_repo}/#{subject.destination_branch}.")
100
+ subject.send(:announce)
101
+ end
102
+ end
103
+
104
+ context "#ask_title" do
105
+ let(:title) { "title" }
106
+
107
+ it "asks for and captures a title for the pull request" do
108
+ cli.should_receive(:prompt).with("Title:") { title }
109
+ subject.send(:ask_title)
110
+ expect(subject.title).to eq(title)
111
+ end
112
+ end
113
+
114
+ context "#ask_pivotal_ids" do
115
+ let(:ids_with_whitespace) { "123 456" }
116
+ let(:ids_with_commas) { "234, 567" }
117
+
118
+ it "asks for and captures IDs for related pivotal tasks" do
119
+ cli.should_receive(:prompt).with("Pivotal Tracker story ID(s):") { ids_with_whitespace }
120
+ subject.send(:ask_pivotal_ids)
121
+ expect(subject.pivotal_ids).to eq(%w(123 456))
122
+ end
123
+
124
+ it "asks for and captures IDs with commas" do
125
+ cli.should_receive(:prompt).with("Pivotal Tracker story ID(s):") { ids_with_commas }
126
+ subject.send(:ask_pivotal_ids)
127
+ expect(subject.pivotal_ids).to eq(%w(234 567))
128
+ end
129
+
130
+ it "sets to an empty array if not provided an ansswer" do
131
+ cli.should_receive(:prompt).with("Pivotal Tracker story ID(s):") { "" }
132
+ subject.send(:ask_pivotal_ids)
133
+ expect(subject.pivotal_ids).to eq([])
134
+ end
135
+ end
136
+
137
+ context "#create_pull_request" do
138
+ let(:attributes) { stub(:hash) }
139
+
140
+ before do
141
+ subject.stub(:pull_request_attributes) { attributes }
142
+ end
143
+
144
+ it "creates and stores the pull request" do
145
+ GitHub::PullRequest.should_receive(:create).with(config.github_repo, attributes) { pull_request }
146
+ subject.send(:create_pull_request)
147
+ expect(subject.pull_request).to eq(pull_request)
148
+ end
149
+ end
150
+
151
+ # GitHub::PullRequest.create config.github_repo, answers.merge({
152
+ # destination_branch: config.deploy_branch,
153
+ # source_branch: Git.current_branch,
154
+ # })
155
+ context "#pull_request_attributes" do
156
+ before do
157
+ subject.title = "title"
158
+ subject.destination_branch = "some-branch",
159
+ subject.pivotal_ids = %w(123)
160
+ end
161
+
162
+ it "combines the anssers with a handful of deault values" do
163
+ subject.send(:pull_request_attributes).should == {
164
+ title: subject.title,
165
+ destination_branch: subject.destination_branch,
166
+ source_branch: git.current_branch,
167
+ pivotal_ids: subject.pivotal_ids,
168
+ jira_ids: subject.jira_ids,
169
+ }
170
+ end
171
+ end
172
+
173
+ context "#update_pivotal" do
174
+ before do
175
+ subject.pivotal_ids = %w(123 234)
176
+ subject.pull_request = stub(url: "test")
177
+ end
178
+ let(:story_commenter) { stub(perform: true) }
179
+
180
+ it "creates a story commenter for each pivotal_id" do
181
+ Pivotal::StoryCommenter.should_receive(:new).with("123", "test") { story_commenter }
182
+ Pivotal::StoryCommenter.should_receive(:new).with("234", "test") { story_commenter }
183
+ subject.send(:update_pivotal)
184
+ end
185
+
186
+ end
187
+
188
+ context "#update_jira" do
189
+ before do
190
+ subject.jira_ids = %w(123 234)
191
+ subject.pull_request = stub(url: "test")
192
+ end
193
+ let(:story_commenter) { stub(perform: true) }
194
+
195
+ it "creates a story commenter for each pivotal_id" do
196
+ Jira::StoryCommenter.should_receive(:new).with("123", "test") { story_commenter }
197
+ Jira::StoryCommenter.should_receive(:new).with("234", "test") { story_commenter }
198
+ subject.send(:update_jira)
199
+ end
200
+
201
+ end
202
+
203
+ context "#open_pull_request" do
204
+ before do
205
+ subject.pull_request = pull_request
206
+ pull_request.stub(:url) { pull_request_url }
207
+ end
208
+
209
+ it "copies the pull request's URL to the clipboard and opens it in the browser" do
210
+ cli.should_receive(:copy_to_clipboard) { pull_request.url}
211
+ cli.should_receive(:open) { pull_request.url }
212
+ subject.send(:open_pull_request)
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end