story_branch 0.2.5 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +15 -7
- data/lib/story_branch.rb +61 -81
- metadata +14 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 27b478138908ff36d93a8c9c641136de6b789f1d
|
4
|
+
data.tar.gz: b7435c40b8257bb544f1379436a7559115100f2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: aa08dfe184feb00523a32995ea457952ef7881d34473d2aaf3423c4dbf2aedfa83050786090d1f84b2651bcf64061c2809ea0ef01eed3f052d53947f629af4e3
|
7
|
+
data.tar.gz: acec4e4033a5b0a6deff313be9b34253e89c379b614a0374c4e0b9a91d424e6377fb03644ecaf9867c14b964721a13d7314ab736509308bd6f44745bd8f6409f
|
data/README.md
CHANGED
@@ -12,12 +12,17 @@ Pivotal Tracker Story. It will get started stories from your active
|
|
12
12
|
project. You can enter text and press TAB to search for a story
|
13
13
|
name, or TAB to show the full list. It will then suggest an editable
|
14
14
|
branch name. When the branch is created the `story_id` will
|
15
|
-
be appended to it.
|
15
|
+
be appended to it. Enter a blank to exit.
|
16
16
|
|
17
17
|
`git start`: Start a story in Pivotal Tracker from the terminal.
|
18
18
|
It'll get all unstarted stories in your current project. You can
|
19
19
|
enter text and press TAB to search for a story name, or TAB to show
|
20
|
-
the full list.
|
20
|
+
the full list. Enter blank to exit.
|
21
|
+
|
22
|
+
`git unstart`: Unstart a started story in Pivotal Tracker from the terminal.
|
23
|
+
It'll get all started stories in your current project. You can
|
24
|
+
enter text and press TAB to search for a story name, or TAB to show
|
25
|
+
the full list. Enter blank to exit.
|
21
26
|
|
22
27
|
`git finish`: Create commit/message for the staged changes, e.g:
|
23
28
|
"[Finishes #1234567] My Story Title" - optionally Finishes the story
|
@@ -48,7 +53,7 @@ Can be saved to `~/` or `./`
|
|
48
53
|
|
49
54
|
You run story_branch from the git/project root folder.
|
50
55
|
|
51
|
-
`start`, `
|
56
|
+
`start`, `unstart` and `story`, are run interactively and will display a
|
52
57
|
list of stories to work with.
|
53
58
|
|
54
59
|
`finish` will scan the current branch name for a story id (as its
|
@@ -56,6 +61,8 @@ suffix) and if a valid, active story is found on pivotal tracker it
|
|
56
61
|
will create a commit with a message to trigger pivotal's git
|
57
62
|
integraton features.
|
58
63
|
|
64
|
+
All text entry supports Readline keyboard shortcuts / bindings, (word fwd/back, undo etc.)
|
65
|
+
|
59
66
|
### Command names
|
60
67
|
|
61
68
|
It's possible to the commands in a few ways, we have deprecated the
|
@@ -63,10 +70,11 @@ old commmand names, and now encourage only the use of the `git`
|
|
63
70
|
style usage.
|
64
71
|
|
65
72
|
git style | deprecated
|
66
|
-
|
67
|
-
git story | story_branch
|
68
|
-
git start | story_start
|
69
|
-
git
|
73
|
+
------------+---------------+--------------
|
74
|
+
git story | story_branch | story-branch
|
75
|
+
git start | story_start | story-start
|
76
|
+
git unstart| story_unstart | story-unstart
|
77
|
+
git finish | story_finish | story-finish
|
70
78
|
|
71
79
|
## Contributing
|
72
80
|
|
data/lib/story_branch.rb
CHANGED
@@ -5,7 +5,7 @@
|
|
5
5
|
# Dominic Wong <dominic.wong.617@gmail.com>
|
6
6
|
# Gabe Hollombe <gabe@neo.com>
|
7
7
|
#
|
8
|
-
# Version: 0.2.
|
8
|
+
# Version: 0.2.6
|
9
9
|
#
|
10
10
|
# ## Description
|
11
11
|
#
|
@@ -86,50 +86,45 @@ require 'pivotal-tracker'
|
|
86
86
|
require 'rb-readline'
|
87
87
|
require 'readline'
|
88
88
|
require 'git'
|
89
|
-
require 'active_support/core_ext/string/inflections'
|
90
89
|
require 'levenshtein'
|
91
90
|
|
92
91
|
trap('INT') {exit}
|
93
92
|
|
94
93
|
module StoryBranch
|
95
|
-
|
96
94
|
class Main
|
97
|
-
|
98
95
|
ERRORS = {
|
99
|
-
|
96
|
+
'Stories in the started state must be estimated.' =>
|
100
97
|
"Error: Pivotal won't allow you to start an unestimated story"
|
101
98
|
}
|
102
99
|
|
103
|
-
PIVOTAL_CONFIG_FILES = ['.story_branch',"#{ENV['HOME']}/.story_branch"]
|
100
|
+
PIVOTAL_CONFIG_FILES = ['.story_branch', "#{ENV['HOME']}/.story_branch"]
|
101
|
+
PIVOTAL_API_CONFIG_FILES = ['.pivotal_api_key', "#{ENV['HOME']}/.pivotal_api_key"]
|
104
102
|
|
105
103
|
attr_accessor :p
|
106
104
|
|
107
105
|
def initialize
|
108
|
-
if config_file
|
109
|
-
|
110
|
-
|
111
|
-
@p
|
112
|
-
@p.
|
113
|
-
@p.project_id = config_value "project", 'PIVOTAL_PROJECT_ID'
|
106
|
+
@pivotal_info = YAML.load_file config_file(PIVOTAL_CONFIG_FILES) if config_file(PIVOTAL_CONFIG_FILES)
|
107
|
+
@pivotal_api_info = YAML.load_file config_file(PIVOTAL_API_CONFIG_FILES) if config_file(PIVOTAL_API_CONFIG_FILES)
|
108
|
+
@p = PivotalUtils.new
|
109
|
+
@p.api_key = api_config_value
|
110
|
+
@p.project_id = config_value 'project', 'PIVOTAL_PROJECT_ID'
|
114
111
|
exit unless @p.valid?
|
115
112
|
end
|
116
113
|
|
117
114
|
def create_story_branch
|
118
115
|
begin
|
119
|
-
puts
|
116
|
+
puts 'Connecting with Pivotal Tracker'
|
120
117
|
@p.get_project
|
121
|
-
puts
|
118
|
+
puts 'Getting stories...'
|
122
119
|
stories = @p.display_stories :started, false
|
123
120
|
if stories.length < 1
|
124
|
-
puts
|
121
|
+
puts 'No stories started, exiting'
|
125
122
|
exit
|
126
123
|
end
|
127
124
|
story = @p.select_story stories
|
128
|
-
if story
|
129
|
-
@p.create_feature_branch story
|
130
|
-
end
|
125
|
+
@p.create_feature_branch story if story
|
131
126
|
rescue RestClient::Unauthorized
|
132
|
-
$stderr.puts
|
127
|
+
$stderr.puts 'Pivotal API key or Project ID invalid'
|
133
128
|
return nil
|
134
129
|
end
|
135
130
|
end
|
@@ -178,39 +173,44 @@ module StoryBranch
|
|
178
173
|
end
|
179
174
|
|
180
175
|
if GitUtils.has_status? :untracked or GitUtils.has_status? :modified
|
181
|
-
puts
|
182
|
-
puts
|
183
|
-
puts
|
176
|
+
puts 'There are unstaged changes'
|
177
|
+
puts 'Use git add to stage changes before running git finish'
|
178
|
+
puts 'Use git stash if you want to hide changes for this commit'
|
184
179
|
return
|
185
180
|
end
|
186
181
|
|
187
182
|
unless GitUtils.has_status? :added or GitUtils.has_status? :staged
|
188
|
-
puts
|
189
|
-
puts
|
183
|
+
puts 'There are no staged changes.'
|
184
|
+
puts 'Nothing to do'
|
190
185
|
return
|
191
186
|
end
|
192
187
|
|
193
|
-
puts
|
188
|
+
puts 'Use standard finishing commit message: [y/N]?'
|
194
189
|
commit_message = "[Finishes ##{GitUtils.current_branch_story_parts[:id]}] #{StringUtils.undashed GitUtils.current_branch_story_parts[:title]}"
|
195
190
|
puts commit_message
|
196
191
|
|
197
|
-
if gets.chomp!.downcase ==
|
192
|
+
if gets.chomp!.downcase == 'y'
|
198
193
|
GitUtils.commit commit_message
|
199
194
|
else
|
200
195
|
puts "Aborted"
|
201
196
|
end
|
202
197
|
rescue RestClient::Unauthorized
|
203
|
-
$stderr.puts
|
198
|
+
$stderr.puts 'Pivotal API key or Project ID invalid'
|
204
199
|
return nil
|
205
200
|
end
|
206
201
|
end
|
207
202
|
|
208
|
-
def config_file
|
209
|
-
|
203
|
+
def config_file file_set
|
204
|
+
file_set.select{|conf| File.exists? conf}.first
|
205
|
+
end
|
206
|
+
|
207
|
+
def api_config_value
|
208
|
+
return @pivotal_api_info['api'] if @pivotal_api_info && @pivotal_api_info['api']
|
209
|
+
config_value 'api', 'PIVOTAL_API_KEY'
|
210
210
|
end
|
211
211
|
|
212
212
|
def config_value key, env
|
213
|
-
value = @pivotal_info[key] if @pivotal_info
|
213
|
+
value = @pivotal_info[key] if @pivotal_info && @pivotal_info[key]
|
214
214
|
value ||= env_required env
|
215
215
|
value
|
216
216
|
end
|
@@ -222,62 +222,52 @@ module StoryBranch
|
|
222
222
|
end
|
223
223
|
ENV[var_name]
|
224
224
|
end
|
225
|
-
|
226
225
|
end
|
227
226
|
|
228
227
|
class StringUtils
|
229
|
-
|
230
228
|
def self.dashed s
|
231
229
|
s.tr(' _,./:;+&', '-')
|
232
230
|
end
|
233
231
|
|
234
232
|
def self.simple_sanitize s
|
235
|
-
strip_newlines
|
233
|
+
strip_newlines s.tr '\'"%!@#$(){}[]*\\?', ''
|
236
234
|
end
|
237
235
|
|
238
236
|
def self.normalised_branch_name s
|
239
|
-
StringUtils.simple_sanitize
|
237
|
+
StringUtils.simple_sanitize StringUtils.dashed(s).downcase.squeeze('-')
|
240
238
|
end
|
241
239
|
|
242
240
|
def self.strip_newlines s
|
243
|
-
s.tr "\n",
|
241
|
+
s.tr "\n", '-'
|
244
242
|
end
|
245
243
|
|
246
244
|
def self.undashed s
|
247
245
|
s.underscore.humanize
|
248
246
|
end
|
249
|
-
|
250
247
|
end
|
251
248
|
|
252
249
|
class GitUtils
|
253
|
-
|
254
250
|
def self.g
|
255
|
-
Git.open
|
251
|
+
Git.open '.'
|
256
252
|
end
|
257
253
|
|
258
254
|
def self.is_existing_branch? name
|
259
255
|
branch_names.each do |n|
|
260
|
-
if Levenshtein.distance(n, name) < 3
|
261
|
-
return true
|
262
|
-
end
|
256
|
+
return true if Levenshtein.distance(n, name) < 3
|
263
257
|
existing_branch_name = n.match(/(.*)(-[1-9][0-9]+$)/)
|
264
258
|
if existing_branch_name
|
265
259
|
levenshtein_distance = Levenshtein.distance existing_branch_name[1], name
|
266
|
-
if levenshtein_distance < 3
|
267
|
-
return true
|
268
|
-
end
|
260
|
+
return true if levenshtein_distance < 3
|
269
261
|
end
|
270
262
|
end
|
271
|
-
|
263
|
+
false
|
272
264
|
end
|
273
265
|
|
274
266
|
def self.is_existing_story? id
|
275
267
|
branch_names.each do |n|
|
276
268
|
branch_id = n.match(/-[1-9][0-9]+$/)
|
277
269
|
if branch_id
|
278
|
-
if branch_id.to_s == "-#{id}"
|
279
|
-
return true
|
280
|
-
end
|
270
|
+
return true if branch_id.to_s == "-#{id}"
|
281
271
|
end
|
282
272
|
end
|
283
273
|
false
|
@@ -297,11 +287,7 @@ module StoryBranch
|
|
297
287
|
|
298
288
|
def self.current_branch_story_parts
|
299
289
|
matches = current_branch.match(/(.*)-(\d+$)/)
|
300
|
-
if matches.length == 3
|
301
|
-
{ title: matches[1], id: matches[2] }
|
302
|
-
else
|
303
|
-
nil
|
304
|
-
end
|
290
|
+
{ title: matches[1], id: matches[2] } if matches.length == 3
|
305
291
|
end
|
306
292
|
|
307
293
|
def self.create_branch name
|
@@ -310,11 +296,7 @@ module StoryBranch
|
|
310
296
|
end
|
311
297
|
|
312
298
|
def self.status_collect status, regex
|
313
|
-
status.select{|e|
|
314
|
-
e.match(regex)
|
315
|
-
}.map{|e|
|
316
|
-
e.match(regex)[1]
|
317
|
-
}
|
299
|
+
status.select { |s| s.match(regex) }.map { |e| e.match(regex)[1] }
|
318
300
|
end
|
319
301
|
|
320
302
|
def self.status
|
@@ -340,11 +322,9 @@ module StoryBranch
|
|
340
322
|
def self.commit message
|
341
323
|
g.commit(message)
|
342
324
|
end
|
343
|
-
|
344
325
|
end
|
345
326
|
|
346
327
|
class PivotalUtils
|
347
|
-
|
348
328
|
attr_accessor :api_key, :project_id, :project
|
349
329
|
|
350
330
|
def valid?
|
@@ -357,11 +337,10 @@ module StoryBranch
|
|
357
337
|
end
|
358
338
|
|
359
339
|
def is_current_branch_a_story?
|
360
|
-
GitUtils.current_story
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
include? GitUtils.current_story[2].to_i
|
340
|
+
GitUtils.current_story && GitUtils.current_story.length == 3 &&
|
341
|
+
filtered_stories_list(:started, true)
|
342
|
+
.map(&:id)
|
343
|
+
.include?(GitUtils.current_story[2].to_i)
|
365
344
|
end
|
366
345
|
|
367
346
|
def story_from_current_branch
|
@@ -377,9 +356,9 @@ module StoryBranch
|
|
377
356
|
stories = project.stories.all({current_state: state})
|
378
357
|
if estimated
|
379
358
|
stories.select{|s|
|
380
|
-
s.story_type ==
|
381
|
-
s.story_type ==
|
382
|
-
(s.story_type ==
|
359
|
+
s.story_type == 'bug' ||
|
360
|
+
s.story_type == 'chore' ||
|
361
|
+
(s.story_type == 'feature' && s.estimate && s.estimate >= 0)}
|
383
362
|
else
|
384
363
|
stories
|
385
364
|
end
|
@@ -421,16 +400,17 @@ module StoryBranch
|
|
421
400
|
dashed_story_name = StringUtils.normalised_branch_name story.name
|
422
401
|
feature_branch_name = nil
|
423
402
|
puts "You are checked out at: #{GitUtils.current_branch}"
|
424
|
-
while feature_branch_name == nil
|
425
|
-
puts
|
426
|
-
feature_branch_name = readline(
|
403
|
+
while feature_branch_name == nil || feature_branch_name == ""
|
404
|
+
puts 'Provide a new branch name... (TAB for suggested name)' if [nil, ''].include? feature_branch_name
|
405
|
+
feature_branch_name = readline('Name of feature branch: ', [dashed_story_name])
|
427
406
|
end
|
428
407
|
feature_branch_name.chomp!
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
408
|
+
|
409
|
+
return unless validate_branch_name feature_branch_name, story.id
|
410
|
+
|
411
|
+
feature_branch_name_with_story_id = "#{feature_branch_name}-#{story.id}"
|
412
|
+
puts "Creating: #{feature_branch_name_with_story_id} with #{GitUtils.current_branch} as parent"
|
413
|
+
GitUtils.create_branch feature_branch_name_with_story_id
|
434
414
|
end
|
435
415
|
|
436
416
|
# Branch name validation
|
@@ -440,7 +420,7 @@ module StoryBranch
|
|
440
420
|
return false
|
441
421
|
end
|
442
422
|
if GitUtils.is_existing_branch? name
|
443
|
-
puts "Error:
|
423
|
+
puts "Error: The name: #{name}, is very similar to an existing branch. To avoid confusion use a more unique name."
|
444
424
|
return false
|
445
425
|
end
|
446
426
|
unless valid_branch_name? name
|
@@ -461,10 +441,10 @@ module StoryBranch
|
|
461
441
|
# Store the state of the terminal
|
462
442
|
RbReadline.clear_history
|
463
443
|
if completions.length > 0
|
464
|
-
completions.each {|i| Readline::HISTORY.push i}
|
465
|
-
RbReadline.rl_completer_word_break_characters =
|
466
|
-
Readline.completion_proc = proc {|s| completions.grep(/#{Regexp.escape(s)}/) }
|
467
|
-
Readline.completion_append_character =
|
444
|
+
completions.each { |i| Readline::HISTORY.push i}
|
445
|
+
RbReadline.rl_completer_word_break_characters = ''
|
446
|
+
Readline.completion_proc = proc { |s| completions.grep(/#{Regexp.escape(s)}/) }
|
447
|
+
Readline.completion_append_character = ''
|
468
448
|
end
|
469
449
|
Readline.readline(prompt, false)
|
470
450
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: story_branch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Milkins
|
@@ -17,70 +17,70 @@ dependencies:
|
|
17
17
|
name: pivotal-tracker
|
18
18
|
requirement: !ruby/object:Gem::Requirement
|
19
19
|
requirements:
|
20
|
-
- - ~>
|
20
|
+
- - "~>"
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: '0.5'
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
|
-
- - ~>
|
27
|
+
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0.5'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: git
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
requirements:
|
34
|
-
- - ~>
|
34
|
+
- - "~>"
|
35
35
|
- !ruby/object:Gem::Version
|
36
36
|
version: '1.2'
|
37
37
|
type: :runtime
|
38
38
|
prerelease: false
|
39
39
|
version_requirements: !ruby/object:Gem::Requirement
|
40
40
|
requirements:
|
41
|
-
- - ~>
|
41
|
+
- - "~>"
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: '1.2'
|
44
44
|
- !ruby/object:Gem::Dependency
|
45
45
|
name: levenshtein-ffi
|
46
46
|
requirement: !ruby/object:Gem::Requirement
|
47
47
|
requirements:
|
48
|
-
- - ~>
|
48
|
+
- - "~>"
|
49
49
|
- !ruby/object:Gem::Version
|
50
50
|
version: '1.0'
|
51
51
|
type: :runtime
|
52
52
|
prerelease: false
|
53
53
|
version_requirements: !ruby/object:Gem::Requirement
|
54
54
|
requirements:
|
55
|
-
- - ~>
|
55
|
+
- - "~>"
|
56
56
|
- !ruby/object:Gem::Version
|
57
57
|
version: '1.0'
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rb-readline
|
60
60
|
requirement: !ruby/object:Gem::Requirement
|
61
61
|
requirements:
|
62
|
-
- - ~>
|
62
|
+
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
64
|
version: '0.5'
|
65
65
|
type: :runtime
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
|
-
- - ~>
|
69
|
+
- - "~>"
|
70
70
|
- !ruby/object:Gem::Version
|
71
71
|
version: '0.5'
|
72
72
|
- !ruby/object:Gem::Dependency
|
73
73
|
name: rspec
|
74
74
|
requirement: !ruby/object:Gem::Requirement
|
75
75
|
requirements:
|
76
|
-
- - ~>
|
76
|
+
- - "~>"
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: '3.0'
|
79
79
|
type: :development
|
80
80
|
prerelease: false
|
81
81
|
version_requirements: !ruby/object:Gem::Requirement
|
82
82
|
requirements:
|
83
|
-
- - ~>
|
83
|
+
- - "~>"
|
84
84
|
- !ruby/object:Gem::Version
|
85
85
|
version: '3.0'
|
86
86
|
description: Simple gem that fetches the available stories in your pivotaltracker
|
@@ -142,17 +142,17 @@ require_paths:
|
|
142
142
|
- lib
|
143
143
|
required_ruby_version: !ruby/object:Gem::Requirement
|
144
144
|
requirements:
|
145
|
-
- -
|
145
|
+
- - ">="
|
146
146
|
- !ruby/object:Gem::Version
|
147
147
|
version: 1.9.3
|
148
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
149
|
requirements:
|
150
|
-
- -
|
150
|
+
- - ">="
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '0'
|
153
153
|
requirements: []
|
154
154
|
rubyforge_project:
|
155
|
-
rubygems_version: 2.
|
155
|
+
rubygems_version: 2.4.3
|
156
156
|
signing_key:
|
157
157
|
specification_version: 4
|
158
158
|
summary: Story Branch - create git branches based on pivotal tracker stories
|