story_branch 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Gem Version](https://badge.fury.io/rb/story_branch.png)](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: []
|