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.
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