story_branch 0.6.1 → 0.7.0.alpha

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