story_branch 0.1.8 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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: {}
|