story_branch 0.6.1 → 0.7.0.alpha

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0adf65a12d1ab52998ada7760645345d76c1dbe817ce52724005a71cf1b7bb30
4
- data.tar.gz: 63a19f6f253be9459618ecfb8d23f099c1e2ea5c65b3152394b49a6dce335094
3
+ metadata.gz: 9a12f882753865d3d70a70e890fd74ad6f2c62437af6156dbc95d351cf6af9ac
4
+ data.tar.gz: 663c2ab9b76745eea8c95e1c30f88c39ca40b1892c1a64eb311a6f91266fefd8
5
5
  SHA512:
6
- metadata.gz: c7a2c1923c37f423f7f2a946e6ecf19371cae814b33489ec59069567711b3de50a3c49e054d4c30042c04ebfe4b16c032584f854a758ab1f0871bb7f27d67ca7
7
- data.tar.gz: a59cb14660ed656598170978e63ccc2e51e414028d28a6af35c3e0de82d96fd9cea10c93e033c4c9a12850b880fbd922b1b60a035e700fea8b73ee292b59ad11
6
+ metadata.gz: d2442c7a38bc27d78a1be6d8219b99d70ccf531fcab8cd25b216b36845cd88c1f4e27748ea8733d8af232ef8c7235bc375f4dd45f3912daa0b7a9838fc3ca1e2
7
+ data.tar.gz: 8814adeb71cc1b479d2e521505fa1d0b58bd7a3f2f5e49781f8c7fce863d2e1490dfd202b6f79c58c58e59dfcdfd8c61cd44d2653a55db80ef5da0aa9ae447ae
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- story_branch (0.6.1)
4
+ story_branch (0.7.0.alpha)
5
5
  blanket_wrapper (~> 3.0)
6
6
  damerau-levenshtein (~> 1.3)
7
7
  jira-ruby (~> 1.7)
@@ -10,6 +10,7 @@ PATH
10
10
  tty-config (~> 0.2.0)
11
11
  tty-pager (~> 0.12)
12
12
  tty-prompt (~> 0.18)
13
+ xdg (~> 3.0)
13
14
 
14
15
  GEM
15
16
  remote: https://rubygems.org/
@@ -35,7 +36,7 @@ GEM
35
36
  httparty (0.17.3)
36
37
  mime-types (~> 3.0)
37
38
  multi_xml (>= 0.5.2)
38
- i18n (1.7.0)
39
+ i18n (1.8.2)
39
40
  concurrent-ruby (~> 1.0)
40
41
  jira-ruby (1.7.1)
41
42
  activesupport
@@ -44,10 +45,10 @@ GEM
44
45
  oauth (~> 0.5, >= 0.5.0)
45
46
  jwt (2.1.0)
46
47
  method_source (0.9.2)
47
- mime-types (3.3)
48
+ mime-types (3.3.1)
48
49
  mime-types-data (~> 3.2015)
49
50
  mime-types-data (3.2019.1009)
50
- minitest (5.13.0)
51
+ minitest (5.14.0)
51
52
  multi_xml (0.6.0)
52
53
  multipart-post (2.1.1)
53
54
  necromancer (0.5.1)
@@ -65,15 +66,15 @@ GEM
65
66
  rspec-core (~> 3.9.0)
66
67
  rspec-expectations (~> 3.9.0)
67
68
  rspec-mocks (~> 3.9.0)
68
- rspec-core (3.9.0)
69
- rspec-support (~> 3.9.0)
69
+ rspec-core (3.9.1)
70
+ rspec-support (~> 3.9.1)
70
71
  rspec-expectations (3.9.0)
71
72
  diff-lcs (>= 1.2.0, < 2.0)
72
73
  rspec-support (~> 3.9.0)
73
- rspec-mocks (3.9.0)
74
+ rspec-mocks (3.9.1)
74
75
  diff-lcs (>= 1.2.0, < 2.0)
75
76
  rspec-support (~> 3.9.0)
76
- rspec-support (3.9.0)
77
+ rspec-support (3.9.2)
77
78
  rspec_junit_formatter (0.4.1)
78
79
  rspec-core (>= 2, < 4, != 2.12.0)
79
80
  strings (0.1.8)
@@ -83,7 +84,7 @@ GEM
83
84
  strings-ansi (0.2.0)
84
85
  thor (0.20.3)
85
86
  thread_safe (0.3.6)
86
- tty-color (0.5.0)
87
+ tty-color (0.5.1)
87
88
  tty-command (0.8.2)
88
89
  pastel (~> 0.7.0)
89
90
  tty-config (0.2.0)
@@ -101,19 +102,20 @@ GEM
101
102
  tty-screen (~> 0.7)
102
103
  wisper (~> 2.0.0)
103
104
  tty-screen (0.7.0)
104
- tty-which (0.4.1)
105
- tzinfo (1.2.5)
105
+ tty-which (0.4.2)
106
+ tzinfo (1.2.6)
106
107
  thread_safe (~> 0.1)
107
- unicode-display_width (1.6.0)
108
+ unicode-display_width (1.6.1)
108
109
  unicode_utils (1.4.0)
109
110
  wisper (2.0.1)
111
+ xdg (3.1.1)
110
112
  zeitwerk (2.2.2)
111
113
 
112
114
  PLATFORMS
113
115
  ruby
114
116
 
115
117
  DEPENDENCIES
116
- bundler (~> 1.16)
118
+ bundler (~> 2.0)
117
119
  fakefs (~> 0.14)
118
120
  git (~> 1.5)
119
121
  ostruct (~> 0.1)
@@ -124,4 +126,4 @@ DEPENDENCIES
124
126
  story_branch!
125
127
 
126
128
  BUNDLED WITH
127
- 2.1.2
129
+ 2.1.3
@@ -0,0 +1,168 @@
1
+ # Story Branch
2
+
3
+ Story branch is a CLI application that interacts with Pivotal Tracker, Github
4
+ and JIRA.
5
+
6
+ Depending on the tracker features, it provides different approaches.
7
+
8
+ - For PivotalTracker, it allows you to start and un-start stories, as well as
9
+ creating branches based on the story name and id and have a final commit message
10
+ marking the story as Finished.
11
+
12
+ - For Github and JIRA because there is not a fixed flow, it allows you to create
13
+ the branches based on the tickets name and numbers. Similarly, it supports one
14
+ final commit with a standard message.
15
+
16
+ [View Changelog](Changelog.md)
17
+
18
+ ## Installing
19
+
20
+ Install the gem:
21
+
22
+ gem install story_branch
23
+
24
+ ## Usage
25
+
26
+ You should run story_branch from the git/project root folder.
27
+
28
+ ## Commands available
29
+
30
+ You can see all the commands available by running
31
+
32
+ ```
33
+ $ story_branch -h
34
+
35
+ Commands:
36
+ story_branch add # Add a new story branch configuration
37
+ story_branch create # Create branch from estimated stories in pivotal tracker
38
+ story_branch finish # Creates a git commit message for the staged changes with a [Finishes] tag
39
+ story_branch help [COMMAND] # Describe available commands or one specific command
40
+ story_branch migrate # Migrate old story branch configuration to the new format
41
+ story_branch start # Mark an estimated story as started in Pivotal Tracker
42
+ story_branch unstart # Mark a started story as un-started in Pivotal Tracker
43
+ story_branch version # story_branch gem version
44
+ ```
45
+
46
+ ## Settings
47
+
48
+ Story branch has a command available that will help you creating the configurations
49
+ for the projects, but essentially you'll be asked for the pivotal tracker project id and your api key.
50
+
51
+ ### Configuring the project id
52
+
53
+ The project id you can get it easily from the url when viewing the project.
54
+ This value will be stored in the local configuration file that will be committed
55
+ to the working repository
56
+
57
+ ### Configuring the api key
58
+
59
+ The api key you can get it from your account settings.
60
+ This value will be stored in your global configuration file that typically is
61
+ not shared with your co-workers in the repository. This way, each user will
62
+ be properly identified in the tracker
63
+
64
+ ### Configuring the finish tag
65
+
66
+ On your local config you can add a line with `finish_tag: <Some random word>`.
67
+ This tag will be used in the commit message when running `story_branch finish`.
68
+
69
+ E.g.
70
+ `finish_tag: Resolves`
71
+
72
+ `story_branch finish` will make a commit with the message
73
+ `[Resolves #12313] story title`
74
+
75
+
76
+ ### .story_branch files
77
+
78
+ When configuring story branch, it will create two .story_branch.yml files: one in
79
+ your home folder (`~/`) and one in your project's root (`./`).
80
+ The one in your home folder will be used to store the different project's configurations
81
+ such as which api key to use. This is done so you don't need to commit your
82
+ api key to the repository but still be able to use different keys in case you
83
+ have different accounts.
84
+
85
+ The one in your project root will keep a reference to the project configuration.
86
+ For now, this reference is the project id. This file can be safely committed to
87
+ the repository and shared amongst your co-workers.
88
+
89
+ ## Commentary
90
+
91
+ `story_branch create`: Creates a git branch with automatic reference to a
92
+ Pivotal Tracker Story. It will get started stories from your active
93
+ project. You can enter text and press TAB to search for a story
94
+ name, or TAB to show the full list. It will then suggest an editable
95
+ branch name. When the branch is created the `story_id` will
96
+ be appended to it.
97
+
98
+ e.g. `my-story-name-1234567`
99
+
100
+ `story_branch finish`: Creates a git commit message for the staged changes.
101
+
102
+ e.g: `[Finishes #1234567] My story name`
103
+
104
+ You must stage all changes (or stash them) first. Note the commit will not
105
+ be pushed. Note: You'll be able to bail out of the commit.
106
+
107
+ `story_branch start`: Start a story in Pivotal Tracker from the terminal.
108
+ It'll get all un-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.
111
+
112
+ `story_branch unstart`: Un-start a story in Pivotal Tracker from the terminal.
113
+ It'll get all started stories in your current project. You can
114
+ enter text and press TAB to search for a story name, or TAB to show
115
+ the full list.
116
+
117
+ ## Configuring PivotalTracker
118
+
119
+ When running the command `story_branch add` you'll be asked 3 things:
120
+ 1. tracker - You should select Pivotal Tracker
121
+ 2. project id - This can be fetched from the PivotalTracker url. E.g in the url `https://www.pivotaltracker.com/n/projects/651417`, the project id would be `651417`
122
+ 3. api key - this is your personal api key. You can get that from [your profile page](https://www.pivotaltracker.com/profile)
123
+
124
+ ## Configuring Github
125
+
126
+ When running the command `story_branch add` you'll be asked 3 things:
127
+ 1. project id - This is the github repository name in the format `<owner>/<repo_name>`. E.g. `story-branch/story_branch`.
128
+ 2. tracker - You should select Github
129
+ 3. api key - this is your personal api token. You can create one under your
130
+ [developer profile tokens page](https://github.com/settings/tokens)
131
+
132
+ ## Configuring JIRA
133
+
134
+ The configuration for JIRA is slightly more complex as the endpoint changes according
135
+ to your project setup. You will need an API token, which you can create a new one in your [JIRA id management page](https://id.atlassian.com/manage/api-tokens)
136
+ 1. tracker - You should select JIRA
137
+ 2. JIRA's subdomain - you should type the JIRA's subdomain that you use to access in your browser. E.g I'd type perxtechnologies to access to https://perxtechnologies.atlassian.net
138
+ 3. JIRA's project key - this should match which project you want to fetch the issues from. E.g. PW is the key for my Project Whistler, so I'd type PW
139
+ 4. API key that you should have gotten in the first description step
140
+ 5. username used for login in the JIRA usually. If you use google email authentication, the username should be your email
141
+
142
+ ## Migrating
143
+
144
+ ### Old configuration
145
+
146
+ If your were using story branch before there are some small changes on the way the
147
+ tool works. But worry not, we've written a command that allows you to migrate your
148
+ configuration. Running
149
+
150
+ `$ story_branch migrate`
151
+
152
+ will grab your existing configuration and convert it into the new format. The only
153
+ thing you'll need to provide is the project name reference.
154
+
155
+ ### Old commands
156
+
157
+ 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
158
+ we try as much as possible to keep the updates retro-compatible, but are nothing
159
+ more than an alias for the CLI commands as follow:
160
+
161
+ - `git-story` runs `story_branch create`
162
+ - `git-finish` runs `story_branch finish`
163
+ - `git-start` runs `story_branch start`
164
+ - `git-unstart` runs `story_branch unstart`
165
+
166
+ ## Contributing
167
+
168
+ All pull requests are welcome and will be reviewed.
@@ -78,17 +78,5 @@ module StoryBranch
78
78
  StoryBranch::Commands::Add.new(options).execute
79
79
  end
80
80
  end
81
-
82
- desc 'migrate', 'Migrate old story branch configuration to the new format'
83
- method_option :help, aliases: '-h', type: :boolean,
84
- desc: 'Display usage information'
85
- def migrate(*)
86
- if options[:help]
87
- invoke :help, ['migrate']
88
- else
89
- require_relative 'commands/migrate'
90
- StoryBranch::Commands::Migrate.new(options).execute
91
- end
92
- end
93
81
  end
94
82
  end
@@ -13,60 +13,61 @@ module StoryBranch
13
13
  # It will try to load the existing global story branch config
14
14
  # and then add the project id specified by the user.
15
15
  class Add < StoryBranch::Command
16
- def initialize(options)
17
- @options = options
18
- @config = ConfigManager.init_config(Dir.home)
19
- @local_config = ConfigManager.init_config('.')
16
+ def initialize(_options)
17
+ @new_config = ConfigManager.new
20
18
  end
21
19
 
22
20
  def execute(_input: $stdin, output: $stdout)
23
- create_local_config
21
+ set_tracker_type
22
+ set_project_key
24
23
  create_global_config
24
+ @new_config.save
25
25
  output.puts 'Configuration added successfully'
26
26
  end
27
27
 
28
28
  private
29
29
 
30
- def create_local_config
31
- return if local_config_has_value?
30
+ def set_tracker_type
31
+ return if @new_config.contains?(project_key)
32
32
 
33
33
  puts "Setting #{tracker}"
34
- @local_config.set(:tracker, value: tracker)
35
-
36
- puts "Appending #{project_id}"
37
- @local_config.append(project_id, to: :project_id)
34
+ @new_config.tracker_type = tracker
35
+ end
38
36
 
39
- @local_config.write(force: true)
37
+ def set_project_key
38
+ puts "Setting #{project_key}"
39
+ @new_config.project_key = project_key
40
40
  end
41
41
 
42
42
  def create_global_config
43
43
  api_key = prompt.ask('Please provide the api key:', required: true)
44
- @config.set(project_id, :api_key, value: api_key)
45
- @config.write(force: true)
44
+ @new_config.api_key = api_key
46
45
 
47
46
  return unless tracker == 'jira'
48
47
 
49
- # rubocop:disable Layout/LineLength
48
+ # rubocop:disable Metrics/LineLength
50
49
  username = prompt.ask('Please provide username (email most of the times) for this key:',
51
50
  required: true)
52
- # rubocop:enable Layout/LineLength
53
- @config.set(project_id, :username, value: username)
54
- @config.write(force: true)
51
+ # rubocop:enable Metrics/LineLength
52
+ @new_config.username = username
55
53
  end
56
54
 
57
- def project_id
58
- return @project_id if @project_id
55
+ def project_key
56
+ return @project_key if @project_key
59
57
 
58
+ @project_key = ask_for_project_key
59
+ end
60
+
61
+ def ask_for_project_key
60
62
  if tracker == 'jira'
61
- # rubocop:disable Layout/LineLength
62
- project_domain = prompt.ask("What is your JIRA's subdomain?", required: true)
63
- project_key = prompt.ask("What is your JIRA's project key?", required: true)
64
- # rubocop:enable Layout/LineLength
65
- @project_id = "#{project_domain}|#{project_key}"
63
+ project_domain = prompt.ask("What is your JIRA's subdomain?",
64
+ required: true)
65
+ project_key = prompt.ask("What is your JIRA's project key?",
66
+ required: true)
67
+
68
+ "#{project_domain}|#{project_key}"
66
69
  else
67
- # rubocop:disable Layout/LineLength
68
- @project_id = prompt.ask("Please provide this project's id:", required: true)
69
- # rubocop:enable Layout/LineLength
70
+ prompt.ask("Please provide this project's id:", required: true)
70
71
  end
71
72
  end
72
73
 
@@ -80,15 +81,6 @@ module StoryBranch
80
81
  }
81
82
  @tracker = prompt.select('Which tracker are you using?', trackers)
82
83
  end
83
-
84
- def local_config_has_value?
85
- config_value = @local_config.fetch(:project_id)
86
- if config_value.is_a? Array
87
- config_value.include?(project_id)
88
- else
89
- config_value == project_id
90
- end
91
- end
92
84
  end
93
85
  end
94
86
  end
@@ -1,20 +1,169 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'tty-config'
4
+ require 'tty-prompt'
5
+ require 'xdg'
4
6
 
5
7
  module StoryBranch
6
- # Config manager class is simply a wrapper around
7
- # TTY::Config with the configuration file name set
8
- # so we DRY our code.
8
+ # Config manager is used to manage all possible configuration settings
9
+ # it uses mainly TTY::Config with the configuration file name set
10
+ # rubocop:disable Metrics/ClassLength
9
11
  class ConfigManager
10
- # TODO: Might be worht moving the configuration filename
11
- # to a constant
12
- def self.init_config(path, should_read = true)
12
+ CONFIG_FILENAME = '.story_branch'
13
+
14
+ attr_reader :errors
15
+
16
+ def initialize
17
+ @prompt = TTY::Prompt.new(interrupt: :exit)
18
+ load_configs
19
+ @config = ::TTY::Config.new
20
+ @config.merge(@local)
21
+ @config.merge(@global)
22
+ @errors = []
23
+ end
24
+
25
+ def tracker_type
26
+ @tracker_type ||= @config.fetch(:tracker, default: 'pivotal-tracker')
27
+ end
28
+
29
+ def tracker_type=(tracker)
30
+ @local.set(:tracker, value: tracker)
31
+ end
32
+
33
+ def issue_placement
34
+ @issue_placement ||= @config.fetch(:issue_placement, default: 'End')
35
+ end
36
+
37
+ def finish_tag
38
+ @finish_tag ||= @config.fetch(project_key,
39
+ :finish_tag, default: 'Finishes')
40
+ end
41
+
42
+ def project_key=(key)
43
+ @project_key = key
44
+ @local.append(key, to: :project_id) unless contains?(key)
45
+ end
46
+
47
+ def api_key=(key)
48
+ @api_key = key
49
+ @global.set(@project_key, :api_key, value: key)
50
+ end
51
+
52
+ def username=(username)
53
+ @username = username
54
+ @global.set(@project_key, :username, value: username)
55
+ end
56
+
57
+ def tracker_params
58
+ {
59
+ tracker_domain: tracker_domain,
60
+ username: username,
61
+ project_id: project_id,
62
+ api_key: api_key,
63
+ extra_query: extra_query
64
+ }
65
+ end
66
+
67
+ def valid?
68
+ validate
69
+ @errors.length.zero?
70
+ end
71
+
72
+ def contains?(project_key)
73
+ project_keys = @config.fetch(:project_id)
74
+ if project_keys.is_a? Array
75
+ project_keys.include?(project_key)
76
+ else
77
+ project_keys == project_key
78
+ end
79
+ end
80
+
81
+ def save
82
+ @local.write(force: true)
83
+ @global.write(force: true)
84
+ end
85
+
86
+ private
87
+
88
+ def api_key
89
+ @api_key ||= @config.fetch(project_key, :api_key)
90
+ end
91
+
92
+ def username
93
+ @username ||= @config.fetch(project_key, :username)
94
+ end
95
+
96
+ def project_key
97
+ return @project_key if @project_key
98
+
99
+ project_keys = @config.fetch(:project_id)
100
+
101
+ @project_key = choose_project_id(project_keys)
102
+ end
103
+
104
+ def extra_query
105
+ @extra_query ||= @config.fetch(project_key, :extra_query, default: '')
106
+ end
107
+
108
+ def project_id
109
+ return @project_id if @project_id
110
+
111
+ @project_id = if tracker_type == 'jira'
112
+ project_key.split('|')[1]
113
+ else
114
+ project_key
115
+ end
116
+ end
117
+
118
+ def tracker_domain
119
+ return @tracker_domain if @tracker_domain
120
+
121
+ @tracker_domain = if tracker_type != 'jira'
122
+ ''
123
+ else
124
+ project_key.split('|')[0]
125
+ end
126
+ end
127
+
128
+ def choose_project_id(project_ids)
129
+ return project_ids unless project_ids.is_a? Array
130
+ return project_ids[0] unless project_ids.length > 1
131
+
132
+ @prompt.select('Which project you want to fetch from?', project_ids)
133
+ end
134
+
135
+ def validate
136
+ @errors << 'Project ID is not set' if project_id.nil?
137
+ end
138
+
139
+ def load_configs
140
+ @local = read_config('.')
141
+ xdg_conf = XDG::Config.new
142
+ home_path = if conf_exist?(Dir.home)
143
+ Dir.home
144
+ else
145
+ xdg_conf.home
146
+ end
147
+ @global = read_config(home_path)
148
+ end
149
+
150
+ def read_config(path)
151
+ config = init_config(path)
152
+ config.read if config.persisted?
153
+ config
154
+ end
155
+
156
+ def conf_exist?(path)
157
+ config = init_config(path)
158
+ config.persisted?
159
+ end
160
+
161
+ def init_config(path)
13
162
  config = ::TTY::Config.new
14
- config.filename = '.story_branch'
163
+ config.filename = CONFIG_FILENAME
15
164
  config.append_path path
16
- config.read if config.persisted? && should_read
17
165
  config
18
166
  end
19
167
  end
168
+ # rubocop:enable Metrics/ClassLength
20
169
  end
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'blanket'
4
+ require 'story_branch/tracker_base'
4
5
  require_relative './project'
5
6
 
6
7
  module StoryBranch
7
8
  module Github
8
9
  # Github API wrapper for story branch tracker
9
- class Tracker
10
+ class Tracker < StoryBranch::TrackerBase
10
11
  API_URL = 'https://api.github.com/'
11
- TYPE = 'github'
12
12
 
13
- attr_reader :type
14
-
15
- def initialize(repo_name, api_key)
13
+ def initialize(project_id:, api_key:, **)
16
14
  # NOTE: RepoName should follow owner/repo_name format
17
- @repo_name = repo_name
15
+ @repo_name = project_id
18
16
  @api_key = api_key
19
- @type = TYPE
20
17
  end
21
18
 
22
19
  def valid?
@@ -37,9 +34,7 @@ module StoryBranch
37
34
 
38
35
  private
39
36
 
40
- def api
41
- raise 'API key must be specified' unless @api_key
42
-
37
+ def configure_api
43
38
  Blanket.wrap API_URL, headers: {
44
39
  'User-Agent' => 'Story Branch',
45
40
  Authorization: "token #{@api_key}"
@@ -6,8 +6,9 @@ module StoryBranch
6
6
  module Jira
7
7
  # Jira Project representation
8
8
  class Project
9
- def initialize(jira_project)
9
+ def initialize(jira_project, query_addon = '')
10
10
  @project = jira_project
11
+ @query_addon = query_addon
11
12
  end
12
13
 
13
14
  # Returns an array of Jira issues (Issue Class)
@@ -17,15 +18,22 @@ module StoryBranch
17
18
  stories = if options[:id]
18
19
  [@project.issues.find(options[:id])]
19
20
  else
20
- # rubocop:disable Layout/LineLength
21
- @project.client.Issue.jql(
22
- "project=#{@project.key} AND status='To Do' AND assignee=currentUser()"
23
- )
24
- # rubocop:enable Layout/LineLength
21
+ @project.client.Issue.jql(jql_query)
25
22
  end
26
23
 
27
24
  stories.map { |s| Issue.new(s, @project) }
28
25
  end
26
+
27
+ private
28
+
29
+ def jql_query
30
+ base_query = "project=#{@project.key} AND assignee=currentUser()"
31
+ if @query_addon.length.positive?
32
+ [base_query, @query_addon].join(' AND ')
33
+ else
34
+ base_query
35
+ end
36
+ end
29
37
  end
30
38
  end
31
39
  end
@@ -5,23 +5,22 @@
5
5
  # my tracker and issues will still provide a similar api. This jira-ruby
6
6
  # is used to get the data.
7
7
  require 'jira-ruby'
8
+ require 'story_branch/tracker_base'
8
9
  require_relative './project'
9
10
 
10
11
  module StoryBranch
11
12
  module Jira
12
13
  # JIRA API wrapper for story branch tracker
13
- class Tracker
14
- TYPE = 'jira'
15
-
16
- attr_reader :type
17
-
18
- def initialize(tracker_domain:, project_id:, api_key:, username:)
14
+ class Tracker < StoryBranch::TrackerBase
15
+ # rubocop:disable Metrics/LineLength
16
+ def initialize(tracker_domain:, project_id:, api_key:, username:, extra_query:)
19
17
  @tracker_url = "https://#{tracker_domain}.atlassian.net"
20
18
  @project_id = project_id
21
19
  @api_key = api_key
22
20
  @username = username
23
- @type = TYPE
21
+ @extra_query = extra_query
24
22
  end
23
+ # rubocop:enable Metrics/LineLength
25
24
 
26
25
  def valid?
27
26
  [@api_key, @project_id, @username, @tracker_url].none?(&:nil?)
@@ -52,10 +51,8 @@ module StoryBranch
52
51
  }
53
52
  end
54
53
 
55
- def api
56
- raise 'API key must be specified' unless @api_key
57
-
58
- @api ||= JIRA::Client.new(options)
54
+ def configure_api
55
+ JIRA::Client.new(options)
59
56
  end
60
57
 
61
58
  def project
@@ -63,7 +60,7 @@ module StoryBranch
63
60
  raise 'project key must be set' unless @project_id
64
61
 
65
62
  jira_project = api.Project.find(@project_id)
66
- @project = Project.new(jira_project)
63
+ @project = Project.new(jira_project, @extra_query)
67
64
  end
68
65
  end
69
66
  end
@@ -17,14 +17,9 @@ module StoryBranch
17
17
  attr_accessor :tracker
18
18
 
19
19
  def initialize
20
- # TODO: Config manager should be responsible for handling the
21
- # configuration and the story branch should only initialize one
22
- # config manager that has attr accessors for needed values
23
- # Read local config and decide what Utility to use
24
- # (e.g. PivotalUtils, GithubUtils, ...)
25
- @local_config = ConfigManager.init_config('.')
26
- @global_config = ConfigManager.init_config(Dir.home)
27
- initialize_tracker
20
+ @config = ConfigManager.new
21
+ abort(@config.errors.join("\n")) unless @config.valid?
22
+ @tracker = initialize_tracker
28
23
  abort('Invalid tracker configuration setting.') unless @tracker.valid?
29
24
  end
30
25
 
@@ -75,11 +70,10 @@ module StoryBranch
75
70
  private
76
71
 
77
72
  def require_pivotal
78
- if @tracker.type != 'pivotal'
79
- prompt.say 'The configured tracker does not support this feature'
80
- return false
81
- end
82
- true
73
+ return true if @tracker.class.name.match?('Pivotal')
74
+
75
+ prompt.say 'The configured tracker does not support this feature'
76
+ false
83
77
  end
84
78
 
85
79
  def current_story
@@ -159,29 +153,8 @@ module StoryBranch
159
153
  @prompt ||= TTY::Prompt.new(interrupt: :exit)
160
154
  end
161
155
 
162
- def finish_tag
163
- return @finish_tag if @finish_tag
164
-
165
- fallback = @global_config.fetch(project_id,
166
- :finish_tag,
167
- default: 'Finishes')
168
- @finish_tag = @local_config.fetch(:finish_tag, default: fallback)
169
- @finish_tag
170
- end
171
-
172
- def issue_placement
173
- return @issue_placement if @issue_placement
174
-
175
- fallback = @global_config.fetch(project_id,
176
- :issue_placement,
177
- default: 'End')
178
- @issue_placement = @local_config.fetch(:issue_placement,
179
- default: fallback)
180
- @issue_placement
181
- end
182
-
183
156
  def build_finish_message
184
- message_tag = [finish_tag, "##{current_story.id}"].join(' ').strip
157
+ message_tag = [@config.finish_tag, "##{current_story.id}"].join(' ').strip
185
158
  "[#{message_tag}] #{current_story.title}"
186
159
  end
187
160
 
@@ -196,10 +169,11 @@ module StoryBranch
196
169
  branch_name = valid_branch_name(story)
197
170
  return unless branch_name
198
171
 
199
- # rubocop:disable Layout/LineLength
172
+ # rubocop:disable Metrics/LineLength
200
173
  feature_branch_name_with_story_id = build_branch_name(branch_name, story.id)
174
+
201
175
  prompt.say("Creating: #{feature_branch_name_with_story_id} with #{current_branch} as parent")
202
- # rubocop:enable Layout/LineLength
176
+ # rubocop:enable Metrics/LineLength
203
177
  GitWrapper.create_branch feature_branch_name_with_story_id
204
178
  end
205
179
 
@@ -233,65 +207,30 @@ module StoryBranch
233
207
  # rubocop:enable Metrics/MethodLength
234
208
 
235
209
  def build_branch_name(branch_name, story_id)
236
- if issue_placement.casecmp('beginning').zero?
210
+ if @config.issue_placement.casecmp('beginning').zero?
237
211
  "#{story_id}-#{branch_name}"
238
212
  else
239
213
  "#{branch_name}-#{story_id}"
240
214
  end
241
215
  end
242
216
 
243
- def project_id
244
- return @project_id if @project_id
245
-
246
- project_ids = @local_config.fetch(:project_id)
247
- @project_id = choose_project_id(project_ids)
248
- end
249
-
250
- def choose_project_id(project_ids)
251
- return project_ids unless project_ids.is_a? Array
252
- return project_ids[0] unless project_ids.length > 1
253
-
254
- prompt.select('Which project you want to fetch from?', project_ids)
255
- end
256
-
257
- def api_key
258
- @api_key ||= @global_config.fetch(project_id, :api_key)
259
- end
260
-
261
- def username
262
- @username ||= @global_config.fetch(project_id, :username)
263
- end
264
-
265
217
  def current_branch
266
218
  @current_branch ||= GitWrapper.current_branch
267
219
  end
268
220
 
269
- # rubocop:disable Metrics/AbcSize
270
- # rubocop:disable Metrics/MethodLength
271
221
  def initialize_tracker
272
- if project_id.nil?
273
- prompt.say 'Project ID not set'
274
- exit 0
222
+ # TODO: Ideally this would be mapped out somewhere so we don't need to
223
+ # evaluate anything from the config here
224
+ tracker_type = @config.tracker_type
225
+ case tracker_type
226
+ when 'github'
227
+ StoryBranch::Github::Tracker.new(@config.tracker_params)
228
+ when 'pivotal-tracker'
229
+ StoryBranch::Pivotal::Tracker.new(@config.tracker_params)
230
+ when 'jira'
231
+ StoryBranch::Jira::Tracker.new(@config.tracker_params)
275
232
  end
276
- tracker_type = @local_config.fetch(:tracker, default: 'pivotal-tracker')
277
- @tracker = case tracker_type
278
- when 'github'
279
- StoryBranch::Github::Tracker.new(project_id, api_key)
280
- when 'pivotal-tracker'
281
- StoryBranch::Pivotal::Tracker.new(project_id, api_key)
282
- when 'jira'
283
- tracker_domain, project_key = project_id.split('|')
284
- options = {
285
- tracker_domain: tracker_domain,
286
- project_id: project_key,
287
- api_key: api_key,
288
- username: username
289
- }
290
- StoryBranch::Jira::Tracker.new(options)
291
- end
292
233
  end
293
- # rubocop:enable Metrics/AbcSize
294
- # rubocop:enable Metrics/MethodLength
295
234
  end
296
235
  # rubocop:enable Metrics/ClassLength
297
236
  end
@@ -1,22 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'blanket'
4
+ require 'story_branch/tracker_base'
4
5
  require_relative './project'
5
6
 
6
7
  module StoryBranch
7
8
  module Pivotal
8
9
  # Utility class for integration with PivotalTracker. It relies on Blanket
9
10
  # wrapper to communicate with pivotal tracker's api.
10
- class Tracker
11
+ class Tracker < StoryBranch::TrackerBase
11
12
  API_URL = 'https://www.pivotaltracker.com/services/v5/'
12
- TYPE = 'pivotal'
13
13
 
14
- attr_reader :type
15
-
16
- def initialize(project_id, api_key)
14
+ def initialize(project_id:, api_key:, **)
17
15
  @project_id = project_id
18
16
  @api_key = api_key
19
- @type = TYPE
20
17
  end
21
18
 
22
19
  def valid?
@@ -41,9 +38,7 @@ module StoryBranch
41
38
 
42
39
  private
43
40
 
44
- def api
45
- raise 'API key must be specified' unless @api_key
46
-
41
+ def configure_api
47
42
  Blanket.wrap API_URL, headers: { 'X-TrackerToken' => @api_key }
48
43
  end
49
44
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pry'
4
+
5
+ module StoryBranch
6
+ # Base story branch tracker class that will define the expected interface
7
+ class TrackerBase
8
+ def valid?
9
+ raise 'valid? > must be implemented in the custom tracker'
10
+ end
11
+
12
+ # TODO: This should probably be renamed to something more meaningful
13
+ # in the sense that it should be workable stories/issues
14
+ # which depend on the tracker's workflow. PivotalTracker they need to
15
+ # be started and estimated, while for Github they just need to be open
16
+ def stories
17
+ []
18
+ end
19
+
20
+ def get_story_by_id(_story_id)
21
+ []
22
+ end
23
+
24
+ private
25
+
26
+ def api
27
+ raise 'API key must be specified' unless @api_key
28
+
29
+ @api ||= configure_api
30
+ end
31
+
32
+ def project
33
+ return @project if @project
34
+ raise 'project key must be set' unless @project_id
35
+
36
+ raise 'project > must be implemented in the custom tracker'
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StoryBranch
4
- VERSION = '0.6.1'
4
+ VERSION = '0.7.0.alpha'
5
5
  end
@@ -61,8 +61,9 @@ Gem::Specification.new do |spec|
61
61
  spec.add_runtime_dependency 'tty-config', '~> 0.2.0'
62
62
  spec.add_runtime_dependency 'tty-pager', '~> 0.12'
63
63
  spec.add_runtime_dependency 'tty-prompt', '~> 0.18'
64
+ spec.add_runtime_dependency 'xdg', '~> 3.0'
64
65
 
65
- spec.add_development_dependency 'bundler', '~> 1.16'
66
+ spec.add_development_dependency 'bundler', '~> 2.0'
66
67
  spec.add_development_dependency 'fakefs', '~> 0.14'
67
68
  spec.add_development_dependency 'git', '~> 1.5'
68
69
  spec.add_development_dependency 'ostruct', '~> 0.1'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: story_branch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.1
4
+ version: 0.7.0.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rui Baltazar
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: exe
14
14
  cert_chain: []
15
- date: 2019-12-23 00:00:00.000000000 Z
15
+ date: 2020-01-25 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: blanket_wrapper
@@ -126,20 +126,34 @@ dependencies:
126
126
  - - "~>"
127
127
  - !ruby/object:Gem::Version
128
128
  version: '0.18'
129
+ - !ruby/object:Gem::Dependency
130
+ name: xdg
131
+ requirement: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - "~>"
134
+ - !ruby/object:Gem::Version
135
+ version: '3.0'
136
+ type: :runtime
137
+ prerelease: false
138
+ version_requirements: !ruby/object:Gem::Requirement
139
+ requirements:
140
+ - - "~>"
141
+ - !ruby/object:Gem::Version
142
+ version: '3.0'
129
143
  - !ruby/object:Gem::Dependency
130
144
  name: bundler
131
145
  requirement: !ruby/object:Gem::Requirement
132
146
  requirements:
133
147
  - - "~>"
134
148
  - !ruby/object:Gem::Version
135
- version: '1.16'
149
+ version: '2.0'
136
150
  type: :development
137
151
  prerelease: false
138
152
  version_requirements: !ruby/object:Gem::Requirement
139
153
  requirements:
140
154
  - - "~>"
141
155
  - !ruby/object:Gem::Version
142
- version: '1.16'
156
+ version: '2.0'
143
157
  - !ruby/object:Gem::Dependency
144
158
  name: fakefs
145
159
  requirement: !ruby/object:Gem::Requirement
@@ -274,6 +288,7 @@ files:
274
288
  - README.md
275
289
  - Rakefile
276
290
  - Roadmap.md
291
+ - docs/index.md
277
292
  - exe/git-finish
278
293
  - exe/git-start
279
294
  - exe/git-story
@@ -286,7 +301,6 @@ files:
286
301
  - lib/story_branch/commands/add.rb
287
302
  - lib/story_branch/commands/create.rb
288
303
  - lib/story_branch/commands/finish.rb
289
- - lib/story_branch/commands/migrate.rb
290
304
  - lib/story_branch/commands/start.rb
291
305
  - lib/story_branch/commands/unstart.rb
292
306
  - lib/story_branch/config_manager.rb
@@ -313,6 +327,7 @@ files:
313
327
  - lib/story_branch/templates/migrate/.gitkeep
314
328
  - lib/story_branch/templates/start/.gitkeep
315
329
  - lib/story_branch/templates/unstart/.gitkeep
330
+ - lib/story_branch/tracker_base.rb
316
331
  - lib/story_branch/version.rb
317
332
  - story_branch.gemspec
318
333
  homepage: https://github.com/story-branch/story_branch
@@ -337,11 +352,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
337
352
  version: '2.7'
338
353
  required_rubygems_version: !ruby/object:Gem::Requirement
339
354
  requirements:
340
- - - ">="
355
+ - - ">"
341
356
  - !ruby/object:Gem::Version
342
- version: '0'
357
+ version: 1.3.1
343
358
  requirements: []
344
- rubygems_version: 3.0.1
359
+ rubygems_version: 3.0.3
345
360
  signing_key:
346
361
  specification_version: 4
347
362
  summary: Create git branches based on your preferred tracker tickets
@@ -1,103 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../config_manager'
4
- require_relative '../command'
5
- require 'yaml'
6
- require 'fileutils'
7
-
8
- module StoryBranch
9
- module Commands
10
- # Migrate command is intended to make the migration from old version
11
- # of story branch to the latest one easier.
12
- class Migrate < StoryBranch::Command
13
- GLOBAL_CONFIG_FILE = "#{Dir.home}/.story_branch"
14
- LOCAL_CONFIG_FILE = '.story_branch'
15
- OLD_CONFIG_FILES = [LOCAL_CONFIG_FILE, GLOBAL_CONFIG_FILE].freeze
16
-
17
- def initialize(options)
18
- @options = options
19
- @config = ConfigManager.init_config(Dir.home)
20
- end
21
-
22
- def execute(_input: $stdin, output: $stdout)
23
- if missing_old_config?
24
- error_migrating(output, old_config_file_not_found)
25
- return
26
- end
27
- @config.set(project_id, :api_key, value: api_key)
28
- @config.write(force: true)
29
- create_local_config
30
- clean_old_config_files
31
- output.puts 'Migration complete'
32
- end
33
-
34
- private
35
-
36
- def project_id
37
- return @project_id if @project_id
38
-
39
- @project_id = old_config_value('project', 'PIVOTAL_PROJECT_ID')
40
- @project_id
41
- end
42
-
43
- def api_key
44
- return @api_key if @api_key
45
-
46
- @api_key = old_config_value('api', 'PIVOTAL_API_KEY')
47
- @api_key
48
- end
49
-
50
- def error_migrating(output, error_message)
51
- output.puts error_message
52
- end
53
-
54
- def missing_old_config?
55
- OLD_CONFIG_FILES.each { |file| return false if File.exist?(file) }
56
- return false if env_set?
57
-
58
- true
59
- end
60
-
61
- def env_set?
62
- ENV['PIVOTAL_API_KEY'].length.positive? ||
63
- ENV['PIVOTAL_PROJECT_ID'].length.positive?
64
- end
65
-
66
- def old_config_value(key, env)
67
- OLD_CONFIG_FILES.each do |config_file|
68
- if File.exist? config_file
69
- old_config = YAML.load_file config_file
70
- return old_config[key].to_s if old_config && old_config[key]
71
- end
72
- end
73
- ENV[env]
74
- end
75
-
76
- def create_local_config
77
- local_config = ConfigManager.init_config('.')
78
- local_config.set(:project_id, value: project_id)
79
- local_config.write
80
- end
81
-
82
- def clean_old_config_files
83
- [GLOBAL_CONFIG_FILE, LOCAL_CONFIG_FILE].each do |file|
84
- FileUtils.rm file if File.exist? file
85
- end
86
- end
87
-
88
- def old_config_file_not_found
89
- <<~MESSAGE
90
- Old configuration not found.
91
- Trying to start from scratch? Use story_branch add
92
- MESSAGE
93
- end
94
-
95
- def cant_migrate_missing_value
96
- <<~MESSAGE
97
- Old configuration not found. Nothing has been migrated
98
- Trying to start from scratch? Use story_branch add
99
- MESSAGE
100
- end
101
- end
102
- end
103
- end