story_branch 0.2.11 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|