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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0f515e7349d6e9e3c97deb9036626e0b609a4961
4
- data.tar.gz: 7cf773b74675fd4060b5c7f664b0dd4dcad6d87d
3
+ metadata.gz: 51fb5eb1e204d970116356bb1f2f273ac0de0804
4
+ data.tar.gz: 6ae1d58a21ae39e5def105f4b58d327147fd83a9
5
5
  SHA512:
6
- metadata.gz: 1146269635ae3f44f03aae8da7556fb566a65648997ae107aa59531958a800589eccf0129bc2c0661d80ddd7150b6a9910808890f8a780fbb770b9b889022e00
7
- data.tar.gz: e39bee90b82d67fd713e9a8155c756b9b9b548f2552c9aaef89a694a9f305ad7561a4f97daa33483c31e5332f46a9d81d95c396b80c3f24b618663fc6428c093
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
- Create a git feature branch with automatic reference to a Pivotal Tracker Story
5
+ ## Description
6
6
 
7
- By default `story_branch` will present a list of started stories from
8
- your Pivotal Tracker project, you select one and then provide
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
- When picking a story, enter the selection number on the left, using the up
13
- arrow (or C-p) will scroll through the selection numbers.
10
+ ### Commentary
14
11
 
15
- Once a story is selected, a feature branch name can be entered, a
16
- suggestion is provided (press the up arrow (or C-p) to see it)
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
- The feature branch name input has full
19
- [Readline](http://tiswww.case.edu/php/chet/readline/rluserman.html#SEC5)
20
- capability, to make it easy and pleasant to edit.
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
- ## Setup
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
- Config the Pivotal API key and Project ID, either in the environment
29
- or using a config YAML file. (`.story_branch` in the git root, or
30
- `~/.story_branch`)
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
- ## Usage
38
+ ### Usage
57
39
 
58
- While checked out at your master branch, and located in the git root.
40
+ Note: Run story_branch from the project root folder.
59
41
 
60
- story_branch
42
+ `start`, `story`, are run interactively and will display a
43
+ list of stories to work with.
61
44
 
62
- Follow the directions on screen. When the process is finished, you'll
63
- be switched automatically to the newly created branch.
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
- ## Aliases
50
+ ### Command names
66
51
 
67
- You can also run story branch using the following aliases.
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
- git story
70
-
71
- git story-branch
72
-
73
- story-branch
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
- If you'd like to contribute to `story_branch` please follow the steps below.
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_finish
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'story_branch'
3
- # require_relative '../lib/story_branch'
4
-
5
- sb = StoryBranch.new()
6
- sb.connect
3
+ sb = StoryBranch::Main.new()
4
+ sb.create_story_branch
data/bin/git-start ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_start
data/bin/git-story CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'story_branch'
3
- # require_relative '../lib/story_branch'
4
-
5
- sb = StoryBranch.new()
6
- sb.connect
3
+ sb = StoryBranch::Main.new()
4
+ sb.create_story_branch
data/bin/git-story-branch CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'story_branch'
3
- # require_relative '../lib/story_branch'
4
-
5
- sb = StoryBranch.new()
6
- sb.connect
3
+ sb = StoryBranch::Main.new()
4
+ sb.create_story_branch
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_finish
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_start
data/bin/story-branch CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'story_branch'
3
- # require_relative '../lib/story_branch'
4
-
5
- sb = StoryBranch.new()
6
- sb.connect
3
+ sb = StoryBranch::Main.new()
4
+ sb.create_story_branch
data/bin/story-finish ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_finish
data/bin/story-start ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_start
data/bin/story_branch CHANGED
@@ -1,6 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'story_branch'
3
- # require_relative '../lib/story_branch'
4
-
5
- sb = StoryBranch.new()
6
- sb.connect
3
+ sb = StoryBranch::Main.new()
4
+ sb.create_story_branch
data/bin/story_finish ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_finish
data/bin/story_start ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'story_branch'
3
+ sb = StoryBranch::Main.new()
4
+ sb.story_start
data/lib/story_branch.rb CHANGED
@@ -1,214 +1,393 @@
1
- # Name: story_branch (recommend: setting a git alias as "git story")
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
- # Version: 0.1.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
- # Description:
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
- # Create a git branch with automatic reference to a Pivotal Tracker
13
- # Story ID
32
+ # ### Installing
14
33
  #
15
- # Commentary:
34
+ # Install the gem:
16
35
  #
17
- # By default story_branch will present a list of started stories from
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
- # When picking a story, enter the selection number on the left (up
23
- # arrow / C-p will scroll through the numbers)
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
- # Once a story is selected, a feature branch name must be entered, a
26
- # suggestion is shown if you press up arrow / C-p
43
+ # ### Usage
27
44
  #
28
- # Usage:
45
+ # Note: Run story_branch from the project root folder.
29
46
  #
30
- # Note: Run story_branch from the project root folder, with the
31
- # master branch checked out, or an error will be thrown.
47
+ # `start`, `branch`, are run interactively and will display a
48
+ # list of stories to work with.
32
49
  #
33
- # You must have a PIVOTAL_API_KEY environment variable set to your
34
- # Pivotal api key, and either a .pivotal-id file or PIVOTAL_PROJECT_ID
35
- # environment variable set, (the file will supersede the environment
36
- # variable)
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
- class StoryBranch
80
+ module StoryBranch
81
+
82
+ class Main
46
83
 
47
- # Config file = .pivotal or ~/.pivotal
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
- # NOTE: Is this Windows friendly? Await freak-outs from those users... *crickets*
53
- PIVOTAL_CONFIG_FILES = ['.story_branch',"#{ENV['HOME']}/.story_branch"]
86
+ attr_accessor :p
54
87
 
55
- def initialize
56
- if config_file
57
- @pivotal_info = YAML.load_file config_file
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
- @api_key = config_value "api", 'PIVOTAL_API_KEY'
61
- @project_id = config_value "project", 'PIVOTAL_PROJECT_ID'
62
- end
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
- def config_file
65
- PIVOTAL_CONFIG_FILES.select{|conf| File.exists? conf}.first
66
- end
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
- def config_value key, env
69
- value = @pivotal_info[key] if @pivotal_info and @pivotal_info[key]
70
- value ||= env_required env
71
- value
72
- end
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
- def connect
75
- begin
76
- pivotal_story_branch @api_key, @project_id
77
- rescue RestClient::Unauthorized
78
- puts "Pivotal API key or Project ID invalid"
79
- exit
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
- def valid?
84
- return (not @api_key.strip.empty? and not @project_id.strip.empty?)
85
- end
175
+ def config_file
176
+ PIVOTAL_CONFIG_FILES.select{|conf| File.exists? conf}.first
177
+ end
86
178
 
87
- private
88
- def env_required var_name
89
- if ENV[var_name].nil?
90
- puts "$#{var_name} must be set or in .story_branch file"
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
- ENV[var_name]
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
- def readline prompt, history=[]
97
- if history.length > 0
98
- history.each {|i| Readline::HISTORY.push i}
195
+ class StringUtils
196
+
197
+ def self.dashed s
198
+ s.tr(' _,./:;', '-')
99
199
  end
100
- begin
101
- Readline.readline(prompt, false)
102
- rescue Interrupt
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
- def simple_sanitize s
112
- s.tr '\'"%!@#$(){}[]*\\?', ''
113
- end
207
+ class GitUtils
114
208
 
115
- # Branch name validation
116
- def validate_branch_name name
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
- #{name}
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
- This is too similar to the name of an existing
129
- branch, a more unique name is required
130
- END
226
+ def self.branch_names
227
+ g.branches.map(&:name)
131
228
  end
132
- end
133
229
 
134
- def valid_branch_name? name
135
- # Valid names begin with a letter and are followed by alphanumeric
136
- # with _ . - as allowed punctuation
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
- # Git operations
234
+ def self.current_story
235
+ current_branch.match(/(.*)-(\d+$)/)
236
+ end
142
237
 
143
- def is_existing_branch? name
144
- # we don't use the Git gem's is_local_branch? because we want to
145
- # ignore the id suffix while still avoiding name collisions
146
- git_branch_names.each do |n|
147
- normalised_branch_name = simple_sanitize(dashed(n.match(/(^.*)(-[1-9][0-9]+$)?/)[1]))
148
- levenshtein_distance = Levenshtein.distance normalised_branch_name, name
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
- def git_branch_names
157
- g = Git.open "."
158
- g.branches.map(&:name)
159
- end
247
+ def self.create_branch name
248
+ g.branch(name).create
249
+ g.branch(name).checkout
250
+ end
160
251
 
161
- def git_current_branch
162
- g = Git.open "."
163
- g.current_branch
164
- end
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
- def git_create_branch name
167
- g = Git.open "."
168
- g.branch(name).create
169
- g.branch(name).checkout
170
- end
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
- # Use Pivotal tracker API to get Stories
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
- def select_story stories
183
- story_selection = nil
184
- while story_selection == nil or story_selection == 0 or story_selection > stories.length + 1
185
- puts "invalid selection" if story_selection != nil
186
- story_selection = readline("Select a story: ", Range.new(1,stories.length).to_a.map(&:to_s)).to_i
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
- def create_feature_branch story
194
- current_branch = git_current_branch
195
- dashed_story_name = simple_sanitize((dashed story.name).downcase).squeeze("-")
196
- feature_branch_name = nil
197
- puts "You are checked out at: #{current_branch}"
198
- while feature_branch_name == nil or feature_branch_name == ""
199
- puts "Provide a new branch name..." if [nil, ""].include? feature_branch_name
200
- feature_branch_name = readline("Name of feature branch: ", [dashed_story_name])
201
- end
202
- feature_branch_name.chomp!
203
- validate_branch_name feature_branch_name
204
- feature_branch_name_with_story_id = "#{feature_branch_name}-#{story.id}"
205
- puts "Creating: #{feature_branch_name_with_story_id} with #{current_branch} as parent"
206
- git_create_branch feature_branch_name_with_story_id
207
- end
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.1.8
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/pivotal-story-branch
113
+ homepage: https://github.com/jasonm23/story_branch
98
114
  licenses:
99
115
  - MIT
100
116
  metadata: {}