story_branch 0.1.8
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 +7 -0
- data/LICENCE +20 -0
- data/README.md +114 -0
- data/bin/git-pivotal-story +6 -0
- data/bin/git-story +6 -0
- data/bin/git-story-branch +6 -0
- data/bin/story-branch +6 -0
- data/bin/story_branch +6 -0
- data/lib/story_branch.rb +214 -0
- metadata +121 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 0f515e7349d6e9e3c97deb9036626e0b609a4961
|
4
|
+
data.tar.gz: 7cf773b74675fd4060b5c7f664b0dd4dcad6d87d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1146269635ae3f44f03aae8da7556fb566a65648997ae107aa59531958a800589eccf0129bc2c0661d80ddd7150b6a9910808890f8a780fbb770b9b889022e00
|
7
|
+
data.tar.gz: e39bee90b82d67fd713e9a8155c756b9b9b548f2552c9aaef89a694a9f305ad7561a4f97daa33483c31e5332f46a9d81d95c396b80c3f24b618663fc6428c093
|
data/LICENCE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Jason Milkins, Gabe Hollombe, Rui Baltazar, Dominic Wong
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
|
+
this software and associated documentation files (the "Software"), to deal in
|
7
|
+
the Software without restriction, including without limitation the rights to
|
8
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
9
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
10
|
+
subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
17
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
18
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
19
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,114 @@
|
|
1
|
+
[](http://badge.fury.io/rb/story_branch)
|
2
|
+
|
3
|
+
# Story Branch
|
4
|
+
|
5
|
+
Create a git feature branch with automatic reference to a Pivotal Tracker Story
|
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.
|
11
|
+
|
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.
|
14
|
+
|
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)
|
17
|
+
|
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.
|
21
|
+
|
22
|
+
## Setup
|
23
|
+
|
24
|
+
Install the gem:
|
25
|
+
|
26
|
+
gem install story_branch
|
27
|
+
|
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`
|
55
|
+
|
56
|
+
## Usage
|
57
|
+
|
58
|
+
While checked out at your master branch, and located in the git root.
|
59
|
+
|
60
|
+
story_branch
|
61
|
+
|
62
|
+
Follow the directions on screen. When the process is finished, you'll
|
63
|
+
be switched automatically to the newly created branch.
|
64
|
+
|
65
|
+
## Aliases
|
66
|
+
|
67
|
+
You can also run story branch using the following aliases.
|
68
|
+
|
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.
|
105
|
+
|
106
|
+
## Contributing
|
107
|
+
|
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.
|
data/bin/git-story
ADDED
data/bin/story-branch
ADDED
data/bin/story_branch
ADDED
data/lib/story_branch.rb
ADDED
@@ -0,0 +1,214 @@
|
|
1
|
+
# Name: story_branch (recommend: setting a git alias as "git story")
|
2
|
+
#
|
3
|
+
# Authors: Jason Milkins <jason@opsmanager.com>
|
4
|
+
# Rui Baltazar <rui.p.baltazar@gmail.com>
|
5
|
+
# Gabe Hollombe <gabe@neo.com>
|
6
|
+
# Dominic Wong <dominic.wong.617@gmail.com>
|
7
|
+
#
|
8
|
+
# Version: 0.1.8
|
9
|
+
#
|
10
|
+
# Description:
|
11
|
+
#
|
12
|
+
# Create a git branch with automatic reference to a Pivotal Tracker
|
13
|
+
# Story ID
|
14
|
+
#
|
15
|
+
# Commentary:
|
16
|
+
#
|
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.
|
21
|
+
#
|
22
|
+
# When picking a story, enter the selection number on the left (up
|
23
|
+
# arrow / C-p will scroll through the numbers)
|
24
|
+
#
|
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
|
27
|
+
#
|
28
|
+
# Usage:
|
29
|
+
#
|
30
|
+
# Note: Run story_branch from the project root folder, with the
|
31
|
+
# master branch checked out, or an error will be thrown.
|
32
|
+
#
|
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)
|
37
|
+
#
|
38
|
+
|
39
|
+
require 'yaml'
|
40
|
+
require 'pivotal-tracker'
|
41
|
+
require 'readline'
|
42
|
+
require 'git'
|
43
|
+
require 'levenshtein'
|
44
|
+
|
45
|
+
class StoryBranch
|
46
|
+
|
47
|
+
# Config file = .pivotal or ~/.pivotal
|
48
|
+
# contains YAML
|
49
|
+
# project: pivotal-id
|
50
|
+
# api: pivotal api key
|
51
|
+
|
52
|
+
# NOTE: Is this Windows friendly? Await freak-outs from those users... *crickets*
|
53
|
+
PIVOTAL_CONFIG_FILES = ['.story_branch',"#{ENV['HOME']}/.story_branch"]
|
54
|
+
|
55
|
+
def initialize
|
56
|
+
if config_file
|
57
|
+
@pivotal_info = YAML.load_file config_file
|
58
|
+
end
|
59
|
+
|
60
|
+
@api_key = config_value "api", 'PIVOTAL_API_KEY'
|
61
|
+
@project_id = config_value "project", 'PIVOTAL_PROJECT_ID'
|
62
|
+
end
|
63
|
+
|
64
|
+
def config_file
|
65
|
+
PIVOTAL_CONFIG_FILES.select{|conf| File.exists? conf}.first
|
66
|
+
end
|
67
|
+
|
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
|
73
|
+
|
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
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def valid?
|
84
|
+
return (not @api_key.strip.empty? and not @project_id.strip.empty?)
|
85
|
+
end
|
86
|
+
|
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
|
92
|
+
end
|
93
|
+
ENV[var_name]
|
94
|
+
end
|
95
|
+
|
96
|
+
def readline prompt, history=[]
|
97
|
+
if history.length > 0
|
98
|
+
history.each {|i| Readline::HISTORY.push i}
|
99
|
+
end
|
100
|
+
begin
|
101
|
+
Readline.readline(prompt, false)
|
102
|
+
rescue Interrupt
|
103
|
+
exit
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def dashed s
|
108
|
+
s.tr(' _,./:;', '-')
|
109
|
+
end
|
110
|
+
|
111
|
+
def simple_sanitize s
|
112
|
+
s.tr '\'"%!@#$(){}[]*\\?', ''
|
113
|
+
end
|
114
|
+
|
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
|
120
|
+
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
|
+
|
126
|
+
#{name}
|
127
|
+
|
128
|
+
This is too similar to the name of an existing
|
129
|
+
branch, a more unique name is required
|
130
|
+
END
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
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
|
140
|
+
|
141
|
+
# Git operations
|
142
|
+
|
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
|
151
|
+
end
|
152
|
+
end
|
153
|
+
return -1
|
154
|
+
end
|
155
|
+
|
156
|
+
def git_branch_names
|
157
|
+
g = Git.open "."
|
158
|
+
g.branches.map(&:name)
|
159
|
+
end
|
160
|
+
|
161
|
+
def git_current_branch
|
162
|
+
g = Git.open "."
|
163
|
+
g.current_branch
|
164
|
+
end
|
165
|
+
|
166
|
+
def git_create_branch name
|
167
|
+
g = Git.open "."
|
168
|
+
g.branch(name).create
|
169
|
+
g.branch(name).checkout
|
170
|
+
end
|
171
|
+
|
172
|
+
# Use Pivotal tracker API to get Stories
|
173
|
+
|
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
|
+
end
|
181
|
+
|
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
|
187
|
+
end
|
188
|
+
story = stories[story_selection - 1]
|
189
|
+
puts "Selected : ##{story.id} : #{story.name}"
|
190
|
+
return story
|
191
|
+
end
|
192
|
+
|
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
|
208
|
+
|
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
|
+
end
|
214
|
+
end
|
metadata
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: story_branch
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.8
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason Milkins
|
8
|
+
- Gabe Hollombe
|
9
|
+
- Rui Baltazar
|
10
|
+
- Dominic Wong
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2014-06-24 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: pivotal-tracker
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
requirements:
|
20
|
+
- - "~>"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0.5'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.5'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: git
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
requirements:
|
34
|
+
- - "~>"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '1.2'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '1.2'
|
44
|
+
- !ruby/object:Gem::Dependency
|
45
|
+
name: levenshtein-ffi
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
47
|
+
requirements:
|
48
|
+
- - "~>"
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '1.0'
|
51
|
+
type: :runtime
|
52
|
+
prerelease: false
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '1.0'
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: rspec
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
description: Simple gem that fetches the available stories in your pivotaltracker
|
73
|
+
project and allows you to create a git branch with the name based on the selected
|
74
|
+
story
|
75
|
+
email:
|
76
|
+
- jasonm23@gmail.com
|
77
|
+
- gabe@neo.com
|
78
|
+
- rui.p.baltazar@gmail.com
|
79
|
+
- dominic.wong.617@gmail.com
|
80
|
+
executables:
|
81
|
+
- story_branch
|
82
|
+
- story-branch
|
83
|
+
- git-story
|
84
|
+
- git-story-branch
|
85
|
+
- git-pivotal-story
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- LICENCE
|
90
|
+
- README.md
|
91
|
+
- bin/git-pivotal-story
|
92
|
+
- bin/git-story
|
93
|
+
- bin/git-story-branch
|
94
|
+
- bin/story-branch
|
95
|
+
- bin/story_branch
|
96
|
+
- lib/story_branch.rb
|
97
|
+
homepage: https://github.com/jasonm23/pivotal-story-branch
|
98
|
+
licenses:
|
99
|
+
- MIT
|
100
|
+
metadata: {}
|
101
|
+
post_install_message:
|
102
|
+
rdoc_options: []
|
103
|
+
require_paths:
|
104
|
+
- lib
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: 1.9.3
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
version: '0'
|
115
|
+
requirements: []
|
116
|
+
rubyforge_project:
|
117
|
+
rubygems_version: 2.2.2
|
118
|
+
signing_key:
|
119
|
+
specification_version: 4
|
120
|
+
summary: Story Branch - create git branches based on pivotal tracker stories
|
121
|
+
test_files: []
|