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 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: {}