story_branch 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +40 -90
- data/bin/git-finish +4 -0
- data/bin/git-pivotal-story +2 -4
- data/bin/git-start +4 -0
- data/bin/git-story +2 -4
- data/bin/git-story-branch +2 -4
- data/bin/git-story-finish +4 -0
- data/bin/git-story-start +4 -0
- data/bin/story-branch +2 -4
- data/bin/story-finish +4 -0
- data/bin/story-start +4 -0
- data/bin/story_branch +2 -4
- data/bin/story_finish +4 -0
- data/bin/story_start +4 -0
- data/lib/story_branch.rb +330 -151
- metadata +24 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 51fb5eb1e204d970116356bb1f2f273ac0de0804
|
4
|
+
data.tar.gz: 6ae1d58a21ae39e5def105f4b58d327147fd83a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 010f313b23b8b1604af7dd9fd679d60f9c67b8109fc4083c39932480be23f1ed544fcbe8ad5a3e488df9b3a68af67581e7e8c3a7edec2f82f7553d3650913fea
|
7
|
+
data.tar.gz: 2d227dfd93c2a3655f78a512f1ee3d7adf5d57c2c41c6fb96264bd88dc738bd94185dc5c611057d1ea52d3a997704b87d14f0f24bf249e9db418714a45ad94e0
|
data/README.md
CHANGED
@@ -2,113 +2,63 @@
|
|
2
2
|
|
3
3
|
# Story Branch
|
4
4
|
|
5
|
-
|
5
|
+
## Description
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
a feature branch name for that story. The branch will be created and
|
10
|
-
the name will include the selected `story_id` as a suffix.
|
7
|
+
A small collection of tools for working with git branches for
|
8
|
+
Pivotal Tracker stories. Story branch, story start, story finish
|
11
9
|
|
12
|
-
|
13
|
-
arrow (or C-p) will scroll through the selection numbers.
|
10
|
+
### Commentary
|
14
11
|
|
15
|
-
|
16
|
-
|
12
|
+
**git story**: Create a git branch with automatic reference to a
|
13
|
+
Pivotal Tracker Story, it will present a list of started stories
|
14
|
+
from your active project. Select a story, and it will suggest a
|
15
|
+
feature branch name for that story, which you can edit or
|
16
|
+
accept. The branch will be created (the story_id will automatically
|
17
|
+
be used as a suffix in the branch name)
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
**git start**: Start a story in Pivotal Tracker from the terminal.
|
20
|
+
List all unstarted stories in your current project. Entering a
|
21
|
+
partial string will fuzzy match against the list.
|
21
22
|
|
22
|
-
|
23
|
+
**git finish**: Create a finishing commit + message, for example:
|
24
|
+
"[Finishes #1234567] My Story Title" - optionally Finishes the story
|
25
|
+
via pivotal tracker's api.
|
26
|
+
|
27
|
+
### Installing
|
23
28
|
|
24
29
|
Install the gem:
|
25
30
|
|
26
31
|
gem install story_branch
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
The environment variables to set are `PIVOTAL_API_KEY` and `PIVOTAL_PROJECT_ID`
|
33
|
-
|
34
|
-
The **Pivotal API** key is visble at the bottom of your Pivotal Tracker
|
35
|
-
Profile page when you're logged in. the **Project ID** is in the URL for
|
36
|
-
your pivotal project.
|
37
|
-
|
38
|
-
If you decide to use the `.story_branch` config file, it should look
|
39
|
-
something like:
|
40
|
-
|
41
|
-
project: 123456
|
42
|
-
api: REHTKHMYKEYISM328974Y32487AND_SO_ON
|
43
|
-
|
44
|
-
Or just:
|
45
|
-
|
46
|
-
project: 123456
|
47
|
-
|
48
|
-
Replace the values with your own. Any value not found in the config
|
49
|
-
will attempt to be set from the environment. An error is thrown for a
|
50
|
-
value that cannot be found anywhere.
|
51
|
-
|
52
|
-
Note, that only one config file will be used at the moment, values
|
53
|
-
**CANNOT** currently be split over `.story_branch` in the git root and
|
54
|
-
`~/.story_branch`
|
33
|
+
You must have a `PIVOTAL_API_KEY` environment variable set to your
|
34
|
+
Pivotal api key, plus either a `.story_branch` file or
|
35
|
+
`PIVOTAL_PROJECT_ID` environment variable set. Note, values in
|
36
|
+
`.story_branch` will override environment variable settings.
|
55
37
|
|
56
|
-
|
38
|
+
### Usage
|
57
39
|
|
58
|
-
|
40
|
+
Note: Run story_branch from the project root folder.
|
59
41
|
|
60
|
-
|
42
|
+
`start`, `story`, are run interactively and will display a
|
43
|
+
list of stories to work with.
|
61
44
|
|
62
|
-
|
63
|
-
|
45
|
+
`finish` will scan the current branch name for a story id (as its
|
46
|
+
suffix) and if a valid, active story is found on pivotal tracker it
|
47
|
+
will create a commit with a message to trigger pivotal's git
|
48
|
+
integraton features.
|
64
49
|
|
65
|
-
|
50
|
+
### Command names
|
66
51
|
|
67
|
-
|
52
|
+
It's possible to the commands in a few ways, we have deprecated the
|
53
|
+
old commmand names, and now encourage only the use of the `git`
|
54
|
+
style usage.
|
68
55
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
## Roadmap
|
77
|
-
|
78
|
-
Prepare a v1.0 release
|
79
|
-
|
80
|
-
## Changelog
|
81
|
-
|
82
|
-
* Banish constraint of master as parent branch
|
83
|
-
* Verify API key / Project ID
|
84
|
-
* Update config method to use YAML .story_branch files (in git root or $HOME) see above.
|
85
|
-
* Build/Publish as Ruby gem
|
86
|
-
* Simple sanitization
|
87
|
-
* Begin test coverage
|
88
|
-
* Refactor to class
|
89
|
-
* Provide readline editing for inputs
|
90
|
-
* Present safe version of story name (dash-cased) for editing
|
91
|
-
* Readline history injection for story selection & branch name suggestion
|
92
|
-
* Validate that branchname is 'legal'
|
93
|
-
* Validate that branchname doesn't already exist (strip pivotal
|
94
|
-
tracker ids suffix from existing names when present)
|
95
|
-
* Use Levenshtein Distance to determine if name is (very) similar to
|
96
|
-
existing branch names
|
97
|
-
* Use Git gem
|
98
|
-
* Use ActiveSupport gem
|
99
|
-
* Use Levenschtein-ffi gem
|
100
|
-
* ~~Look for pivotal project id (.pivotal-id) in repo root (we assume
|
101
|
-
we're in project root.) fallback to `PIVOTAL_PROJECT_ID` environment
|
102
|
-
var~~ **PLEASE NOTE:** Now uses `.story_branch` or `~/.story_branch`
|
103
|
-
as config file, containing YAML. Only one is used, the local root
|
104
|
-
`.story_branch` is favoured.
|
56
|
+
git style | deprecated
|
57
|
+
------------+--------------+--------------
|
58
|
+
git story | story_branch | story-branch
|
59
|
+
git start | story_start | story-start
|
60
|
+
git finish | story_finish | story-finish
|
105
61
|
|
106
62
|
## Contributing
|
107
63
|
|
108
|
-
|
109
|
-
|
110
|
-
* Fork, start a feature branch and check it out
|
111
|
-
* Write tests / Pass them
|
112
|
-
* Send a pull request
|
113
|
-
|
114
|
-
**Note:** Pull requests require full test coverage to be accepted.
|
64
|
+
All pull requests are welcome and will be reviewed.
|
data/bin/git-finish
ADDED
data/bin/git-pivotal-story
CHANGED
data/bin/git-start
ADDED
data/bin/git-story
CHANGED
data/bin/git-story-branch
CHANGED
data/bin/git-story-start
ADDED
data/bin/story-branch
CHANGED
data/bin/story-finish
ADDED
data/bin/story-start
ADDED
data/bin/story_branch
CHANGED
data/bin/story_finish
ADDED
data/bin/story_start
ADDED
data/lib/story_branch.rb
CHANGED
@@ -1,214 +1,393 @@
|
|
1
|
-
# Name:
|
1
|
+
# Name: story branch
|
2
2
|
#
|
3
3
|
# Authors: Jason Milkins <jason@opsmanager.com>
|
4
4
|
# Rui Baltazar <rui.p.baltazar@gmail.com>
|
5
|
-
# Gabe Hollombe <gabe@neo.com>
|
6
5
|
# Dominic Wong <dominic.wong.617@gmail.com>
|
6
|
+
# Gabe Hollombe <gabe@neo.com>
|
7
|
+
#
|
8
|
+
# Version: 0.2.0
|
9
|
+
#
|
10
|
+
# ## Description
|
11
|
+
#
|
12
|
+
# A small collection of tools for working with git branches for
|
13
|
+
# Pivotal Tracker stories. Story branch, start, finish
|
14
|
+
#
|
15
|
+
# ### Commentary
|
16
|
+
#
|
17
|
+
# **branch**: Create a git branch with automatic reference to a
|
18
|
+
# Pivotal Tracker Story, it will present a list of started stories
|
19
|
+
# from your active project. Select a story, and it will suggest a
|
20
|
+
# feature branch name for that story, which you can edit or
|
21
|
+
# accept. The branch will be created (the story_id will automatically
|
22
|
+
# be used as a suffix in the branch name)
|
7
23
|
#
|
8
|
-
#
|
24
|
+
# **start**: Start a story in Pivotal Tracker from the terminal.
|
25
|
+
# List all unstarted stories in your current project. Entering a
|
26
|
+
# partial string will fuzzy match against the list.
|
9
27
|
#
|
10
|
-
#
|
28
|
+
# **finish**: Create a finishing commit + message, for example:
|
29
|
+
# "[Finishes #1234567] My Story Title" - optionally Finishes the story
|
30
|
+
# via pivotal tracker's api.
|
11
31
|
#
|
12
|
-
#
|
13
|
-
# Story ID
|
32
|
+
# ### Installing
|
14
33
|
#
|
15
|
-
#
|
34
|
+
# Install the gem:
|
16
35
|
#
|
17
|
-
#
|
18
|
-
# your active PivotalTracker project, you select one and then provide
|
19
|
-
# a feature branch name for that story. The branch will be created and
|
20
|
-
# the name will include the story_id as a suffix.
|
36
|
+
# gem install story_branch
|
21
37
|
#
|
22
|
-
#
|
23
|
-
#
|
38
|
+
# You must have a `PIVOTAL_API_KEY` environment variable set to your
|
39
|
+
# Pivotal api key, plus either a `.story_branch` file or
|
40
|
+
# `PIVOTAL_PROJECT_ID` environment variable set. Note, values in
|
41
|
+
# `.story_branch` will override environment variable settings.
|
24
42
|
#
|
25
|
-
#
|
26
|
-
# suggestion is shown if you press up arrow / C-p
|
43
|
+
# ### Usage
|
27
44
|
#
|
28
|
-
#
|
45
|
+
# Note: Run story_branch from the project root folder.
|
29
46
|
#
|
30
|
-
#
|
31
|
-
#
|
47
|
+
# `start`, `branch`, are run interactively and will display a
|
48
|
+
# list of stories to work with.
|
32
49
|
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
50
|
+
# `finish` will scan the current branch name for a story id (as its
|
51
|
+
# suffix) and if a valid, active story is found on pivotal tracker it
|
52
|
+
# will create a commit with a message to trigger pivotal's git
|
53
|
+
# integraton features.
|
37
54
|
#
|
55
|
+
# ### Command names
|
56
|
+
#
|
57
|
+
# It's possible to the commands in a few ways, we have deprecated the
|
58
|
+
# old commmand names, and now encourage only the use of the `git`
|
59
|
+
# style usage.
|
60
|
+
#
|
61
|
+
# git style | deprecated
|
62
|
+
# ------------+--------------+--------------
|
63
|
+
# git story | story_branch | story-branch
|
64
|
+
# git start | story_start | story-start
|
65
|
+
# git finish | story_finish | story-finish
|
66
|
+
#
|
67
|
+
# ## Contributing
|
68
|
+
#
|
69
|
+
# All pull requests are welcome and will be reviewed.
|
70
|
+
#
|
71
|
+
# Code:
|
38
72
|
|
39
73
|
require 'yaml'
|
40
74
|
require 'pivotal-tracker'
|
41
75
|
require 'readline'
|
42
76
|
require 'git'
|
77
|
+
require 'active_support/core_ext/string/inflections'
|
43
78
|
require 'levenshtein'
|
44
79
|
|
45
|
-
|
80
|
+
module StoryBranch
|
81
|
+
|
82
|
+
class Main
|
46
83
|
|
47
|
-
|
48
|
-
# contains YAML
|
49
|
-
# project: pivotal-id
|
50
|
-
# api: pivotal api key
|
84
|
+
PIVOTAL_CONFIG_FILES = ['.story_branch',"#{ENV['HOME']}/.story_branch"]
|
51
85
|
|
52
|
-
|
53
|
-
PIVOTAL_CONFIG_FILES = ['.story_branch',"#{ENV['HOME']}/.story_branch"]
|
86
|
+
attr_accessor :p
|
54
87
|
|
55
|
-
|
56
|
-
|
57
|
-
|
88
|
+
def initialize
|
89
|
+
if config_file
|
90
|
+
@pivotal_info = YAML.load_file config_file
|
91
|
+
end
|
92
|
+
@p = PivotalUtils.new
|
93
|
+
@p.api_key = config_value "api", 'PIVOTAL_API_KEY'
|
94
|
+
@p.project_id = config_value "project", 'PIVOTAL_PROJECT_ID'
|
95
|
+
exit unless @p.valid?
|
58
96
|
end
|
59
97
|
|
60
|
-
|
61
|
-
|
62
|
-
|
98
|
+
def create_story_branch
|
99
|
+
begin
|
100
|
+
@p.get_project
|
101
|
+
stories = @p.display_stories :started
|
102
|
+
if stories.length < 1
|
103
|
+
puts "No stories started... exiting"
|
104
|
+
exit
|
105
|
+
end
|
106
|
+
puts "[0] Exit"
|
107
|
+
story = @p.select_story stories
|
108
|
+
if story
|
109
|
+
@p.create_feature_branch story
|
110
|
+
end
|
111
|
+
rescue RestClient::Unauthorized
|
112
|
+
puts "Pivotal API key or Project ID invalid"
|
113
|
+
return nil
|
114
|
+
end
|
115
|
+
end
|
63
116
|
|
64
|
-
|
65
|
-
|
66
|
-
|
117
|
+
def story_start
|
118
|
+
begin
|
119
|
+
@p.get_project
|
120
|
+
# TODO: Use a predicate for Estimated and Backlog'ed stories
|
121
|
+
stories = @p.display_stories :unstarted
|
122
|
+
puts "[0] Exit"
|
123
|
+
story = @p.select_story stories
|
124
|
+
if story
|
125
|
+
story.update :current_state => "started"
|
126
|
+
puts "#{story.id} started"
|
127
|
+
end
|
128
|
+
rescue RestClient::Unauthorized
|
129
|
+
puts "Pivotal API key or Project ID invalid"
|
130
|
+
return nil
|
131
|
+
end
|
132
|
+
end
|
67
133
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
134
|
+
def story_unstart
|
135
|
+
# TODO: unstart a started story.
|
136
|
+
end
|
137
|
+
|
138
|
+
def story_finish
|
139
|
+
begin
|
140
|
+
@p.get_project
|
141
|
+
unless @p.is_current_branch_a_story?
|
142
|
+
puts "Your current branch: #{GitUtils.current_branch}"
|
143
|
+
puts "is not linked to a started story."
|
144
|
+
return
|
145
|
+
end
|
146
|
+
|
147
|
+
if GitUtils.has_status? :untracked or GitUtils.has_status? :modified
|
148
|
+
puts "There are unstaged changes"
|
149
|
+
puts "Use git add to stage changes before running git finish"
|
150
|
+
puts "Use git stash if you want to hide changes for this commit"
|
151
|
+
return
|
152
|
+
end
|
153
|
+
|
154
|
+
unless GitUtils.has_status? :added or GitUtils.has_status? :staged
|
155
|
+
puts "There are no staged changes."
|
156
|
+
puts "Nothing to do"
|
157
|
+
return
|
158
|
+
end
|
73
159
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
160
|
+
puts "Use standard finishing commit message: [y/N]?"
|
161
|
+
commit_message = "[Finishes ##{GitUtils.current_branch_story_parts[:id]}] #{GitUtils.current_branch_story_parts[:description]}"
|
162
|
+
puts commit_message
|
163
|
+
|
164
|
+
if gets.chomp!.downcase == "y"
|
165
|
+
GitUtils.commit commit_message
|
166
|
+
else
|
167
|
+
puts "Aborted"
|
168
|
+
end
|
169
|
+
rescue RestClient::Unauthorized
|
170
|
+
puts "Pivotal API key or Project ID invalid"
|
171
|
+
return nil
|
172
|
+
end
|
80
173
|
end
|
81
|
-
end
|
82
174
|
|
83
|
-
|
84
|
-
|
85
|
-
|
175
|
+
def config_file
|
176
|
+
PIVOTAL_CONFIG_FILES.select{|conf| File.exists? conf}.first
|
177
|
+
end
|
86
178
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
exit
|
179
|
+
def config_value key, env
|
180
|
+
value = @pivotal_info[key] if @pivotal_info and @pivotal_info[key]
|
181
|
+
value ||= env_required env
|
182
|
+
value
|
92
183
|
end
|
93
|
-
|
184
|
+
|
185
|
+
def env_required var_name
|
186
|
+
if ENV[var_name].nil?
|
187
|
+
puts "$#{var_name} must be set or in .story_branch file"
|
188
|
+
return nil
|
189
|
+
end
|
190
|
+
ENV[var_name]
|
191
|
+
end
|
192
|
+
|
94
193
|
end
|
95
194
|
|
96
|
-
|
97
|
-
|
98
|
-
|
195
|
+
class StringUtils
|
196
|
+
|
197
|
+
def self.dashed s
|
198
|
+
s.tr(' _,./:;', '-')
|
99
199
|
end
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
exit
|
200
|
+
|
201
|
+
def self.simple_sanitize s
|
202
|
+
s.tr '\'"%!@#$(){}[]*\\?', ''
|
104
203
|
end
|
105
|
-
end
|
106
204
|
|
107
|
-
def dashed s
|
108
|
-
s.tr(' _,./:;', '-')
|
109
205
|
end
|
110
206
|
|
111
|
-
|
112
|
-
s.tr '\'"%!@#$(){}[]*\\?', ''
|
113
|
-
end
|
207
|
+
class GitUtils
|
114
208
|
|
115
|
-
|
116
|
-
|
117
|
-
unless valid_branch_name? name
|
118
|
-
puts "Error: #{name}\nis an invalid name."
|
119
|
-
return false
|
209
|
+
def self.g
|
210
|
+
Git.open "."
|
120
211
|
end
|
121
|
-
existing_name_score = is_existing_branch?(name)
|
122
|
-
unless existing_name_score == -1
|
123
|
-
puts <<-END.strip_heredoc
|
124
|
-
Name Collision Error:
|
125
212
|
|
126
|
-
|
213
|
+
def self.is_existing_branch? name
|
214
|
+
# we don't use the Git gem's is_local_branch? because we want to
|
215
|
+
# ignore the id suffix while still avoiding name collisions
|
216
|
+
branch_names.each do |n|
|
217
|
+
normalised_branch_name = StringUtils.simple_sanitize(StringUtils.dashed(n.match(/(^.*)(-[1-9][0-9]+$)?/)[1]))
|
218
|
+
levenshtein_distance = Levenshtein.distance normalised_branch_name, name
|
219
|
+
if levenshtein_distance < 2
|
220
|
+
return levenshtein_distance
|
221
|
+
end
|
222
|
+
end
|
223
|
+
return -1
|
224
|
+
end
|
127
225
|
|
128
|
-
|
129
|
-
|
130
|
-
END
|
226
|
+
def self.branch_names
|
227
|
+
g.branches.map(&:name)
|
131
228
|
end
|
132
|
-
end
|
133
229
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
valid = /[a-zA-Z][-._0-9a-zA-Z]*/
|
138
|
-
name.match valid
|
139
|
-
end
|
230
|
+
def self.current_branch
|
231
|
+
g.current_branch
|
232
|
+
end
|
140
233
|
|
141
|
-
|
234
|
+
def self.current_story
|
235
|
+
current_branch.match(/(.*)-(\d+$)/)
|
236
|
+
end
|
142
237
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
if levenshtein_distance < 2
|
150
|
-
return levenshtein_distance
|
238
|
+
def self.current_branch_story_parts
|
239
|
+
matches = current_branch.match(/(.*)-(\d+$)/)
|
240
|
+
if matches.length == 3
|
241
|
+
{ description: matches[1], id: matches[2] }
|
242
|
+
else
|
243
|
+
nil
|
151
244
|
end
|
152
245
|
end
|
153
|
-
return -1
|
154
|
-
end
|
155
246
|
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
247
|
+
def self.create_branch name
|
248
|
+
g.branch(name).create
|
249
|
+
g.branch(name).checkout
|
250
|
+
end
|
160
251
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
252
|
+
def self.status_collect status, regex
|
253
|
+
status.select{|e|
|
254
|
+
e.match(regex)
|
255
|
+
}.map{|e|
|
256
|
+
e.match(regex)[1]
|
257
|
+
}
|
258
|
+
end
|
165
259
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
260
|
+
def self.status
|
261
|
+
modified_rx = /^ M (.*)/
|
262
|
+
untracked_rx = /^\?\? (.*)/
|
263
|
+
staged_rx = /^M (.*)/
|
264
|
+
added_rx = /^A (.*)/
|
265
|
+
status = g.lib.send(:command, "status", "-s").lines
|
266
|
+
return nil if status.length == 0
|
267
|
+
{
|
268
|
+
modified: status_collect(status, modified_rx),
|
269
|
+
untracked: status_collect(status, untracked_rx),
|
270
|
+
added: status_collect(status, added_rx),
|
271
|
+
staged: status_collect(status, staged_rx)
|
272
|
+
}
|
273
|
+
end
|
171
274
|
|
172
|
-
|
275
|
+
def self.has_status? state
|
276
|
+
return false unless status
|
277
|
+
status[state].length > 0
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.commit message
|
281
|
+
g.commit(message)
|
282
|
+
end
|
173
283
|
|
174
|
-
def list_pivotal_stories api_key, project_id
|
175
|
-
PivotalTracker::Client.token = api_key
|
176
|
-
project = PivotalTracker::Project.find(project_id.to_i)
|
177
|
-
stories = project.stories.all({current_state: :started})
|
178
|
-
stories.each_with_index{|s,i| puts "[#{i+1}] ##{s.id} : #{s.name}"}
|
179
|
-
stories
|
180
284
|
end
|
181
285
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
286
|
+
class PivotalUtils
|
287
|
+
|
288
|
+
attr_accessor :api_key, :project_id, :project
|
289
|
+
|
290
|
+
def valid?
|
291
|
+
return (not @api_key.nil? and not @project_id.nil?)
|
187
292
|
end
|
188
|
-
story = stories[story_selection - 1]
|
189
|
-
puts "Selected : ##{story.id} : #{story.name}"
|
190
|
-
return story
|
191
|
-
end
|
192
293
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
294
|
+
def get_project
|
295
|
+
PivotalTracker::Client.token = @api_key
|
296
|
+
@project = PivotalTracker::Project.find @project_id.to_i
|
297
|
+
end
|
298
|
+
|
299
|
+
def is_current_branch_a_story?
|
300
|
+
GitUtils.current_story.length == 3 and
|
301
|
+
filtered_stories_list(:started).map(&:id).include? GitUtils.current_story[2].to_i
|
302
|
+
end
|
303
|
+
|
304
|
+
def story_from_current_branch
|
305
|
+
get_project.stories.find(GitUtils.current_story[2].to_i) if GitUtils.current_story.length == 3
|
306
|
+
end
|
307
|
+
|
308
|
+
# TODO: Add some other predicates as we need them...
|
309
|
+
# Filtering on where a story lives (Backlog, IceBox)
|
310
|
+
# Filtering on tags/labels
|
311
|
+
# Filtering on estimation (estimated?, 0 point, 1 point etc.)
|
312
|
+
|
313
|
+
def filtered_stories_list state
|
314
|
+
project = get_project
|
315
|
+
project.stories.all({current_state: state})
|
316
|
+
end
|
317
|
+
|
318
|
+
def display_stories state
|
319
|
+
filtered_stories_list(state).each_with_index {|s,i| puts one_line_story s, i}
|
320
|
+
end
|
321
|
+
|
322
|
+
def one_line_story s, i
|
323
|
+
"[#{i+1}] ##{s.id} : #{s.name}"
|
324
|
+
end
|
325
|
+
|
326
|
+
def select_story stories
|
327
|
+
story_selection = nil
|
328
|
+
while story_selection == nil or story_selection.to_i > stories.length + 1
|
329
|
+
puts "invalid selection" if story_selection != nil
|
330
|
+
story_texts = Range.new(1,stories.length).to_a.map(&:to_s)
|
331
|
+
story_selection = readline("Select a story: ", story_texts)
|
332
|
+
end
|
333
|
+
if story_selection.to_i == 0
|
334
|
+
return nil
|
335
|
+
end
|
336
|
+
story = stories[story_selection.to_i - 1]
|
337
|
+
puts "Selected : ##{story.id} : #{story.name}"
|
338
|
+
return story
|
339
|
+
end
|
340
|
+
|
341
|
+
def create_feature_branch story
|
342
|
+
dashed_story_name = StringUtils.simple_sanitize((StringUtils.dashed story.name).downcase).squeeze("-")
|
343
|
+
feature_branch_name = nil
|
344
|
+
puts "You are checked out at: #{GitUtils.current_branch}"
|
345
|
+
while feature_branch_name == nil or feature_branch_name == ""
|
346
|
+
puts "Provide a new branch name... (C-p or <up> for suggested name)" if [nil, ""].include? feature_branch_name
|
347
|
+
feature_branch_name = readline("Name of feature branch: ", [dashed_story_name])
|
348
|
+
end
|
349
|
+
feature_branch_name.chomp!
|
350
|
+
validate_branch_name feature_branch_name
|
351
|
+
feature_branch_name_with_story_id = "#{feature_branch_name}-#{story.id}"
|
352
|
+
puts "Creating: #{feature_branch_name_with_story_id} with #{GitUtils.current_branch} as parent"
|
353
|
+
GitUtils.create_branch feature_branch_name_with_story_id
|
354
|
+
end
|
355
|
+
|
356
|
+
# Branch name validation
|
357
|
+
def validate_branch_name name
|
358
|
+
unless valid_branch_name? name
|
359
|
+
puts "Error: #{name}\nis an invalid name."
|
360
|
+
return false
|
361
|
+
end
|
362
|
+
existing_name_score = GitUtils.is_existing_branch?(name)
|
363
|
+
unless existing_name_score == -1
|
364
|
+
puts <<-END.strip_heredoc
|
365
|
+
Name Collision Error:
|
366
|
+
|
367
|
+
#{name}
|
368
|
+
|
369
|
+
This is too similar to the name of an existing
|
370
|
+
branch, a more unique name is required
|
371
|
+
END
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def valid_branch_name? name
|
376
|
+
# Valid names begin with a letter and are followed by alphanumeric
|
377
|
+
# with _ . - as allowed punctuation
|
378
|
+
valid = /[a-zA-Z][-._0-9a-zA-Z]*/
|
379
|
+
name.match valid
|
380
|
+
end
|
381
|
+
|
382
|
+
def readline prompt, history=[]
|
383
|
+
if history.length > 0
|
384
|
+
history.each {|i| Readline::HISTORY.push i}
|
385
|
+
end
|
386
|
+
begin
|
387
|
+
Readline.readline(prompt, false)
|
388
|
+
rescue Interrupt
|
389
|
+
end
|
390
|
+
end
|
208
391
|
|
209
|
-
def pivotal_story_branch api_key, project_id
|
210
|
-
stories = list_pivotal_stories api_key, project_id
|
211
|
-
story = select_story stories
|
212
|
-
create_feature_branch story
|
213
392
|
end
|
214
393
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: story_branch
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jason Milkins
|
8
|
-
- Gabe Hollombe
|
9
8
|
- Rui Baltazar
|
10
9
|
- Dominic Wong
|
10
|
+
- Gabe Hollombe
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
@@ -59,42 +59,58 @@ dependencies:
|
|
59
59
|
name: rspec
|
60
60
|
requirement: !ruby/object:Gem::Requirement
|
61
61
|
requirements:
|
62
|
-
- - "
|
62
|
+
- - "~>"
|
63
63
|
- !ruby/object:Gem::Version
|
64
|
-
version: '0'
|
64
|
+
version: '3.0'
|
65
65
|
type: :development
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
|
-
- - "
|
69
|
+
- - "~>"
|
70
70
|
- !ruby/object:Gem::Version
|
71
|
-
version: '0'
|
71
|
+
version: '3.0'
|
72
72
|
description: Simple gem that fetches the available stories in your pivotaltracker
|
73
73
|
project and allows you to create a git branch with the name based on the selected
|
74
74
|
story
|
75
75
|
email:
|
76
76
|
- jasonm23@gmail.com
|
77
|
-
- gabe@neo.com
|
78
77
|
- rui.p.baltazar@gmail.com
|
79
78
|
- dominic.wong.617@gmail.com
|
79
|
+
- gabe@neo.com
|
80
80
|
executables:
|
81
|
+
- story_start
|
82
|
+
- story-start
|
83
|
+
- git-start
|
84
|
+
- git-story-start
|
81
85
|
- story_branch
|
82
86
|
- story-branch
|
83
87
|
- git-story
|
84
88
|
- git-story-branch
|
85
89
|
- git-pivotal-story
|
90
|
+
- story_finish
|
91
|
+
- story-finish
|
92
|
+
- git-finish
|
93
|
+
- git-story-finish
|
86
94
|
extensions: []
|
87
95
|
extra_rdoc_files: []
|
88
96
|
files:
|
89
97
|
- LICENCE
|
90
98
|
- README.md
|
99
|
+
- bin/git-finish
|
91
100
|
- bin/git-pivotal-story
|
101
|
+
- bin/git-start
|
92
102
|
- bin/git-story
|
93
103
|
- bin/git-story-branch
|
104
|
+
- bin/git-story-finish
|
105
|
+
- bin/git-story-start
|
94
106
|
- bin/story-branch
|
107
|
+
- bin/story-finish
|
108
|
+
- bin/story-start
|
95
109
|
- bin/story_branch
|
110
|
+
- bin/story_finish
|
111
|
+
- bin/story_start
|
96
112
|
- lib/story_branch.rb
|
97
|
-
homepage: https://github.com/jasonm23/
|
113
|
+
homepage: https://github.com/jasonm23/story_branch
|
98
114
|
licenses:
|
99
115
|
- MIT
|
100
116
|
metadata: {}
|