github-pivotal-flow 0.0.8 → 0.0.9
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.
- checksums.yaml +4 -4
- data/lib/github_pivotal_flow.rb +16 -0
- data/lib/github_pivotal_flow/command.rb +3 -14
- data/lib/github_pivotal_flow/configuration.rb +106 -42
- data/lib/github_pivotal_flow/finish.rb +1 -1
- data/lib/github_pivotal_flow/git.rb +22 -0
- data/lib/github_pivotal_flow/github_api.rb +10 -2
- data/lib/github_pivotal_flow/project.rb +29 -13
- data/lib/github_pivotal_flow/start.rb +4 -8
- data/lib/github_pivotal_flow/story.rb +68 -51
- data/lib/github_pivotal_flow/version.rb +3 -0
- data/spec/github_pivotal_flow/command_spec.rb +31 -0
- data/spec/github_pivotal_flow/configuration_spec.rb +76 -65
- data/spec/github_pivotal_flow/finish_spec.rb +7 -3
- data/spec/github_pivotal_flow/git_spec.rb +1 -1
- data/spec/github_pivotal_flow/start_spec.rb +10 -7
- data/spec/github_pivotal_flow/story_spec.rb +44 -34
- metadata +10 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 423519e9574e2048faaaec36c2583c3ec79273eb
|
4
|
+
data.tar.gz: 03fb90bd37c4fb777432e1e9c6c9732ac90b25cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2aa8e365a3df85d33431476ca8c8b93bf022edce02ec2f2655f9e32e547469fc6ac36aa9978928b257c09f925505c7d0ce69f77cc24f033b28f0237f9826fa44
|
7
|
+
data.tar.gz: e6a57b30856b8cfffad4b65b6f34e51109a6240b666e1c4cd1026b2275e2e7b0f40a0dfc3f7102432ece7a5ca4354b86f6fc0c1ae3a6a905b1b9e75dded30bf3
|
data/lib/github_pivotal_flow.rb
CHANGED
@@ -20,6 +20,22 @@ require 'pivotal-tracker'
|
|
20
20
|
|
21
21
|
require File.join('core_ext', 'object', 'blank')
|
22
22
|
|
23
|
+
module GithubPivotalFlow
|
24
|
+
KEY_USER_NAME = 'user.name'.freeze
|
25
|
+
KEY_API_TOKEN = 'pivotal.api-token'.freeze
|
26
|
+
KEY_PROJECT_ID = 'pivotal.project-id'.freeze
|
27
|
+
KEY_STORY_ID = 'pivotal-story-id'.freeze
|
28
|
+
KEY_FEATURE_PREFIX = 'gitflow.prefix.feature'.freeze
|
29
|
+
KEY_HOTFIX_PREFIX = 'gitflow.prefix.hotfix'.freeze
|
30
|
+
KEY_RELEASE_PREFIX = 'gitflow.prefix.release'.freeze
|
31
|
+
KEY_DEVELOPMENT_BRANCH = 'gitflow.branch.develop'.freeze
|
32
|
+
KEY_MASTER_BRANCH = 'gitflow.branch.master'.freeze
|
33
|
+
KEY_GITHUB_USERNAME = 'github.username'.freeze
|
34
|
+
KEY_GITHUB_API_TOKEN = 'github.api-token'.freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
require File.join('github_pivotal_flow', 'version')
|
38
|
+
|
23
39
|
require File.join('github_pivotal_flow', 'shell')
|
24
40
|
require File.join('github_pivotal_flow', 'git')
|
25
41
|
require File.join('github_pivotal_flow', 'project')
|
@@ -15,21 +15,10 @@ module GithubPivotalFlow
|
|
15
15
|
@options = {}
|
16
16
|
args = parse_argv(*args)
|
17
17
|
@options[:args] = args
|
18
|
-
|
19
|
-
@repository_root = Git.repository_root
|
20
18
|
@configuration = Configuration.new(@options)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
@project = PivotalTracker::Project.find @configuration.project_id
|
26
|
-
|
27
|
-
# Make sure that all the git flow config options are set up
|
28
|
-
@configuration.development_branch
|
29
|
-
@configuration.master_branch
|
30
|
-
@configuration.feature_prefix
|
31
|
-
@configuration.hotfix_prefix
|
32
|
-
@configuration.release_prefix
|
19
|
+
# Validate the configuration to make sure everything is set up correctly
|
20
|
+
@configuration.validate
|
21
|
+
@project = @configuration.project
|
33
22
|
end
|
34
23
|
|
35
24
|
# The main entry point to the command's execution
|
@@ -9,6 +9,31 @@ module GithubPivotalFlow
|
|
9
9
|
@github_password_cache = {}
|
10
10
|
end
|
11
11
|
|
12
|
+
def validate
|
13
|
+
repository_root
|
14
|
+
user_name
|
15
|
+
ensure_github_api_token
|
16
|
+
ensure_pivotal_api_token
|
17
|
+
ensure_gitflow_config
|
18
|
+
project.config = self
|
19
|
+
return true
|
20
|
+
end
|
21
|
+
|
22
|
+
def repository_root
|
23
|
+
@repository_root ||= Git.repository_root
|
24
|
+
end
|
25
|
+
|
26
|
+
def user_name
|
27
|
+
user_name = Git.get_config(KEY_USER_NAME, :inherited).strip
|
28
|
+
if user_name.blank?
|
29
|
+
user_name = ask('Github user name (Should be the same as in your Pivotal profile): ').strip
|
30
|
+
Git.set_config(KEY_USER_NAME, user_name, :local) unless user_name.blank?
|
31
|
+
puts
|
32
|
+
end
|
33
|
+
|
34
|
+
user_name
|
35
|
+
end
|
36
|
+
|
12
37
|
# Returns the user's Pivotal Tracker API token. If this token has not been
|
13
38
|
# configured, prompts the user for the value. The value is checked for in
|
14
39
|
# the _inherited_ Git configuration, but is stored in the _global_ Git
|
@@ -18,15 +43,33 @@ module GithubPivotalFlow
|
|
18
43
|
def api_token
|
19
44
|
api_token = @options[:api_token] || Git.get_config(KEY_API_TOKEN, :inherited)
|
20
45
|
|
21
|
-
if api_token.
|
46
|
+
if api_token.blank?
|
22
47
|
api_token = ask('Pivotal API Token (found at https://www.pivotaltracker.com/profile): ').strip
|
23
|
-
Git.set_config
|
48
|
+
Git.set_config(KEY_API_TOKEN, api_token, :local) unless api_token.blank?
|
24
49
|
puts
|
25
50
|
end
|
26
51
|
|
27
52
|
api_token
|
28
53
|
end
|
29
54
|
|
55
|
+
def ensure_pivotal_api_token
|
56
|
+
PivotalTracker::Client.use_ssl = true
|
57
|
+
while (PivotalTracker::Client.token = self.api_token).blank? || PivotalTracker::Project.all.empty?
|
58
|
+
puts "No projects found."
|
59
|
+
clear_pivotal_api_token!
|
60
|
+
end
|
61
|
+
return true
|
62
|
+
rescue RestClient::Unauthorized => e
|
63
|
+
puts "Invalid Pivotal token"
|
64
|
+
clear_pivotal_api_token!
|
65
|
+
retry
|
66
|
+
end
|
67
|
+
|
68
|
+
def clear_pivotal_api_token!
|
69
|
+
PivotalTracker::Client.token = nil
|
70
|
+
Git.delete_config(KEY_API_TOKEN, :local)
|
71
|
+
end
|
72
|
+
|
30
73
|
# Returns the Pivotal Tracker project id for this repository. If this id
|
31
74
|
# has not been configuration, prompts the user for the value. The value is
|
32
75
|
# checked for in the _inherited_ Git configuration, but is stored in the
|
@@ -45,18 +88,22 @@ module GithubPivotalFlow
|
|
45
88
|
end
|
46
89
|
end
|
47
90
|
|
48
|
-
Git.set_config
|
91
|
+
Git.set_config(KEY_PROJECT_ID, project_id, :local)
|
49
92
|
puts
|
50
93
|
end
|
51
94
|
|
52
95
|
project_id
|
53
96
|
end
|
54
97
|
|
55
|
-
|
98
|
+
def project
|
99
|
+
@project ||= Project.new(config: self)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the story associated with the branch
|
56
103
|
#
|
57
|
-
# @
|
58
|
-
|
59
|
-
|
104
|
+
# @return [Story] the story associated with the current development branch
|
105
|
+
def story
|
106
|
+
return @story if @story
|
60
107
|
story_id = Git.get_config(KEY_STORY_ID, :branch)
|
61
108
|
if story_id.blank? && (matchdata = /^[a-z0-9_\-]+\/(\d+)(-[a-z0-9_\-]+)?$/i.match(Git.current_branch))
|
62
109
|
story_id = matchdata[1]
|
@@ -67,7 +114,7 @@ module GithubPivotalFlow
|
|
67
114
|
Git.set_config(KEY_STORY_ID, story_id, :branch) unless story_id.blank?
|
68
115
|
end
|
69
116
|
return nil if story_id.blank?
|
70
|
-
Story.new(project.stories.find(story_id.to_i), :
|
117
|
+
return (@story = Story.new(project, project.stories.find(story_id.to_i), branch_name: Git.current_branch))
|
71
118
|
end
|
72
119
|
|
73
120
|
# Stores the story associated with the current development branch
|
@@ -86,7 +133,6 @@ module GithubPivotalFlow
|
|
86
133
|
feature_prefix = 'feature/' if feature_prefix.blank?
|
87
134
|
feature_prefix = "#{feature_prefix}/" unless feature_prefix[-1,1] == '/'
|
88
135
|
Git.set_config KEY_FEATURE_PREFIX, feature_prefix, :local
|
89
|
-
puts
|
90
136
|
end
|
91
137
|
|
92
138
|
feature_prefix
|
@@ -100,7 +146,6 @@ module GithubPivotalFlow
|
|
100
146
|
hotfix_prefix = 'hotfix/' if hotfix_prefix.blank?
|
101
147
|
hotfix_prefix = "#{hotfix_prefix}/" unless hotfix_prefix[-1,1] == '/'
|
102
148
|
Git.set_config KEY_HOTFIX_PREFIX, hotfix_prefix, :local
|
103
|
-
puts
|
104
149
|
end
|
105
150
|
|
106
151
|
hotfix_prefix
|
@@ -113,8 +158,7 @@ module GithubPivotalFlow
|
|
113
158
|
release_prefix = ask('Please enter your git-flow release branch prefix: [release/]').strip
|
114
159
|
release_prefix = 'release' if release_prefix.blank?
|
115
160
|
release_prefix = "#{release_prefix}/" unless release_prefix[-1,1] == '/'
|
116
|
-
Git.set_config
|
117
|
-
puts
|
161
|
+
Git.set_config(KEY_RELEASE_PREFIX, release_prefix, :local)
|
118
162
|
end
|
119
163
|
|
120
164
|
release_prefix
|
@@ -125,9 +169,8 @@ module GithubPivotalFlow
|
|
125
169
|
|
126
170
|
if development_branch.empty?
|
127
171
|
development_branch = ask('Please enter your git-flow development branch name: [development]').strip
|
128
|
-
development_branch = 'development' if development_branch.
|
172
|
+
development_branch = 'development' if development_branch.blank?
|
129
173
|
Git.set_config KEY_DEVELOPMENT_BRANCH, development_branch, :local
|
130
|
-
puts
|
131
174
|
end
|
132
175
|
Git.ensure_branch_exists(development_branch)
|
133
176
|
|
@@ -139,20 +182,27 @@ module GithubPivotalFlow
|
|
139
182
|
|
140
183
|
if master_branch.blank?
|
141
184
|
master_branch = ask('Please enter your git-flow production branch name: [master]').strip
|
142
|
-
master_branch = 'master' if master_branch.
|
185
|
+
master_branch = 'master' if master_branch.blank?
|
143
186
|
Git.set_config KEY_MASTER_BRANCH, master_branch, :local
|
144
|
-
puts
|
145
187
|
end
|
146
188
|
Git.ensure_branch_exists(master_branch)
|
147
189
|
|
148
190
|
master_branch
|
149
191
|
end
|
150
192
|
|
151
|
-
def
|
152
|
-
|
193
|
+
def ensure_gitflow_config
|
194
|
+
development_branch && master_branch && feature_prefix && hotfix_prefix && release_prefix
|
195
|
+
end
|
196
|
+
|
197
|
+
def github_client
|
198
|
+
@ghclient ||= GitHubAPI.new(self, :app_url => 'http://github.com/roomorama/github-pivotal-flow')
|
153
199
|
end
|
154
200
|
|
155
|
-
def
|
201
|
+
def github_host
|
202
|
+
project.host
|
203
|
+
end
|
204
|
+
|
205
|
+
def github_username(host = github_host)
|
156
206
|
return ENV['GITHUB_USER'] unless ENV['GITHUB_USER'].to_s.blank?
|
157
207
|
github_username = Git.get_config KEY_GITHUB_USERNAME, :inherited
|
158
208
|
if github_username.blank?
|
@@ -162,27 +212,54 @@ module GithubPivotalFlow
|
|
162
212
|
github_username
|
163
213
|
end
|
164
214
|
|
165
|
-
def github_username=(
|
166
|
-
Git.set_config KEY_GITHUB_USERNAME,
|
215
|
+
def github_username=(username)
|
216
|
+
Git.set_config KEY_GITHUB_USERNAME, username, :local unless username.blank?
|
167
217
|
end
|
168
218
|
|
169
|
-
def github_password(host, user)
|
170
|
-
return ENV['GITHUB_PASSWORD'] unless ENV['GITHUB_PASSWORD'].to_s.
|
171
|
-
|
219
|
+
def github_password(host = github_host, user = nil)
|
220
|
+
return ENV['GITHUB_PASSWORD'] unless ENV['GITHUB_PASSWORD'].to_s.blank?
|
221
|
+
user ||= github_username(host)
|
222
|
+
@github_password_cache["#{user}"] ||= ask_github_password(user)
|
172
223
|
end
|
173
224
|
|
174
|
-
def
|
225
|
+
def clear_github_auth_data!
|
226
|
+
@github_password_cache = {}
|
227
|
+
Git.delete_config(KEY_GITHUB_USERNAME, :local)
|
228
|
+
end
|
229
|
+
|
230
|
+
def ensure_github_api_token
|
231
|
+
begin
|
232
|
+
repo_found = github_client.repo_exists?(project)
|
233
|
+
end while github_api_token.blank?
|
234
|
+
raise("Could not find github project") unless repo_found
|
235
|
+
rescue Net::HTTPServerException => e
|
236
|
+
case e.response.code
|
237
|
+
when '401'
|
238
|
+
say "Invalid username/password combination. Please try again:"
|
239
|
+
else
|
240
|
+
say "Unknown error (#{e.response.code}). Please try again: "
|
241
|
+
end
|
242
|
+
clear_github_auth_data!
|
243
|
+
retry
|
244
|
+
end
|
245
|
+
|
246
|
+
def github_api_token(host = nil, user = nil)
|
247
|
+
host ||= github_host
|
248
|
+
user ||= github_username
|
175
249
|
github_token = Git.get_config KEY_GITHUB_API_TOKEN, :inherited
|
176
250
|
if github_token.blank?
|
177
|
-
|
178
|
-
|
251
|
+
if block_given?
|
252
|
+
github_token = yield
|
253
|
+
end
|
254
|
+
Git.set_config(KEY_GITHUB_API_TOKEN, github_token, :global) unless github_token.blank?
|
179
255
|
end
|
180
256
|
github_token
|
181
257
|
end
|
182
258
|
|
183
259
|
# special prompt that has hidden input
|
184
|
-
def
|
185
|
-
|
260
|
+
def ask_github_password(username = nil)
|
261
|
+
username ||= github_username
|
262
|
+
print "Github password for #{username} (never stored): "
|
186
263
|
if $stdin.tty?
|
187
264
|
password = askpass
|
188
265
|
puts ''
|
@@ -247,18 +324,5 @@ module GithubPivotalFlow
|
|
247
324
|
URI.parse proxy
|
248
325
|
end
|
249
326
|
end
|
250
|
-
|
251
|
-
private
|
252
|
-
|
253
|
-
KEY_API_TOKEN = 'pivotal.api-token'.freeze
|
254
|
-
KEY_PROJECT_ID = 'pivotal.project-id'.freeze
|
255
|
-
KEY_STORY_ID = 'pivotal-story-id'.freeze
|
256
|
-
KEY_FEATURE_PREFIX = 'gitflow.prefix.feature'.freeze
|
257
|
-
KEY_HOTFIX_PREFIX = 'gitflow.prefix.hotfix'.freeze
|
258
|
-
KEY_RELEASE_PREFIX = 'gitflow.prefix.release'.freeze
|
259
|
-
KEY_DEVELOPMENT_BRANCH = 'gitflow.branch.develop'.freeze
|
260
|
-
KEY_MASTER_BRANCH = 'gitflow.branch.master'.freeze
|
261
|
-
KEY_GITHUB_USERNAME = 'github.username'.freeze
|
262
|
-
KEY_GITHUB_API_TOKEN = 'github.api-token'.freeze
|
263
327
|
end
|
264
328
|
end
|
@@ -45,6 +45,7 @@ module GithubPivotalFlow
|
|
45
45
|
def self.merge(branch_name, options = {})
|
46
46
|
command = "git merge --quiet"
|
47
47
|
command << " --no-ff" if options[:no_ff]
|
48
|
+
command << " --ff" if options[:ff] && !options[:no_ff]
|
48
49
|
command << " -m \"#{options[:commit_message]}\"" unless options[:commit_message].blank?
|
49
50
|
exec "#{command} #{branch_name}"
|
50
51
|
puts 'OK'
|
@@ -118,6 +119,19 @@ module GithubPivotalFlow
|
|
118
119
|
else
|
119
120
|
raise "Unable to set Git configuration for scope '#{scope}'"
|
120
121
|
end
|
122
|
+
return value
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.delete_config(key, scope = :local)
|
126
|
+
if :branch == scope
|
127
|
+
exec "git config --local --unset branch.#{self.current_branch}.#{key}"
|
128
|
+
elsif :global == scope
|
129
|
+
exec "git config --global --unset #{key}"
|
130
|
+
elsif :local == scope
|
131
|
+
exec "git config --local --unset #{key}"
|
132
|
+
else
|
133
|
+
raise "Unable to delete Git configuration for scope '#{scope}'"
|
134
|
+
end
|
121
135
|
end
|
122
136
|
|
123
137
|
def self.repository_root
|
@@ -149,6 +163,14 @@ module GithubPivotalFlow
|
|
149
163
|
end
|
150
164
|
end
|
151
165
|
|
166
|
+
def self.clean_working_tree?
|
167
|
+
exec("git diff --no-ext-diff --ignore-submodules --quiet --exit-code", false)
|
168
|
+
fail("fatal: Working tree contains unstaged changes. Aborting.") if $?.exitstatus != 0
|
169
|
+
exec("git diff-index --cached --quiet --ignore-submodules HEAD --", false)
|
170
|
+
fail("fatal: Index contains uncommited changes. Aborting.") if $?.exitstatus != 0
|
171
|
+
return true
|
172
|
+
end
|
173
|
+
|
152
174
|
private
|
153
175
|
def self.escape_commit_message(message)
|
154
176
|
message.gsub('"', '\"').sub(/!\z/, '! ')
|
@@ -34,6 +34,15 @@ module GithubPivotalFlow
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
def repo_info project
|
38
|
+
get "https://%s/repos/%s/%s" %
|
39
|
+
[api_host(project.host), project.owner, project.name]
|
40
|
+
end
|
41
|
+
|
42
|
+
def repo_exists? project
|
43
|
+
repo_info(project).success?
|
44
|
+
end
|
45
|
+
|
37
46
|
# Returns parsed data from the new pull request.
|
38
47
|
def create_pullrequest options
|
39
48
|
project = options.fetch(:project)
|
@@ -124,7 +133,7 @@ module GithubPivotalFlow
|
|
124
133
|
create_connection host_url
|
125
134
|
end
|
126
135
|
|
127
|
-
req['User-Agent'] = "
|
136
|
+
req['User-Agent'] = "Github-pivotal-flow #{GithubPivotalFlow::VERSION}"
|
128
137
|
apply_authentication(req, url)
|
129
138
|
yield req if block_given?
|
130
139
|
finalize_request(req, url)
|
@@ -204,7 +213,6 @@ module GithubPivotalFlow
|
|
204
213
|
|
205
214
|
def obtain_oauth_token host, user, two_factor_code = nil
|
206
215
|
auth_url = URI.parse("https://%s@%s/authorizations" % [CGI.escape(user), host])
|
207
|
-
|
208
216
|
# dummy request to trigger a 2FA SMS since a HTTP GET won't do it
|
209
217
|
post(auth_url) if !two_factor_code
|
210
218
|
|
@@ -1,21 +1,29 @@
|
|
1
1
|
module GithubPivotalFlow
|
2
|
-
class Project
|
3
|
-
|
2
|
+
class Project
|
3
|
+
attr_accessor :owner, :name, :host, :config
|
4
|
+
|
5
|
+
def self.find(id)
|
6
|
+
id = id.to_i if id.is_a?(String)
|
7
|
+
return PivotalTracker::Project.find(id)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(args = {})
|
11
|
+
args.each do |k,v|
|
12
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
13
|
+
end
|
14
|
+
url = Git.get_config("remote.#{Git.get_remote}.url")
|
4
15
|
if (matchdata = /^git@([a-z0-9\._-]+):([a-z0-9_-]+)\/([a-z0-9_-]+)(\.git)?$/.match(url.strip))
|
5
|
-
host
|
6
|
-
owner
|
7
|
-
name
|
16
|
+
self.host ||= matchdata[1]
|
17
|
+
self.owner ||= matchdata[2]
|
18
|
+
self.name ||= matchdata[3]
|
8
19
|
else
|
9
20
|
url = URI(url) if !url.is_a?(URI)
|
10
|
-
|
11
|
-
|
21
|
+
path_components = url.path.split('/', 4)
|
22
|
+
self.owner ||= path_components[1]
|
23
|
+
self.name ||= path_components[2]
|
24
|
+
self.host ||= url.host
|
12
25
|
end
|
13
|
-
self.
|
14
|
-
end
|
15
|
-
|
16
|
-
def initialize(*args)
|
17
|
-
super
|
18
|
-
self.name = self.name.tr(' ', '-')
|
26
|
+
self.name = self.name.tr(' ', '-').sub(/\.git$/, '')
|
19
27
|
self.host ||= 'github.com'
|
20
28
|
self.host = host.sub(/^ssh\./i, '') if 'ssh.github.com' == host.downcase
|
21
29
|
end
|
@@ -27,5 +35,13 @@ module GithubPivotalFlow
|
|
27
35
|
def ==(other)
|
28
36
|
name_with_owner == other.name_with_owner
|
29
37
|
end
|
38
|
+
|
39
|
+
def pivotal_project
|
40
|
+
@pivotal_project ||= self.class.find(self.config.project_id)
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(m, *args, &block)
|
44
|
+
return pivotal_project.send(m, *args, &block)
|
45
|
+
end
|
30
46
|
end
|
31
47
|
end
|
@@ -11,8 +11,10 @@ module GithubPivotalFlow
|
|
11
11
|
@configuration.story = story # Tag the branch with story attributes
|
12
12
|
Git.add_hook 'prepare-commit-msg', File.join(File.dirname(__FILE__), 'prepare-commit-msg.sh')
|
13
13
|
unless story.release?
|
14
|
-
|
15
|
-
|
14
|
+
print "Creating pull-request on Github... "
|
15
|
+
pull_request_params = story.params_for_pull_request.merge(project: @configuration.project)
|
16
|
+
@configuration.github_client.create_pullrequest(pull_request_params)
|
17
|
+
puts 'OK'
|
16
18
|
end
|
17
19
|
story.mark_started!
|
18
20
|
return 0
|
@@ -20,12 +22,6 @@ module GithubPivotalFlow
|
|
20
22
|
|
21
23
|
private
|
22
24
|
|
23
|
-
def create_pull_request_for_story!(story)
|
24
|
-
print "Creating pull-request on Github... "
|
25
|
-
@ghclient.create_pullrequest({:project => @configuration.github_project}.merge(story.params_for_pull_request))
|
26
|
-
puts 'OK'
|
27
|
-
end
|
28
|
-
|
29
25
|
def parse_argv(*args)
|
30
26
|
OptionParser.new do |opts|
|
31
27
|
opts.banner = "Usage: git start <feature|chore|bug|story_id>"
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Utilities for dealing with +PivotalTracker::Story+s
|
2
2
|
module GithubPivotalFlow
|
3
3
|
class Story
|
4
|
-
attr_accessor :
|
4
|
+
attr_accessor :pivotal_story, :project, :branch_name, :root_branch_name
|
5
5
|
|
6
6
|
# Print a human readable version of a story. This pretty prints the title,
|
7
7
|
# description, and notes for the story.
|
@@ -28,7 +28,7 @@ module GithubPivotalFlow
|
|
28
28
|
|
29
29
|
# Selects a Pivotal Tracker story by doing the following steps:
|
30
30
|
#
|
31
|
-
# @param [
|
31
|
+
# @param [Project] project the project to select stories from
|
32
32
|
# @param [String, nil] filter a filter for selecting the story to start. This
|
33
33
|
# filter can be either:
|
34
34
|
# * a story id: selects the story represented by the id
|
@@ -42,20 +42,22 @@ module GithubPivotalFlow
|
|
42
42
|
else
|
43
43
|
story = find_story project, filter, limit
|
44
44
|
end
|
45
|
-
self.new(story)
|
45
|
+
self.new(project, story)
|
46
46
|
end
|
47
47
|
|
48
|
-
# @param [
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
# @param [Project] project the Project for this repo
|
49
|
+
# @param [PivotalTracker::Story] pivotal_story the Pivotal tracker story to wrap
|
50
|
+
def initialize(project, pivotal_story, options = {})
|
51
|
+
raise "Invalid PivotalTracker::Story" if pivotal_story.nil?
|
52
|
+
@project = project
|
53
|
+
@pivotal_story = pivotal_story
|
52
54
|
@branch_name = options.delete(:branch_name)
|
53
55
|
@branch_suffix = @branch_name.split('-').last if @branch_name
|
54
56
|
@branch_suffix ||= nil
|
55
57
|
end
|
56
58
|
|
57
59
|
def release?
|
58
|
-
|
60
|
+
story_type == 'release'
|
59
61
|
end
|
60
62
|
|
61
63
|
def unestimated?
|
@@ -63,22 +65,22 @@ module GithubPivotalFlow
|
|
63
65
|
end
|
64
66
|
|
65
67
|
def request_estimation!
|
66
|
-
self.
|
67
|
-
:
|
68
|
+
self.update(
|
69
|
+
estimate: ask('Story is not yet estimated. Please estimate difficulty: ')
|
68
70
|
)
|
69
71
|
end
|
70
72
|
|
71
73
|
def mark_started!
|
72
74
|
print 'Starting story on Pivotal Tracker... '
|
73
|
-
self.
|
74
|
-
:
|
75
|
-
:
|
75
|
+
self.update(
|
76
|
+
current_state: 'started',
|
77
|
+
owned_by: Git.get_config(KEY_USER_NAME, :inherited).strip
|
76
78
|
)
|
77
79
|
puts 'OK'
|
78
80
|
end
|
79
81
|
|
80
82
|
def create_branch!(commit_message = nil, options = {})
|
81
|
-
commit_message ||= "Starting [#{
|
83
|
+
commit_message ||= "Starting [#{story_type} ##{id}]: #{name}"
|
82
84
|
commit_message << " [ci skip]" unless options[:run_ci]
|
83
85
|
print "Creating branch for story with branch name #{branch_name} from #{root_branch_name}... "
|
84
86
|
Git.checkout(root_branch_name)
|
@@ -94,32 +96,44 @@ module GithubPivotalFlow
|
|
94
96
|
|
95
97
|
def merge_to_root!(commit_message = nil, options = {})
|
96
98
|
commit_message ||= "Merge #{branch_name} to #{root_branch_name}"
|
97
|
-
commit_message << "\n\n[#{options[:no_complete] ? '' : 'Completes '}##{
|
99
|
+
commit_message << "\n\n[#{options[:no_complete] ? '' : 'Completes '}##{id}] "
|
98
100
|
print "Merging #{branch_name} to #{root_branch_name}... "
|
99
101
|
Git.checkout(root_branch_name)
|
100
102
|
Git.pull_remote(root_branch_name)
|
101
|
-
|
102
|
-
|
103
|
+
if trivial_merge?
|
104
|
+
Git.merge(branch_name, commit_message: commit_message, ff: true)
|
105
|
+
else
|
106
|
+
Git.merge(branch_name, commit_message: commit_message, no_ff: true)
|
107
|
+
end
|
103
108
|
Git.push(root_branch_name)
|
109
|
+
self.delete_branch!
|
104
110
|
self.cleanup!
|
105
111
|
end
|
106
112
|
|
107
113
|
def merge_release!(commit_message = nil, options = {})
|
108
|
-
commit_message ||= "Release #{
|
109
|
-
commit_message << "\n\n[#{options[:no_complete] ? '' : 'Completes '}##{
|
114
|
+
commit_message ||= "Release #{name}"
|
115
|
+
commit_message << "\n\n[#{options[:no_complete] ? '' : 'Completes '}##{id}] "
|
110
116
|
print "Merging #{branch_name} to #{master_branch_name}... "
|
111
117
|
Git.checkout(master_branch_name)
|
112
118
|
Git.pull_remote(master_branch_name)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
Git.
|
119
|
+
if trivial_merge?(master_branch_name)
|
120
|
+
Git.merge(branch_name, commit_message: commit_message, ff: true)
|
121
|
+
else
|
122
|
+
Git.merge(branch_name, commit_message: commit_message, no_ff: true)
|
123
|
+
end
|
124
|
+
Git.tag(name)
|
125
|
+
print "Merging #{branch_name} to #{development_branch_name}... "
|
126
|
+
Git checkout(development_branch_name)
|
127
|
+
Git.pull_remote(development_branch_name)
|
128
|
+
if trivial_merge?(development_branch_name)
|
129
|
+
Git.merge(branch_name, commit_message: commit_message, ff: true)
|
130
|
+
else
|
131
|
+
Git.merge(branch_name, commit_message: commit_message, no_ff: true)
|
132
|
+
end
|
119
133
|
Git.checkout(master_branch_name)
|
120
|
-
|
121
|
-
Git.push(master_branch_name, root_branch_name)
|
134
|
+
Git.push(master_branch_name, development_branch_name)
|
122
135
|
Git.push_tags
|
136
|
+
self.delete_branch!
|
123
137
|
self.cleanup!
|
124
138
|
end
|
125
139
|
|
@@ -138,17 +152,17 @@ module GithubPivotalFlow
|
|
138
152
|
#end
|
139
153
|
|
140
154
|
def branch_name
|
141
|
-
@branch_name ||= branch_name_from(branch_prefix,
|
155
|
+
@branch_name ||= branch_name_from(branch_prefix, id, branch_suffix)
|
142
156
|
end
|
143
157
|
|
144
158
|
def branch_suffix
|
145
|
-
@branch_suffix ||= ask("Enter branch name (#{branch_name_from(branch_prefix,
|
159
|
+
@branch_suffix ||= ask("Enter branch name (#{branch_name_from(branch_prefix, id, "<branch-name>")}): ")
|
146
160
|
end
|
147
161
|
|
148
162
|
def branch_name_from(branch_prefix, story_id, branch_name)
|
149
163
|
if story_type == 'release'
|
150
164
|
# For release branches the format is release/5.0
|
151
|
-
"#{Git.get_config(
|
165
|
+
"#{Git.get_config(KEY_RELEASE_PREFIX, :inherited)}#{branch_name}"
|
152
166
|
else
|
153
167
|
n = "#{branch_prefix}#{story_id}"
|
154
168
|
n << "-#{branch_name}" unless branch_name.blank?
|
@@ -168,42 +182,43 @@ module GithubPivotalFlow
|
|
168
182
|
end
|
169
183
|
|
170
184
|
def master_branch_name
|
171
|
-
Git.get_config(
|
185
|
+
Git.get_config(KEY_MASTER_BRANCH, :inherited)
|
172
186
|
end
|
173
187
|
|
174
188
|
def development_branch_name
|
175
|
-
Git.get_config(
|
189
|
+
Git.get_config(KEY_DEVELOPMENT_BRANCH, :inherited)
|
176
190
|
end
|
177
191
|
|
178
192
|
def labels
|
179
|
-
return [] if
|
180
|
-
|
193
|
+
return [] if pivotal_story.labels.blank?
|
194
|
+
pivotal_story.labels.split(',').collect(&:strip)
|
181
195
|
end
|
182
196
|
|
183
197
|
def params_for_pull_request
|
184
198
|
{
|
185
|
-
:
|
186
|
-
:
|
187
|
-
:
|
188
|
-
:
|
199
|
+
base: root_branch_name,
|
200
|
+
head: branch_name,
|
201
|
+
title: name,
|
202
|
+
body: description,
|
189
203
|
}
|
190
204
|
end
|
191
205
|
|
192
206
|
def method_missing(m, *args, &block)
|
193
|
-
return @
|
207
|
+
return @pivotal_story.send(m, *args, &block)
|
194
208
|
end
|
195
209
|
|
196
210
|
def can_merge?
|
197
|
-
|
198
|
-
|
199
|
-
root_tip = Shell.exec "git rev-parse #{root_branch_name}"
|
200
|
-
common_ancestor = Shell.exec "git merge-base #{root_branch_name} #{branch_name}"
|
211
|
+
Git.clean_working_tree?
|
212
|
+
end
|
201
213
|
|
214
|
+
def trivial_merge?(to_branch = nil)
|
215
|
+
to_branch ||= root_branch_name
|
216
|
+
root_tip = Shell.exec "git rev-parse #{to_branch}"
|
217
|
+
common_ancestor = Shell.exec "git merge-base #{to_branch} #{branch_name}"
|
202
218
|
if root_tip != common_ancestor
|
203
|
-
|
219
|
+
return false
|
204
220
|
end
|
205
|
-
|
206
|
-
puts 'OK'
|
221
|
+
return true
|
207
222
|
end
|
208
223
|
|
209
224
|
private
|
@@ -260,16 +275,18 @@ module GithubPivotalFlow
|
|
260
275
|
end
|
261
276
|
|
262
277
|
def branch_prefix
|
263
|
-
case
|
278
|
+
case story_type
|
264
279
|
when 'feature'
|
265
|
-
Git.get_config(
|
280
|
+
prefix = Git.get_config(KEY_FEATURE_PREFIX, :inherited)
|
266
281
|
when 'bug'
|
267
|
-
|
282
|
+
prefix = labels.include?('hotfix') ? Git.get_config(KEY_HOTFIX_PREFIX, :inherited) : Git.get_config(KEY_FEATURE_PREFIX, :inherited)
|
268
283
|
when 'release'
|
269
|
-
Git.get_config(
|
284
|
+
prefix = Git.get_config(KEY_RELEASE_PREFIX, :inherited)
|
270
285
|
else
|
271
|
-
'misc/'
|
286
|
+
prefix = 'misc/'
|
272
287
|
end
|
288
|
+
prefix = "#{prefix.strip}/" unless prefix.strip[-1,1] == '/'
|
289
|
+
return prefix.strip
|
273
290
|
end
|
274
291
|
end
|
275
292
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module GithubPivotalFlow
|
4
|
+
describe Command do
|
5
|
+
before do
|
6
|
+
$stdout = StringIO.new
|
7
|
+
$stderr = StringIO.new
|
8
|
+
@configuration = double('configuration')
|
9
|
+
@project = double('project')
|
10
|
+
allow(Configuration).to receive(:new).and_return(@configuration)
|
11
|
+
allow(PivotalTracker::Project).to receive(:find).and_return(@project)
|
12
|
+
@configuration.stub(
|
13
|
+
validate: true,
|
14
|
+
project_id: 123456,
|
15
|
+
project: @project,
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#initialize' do
|
20
|
+
it 'validates the configuration when started' do
|
21
|
+
expect(@configuration).to receive(:validate).once
|
22
|
+
@start = Command.new
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'finds the project corresponding to the current repo' do
|
26
|
+
expect(@configuration).to receive(:project).once.and_return(@project)
|
27
|
+
@start = Command.new
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -7,105 +7,116 @@ module GithubPivotalFlow
|
|
7
7
|
$stdout = StringIO.new
|
8
8
|
$stderr = StringIO.new
|
9
9
|
@configuration = Configuration.new
|
10
|
+
@project = double('project')
|
10
11
|
end
|
11
12
|
|
12
|
-
|
13
|
-
Git.should_receive(:get_config).with('pivotal.api-token', :inherited).and_return('test_api_token')
|
13
|
+
describe '#validate' do
|
14
14
|
|
15
|
-
api_token = @configuration.api_token
|
16
|
-
|
17
|
-
expect(api_token).to eq('test_api_token')
|
18
15
|
end
|
19
16
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
Git.should_receive(:set_config).with('pivotal.api-token', 'test_api_token', :global)
|
24
|
-
api_token = @configuration.api_token
|
25
|
-
expect(api_token).to eq('test_api_token')
|
26
|
-
end
|
17
|
+
describe '#api_token' do
|
18
|
+
it 'does not prompt the user for the API token if it is already configured' do
|
19
|
+
Git.should_receive(:get_config).with('pivotal.api-token', :inherited).and_return('test_api_token')
|
27
20
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
api_token = @configuration.api_token
|
22
|
+
|
23
|
+
expect(api_token).to eq('test_api_token')
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'prompts the user for the API token if it is not configured and stores it in the local git config' do
|
27
|
+
Git.should_receive(:get_config).with('pivotal.api-token', :inherited).and_return('')
|
28
|
+
@configuration.should_receive(:ask).and_return('test_api_token')
|
29
|
+
Git.should_receive(:set_config).with('pivotal.api-token', 'test_api_token', :local)
|
30
|
+
api_token = @configuration.api_token
|
31
|
+
expect(api_token).to eq('test_api_token')
|
32
|
+
end
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
35
|
+
describe '#project_id' do
|
36
|
+
it 'does not prompt the user for the project id if it is already configured' do
|
37
|
+
Git.should_receive(:get_config).with('pivotal.project-id', :inherited).and_return('test_project_id')
|
38
|
+
project_id = @configuration.project_id
|
39
|
+
expect(project_id).to eq('test_project_id')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'prompts the user for the project id if it is not configured' do
|
43
|
+
Git.should_receive(:get_config).with('pivotal.project-id', :inherited).and_return('')
|
44
|
+
menu = double('menu')
|
45
|
+
menu.should_receive(:prompt=)
|
46
|
+
PivotalTracker::Project.should_receive(:all).and_return([
|
47
|
+
PivotalTracker::Project.new(:id => 'id-2', :name => 'name-2'),
|
48
|
+
PivotalTracker::Project.new(:id => 'id-1', :name => 'name-1')])
|
49
|
+
menu.should_receive(:choice).with('name-1')
|
50
|
+
menu.should_receive(:choice).with('name-2')
|
51
|
+
@configuration.should_receive(:choose) { |&arg| arg.call menu }.and_return('test_project_id')
|
52
|
+
Git.should_receive(:set_config).with('pivotal.project-id', 'test_project_id', :local)
|
53
|
+
|
54
|
+
project_id = @configuration.project_id
|
55
|
+
|
56
|
+
expect(project_id).to eq('test_project_id')
|
57
|
+
end
|
49
58
|
end
|
50
59
|
|
51
|
-
|
52
|
-
|
60
|
+
describe '#story=' do
|
61
|
+
it 'persists the story when requested' do
|
62
|
+
expect(Git).to receive(:set_config).with('pivotal-story-id', 12345678, :branch)
|
53
63
|
|
54
|
-
|
64
|
+
@configuration.story = Story.new(@project, PivotalTracker::Story.new(:id => 12345678))
|
65
|
+
end
|
55
66
|
end
|
56
67
|
|
57
|
-
describe 'story' do
|
68
|
+
describe '#story' do
|
69
|
+
let(:project) { double('project') }
|
70
|
+
let(:stories) { double('stories') }
|
71
|
+
let(:pivotal_story) { double('pivotal_story') }
|
72
|
+
|
73
|
+
before do
|
74
|
+
allow(@configuration).to receive(:project).and_return(project)
|
75
|
+
end
|
76
|
+
|
58
77
|
it 'fetches the story based on the story id stored inside the git config' do
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
Git.should_receive(:get_config).with('pivotal-story-id', :branch).and_return('12345678')
|
63
|
-
project.should_receive(:stories).and_return(stories)
|
64
|
-
stories.should_receive(:find).with(12345678).and_return(story)
|
78
|
+
expect(Git).to receive(:get_config).with('pivotal-story-id', :branch).and_return('12345678')
|
79
|
+
expect(project).to receive(:stories).and_return(stories)
|
80
|
+
expect(stories).to receive(:find).with(12345678).and_return(pivotal_story)
|
65
81
|
|
66
|
-
result = @configuration.story
|
82
|
+
result = @configuration.story
|
83
|
+
expect(result).to be_a(Story)
|
67
84
|
|
68
|
-
expect(result.
|
85
|
+
expect(result.pivotal_story).to eq(pivotal_story)
|
69
86
|
end
|
70
87
|
|
71
88
|
it 'uses the branch name to deduce the story id if no git config is found' do
|
72
|
-
project = double('project')
|
73
|
-
stories = double('stories')
|
74
|
-
story = double('story')
|
75
89
|
Git.stub(:current_branch).and_return('feature/12345678-sample_feature')
|
76
|
-
Git.
|
77
|
-
project.
|
78
|
-
stories.
|
90
|
+
expect(Git).to receive(:get_config).with('pivotal-story-id', :branch).and_return(' ')
|
91
|
+
expect(project).to receive(:stories).and_return(stories)
|
92
|
+
expect(stories).to receive(:find).with(12345678).and_return(pivotal_story)
|
79
93
|
|
80
|
-
result = @configuration.story
|
94
|
+
result = @configuration.story
|
81
95
|
|
82
|
-
expect(result.
|
96
|
+
expect(result.pivotal_story).to eq(pivotal_story)
|
83
97
|
end
|
84
98
|
|
85
99
|
it 'prompts for the story id if the branch name does not match the known format' do
|
86
|
-
project = double('project')
|
87
|
-
stories = double('stories')
|
88
|
-
story = double('story')
|
89
100
|
Git.stub(:current_branch).and_return('unknownformat')
|
90
|
-
Git.
|
91
|
-
@configuration.
|
92
|
-
Git.
|
93
|
-
project.
|
94
|
-
stories.
|
101
|
+
expect(Git).to receive(:get_config).with('pivotal-story-id', :branch).and_return(' ')
|
102
|
+
expect(@configuration).to receive(:ask).and_return('12345678')
|
103
|
+
expect(Git).to receive(:set_config).with('pivotal-story-id', '12345678', :branch)
|
104
|
+
expect(project).to receive(:stories).and_return(stories)
|
105
|
+
expect(stories).to receive(:find).with(12345678).and_return(pivotal_story)
|
95
106
|
|
96
|
-
result = @configuration.story
|
107
|
+
result = @configuration.story
|
97
108
|
|
98
|
-
expect(result.
|
109
|
+
expect(result.pivotal_story).to eq(pivotal_story)
|
99
110
|
end
|
100
111
|
end
|
101
112
|
|
102
|
-
describe '
|
113
|
+
describe '#project' do
|
103
114
|
it 'supports working with git urls from the configuration' do
|
104
115
|
Git.should_receive(:get_remote).and_return('origin')
|
105
116
|
Git.should_receive(:get_config).with('remote.origin.url').and_return('git@github.com:roomorama/github-pivotal-flow.git')
|
106
|
-
|
107
|
-
expect(
|
108
|
-
expect(
|
117
|
+
project = @configuration.project
|
118
|
+
expect(project.owner).to eq('roomorama')
|
119
|
+
expect(project.name).to eq('github-pivotal-flow')
|
109
120
|
end
|
110
121
|
end
|
111
122
|
end
|
@@ -18,11 +18,15 @@ module GithubPivotalFlow
|
|
18
18
|
release_prefix: 'release/',
|
19
19
|
api_token: 'token',
|
20
20
|
project_id: '123',
|
21
|
-
|
22
|
-
|
21
|
+
project: @project,
|
22
|
+
github_client: @ghclient,
|
23
|
+
story: @story,
|
24
|
+
validate: true,
|
25
|
+
)
|
23
26
|
allow(Configuration).to receive(:new).and_return(@configuration)
|
24
|
-
allow(
|
27
|
+
allow(Project).to receive(:find).and_return(@project)
|
25
28
|
@finish = Finish.new
|
29
|
+
expect(@configuration).to receive(:story).and_return(@story)
|
26
30
|
end
|
27
31
|
|
28
32
|
it 'merges the branch back to its root by default' do
|
@@ -112,7 +112,7 @@ module GithubPivotalFlow
|
|
112
112
|
expect(File).to receive(:exist?).with(hook).and_return(true)
|
113
113
|
|
114
114
|
Git.add_hook 'prepare-commit-msg', __FILE__
|
115
|
-
|
115
|
+
allow(File).to receive(:exist?).and_call_original
|
116
116
|
expect(File.exist?(hook)).to be_false
|
117
117
|
end
|
118
118
|
end
|
@@ -5,9 +5,11 @@ module GithubPivotalFlow
|
|
5
5
|
before do
|
6
6
|
$stdout = StringIO.new
|
7
7
|
$stderr = StringIO.new
|
8
|
-
|
9
|
-
@project = double('project')
|
10
8
|
@story = double('story')
|
9
|
+
@project = double('project')
|
10
|
+
@project.stub(
|
11
|
+
stories: [@story]
|
12
|
+
)
|
11
13
|
@ghclient = double('ghclient')
|
12
14
|
@ghproject = double('ghproject')
|
13
15
|
@configuration = double('configuration')
|
@@ -19,16 +21,17 @@ module GithubPivotalFlow
|
|
19
21
|
release_prefix: 'release/',
|
20
22
|
api_token: 'token',
|
21
23
|
project_id: '123',
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
project: @project,
|
25
|
+
github_client: @ghclient,
|
26
|
+
story: @story,
|
27
|
+
validate: true,
|
28
|
+
)
|
26
29
|
allow(Configuration).to receive(:new).and_return(@configuration)
|
27
30
|
allow(PivotalTracker::Project).to receive(:find).and_return(@project)
|
28
31
|
@start = Start.new()
|
29
32
|
end
|
30
33
|
|
31
|
-
it 'should
|
34
|
+
it 'should runs correctly' do
|
32
35
|
@start.options[:args] = 'test_filter'
|
33
36
|
@story.stub(:unestimated? => false, :release? => false, params_for_pull_request: {})
|
34
37
|
|
@@ -10,7 +10,10 @@ module GithubPivotalFlow
|
|
10
10
|
@project = double('project')
|
11
11
|
@stories = double('stories')
|
12
12
|
@story = double('story')
|
13
|
+
@pivotal_story = double('pivotal_story')
|
13
14
|
@menu = double('menu')
|
15
|
+
allow(@project).to receive(:stories).and_return(@stories)
|
16
|
+
allow(@story).to receive(:pivotal_story).and_return(@pivotal_story)
|
14
17
|
end
|
15
18
|
|
16
19
|
describe '.pretty_print' do
|
@@ -59,32 +62,28 @@ module GithubPivotalFlow
|
|
59
62
|
|
60
63
|
describe '.select_story' do
|
61
64
|
it 'selects a story directly if the filter is a number' do
|
62
|
-
@
|
63
|
-
@stories.should_receive(:find).with(12345678).and_return(@story)
|
64
|
-
|
65
|
+
expect(@stories).to receive(:find).with(12345678).and_return(@pivotal_story)
|
65
66
|
story = Story.select_story @project, '12345678'
|
66
67
|
|
67
68
|
expect(story).to be_a(Story)
|
68
|
-
expect(story.
|
69
|
+
expect(story.pivotal_story).to eq(@pivotal_story)
|
69
70
|
end
|
70
71
|
|
71
72
|
it 'selects a story if the result of the query is a single story' do
|
72
|
-
@
|
73
|
-
@stories.should_receive(:all).with(
|
73
|
+
expect(@stories).to receive(:all).with(
|
74
74
|
:current_state => %w(rejected unstarted unscheduled),
|
75
75
|
:limit => 1,
|
76
76
|
:story_type => 'release'
|
77
|
-
).and_return([@
|
77
|
+
).and_return([@pivotal_story])
|
78
78
|
|
79
79
|
story = Story.select_story @project, 'release', 1
|
80
80
|
|
81
81
|
expect(story).to be_a(Story)
|
82
|
-
expect(story.
|
82
|
+
expect(story.pivotal_story).to eq(@pivotal_story)
|
83
83
|
end
|
84
84
|
|
85
85
|
it 'prompts the user for a story if the result of the query is more than a single story' do
|
86
|
-
@
|
87
|
-
@stories.should_receive(:all).with(
|
86
|
+
expect(@stories).to receive(:all).with(
|
88
87
|
:current_state => %w(rejected unstarted unscheduled),
|
89
88
|
:limit => 5,
|
90
89
|
:story_type => 'feature'
|
@@ -92,66 +91,77 @@ module GithubPivotalFlow
|
|
92
91
|
PivotalTracker::Story.new(:name => 'name-1'),
|
93
92
|
PivotalTracker::Story.new(:name => 'name-2')
|
94
93
|
])
|
95
|
-
@menu.
|
96
|
-
@menu.
|
97
|
-
@menu.
|
98
|
-
Story.
|
94
|
+
expect(@menu).to receive(:prompt=)
|
95
|
+
expect(@menu).to receive(:choice).with('name-1')
|
96
|
+
expect(@menu).to receive(:choice).with('name-2')
|
97
|
+
expect(Story).to receive(:choose) { |&arg| arg.call @menu }.and_return(@pivotal_story)
|
99
98
|
|
100
99
|
story = Story.select_story @project, 'feature'
|
101
100
|
|
102
101
|
expect(story).to be_a(Story)
|
103
|
-
expect(story.
|
102
|
+
expect(story.pivotal_story).to eq(@pivotal_story)
|
104
103
|
end
|
105
104
|
|
106
105
|
it 'prompts the user with the story type if no filter is specified' do
|
107
|
-
@
|
108
|
-
@stories.should_receive(:all).with(
|
106
|
+
expect(@stories).to receive(:all).with(
|
109
107
|
:current_state => %w(rejected unstarted unscheduled),
|
110
108
|
:limit => 5
|
111
109
|
).and_return([
|
112
110
|
PivotalTracker::Story.new(:story_type => 'chore', :name => 'name-1'),
|
113
111
|
PivotalTracker::Story.new(:story_type => 'bug', :name => 'name-2')
|
114
112
|
])
|
115
|
-
@menu.
|
116
|
-
@menu.
|
117
|
-
@menu.
|
118
|
-
Story.
|
113
|
+
expect(@menu).to receive(:prompt=)
|
114
|
+
expect(@menu).to receive(:choice).with('CHORE name-1')
|
115
|
+
expect(@menu).to receive(:choice).with('BUG name-2')
|
116
|
+
expect(Story).to receive(:choose) { |&arg| arg.call @menu }.and_return(@pivotal_story)
|
119
117
|
|
120
118
|
story = Story.select_story @project
|
121
119
|
|
122
120
|
expect(story).to be_a(Story)
|
123
|
-
expect(story.
|
121
|
+
expect(story.pivotal_story).to eq(@pivotal_story)
|
124
122
|
end
|
125
123
|
end
|
126
124
|
|
127
125
|
describe '#create_branch!' do
|
128
126
|
before do
|
129
|
-
|
130
|
-
|
131
|
-
|
127
|
+
Git.stub(
|
128
|
+
checkout: nil,
|
129
|
+
pull_remote: nil,
|
130
|
+
create_branch: nil,
|
131
|
+
set_config: nil,
|
132
|
+
get_config: nil,
|
133
|
+
push: nil,
|
134
|
+
commit: nil,
|
135
|
+
get_remote: 'origin',
|
136
|
+
)
|
137
|
+
@pivotal_story.stub(
|
138
|
+
story_type: 'feature',
|
139
|
+
id: '123456',
|
140
|
+
name: 'test',
|
141
|
+
description: 'description')
|
142
|
+
@story = GithubPivotalFlow::Story.new(@project, @pivotal_story)
|
143
|
+
allow(@story).to receive(:ask).and_return('test')
|
132
144
|
end
|
133
145
|
|
134
146
|
it 'prompts the user for a branch extension name' do
|
135
|
-
|
136
|
-
expect(@
|
147
|
+
allow(@story).to receive(:branch_prefix).and_return('feature/')
|
148
|
+
expect(@story).to receive(:ask).with("Enter branch name (feature/123456-<branch-name>): ").and_return('super-branch')
|
137
149
|
|
138
|
-
@
|
150
|
+
@story.create_branch!('Message')
|
139
151
|
end
|
140
152
|
|
141
153
|
it 'includes a tag to skip the ci build for the initial blank commit' do
|
142
154
|
@story.stub(branch_name: 'feature/123456-my_branch')
|
143
|
-
Git.
|
144
|
-
Git.should_receive(:commit).with(hash_including(commit_message: 'Message [ci skip]')).and_return(true)
|
155
|
+
expect(Git).to receive(:commit).with(hash_including(commit_message: 'Message [ci skip]')).and_return(true)
|
145
156
|
|
146
|
-
@
|
157
|
+
@story.create_branch!('Message')
|
147
158
|
end
|
148
159
|
|
149
160
|
it 'pushes the local branch and sets the upstream using the -u flag' do
|
150
161
|
@story.stub(branch_name: 'feature/123456-my_branch')
|
151
|
-
Git.
|
152
|
-
Git.should_receive(:push).with(instance_of(String), hash_including(set_upstream: true))
|
162
|
+
expect(Git).to receive(:push).with(instance_of(String), hash_including(set_upstream: true))
|
153
163
|
|
154
|
-
@
|
164
|
+
@story.create_branch!('Message')
|
155
165
|
end
|
156
166
|
end
|
157
167
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: github-pivotal-flow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Donald Piret
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-01-
|
11
|
+
date: 2014-01-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: highline
|
@@ -108,20 +108,6 @@ dependencies:
|
|
108
108
|
- - ~>
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.14'
|
111
|
-
- !ruby/object:Gem::Dependency
|
112
|
-
name: debugger
|
113
|
-
requirement: !ruby/object:Gem::Requirement
|
114
|
-
requirements:
|
115
|
-
- - ~>
|
116
|
-
- !ruby/object:Gem::Version
|
117
|
-
version: '1.6'
|
118
|
-
type: :development
|
119
|
-
prerelease: false
|
120
|
-
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
requirements:
|
122
|
-
- - ~>
|
123
|
-
- !ruby/object:Gem::Version
|
124
|
-
version: '1.6'
|
125
111
|
- !ruby/object:Gem::Dependency
|
126
112
|
name: simplecov
|
127
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -161,20 +147,22 @@ extra_rdoc_files: []
|
|
161
147
|
files:
|
162
148
|
- LICENSE
|
163
149
|
- README.md
|
150
|
+
- bin/git-finish
|
151
|
+
- bin/git-start
|
164
152
|
- lib/core_ext/object/blank.rb
|
153
|
+
- lib/github_pivotal_flow.rb
|
165
154
|
- lib/github_pivotal_flow/command.rb
|
166
155
|
- lib/github_pivotal_flow/configuration.rb
|
167
156
|
- lib/github_pivotal_flow/finish.rb
|
168
157
|
- lib/github_pivotal_flow/git.rb
|
169
158
|
- lib/github_pivotal_flow/github_api.rb
|
159
|
+
- lib/github_pivotal_flow/prepare-commit-msg.sh
|
170
160
|
- lib/github_pivotal_flow/project.rb
|
171
161
|
- lib/github_pivotal_flow/shell.rb
|
172
162
|
- lib/github_pivotal_flow/start.rb
|
173
163
|
- lib/github_pivotal_flow/story.rb
|
174
|
-
- lib/github_pivotal_flow.rb
|
175
|
-
-
|
176
|
-
- bin/git-finish
|
177
|
-
- bin/git-start
|
164
|
+
- lib/github_pivotal_flow/version.rb
|
165
|
+
- spec/github_pivotal_flow/command_spec.rb
|
178
166
|
- spec/github_pivotal_flow/configuration_spec.rb
|
179
167
|
- spec/github_pivotal_flow/finish_spec.rb
|
180
168
|
- spec/github_pivotal_flow/git_spec.rb
|
@@ -201,11 +189,12 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
189
|
version: '0'
|
202
190
|
requirements: []
|
203
191
|
rubyforge_project:
|
204
|
-
rubygems_version: 2.1
|
192
|
+
rubygems_version: 2.2.1
|
205
193
|
signing_key:
|
206
194
|
specification_version: 4
|
207
195
|
summary: Git commands for integration with Pivotal Tracker and Github pull requests
|
208
196
|
test_files:
|
197
|
+
- spec/github_pivotal_flow/command_spec.rb
|
209
198
|
- spec/github_pivotal_flow/configuration_spec.rb
|
210
199
|
- spec/github_pivotal_flow/finish_spec.rb
|
211
200
|
- spec/github_pivotal_flow/git_spec.rb
|