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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +37 -0
  3. data/.github/ISSUE_TEMPLATE.md +12 -0
  4. data/.gitignore +3 -0
  5. data/.rspec +2 -0
  6. data/Gemfile +6 -0
  7. data/Gemfile.lock +94 -0
  8. data/{LICENCE → LICENSE.txt} +1 -1
  9. data/README.md +79 -60
  10. data/Rakefile +6 -0
  11. data/exe/git-finish +3 -0
  12. data/exe/git-start +3 -0
  13. data/exe/git-story +3 -0
  14. data/exe/git-unstart +3 -0
  15. data/exe/story_branch +18 -0
  16. data/lib/story_branch.rb +2 -477
  17. data/lib/story_branch/cli.rb +93 -0
  18. data/lib/story_branch/command.rb +121 -0
  19. data/lib/story_branch/commands/.gitkeep +1 -0
  20. data/lib/story_branch/commands/add.rb +48 -0
  21. data/lib/story_branch/commands/create.rb +21 -0
  22. data/lib/story_branch/commands/finish.rb +20 -0
  23. data/lib/story_branch/commands/migrate.rb +100 -0
  24. data/lib/story_branch/commands/start.rb +20 -0
  25. data/lib/story_branch/commands/unstart.rb +20 -0
  26. data/lib/story_branch/config_manager.rb +18 -0
  27. data/lib/story_branch/git_utils.rb +85 -0
  28. data/lib/story_branch/main.rb +124 -0
  29. data/lib/story_branch/pivotal_utils.rb +146 -0
  30. data/lib/story_branch/string_utils.rb +23 -0
  31. data/lib/story_branch/templates/.gitkeep +1 -0
  32. data/lib/story_branch/templates/add/.gitkeep +1 -0
  33. data/lib/story_branch/templates/config/.gitkeep +1 -0
  34. data/lib/story_branch/templates/create/.gitkeep +1 -0
  35. data/lib/story_branch/templates/finish/.gitkeep +1 -0
  36. data/lib/story_branch/templates/migrate/.gitkeep +1 -0
  37. data/lib/story_branch/templates/start/.gitkeep +1 -0
  38. data/lib/story_branch/templates/unstart/.gitkeep +1 -0
  39. data/lib/story_branch/version.rb +3 -0
  40. data/story_branch.gemspec +54 -0
  41. metadata +168 -22
  42. data/bin/git-finish +0 -4
  43. data/bin/git-start +0 -4
  44. data/bin/git-story +0 -4
  45. data/bin/git-unstart +0 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 87dd3ed376746ad1ef5041b8163ca121b6c0e465
4
- data.tar.gz: 76a8b44ef9a1d240c39f84c0a6f7ce8cbca7b561
3
+ metadata.gz: 601d26c296267a0ad82258cb4978d82a538e88b6
4
+ data.tar.gz: 3b667d1a51fb3a1a8ad509fb98a956c241e8edc2
5
5
  SHA512:
6
- metadata.gz: a20f1ef3bb406a75c1c590765ce45802a9ce29adec86366132fcd69e29950c77110f1b762126c8fbca98751a201fad549535ceb5acfcef8e776490148e799b0d
7
- data.tar.gz: 0af67bc5bc79e81e16b206ef799b643181aaea897797cf2e93bd96b097bfb3392c586f692f37789b8469c591a1762f3cfe73aa480338801a3d84b35cf88e25e0
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
@@ -0,0 +1,3 @@
1
+ .DS_Store
2
+ story_branch-*.gem
3
+ /.pairs
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in story_branch.gemspec
6
+ gemspec
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
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2014 Jason Milkins, Gabe Hollombe, Rui Baltazar, Dominic Wong
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
- A small collection of tools for working with git branches and Pivotal
6
- Tracker stories. `git story`, `git finish`, `git start` and `git
7
- unstart`.
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
- ### Commentary
10
+ ## Installing
10
11
 
11
- `git story`: Creates a git branch with automatic reference to a
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
- e.g. `my-story-name-1234567`
14
+ gem install story_branch
19
15
 
20
- `git finish`: Creates a git commit message for the staged changes.
16
+ ## Usage
21
17
 
22
- e.g: `[Finishes #1234567] My story name`
18
+ You should run story_branch from the git/project root folder.
23
19
 
24
- You must stage all changes (or stash them) first. Note the commit will not
25
- be pushed. Note: You'll be able to bail out of the commit.
20
+ ## Commands available
26
21
 
27
- `git start`: Start a story in Pivotal Tracker from the terminal.
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
- `git unstart`: Unstart a story in Pivotal Tracker from the terminal.
33
- It'll get all started stories in your current project. You can
34
- enter text and press TAB to search for a story name, or TAB to show
35
- the full list.
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
- ### Installing
38
+ ## Settings
38
39
 
39
- Install the gem:
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
- gem install story_branch
43
+ The project id you can get it easily from the url when viewing the project.
42
44
 
43
- #### Settings
45
+ The api key you can get it from your account settings.
44
46
 
45
- You can have your settings set either on a `.story_branch` file
46
- or in environment variables.
47
+ ### .story_branch files
47
48
 
48
- The `.story_branch` files have priority over environment variables so if you set both,
49
- the configuration within `.story_branch` will be the one used.
50
- Also, the `.story_branch` file inside a project directory has priority over the global one.
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 settings will be loaded firstly from local config file, if not found then from global
53
- config file and ultimately from the environment variables. If none are found, an error
54
- will be thrown.
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
- This means that you can have a globally set api key and for each project using Pivotal Tracker
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
- $ cat ~/your_project_dir/.story_branch
65
- project: 123123
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
- In case you prefer to use environment variables, you can set
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
- #### .story_branch file
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
- A YAML file with either/both of:
73
+ ### Old commands
75
74
 
76
- api: YOUR.PIVOTAL.API.KEY.STRING
77
- project: YOUR.PROJECT.ID.NUMBER
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
- Can be saved to `~/` or `./` (ie. your project folder)
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
- ### Usage
84
+ ## Commentary
82
85
 
83
- You run story_branch from the git/project root folder.
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
- `git story`, `git start` and `git unstart` are run interactively and
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
- `git finish` will scan the current branch name for a story id (as its
89
- suffix) and if a valid, active story is found on pivotal tracker it
90
- will create a commit with a message to trigger pivotal's git
91
- integraton features.
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
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/exe/git-finish ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `story_branch finish`
data/exe/git-start ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `story_branch start`
data/exe/git-story ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `story_branch create`
data/exe/git-unstart ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ `story_branch unstart`
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
- # Name: story branch
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
- class Main
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