story_branch 0.2.11 → 0.3.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 +4 -4
- data/.circleci/config.yml +37 -0
- data/.github/ISSUE_TEMPLATE.md +12 -0
- data/.gitignore +3 -0
- data/.rspec +2 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +94 -0
- data/{LICENCE → LICENSE.txt} +1 -1
- data/README.md +79 -60
- data/Rakefile +6 -0
- data/exe/git-finish +3 -0
- data/exe/git-start +3 -0
- data/exe/git-story +3 -0
- data/exe/git-unstart +3 -0
- data/exe/story_branch +18 -0
- data/lib/story_branch.rb +2 -477
- data/lib/story_branch/cli.rb +93 -0
- data/lib/story_branch/command.rb +121 -0
- data/lib/story_branch/commands/.gitkeep +1 -0
- data/lib/story_branch/commands/add.rb +48 -0
- data/lib/story_branch/commands/create.rb +21 -0
- data/lib/story_branch/commands/finish.rb +20 -0
- data/lib/story_branch/commands/migrate.rb +100 -0
- data/lib/story_branch/commands/start.rb +20 -0
- data/lib/story_branch/commands/unstart.rb +20 -0
- data/lib/story_branch/config_manager.rb +18 -0
- data/lib/story_branch/git_utils.rb +85 -0
- data/lib/story_branch/main.rb +124 -0
- data/lib/story_branch/pivotal_utils.rb +146 -0
- data/lib/story_branch/string_utils.rb +23 -0
- data/lib/story_branch/templates/.gitkeep +1 -0
- data/lib/story_branch/templates/add/.gitkeep +1 -0
- data/lib/story_branch/templates/config/.gitkeep +1 -0
- data/lib/story_branch/templates/create/.gitkeep +1 -0
- data/lib/story_branch/templates/finish/.gitkeep +1 -0
- data/lib/story_branch/templates/migrate/.gitkeep +1 -0
- data/lib/story_branch/templates/start/.gitkeep +1 -0
- data/lib/story_branch/templates/unstart/.gitkeep +1 -0
- data/lib/story_branch/version.rb +3 -0
- data/story_branch.gemspec +54 -0
- metadata +168 -22
- data/bin/git-finish +0 -4
- data/bin/git-start +0 -4
- data/bin/git-story +0 -4
- data/bin/git-unstart +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 601d26c296267a0ad82258cb4978d82a538e88b6
|
4
|
+
data.tar.gz: 3b667d1a51fb3a1a8ad509fb98a956c241e8edc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1cdef1c858ccbfca16e4a79f98712eabfae71aa08aa6b8faed758eded648eae82df1c8d4ff7ec1af79b4f2ce8d9661f00cd0347048d14d18ac67adba7b5a130
|
7
|
+
data.tar.gz: 4a75f85aeea2b9a293378c5e05f0c258079c8d748f8bd553719372fe02aaf6d02e9e2d2a422024338641fd34287e8742a86383a516906fd933da1a70c0003b0c
|
@@ -0,0 +1,37 @@
|
|
1
|
+
version: 2
|
2
|
+
jobs:
|
3
|
+
build:
|
4
|
+
docker:
|
5
|
+
- image: circleci/ruby:2.4.1-node-browsers
|
6
|
+
working_directory: ~/story_branch
|
7
|
+
steps:
|
8
|
+
- checkout
|
9
|
+
- restore_cache:
|
10
|
+
keys:
|
11
|
+
- v1-dependencies-
|
12
|
+
- run:
|
13
|
+
name: install dependencies
|
14
|
+
command: |
|
15
|
+
bundle install --jobs=4 --retry=3 --path vendor/bundle
|
16
|
+
- save_cache:
|
17
|
+
paths:
|
18
|
+
- ./vendor/bundle
|
19
|
+
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
|
20
|
+
|
21
|
+
- run:
|
22
|
+
name: run tests
|
23
|
+
command: |
|
24
|
+
mkdir -p /tmp/test-results
|
25
|
+
|
26
|
+
TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)"
|
27
|
+
|
28
|
+
bundle exec rspec --format documentation \
|
29
|
+
--format RspecJunitFormatter \
|
30
|
+
--out /tmp/test-results/rspec.xml \
|
31
|
+
-- $(sed -e 's/\n/\\n/' -e 's/ /\ /' <<< "${TEST_FILES}")
|
32
|
+
|
33
|
+
- store_test_results:
|
34
|
+
path: /tmp/test-results
|
35
|
+
- store_artifacts:
|
36
|
+
path: /tmp/test-results
|
37
|
+
destination: test-results
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Before submitting an issue, please be sure to update to the latest Gem version
|
2
|
+
|
3
|
+
### Describe the steps to reproduce
|
4
|
+
|
5
|
+
### What did you expect to happen?
|
6
|
+
|
7
|
+
### What happened instead?
|
8
|
+
|
9
|
+
Paste in any error messages using [code-fences](https://help.github.com/articles/creating-and-highlighting-code-blocks/)
|
10
|
+
|
11
|
+
### Additional information
|
12
|
+
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
story_branch (0.3.0)
|
5
|
+
blanket_wrapper (~> 3.0)
|
6
|
+
git (~> 1.2)
|
7
|
+
levenshtein-ffi (~> 1.0)
|
8
|
+
pastel (~> 0.7.2)
|
9
|
+
rb-readline (~> 0.5)
|
10
|
+
thor (~> 0.20.0)
|
11
|
+
tty-command (~> 0.8.0)
|
12
|
+
tty-config (~> 0.2.0)
|
13
|
+
tty-pager (~> 0.11.0)
|
14
|
+
tty-prompt (~> 0.16.1)
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: https://rubygems.org/
|
18
|
+
specs:
|
19
|
+
blanket_wrapper (3.0.2)
|
20
|
+
httparty
|
21
|
+
recursive-open-struct
|
22
|
+
diff-lcs (1.3)
|
23
|
+
equatable (0.5.0)
|
24
|
+
fakefs (0.14.2)
|
25
|
+
ffi (1.9.23)
|
26
|
+
git (1.4.0)
|
27
|
+
hitimes (1.2.6)
|
28
|
+
httparty (0.16.2)
|
29
|
+
multi_xml (>= 0.5.2)
|
30
|
+
levenshtein-ffi (1.1.0)
|
31
|
+
ffi (~> 1.9)
|
32
|
+
multi_xml (0.6.0)
|
33
|
+
necromancer (0.4.0)
|
34
|
+
pastel (0.7.2)
|
35
|
+
equatable (~> 0.5.0)
|
36
|
+
tty-color (~> 0.4.0)
|
37
|
+
rake (10.4.2)
|
38
|
+
rb-readline (0.5.5)
|
39
|
+
recursive-open-struct (1.1.0)
|
40
|
+
rspec (3.0.0)
|
41
|
+
rspec-core (~> 3.0.0)
|
42
|
+
rspec-expectations (~> 3.0.0)
|
43
|
+
rspec-mocks (~> 3.0.0)
|
44
|
+
rspec-core (3.0.4)
|
45
|
+
rspec-support (~> 3.0.0)
|
46
|
+
rspec-expectations (3.0.4)
|
47
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
48
|
+
rspec-support (~> 3.0.0)
|
49
|
+
rspec-mocks (3.0.4)
|
50
|
+
rspec-support (~> 3.0.0)
|
51
|
+
rspec-support (3.0.4)
|
52
|
+
strings (0.1.1)
|
53
|
+
unicode-display_width (~> 1.3.0)
|
54
|
+
unicode_utils (~> 1.4.0)
|
55
|
+
thor (0.20.0)
|
56
|
+
timers (4.1.2)
|
57
|
+
hitimes
|
58
|
+
tty-color (0.4.2)
|
59
|
+
tty-command (0.8.1)
|
60
|
+
pastel (~> 0.7.0)
|
61
|
+
tty-config (0.2.0)
|
62
|
+
tty-cursor (0.5.0)
|
63
|
+
tty-pager (0.11.0)
|
64
|
+
strings (~> 0.1.0)
|
65
|
+
tty-screen (~> 0.6.4)
|
66
|
+
tty-which (~> 0.3.0)
|
67
|
+
tty-prompt (0.16.1)
|
68
|
+
necromancer (~> 0.4.0)
|
69
|
+
pastel (~> 0.7.0)
|
70
|
+
timers (~> 4.0)
|
71
|
+
tty-cursor (~> 0.5.0)
|
72
|
+
tty-reader (~> 0.3.0)
|
73
|
+
tty-reader (0.3.0)
|
74
|
+
tty-cursor (~> 0.5.0)
|
75
|
+
tty-screen (~> 0.6.4)
|
76
|
+
wisper (~> 2.0.0)
|
77
|
+
tty-screen (0.6.4)
|
78
|
+
tty-which (0.3.0)
|
79
|
+
unicode-display_width (1.3.0)
|
80
|
+
unicode_utils (1.4.0)
|
81
|
+
wisper (2.0.0)
|
82
|
+
|
83
|
+
PLATFORMS
|
84
|
+
ruby
|
85
|
+
|
86
|
+
DEPENDENCIES
|
87
|
+
bundler (~> 1.16)
|
88
|
+
fakefs (~> 0.14)
|
89
|
+
rake (~> 10.0)
|
90
|
+
rspec (~> 3.0)
|
91
|
+
story_branch!
|
92
|
+
|
93
|
+
BUNDLED WITH
|
94
|
+
1.16.2
|
data/{LICENCE → LICENSE.txt}
RENAMED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c)
|
3
|
+
Copyright (c) 2018 Story Branch
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
6
6
|
this software and associated documentation files (the "Software"), to deal in
|
data/README.md
CHANGED
@@ -2,93 +2,112 @@
|
|
2
2
|
|
3
3
|
# Story Branch
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
Story branch is a CLI application that interacts with Pivotal Tracker at the
|
6
|
+
at the moment. It allows you to start and un-start stories as well as creating
|
7
|
+
branches based on the story name and id and have a final commit message marking
|
8
|
+
the story as Finished.
|
8
9
|
|
9
|
-
|
10
|
+
## Installing
|
10
11
|
|
11
|
-
|
12
|
-
Pivotal Tracker Story. It will get started stories from your active
|
13
|
-
project. You can enter text and press TAB to search for a story
|
14
|
-
name, or TAB to show the full list. It will then suggest an editable
|
15
|
-
branch name. When the branch is created the `story_id` will
|
16
|
-
be appended to it.
|
12
|
+
Install the gem:
|
17
13
|
|
18
|
-
|
14
|
+
gem install story_branch
|
19
15
|
|
20
|
-
|
16
|
+
## Usage
|
21
17
|
|
22
|
-
|
18
|
+
You should run story_branch from the git/project root folder.
|
23
19
|
|
24
|
-
|
25
|
-
be pushed. Note: You'll be able to bail out of the commit.
|
20
|
+
## Commands available
|
26
21
|
|
27
|
-
|
28
|
-
It'll get all unstarted stories in your current project. You can
|
29
|
-
enter text and press TAB to search for a story name, or TAB to show
|
30
|
-
the full list.
|
22
|
+
You can see all the commands available by running
|
31
23
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
24
|
+
```
|
25
|
+
$ story_branch -h
|
26
|
+
|
27
|
+
Commands:
|
28
|
+
story_branch add # Add a new story branch configuration
|
29
|
+
story_branch create # Create branch from estimated stories in pivotal tracker
|
30
|
+
story_branch finish # Creates a git commit message for the staged changes with a [Finishes] tag
|
31
|
+
story_branch help [COMMAND] # Describe available commands or one specific command
|
32
|
+
story_branch migrate # Migrate old story branch configuration to the new format
|
33
|
+
story_branch start # Mark an estimated story as started in Pivotal Tracker
|
34
|
+
story_branch unstart # Mark a started story as un-started in Pivotal Tracker
|
35
|
+
story_branch version # story_branch gem version
|
36
|
+
```
|
36
37
|
|
37
|
-
|
38
|
+
## Settings
|
38
39
|
|
39
|
-
|
40
|
+
Story branch has a command available that will help you creating the configurations
|
41
|
+
for the projects, but essentially you'll be asked for the pivotal tracker project id and your api key.
|
40
42
|
|
41
|
-
|
43
|
+
The project id you can get it easily from the url when viewing the project.
|
42
44
|
|
43
|
-
|
45
|
+
The api key you can get it from your account settings.
|
44
46
|
|
45
|
-
|
46
|
-
or in environment variables.
|
47
|
+
### .story_branch files
|
47
48
|
|
48
|
-
|
49
|
-
|
50
|
-
|
49
|
+
When configuring story branch, it will create two .story_branch.yml files: one in
|
50
|
+
your home folder (`~/`) and one in your project's root (`./`).
|
51
|
+
The one in your home folder will be used to store the different project's configurations
|
52
|
+
such as which api key to use. This is done so you don't need to commit your
|
53
|
+
api key to the repository but still be able to use different keys in case you
|
54
|
+
have different accounts.
|
51
55
|
|
52
|
-
The
|
53
|
-
|
54
|
-
|
56
|
+
The one in your project root will keep a reference to the project configuration.
|
57
|
+
For now, this reference is the project id. This file can be safely committed to
|
58
|
+
the repository and shared amongst your co-workers.
|
55
59
|
|
56
|
-
|
57
|
-
have a local config inside the project folder.
|
58
|
-
E.g.
|
60
|
+
## Migrating
|
59
61
|
|
60
|
-
|
61
|
-
$ cat ~/.story_branch
|
62
|
-
api: your_API_key_that_you_get_from_pivotal_tracker
|
62
|
+
### Old configuration
|
63
63
|
|
64
|
-
|
65
|
-
|
66
|
-
|
64
|
+
If your were using story branch before there are some small changes on the way the
|
65
|
+
tool works. But worry not, we've written a command that allows you to migrate your
|
66
|
+
configuration. Running
|
67
67
|
|
68
|
-
|
69
|
-
`PIVOTAL_API_KEY` to your pivotal tracker api key and set
|
70
|
-
`PIVOTAL_PROJECT_ID` to your project id.
|
68
|
+
`$ story_branch migrate`
|
71
69
|
|
72
|
-
|
70
|
+
will grab your existing configuration and convert it into the new format. The only
|
71
|
+
thing you'll need to provide is the project name reference.
|
73
72
|
|
74
|
-
|
73
|
+
### Old commands
|
75
74
|
|
76
|
-
|
77
|
-
|
75
|
+
Story branch was built providing a set of bin commands such as `git-story`, `git-finish`, `git-start` and `git-unstart`. These will be available still as
|
76
|
+
we try as much as possible to keep the updates retro-compatible, but are nothing
|
77
|
+
more than an alias for the CLI commands as follow:
|
78
78
|
|
79
|
-
|
79
|
+
- `git-story` runs `story_branch create`
|
80
|
+
- `git-finish` runs `story_branch finish`
|
81
|
+
- `git-start` runs `story_branch start`
|
82
|
+
- `git-unstart` runs `story_branch unstart`
|
80
83
|
|
81
|
-
|
84
|
+
## Commentary
|
82
85
|
|
83
|
-
|
86
|
+
`story_branch create`: Creates a git branch with automatic reference to a
|
87
|
+
Pivotal Tracker Story. It will get started stories from your active
|
88
|
+
project. You can enter text and press TAB to search for a story
|
89
|
+
name, or TAB to show the full list. It will then suggest an editable
|
90
|
+
branch name. When the branch is created the `story_id` will
|
91
|
+
be appended to it.
|
92
|
+
|
93
|
+
e.g. `my-story-name-1234567`
|
84
94
|
|
85
|
-
`
|
86
|
-
will display a list of stories to work with.
|
95
|
+
`story_branch finish`: Creates a git commit message for the staged changes.
|
87
96
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
97
|
+
e.g: `[Finishes #1234567] My story name`
|
98
|
+
|
99
|
+
You must stage all changes (or stash them) first. Note the commit will not
|
100
|
+
be pushed. Note: You'll be able to bail out of the commit.
|
101
|
+
|
102
|
+
`story_branch start`: Start a story in Pivotal Tracker from the terminal.
|
103
|
+
It'll get all un-started stories in your current project. You can
|
104
|
+
enter text and press TAB to search for a story name, or TAB to show
|
105
|
+
the full list.
|
106
|
+
|
107
|
+
`story_branch unstart`: Un-start a story in Pivotal Tracker from the terminal.
|
108
|
+
It'll get all started stories in your current project. You can
|
109
|
+
enter text and press TAB to search for a story name, or TAB to show
|
110
|
+
the full list.
|
92
111
|
|
93
112
|
## Contributing
|
94
113
|
|
data/Rakefile
ADDED
data/exe/git-finish
ADDED
data/exe/git-start
ADDED
data/exe/git-story
ADDED
data/exe/git-unstart
ADDED
data/exe/story_branch
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
lib_path = File.expand_path('../lib', __dir__)
|
5
|
+
$:.unshift(lib_path) if !$:.include?(lib_path)
|
6
|
+
require 'story_branch/cli'
|
7
|
+
|
8
|
+
Signal.trap('INT') do
|
9
|
+
warn("\n#{caller.join("\n")}: interrupted")
|
10
|
+
exit(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
begin
|
14
|
+
StoryBranch::CLI.start
|
15
|
+
rescue StoryBranch::CLI::Error => err
|
16
|
+
puts "ERROR: #{err.message}"
|
17
|
+
exit 1
|
18
|
+
end
|
data/lib/story_branch.rb
CHANGED
@@ -1,480 +1,5 @@
|
|
1
|
-
|
2
|
-
#
|
3
|
-
# Authors: Jason Milkins <jason@opsmanager.com>
|
4
|
-
# Rui Baltazar <rui.p.baltazar@gmail.com>
|
5
|
-
# Dominic Wong <dominic.wong.617@gmail.com>
|
6
|
-
# Ranhiru Cooray <ranhiru@gmail.com>
|
7
|
-
# Gabe Hollombe <gabe@neo.com>
|
8
|
-
#
|
9
|
-
# Version: 0.2.9
|
10
|
-
#
|
11
|
-
# ## Description
|
12
|
-
# A small collection of tools for working with git branches and Pivotal
|
13
|
-
# Tracker stories. `git story`, `git finish`, `git start` and `git
|
14
|
-
# unstart`.
|
15
|
-
#
|
16
|
-
# ### Commentary
|
17
|
-
#
|
18
|
-
# `git story`: Creates a git branch with automatic reference to a
|
19
|
-
# Pivotal Tracker Story. It will get started stories from your active
|
20
|
-
# project. You can enter text and press TAB to search for a story
|
21
|
-
# name, or TAB to show the full list. It will then suggest an editable
|
22
|
-
# branch name. When the branch is created the `story_id` will
|
23
|
-
# be appended to it.
|
24
|
-
#
|
25
|
-
# e.g. `my-story-name-1234567`
|
26
|
-
#
|
27
|
-
# `git finish`: Creates a git commit message for the staged changes.
|
28
|
-
#
|
29
|
-
# e.g: `[Finishes #1234567] My story name`
|
30
|
-
#
|
31
|
-
# You must stage all changes (or stash them) first. Note the commit will not
|
32
|
-
# be pushed. Note: You'll be able to bail out of the commit.
|
33
|
-
#
|
34
|
-
# `git start`: Start a story in Pivotal Tracker from the terminal.
|
35
|
-
# It'll get all unstarted stories in your current project. You can
|
36
|
-
# enter text and press TAB to search for a story name, or TAB to show
|
37
|
-
# the full list.
|
38
|
-
#
|
39
|
-
# `git unstart`: Unstart a story in Pivotal Tracker from the terminal.
|
40
|
-
# It'll get all started stories in your current project. You can
|
41
|
-
# enter text and press TAB to search for a story name, or TAB to show
|
42
|
-
# the full list.
|
43
|
-
#
|
44
|
-
# ### Installing
|
45
|
-
#
|
46
|
-
# Install the gem:
|
47
|
-
#
|
48
|
-
# gem install story_branch
|
49
|
-
#
|
50
|
-
# #### Settings
|
51
|
-
#
|
52
|
-
# You must have a `PIVOTAL_API_KEY` environment variable set
|
53
|
-
# to your Pivotal api key, plus either a `.story_branch` file or
|
54
|
-
# `PIVOTAL_PROJECT_ID` environment variable set. Note, values in
|
55
|
-
# `.story_branch` will override environment variable settings.
|
56
|
-
#
|
57
|
-
# #### .story_branch file
|
58
|
-
#
|
59
|
-
# A YAML file with either/both of:
|
60
|
-
#
|
61
|
-
# api: YOUR.PIVOTAL.API.KEY.STRING
|
62
|
-
# project: YOUR.PROJECT.ID.NUMBER
|
63
|
-
#
|
64
|
-
# Can be saved to `~/` or `./` (ie. your project folder)
|
65
|
-
#
|
66
|
-
# ### Usage
|
67
|
-
#
|
68
|
-
# You run story_branch from the git/project root folder.
|
69
|
-
#
|
70
|
-
# `git story`, `git start` and `git unstart` are run interactively and
|
71
|
-
# will display a list of stories to work with.
|
72
|
-
#
|
73
|
-
# `git finish` will scan the current branch name for a story id (as its
|
74
|
-
# suffix) and if a valid, active story is found on pivotal tracker it
|
75
|
-
# will create a commit with a message to trigger pivotal's git
|
76
|
-
# integraton features.
|
77
|
-
#
|
78
|
-
# ## Contributing
|
79
|
-
#
|
80
|
-
# All pull requests are welcome and will be reviewed.
|
81
|
-
#
|
82
|
-
# Code:
|
83
|
-
|
84
|
-
require 'byebug'
|
85
|
-
require 'yaml'
|
86
|
-
require 'blanket'
|
87
|
-
require 'rb-readline'
|
88
|
-
require 'readline'
|
89
|
-
require 'git'
|
90
|
-
require 'levenshtein'
|
91
|
-
|
92
|
-
trap('INT') { exit }
|
1
|
+
require 'story_branch/version'
|
93
2
|
|
94
3
|
module StoryBranch
|
95
|
-
|
96
|
-
ERRORS = {
|
97
|
-
'Stories in the started state must be estimated.' =>
|
98
|
-
"Error: Pivotal won't allow you to start an unestimated story"
|
99
|
-
}
|
100
|
-
|
101
|
-
PIVOTAL_CONFIG_FILES = ['.story_branch', "#{ENV['HOME']}/.story_branch"]
|
102
|
-
|
103
|
-
attr_accessor :p
|
104
|
-
|
105
|
-
def initialize
|
106
|
-
@p = PivotalUtils.new
|
107
|
-
@p.api_key = config_value 'api', 'PIVOTAL_API_KEY'
|
108
|
-
@p.project_id = config_value 'project', 'PIVOTAL_PROJECT_ID'
|
109
|
-
exit unless @p.valid?
|
110
|
-
end
|
111
|
-
|
112
|
-
def unauthorised_message
|
113
|
-
$stderr.puts 'Pivotal API key or Project ID invalid'
|
114
|
-
end
|
115
|
-
|
116
|
-
def create_story_branch
|
117
|
-
begin
|
118
|
-
puts 'Connecting with Pivotal Tracker'
|
119
|
-
@p.get_project
|
120
|
-
puts 'Getting stories...'
|
121
|
-
stories = @p.display_stories :started, false
|
122
|
-
if stories.length < 1
|
123
|
-
puts 'No stories started, exiting'
|
124
|
-
exit
|
125
|
-
end
|
126
|
-
story = @p.select_story stories
|
127
|
-
if story
|
128
|
-
@p.create_feature_branch story
|
129
|
-
end
|
130
|
-
rescue Blanket::Unauthorized
|
131
|
-
unauthorised_message
|
132
|
-
return nil
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
def pick_and_update filter, hash, msg, is_estimated
|
137
|
-
begin
|
138
|
-
puts 'Connecting with Pivotal Tracker'
|
139
|
-
@p.get_project
|
140
|
-
puts 'Getting stories...'
|
141
|
-
stories = @p.filtered_stories_list filter, is_estimated
|
142
|
-
story = @p.select_story stories
|
143
|
-
if story
|
144
|
-
result = @p.story_update story, hash
|
145
|
-
fail result.error if result.error
|
146
|
-
puts "#{story.id} #{msg}"
|
147
|
-
end
|
148
|
-
rescue Blanket::Unauthorized
|
149
|
-
unauthorised_message
|
150
|
-
return nil
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
def story_start
|
155
|
-
pick_and_update(:unstarted, { current_state: 'started' }, 'started', true)
|
156
|
-
end
|
157
|
-
|
158
|
-
def story_unstart
|
159
|
-
pick_and_update(:started, { current_state: 'unstarted' }, 'unstarted', false)
|
160
|
-
end
|
161
|
-
|
162
|
-
def story_estimate
|
163
|
-
# TODO: estimate a story
|
164
|
-
end
|
165
|
-
|
166
|
-
def story_finish
|
167
|
-
begin
|
168
|
-
puts 'Connecting with Pivotal Tracker'
|
169
|
-
@p.get_project
|
170
|
-
|
171
|
-
unless @p.is_current_branch_a_story?
|
172
|
-
puts "Your current branch: '#{GitUtils.current_branch}' is not linked to a Pivotal Tracker story."
|
173
|
-
return nil
|
174
|
-
end
|
175
|
-
|
176
|
-
if GitUtils.has_status? :untracked or GitUtils.has_status? :modified
|
177
|
-
puts 'There are unstaged changes'
|
178
|
-
puts 'Use git add to stage changes before running git finish'
|
179
|
-
puts 'Use git stash if you want to hide changes for this commit'
|
180
|
-
return nil
|
181
|
-
end
|
182
|
-
|
183
|
-
unless GitUtils.has_status? :added or GitUtils.has_status? :staged
|
184
|
-
puts 'There are no staged changes.'
|
185
|
-
puts 'Nothing to do'
|
186
|
-
return nil
|
187
|
-
end
|
188
|
-
|
189
|
-
puts 'Use standard finishing commit message: [y/N]?'
|
190
|
-
commit_message = "[Finishes ##{GitUtils.current_branch_story_parts[:id]}] #{StringUtils.undashed GitUtils.current_branch_story_parts[:title]}"
|
191
|
-
puts commit_message
|
192
|
-
|
193
|
-
if gets.chomp!.downcase == 'y'
|
194
|
-
GitUtils.commit commit_message
|
195
|
-
else
|
196
|
-
puts 'Aborted'
|
197
|
-
end
|
198
|
-
rescue Blanket::Unauthorized
|
199
|
-
unauthorised_message
|
200
|
-
return nil
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
def config_value key, env
|
205
|
-
PIVOTAL_CONFIG_FILES.each do |config_file|
|
206
|
-
if File.exists? config_file
|
207
|
-
pivotal_info = YAML.load_file config_file
|
208
|
-
return pivotal_info[key] if pivotal_info[key]
|
209
|
-
end
|
210
|
-
end
|
211
|
-
value ||= env_required env
|
212
|
-
value
|
213
|
-
end
|
214
|
-
|
215
|
-
def env_required var_name
|
216
|
-
if ENV[var_name].nil?
|
217
|
-
$stderr.puts "$#{var_name} must be set or in .story_branch file"
|
218
|
-
return nil
|
219
|
-
end
|
220
|
-
ENV[var_name]
|
221
|
-
end
|
222
|
-
|
223
|
-
end
|
224
|
-
|
225
|
-
class StringUtils
|
226
|
-
|
227
|
-
def self.dashed s
|
228
|
-
s.tr(' _,./:;+&', '-')
|
229
|
-
end
|
230
|
-
|
231
|
-
def self.simple_sanitize s
|
232
|
-
strip_newlines (s.tr '\'"%!@#$(){}[]*\\?', '')
|
233
|
-
end
|
234
|
-
|
235
|
-
def self.normalised_branch_name s
|
236
|
-
simple_sanitize((dashed s).downcase).squeeze('-')
|
237
|
-
end
|
238
|
-
|
239
|
-
def self.strip_newlines s
|
240
|
-
s.tr "\n", '-'
|
241
|
-
end
|
242
|
-
|
243
|
-
def self.undashed s
|
244
|
-
s.gsub(/-/, ' ').capitalize
|
245
|
-
end
|
246
|
-
|
247
|
-
end
|
248
|
-
|
249
|
-
class GitUtils
|
250
|
-
def self.g
|
251
|
-
Git.open '.'
|
252
|
-
end
|
253
|
-
|
254
|
-
def self.is_existing_branch? name
|
255
|
-
branch_names.each do |n|
|
256
|
-
if Levenshtein.distance(n, name) < 3
|
257
|
-
return true
|
258
|
-
end
|
259
|
-
existing_branch_name = n.match(/(.*)(-[1-9][0-9]+$)/)
|
260
|
-
if existing_branch_name
|
261
|
-
levenshtein_distance = Levenshtein.distance existing_branch_name[1], name
|
262
|
-
if levenshtein_distance < 3
|
263
|
-
return true
|
264
|
-
end
|
265
|
-
end
|
266
|
-
end
|
267
|
-
return false
|
268
|
-
end
|
269
|
-
|
270
|
-
def self.is_existing_story? id
|
271
|
-
branch_names.each do |n|
|
272
|
-
branch_id = n.match(/-[1-9][0-9]+$/)
|
273
|
-
if branch_id
|
274
|
-
if branch_id.to_s == "-#{id}"
|
275
|
-
return true
|
276
|
-
end
|
277
|
-
end
|
278
|
-
end
|
279
|
-
false
|
280
|
-
end
|
281
|
-
|
282
|
-
def self.branch_names
|
283
|
-
g.branches.map(&:name)
|
284
|
-
end
|
285
|
-
|
286
|
-
def self.current_branch
|
287
|
-
g.current_branch
|
288
|
-
end
|
289
|
-
|
290
|
-
def self.current_story
|
291
|
-
current_branch.match(/(.*)-(\d+$)/)
|
292
|
-
end
|
293
|
-
|
294
|
-
def self.current_branch_story_parts
|
295
|
-
matches = current_story
|
296
|
-
if matches.length == 3
|
297
|
-
{ title: matches[1], id: matches[2] }
|
298
|
-
else
|
299
|
-
nil
|
300
|
-
end
|
301
|
-
end
|
302
|
-
|
303
|
-
def self.create_branch name
|
304
|
-
g.branch(name).create
|
305
|
-
g.branch(name).checkout
|
306
|
-
end
|
307
|
-
|
308
|
-
def self.status_collect status, regex
|
309
|
-
status.select{|e|
|
310
|
-
e.match(regex)
|
311
|
-
}.map{|e|
|
312
|
-
e.match(regex)[1]
|
313
|
-
}
|
314
|
-
end
|
315
|
-
|
316
|
-
def self.status
|
317
|
-
modified_rx = /^ M (.*)/
|
318
|
-
untracked_rx = /^\?\? (.*)/
|
319
|
-
staged_rx = /^M (.*)/
|
320
|
-
added_rx = /^A (.*)/
|
321
|
-
status = g.lib.send(:command, 'status', '-s').lines
|
322
|
-
return nil if status.length == 0
|
323
|
-
{
|
324
|
-
modified: status_collect(status, modified_rx),
|
325
|
-
untracked: status_collect(status, untracked_rx),
|
326
|
-
added: status_collect(status, added_rx),
|
327
|
-
staged: status_collect(status, staged_rx)
|
328
|
-
}
|
329
|
-
end
|
330
|
-
|
331
|
-
def self.has_status? state
|
332
|
-
return false unless status
|
333
|
-
status[state].length > 0
|
334
|
-
end
|
335
|
-
|
336
|
-
def self.commit message
|
337
|
-
g.commit(message)
|
338
|
-
end
|
339
|
-
|
340
|
-
end
|
341
|
-
|
342
|
-
class PivotalUtils
|
343
|
-
API_URL = 'https://www.pivotaltracker.com/services/v5/'
|
344
|
-
attr_accessor :api_key, :project_id
|
345
|
-
|
346
|
-
def valid?
|
347
|
-
!@api_key.nil? && !@project_id.nil?
|
348
|
-
end
|
349
|
-
|
350
|
-
def api
|
351
|
-
fail 'API key must be specified' unless @api_key
|
352
|
-
Blanket.wrap API_URL, headers: { 'X-TrackerToken' => @api_key }
|
353
|
-
end
|
354
|
-
|
355
|
-
def get_project
|
356
|
-
fail 'Project ID must be set' unless @project_id
|
357
|
-
api.projects(@project_id.to_i)
|
358
|
-
end
|
359
|
-
|
360
|
-
def story_accessor
|
361
|
-
get_project.stories
|
362
|
-
end
|
363
|
-
|
364
|
-
def is_current_branch_a_story?
|
365
|
-
GitUtils.current_story and
|
366
|
-
GitUtils.current_story.length == 3 and
|
367
|
-
filtered_stories_list(:started, true)
|
368
|
-
.map(&:id)
|
369
|
-
.include? GitUtils.current_story[2].to_i
|
370
|
-
end
|
371
|
-
|
372
|
-
def story_from_current_branch
|
373
|
-
story_accessor.get(GitUtils.current_story[2].to_i) if GitUtils.current_story.length == 3
|
374
|
-
end
|
375
|
-
|
376
|
-
# TODO: Maybe add some other predicates
|
377
|
-
# - Filtering on where a story lives (Backlog, IceBox)
|
378
|
-
# - Filtering on labels
|
379
|
-
# as the need arises...
|
380
|
-
#
|
381
|
-
def filtered_stories_list state, estimated
|
382
|
-
options = { with_state: state.to_s }
|
383
|
-
stories = [* story_accessor.get(params: options).payload]
|
384
|
-
if estimated
|
385
|
-
stories.select do |s|
|
386
|
-
s.story_type == 'bug' || s.story_type == 'chore' ||
|
387
|
-
(s.story_type == 'feature' && s.estimate && s.estimate >= 0)
|
388
|
-
end
|
389
|
-
else
|
390
|
-
stories
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
def display_stories state, estimated
|
395
|
-
filtered_stories_list(state, estimated).each {|s| puts one_line_story s }
|
396
|
-
end
|
397
|
-
|
398
|
-
def one_line_story s
|
399
|
-
"#{s.id} - #{s.name}"
|
400
|
-
end
|
401
|
-
|
402
|
-
def select_story stories
|
403
|
-
story_texts = stories.map{|s| one_line_story s }
|
404
|
-
puts 'Leave blank to exit, use <up>/<down> to scroll through stories, TAB to list all and auto-complete'
|
405
|
-
story_selection = readline('Select a story: ', story_texts)
|
406
|
-
return nil if story_selection == '' or story_selection.nil?
|
407
|
-
story = stories.select{|s| story_matcher s, story_selection }.first
|
408
|
-
if story.nil?
|
409
|
-
puts "Not found: #{story_selection}"
|
410
|
-
return nil
|
411
|
-
else
|
412
|
-
puts "Selected : #{one_line_story story}"
|
413
|
-
return story
|
414
|
-
end
|
415
|
-
end
|
416
|
-
|
417
|
-
def story_update story, hash
|
418
|
-
get_project.stories(story.id).put(body: hash).payload
|
419
|
-
end
|
420
|
-
|
421
|
-
def story_matcher story, selection
|
422
|
-
m = selection.match(/^(\d*) /)
|
423
|
-
return false unless m
|
424
|
-
id = m.captures.first
|
425
|
-
return story.id.to_s == id
|
426
|
-
end
|
427
|
-
|
428
|
-
def create_feature_branch story
|
429
|
-
dashed_story_name = StringUtils.normalised_branch_name story.name
|
430
|
-
feature_branch_name = nil
|
431
|
-
puts "You are checked out at: #{GitUtils.current_branch}"
|
432
|
-
while feature_branch_name == nil or feature_branch_name == ''
|
433
|
-
puts 'Provide a new branch name... (TAB for suggested name)' if [nil, ''].include? feature_branch_name
|
434
|
-
feature_branch_name = readline('Name of feature branch: ', [dashed_story_name])
|
435
|
-
end
|
436
|
-
feature_branch_name.chomp!
|
437
|
-
if validate_branch_name feature_branch_name, story.id
|
438
|
-
feature_branch_name_with_story_id = "#{feature_branch_name}-#{story.id}"
|
439
|
-
puts "Creating: #{feature_branch_name_with_story_id} with #{GitUtils.current_branch} as parent"
|
440
|
-
GitUtils.create_branch feature_branch_name_with_story_id
|
441
|
-
end
|
442
|
-
end
|
443
|
-
|
444
|
-
# Branch name validation
|
445
|
-
def validate_branch_name name, id
|
446
|
-
if GitUtils.is_existing_story? id
|
447
|
-
puts "Error: An existing branch has the same story id: #{id}"
|
448
|
-
return false
|
449
|
-
end
|
450
|
-
if GitUtils.is_existing_branch? name
|
451
|
-
puts 'Error: This name is very similar to an existing branch. Avoid confusion and use a more unique name.'
|
452
|
-
return false
|
453
|
-
end
|
454
|
-
unless valid_branch_name? name
|
455
|
-
puts "Error: #{name}\nis an invalid name."
|
456
|
-
return false
|
457
|
-
end
|
458
|
-
true
|
459
|
-
end
|
460
|
-
|
461
|
-
def valid_branch_name? name
|
462
|
-
# Valid names begin with a letter and are followed by alphanumeric
|
463
|
-
# with _ . - as allowed punctuation
|
464
|
-
valid = /[a-zA-Z][-._0-9a-zA-Z]*/
|
465
|
-
name.match valid
|
466
|
-
end
|
467
|
-
|
468
|
-
def readline prompt, completions=[]
|
469
|
-
# Store the state of the terminal
|
470
|
-
RbReadline.clear_history
|
471
|
-
if completions.length > 0
|
472
|
-
completions.each {|i| Readline::HISTORY.push i}
|
473
|
-
RbReadline.rl_completer_word_break_characters = ''
|
474
|
-
Readline.completion_proc = proc { |s| completions.grep(/#{Regexp.escape(s)}/) }
|
475
|
-
Readline.completion_append_character = ''
|
476
|
-
end
|
477
|
-
Readline.readline(prompt, false)
|
478
|
-
end
|
479
|
-
end
|
4
|
+
# Your code goes here...
|
480
5
|
end
|